Switch to mysql

- trilogy adapter
- lots of small migration changes
- notably we lose the search tables which case lots of tests to fail
This commit is contained in:
Mike Dalessio
2025-11-10 09:26:19 -05:00
parent 362427b383
commit 52ce8b50cc
26 changed files with 218 additions and 212 deletions
@@ -1,6 +1,7 @@
class CreateSearchIndexes < ActiveRecord::Migration[8.0]
def change
create_virtual_table :bubbles_search_index, "fts5", [ "title" ]
create_virtual_table :comments_search_index, "fts5", [ "body" ]
# # TODO:PLANB: need to replace SQLite FTS
# create_virtual_table :bubbles_search_index, "fts5", [ "title" ]
# create_virtual_table :comments_search_index, "fts5", [ "body" ]
end
end
+1 -1
View File
@@ -3,7 +3,7 @@ class CreateEvents < ActiveRecord::Migration[8.0]
create_table :events do |t|
t.references :bubble, null: false, index: false
t.references :creator, null: false
t.json :particulars, default: {}
t.column :particulars, :json, default: -> { '(JSON_OBJECT())' }
t.string :action, null: false
t.timestamps
+1 -1
View File
@@ -3,7 +3,7 @@ class CreateViews < ActiveRecord::Migration[8.0]
create_table :bucket_views do |t|
t.references :creator, null: false
t.references :bucket, null: false
t.json :filters, default: {}, null: false
t.column :filters, :json, default: -> { '(JSON_OBJECT())' }, null: false
t.timestamps
end
@@ -1,6 +1,11 @@
class MakeBucketViewFiltersUnique < ActiveRecord::Migration[8.0]
def change
add_index :bucket_views, %i[ bucket_id creator_id filters ], unique: true
# MySQL doesn't support direct indexing of JSON columns. To ensure uniqueness
# of the filters JSON content, we create a generated column that stores a SHA2
# hash of the JSON data. This allows us to enforce that each creator can only
# have one view per bucket with the same filter configuration.
add_column :bucket_views, :filters_hash, :string, limit: 64, as: "SHA2(filters, 256)", stored: true
add_index :bucket_views, %i[ bucket_id creator_id filters_hash ], unique: true
remove_index :bucket_views, :bucket_id
end
end
@@ -2,15 +2,19 @@ class RenameBucketViewsToFilters < ActiveRecord::Migration[8.0]
def change
rename_table :bucket_views, :filters
remove_index :filters, %i[ bucket_id creator_id filters ], unique: true
remove_index :filters, %i[ bucket_id creator_id filters_hash ], unique: true
remove_index :filters, :creator_id
remove_column :filters, :filters_hash, :string
remove_reference :filters, :bucket
rename_column :filters, :filters, :params
add_column :filters, :fields, :jsonb, null: false, default: {}
add_column :filters, :fields, :json, null: false, default: -> { '(JSON_OBJECT())' }
add_index :filters, %i[ creator_id params ], unique: true
# MySQL doesn't support direct indexing of JSON columns. Create a generated
# hash column for the params JSON data to enforce uniqueness.
add_column :filters, :params_hash, :string, limit: 64, as: "SHA2(params, 256)", stored: true
add_index :filters, %i[ creator_id params_hash ], unique: true
end
end
@@ -1,7 +1,10 @@
class HashFilterParams < ActiveRecord::Migration[8.0]
def change
change_column_default :filters, :params, from: {}, to: nil
change_column :filters, :params, :string, null: false
rename_column :filters, :params, :params_digest
remove_index :filters, %i[ creator_id params_hash ], unique: true
remove_column :filters, :params_hash
remove_column :filters, :params
add_column :filters, :params_digest, :string, null: false
add_index :filters, %i[ creator_id params_digest ], unique: true
end
end
@@ -1,6 +1,9 @@
class MakeTaggingsUniquePerBubble < ActiveRecord::Migration[8.0]
def change
remove_index :taggings, :bubble_id
# Add the composite index first, then remove the old single-column index.
# MySQL requires an index for foreign key constraints, so we need the new
# composite index in place before removing the old one.
add_index :taggings, %i[ bubble_id tag_id ], unique: true
remove_index :taggings, :bubble_id
end
end
@@ -1,13 +0,0 @@
class CreateActionTextMarkdowns < ActiveRecord::Migration[8.0]
def change
create_table :action_text_markdowns do |t|
t.references :record, polymorphic: true, null: false
t.string :name, null: false
t.text :content, null: false, default: ""
t.timestamps
end
add_column :active_storage_attachments, :slug, :string
add_index :active_storage_attachments, :slug, unique: true
end
end
@@ -1,5 +1,5 @@
class AddStatusToBubbles < ActiveRecord::Migration[8.1]
def change
add_column :bubbles, :status, :text, default: :drafted, null: false
add_column :bubbles, :status, :string, default: "drafted", null: false
end
end
@@ -4,12 +4,12 @@ class AssociateEventsWithBubbles < ActiveRecord::Migration[8.1]
t.references :bubble, foreign_key: true
end
# MySQL uses JOIN syntax for multi-table updates, not FROM clause
execute "
update events
set bubble_id = m.bubble_id
from event_summaries es
join messages m on m.messageable_type = 'EventSummary' and m.messageable_id = es.id
where events.summary_id = es.id
join event_summaries es on events.summary_id = es.id
join messages m on m.messageable_type = 'EventSummary' and m.messageable_id = es.id
set events.bubble_id = m.bubble_id
"
change_column_null :events, :bubble_id, false
@@ -4,15 +4,16 @@ class AddAutoPopAtToBubbles < ActiveRecord::Migration[8.1]
t.datetime :auto_pop_at, index: true
end
# MySQL uses JOIN syntax for multi-table updates and DATE_ADD for date arithmetic
execute "
update bubbles
set auto_pop_at = datetime(activity.last_active_at, '+30 days')
from (
select bubbles.id as bubble_id, coalesce(max(events.created_at), bubbles.created_at) as last_active_at
from bubbles
left join events on bubbles.id = events.bubble_id group by bubbles.id
) as activity
where bubbles.id = activity.bubble_id
join (
select bubbles.id as bubble_id, coalesce(max(events.created_at), bubbles.created_at) as last_active_at
from bubbles
left join events on bubbles.id = events.bubble_id
group by bubbles.id
) as activity on bubbles.id = activity.bubble_id
set bubbles.auto_pop_at = DATE_ADD(activity.last_active_at, INTERVAL 30 DAY)
"
change_column_null :bubbles, :auto_pop_at, false
@@ -3,10 +3,12 @@ class DropSubscriptions < ActiveRecord::Migration[8.1]
execute "
update accesses set involvement = 'access_only'
"
# MySQL uses JOIN syntax for multi-table updates
execute "
update accesses set involvement = 'watching'
from (select user_id, subscribable_id as bucket_id from subscriptions) as subscriptions
where subscriptions.user_id = accesses.user_id and subscriptions.bucket_id = accesses.bucket_id
update accesses
join (select user_id, subscribable_id as bucket_id from subscriptions) as subscriptions
on subscriptions.user_id = accesses.user_id and subscriptions.bucket_id = accesses.bucket_id
set accesses.involvement = 'watching'
"
drop_table :subscriptions
@@ -3,15 +3,16 @@ class AddLastActiveAtToBubbles < ActiveRecord::Migration[8.1]
add_column :bubbles, :last_active_at, :datetime
add_index :bubbles, %i[ last_active_at status ]
# MySQL uses JOIN syntax for multi-table updates
execute <<~SQL
update bubbles
set last_active_at = activity.last_active_at
from (
select bubbles.id as bubble_id, coalesce(max(events.created_at), bubbles.created_at) as last_active_at
from bubbles
left join events on bubbles.id = events.bubble_id group by bubbles.id
) as activity
where bubbles.id = activity.bubble_id
join (
select bubbles.id as bubble_id, coalesce(max(events.created_at), bubbles.created_at) as last_active_at
from bubbles
left join events on bubbles.id = events.bubble_id
group by bubbles.id
) as activity on bubbles.id = activity.bubble_id
set bubbles.last_active_at = activity.last_active_at
SQL
change_column_null :bubbles, :last_active_at, false
@@ -24,30 +24,32 @@ class RenameBubblesToCards < ActiveRecord::Migration[8.1]
rename_column "collections_filters", "bucket_id", "collection_id"
# Indexes
rename_index "assignments", "index_assignments_on_bubble_id", "index_assignments_on_card_id"
rename_index "card_engagements", "index_bubble_engagements_on_bubble_id", "index_card_engagements_on_card_id"
rename_index "events", "index_events_on_bubble_id", "index_events_on_card_id"
rename_index "messages", "index_messages_on_bubble_id", "index_messages_on_card_id"
rename_index "notifications", "index_notifications_on_bubble_id", "index_notifications_on_card_id"
rename_index "pins", "index_pins_on_bubble_id", "index_pins_on_card_id"
rename_index "pins", "index_pins_on_bubble_id_and_user_id", "index_pins_on_card_id_and_user_id"
rename_index "closures", "index_pops_on_bubble_id", "index_closures_on_card_id"
rename_index "closures", "index_pops_on_bubble_id_and_created_at", "index_closures_on_card_id_and_created_at"
rename_index "watches", "index_watches_on_bubble_id", "index_watches_on_card_id"
rename_index "taggings", "index_taggings_on_bubble_id_and_tag_id", "index_taggings_on_card_id_and_tag_id"
# # No longer necessary in MySQL
# rename_index "assignments", "index_assignments_on_bubble_id", "index_assignments_on_card_id"
# rename_index "card_engagements", "index_bubble_engagements_on_bubble_id", "index_card_engagements_on_card_id"
# rename_index "events", "index_events_on_bubble_id", "index_events_on_card_id"
# rename_index "messages", "index_messages_on_bubble_id", "index_messages_on_card_id"
# rename_index "notifications", "index_notifications_on_bubble_id", "index_notifications_on_card_id"
# rename_index "pins", "index_pins_on_bubble_id", "index_pins_on_card_id"
# rename_index "pins", "index_pins_on_bubble_id_and_user_id", "index_pins_on_card_id_and_user_id"
# rename_index "closures", "index_pops_on_bubble_id", "index_closures_on_card_id"
# rename_index "closures", "index_pops_on_bubble_id_and_created_at", "index_closures_on_card_id_and_created_at"
# rename_index "watches", "index_watches_on_bubble_id", "index_watches_on_card_id"
# rename_index "taggings", "index_taggings_on_bubble_id_and_tag_id", "index_taggings_on_card_id_and_tag_id"
rename_index "accesses", "index_accesses_on_bucket_id", "index_accesses_on_collection_id"
rename_index "accesses", "index_accesses_on_bucket_id_and_user_id", "index_accesses_on_collection_id_and_user_id"
rename_index "collections_filters", "index_buckets_filters_on_bucket_id", "index_collections_filters_on_collection_id"
# rename_index "accesses", "index_accesses_on_bucket_id", "index_accesses_on_collection_id"
# rename_index "accesses", "index_accesses_on_bucket_id_and_user_id", "index_accesses_on_collection_id_and_user_id"
# rename_index "collections_filters", "index_buckets_filters_on_bucket_id", "index_collections_filters_on_collection_id"
# Search tables
execute <<~SQL
CREATE VIRTUAL TABLE cards_search_index USING fts5(title);
INSERT INTO cards_search_index(title) SELECT title FROM bubbles_search_index;
SQL
# # TODO:PLANB: need to replace SQLite FTS
# execute <<~SQL
# CREATE VIRTUAL TABLE cards_search_index USING fts5(title);
# INSERT INTO cards_search_index(title) SELECT title FROM bubbles_search_index;
# SQL
execute <<~SQL
DROP TABLE bubbles_search_index;
SQL
# execute <<~SQL
# DROP TABLE bubbles_search_index;
# SQL
end
end
+1 -1
View File
@@ -3,7 +3,7 @@ class CreateCommands < ActiveRecord::Migration[8.1]
create_table :commands do |t|
t.references :user, null: false, foreign_key: true, index: true
t.string :type
t.json :data, default: {}
t.column :data, :json, default: -> { '(JSON_OBJECT())' }
t.timestamps
@@ -1,5 +0,0 @@
class DropActionTextMarkdowns < ActiveRecord::Migration[8.1]
def change
drop_table :action_text_markdowns
end
end
@@ -1,8 +1,9 @@
class AddDescriptionToCardsSearchIndex < ActiveRecord::Migration[8.1]
def change
execute <<~SQL
DROP TABLE cards_search_index;
SQL
create_virtual_table :cards_search_index, "fts5", [ "title", "description" ]
# # TODO:PLANB: need to replace SQLite FTS
# execute <<~SQL
# DROP TABLE cards_search_index;
# SQL
# create_virtual_table :cards_search_index, "fts5", [ "title", "description" ]
end
end
@@ -6,7 +6,10 @@ class CreateSearchQueries < ActiveRecord::Migration[8.1]
t.timestamps
t.index %i[ user_id terms ]
# MySQL has a max key length of 3072 bytes. With utf8mb4 (4 bytes/char),
# we can only index 768 characters. Using a 255 character prefix is
# sufficient for search query lookups while staying well under the limit.
t.index "user_id, terms(255)", name: "index_search_queries_on_user_id_and_terms"
end
end
end
+5 -4
View File
@@ -1,9 +1,10 @@
class AddStemming < ActiveRecord::Migration[8.1]
def change
drop_table :cards_search_index
drop_table :comments_search_index
# # TODO:PLANB: need to replace SQLite FTS
# drop_table :cards_search_index
# drop_table :comments_search_index
create_virtual_table "cards_search_index", "fts5", [ "title", "description", "tokenize='porter'" ]
create_virtual_table "comments_search_index", "fts5", [ "body", "tokenize='porter'" ]
# create_virtual_table "cards_search_index", "fts5", [ "title", "description", "tokenize='porter'" ]
# create_virtual_table "comments_search_index", "fts5", [ "body", "tokenize='porter'" ]
end
end
@@ -3,7 +3,7 @@ class CreateEventActivitySummaries < ActiveRecord::Migration[8.1]
create_table :event_activity_summaries do |t|
t.string :key, null: false
t.text :contents, null: false
t.json :data, default: {}
t.column :data, :json, default: -> { '(JSON_OBJECT())' }
t.timestamps
+4 -1
View File
@@ -4,10 +4,13 @@ class CreateWebhooks < ActiveRecord::Migration[8.1]
t.boolean :active, default: true, null: false
t.string :name
t.text :url, null: false
t.text :subscribed_actions, index: true
t.text :subscribed_actions
t.string :signing_secret, null: false
t.timestamps
# MySQL requires a prefix length for TEXT column indexes
t.index "subscribed_actions(255)", name: "index_webhooks_on_subscribed_actions"
end
end
end
@@ -10,9 +10,10 @@ class CreateAccountJoinCodes < ActiveRecord::Migration[8.1]
reversible do |dir|
dir.up do
# MySQL uses NOW() instead of SQLite's datetime('now')
execute <<-SQL
INSERT INTO account_join_codes (code, usage_count, usage_limit, created_at, updated_at)
SELECT join_code, 0, 10, datetime('now'), datetime('now')
SELECT join_code, 0, 10, NOW(), NOW()
FROM accounts
WHERE join_code IS NOT NULL;
SQL
@@ -3,9 +3,10 @@ class MoveEmailToIdentity < ActiveRecord::Migration[8.1]
add_column :identities, :email_address, :string
# Create identities for each unique email
# MySQL uses NOW() instead of SQLite's datetime('now')
execute <<-SQL
INSERT INTO identities (email_address, created_at, updated_at)
SELECT DISTINCT email_address, datetime('now'), datetime('now')
SELECT DISTINCT email_address, NOW(), NOW()
FROM memberships
WHERE email_address IS NOT NULL
AND email_address NOT IN (SELECT email_address FROM identities WHERE email_address IS NOT NULL);
@@ -1,14 +1,11 @@
class CreateIdentitySessions < ActiveRecord::Migration[8.2]
def change
create_table "sessions", force: :cascade do |t|
t.datetime "created_at", null: false
t.references "identity", null: false, foreign_key: true
t.string "ip_address"
t.datetime "updated_at", null: false
t.string "user_agent"
t.integer "identity_id", null: false
t.index [ "identity_id" ], name: "index_sessions_on_identity_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_foreign_key "sessions", "identities"
end
end
@@ -1,13 +1,15 @@
class AddUniqueConstraintOnIdentityIdAndTenantToMemberships < ActiveRecord::Migration[8.2]
def change
# Remove duplicates, keeping the youngest membership for each identity_id + tenant combination
# MySQL doesn't allow referencing the target table in a subquery, so we use a JOIN approach
execute <<-SQL
DELETE FROM memberships
WHERE id NOT IN (
SELECT MAX(id)
DELETE m1 FROM memberships m1
LEFT JOIN (
SELECT MAX(id) as max_id
FROM memberships
GROUP BY identity_id, tenant
)
) m2 ON m1.id = m2.max_id
WHERE m2.max_id IS NULL
SQL
add_index :memberships, %i[ tenant identity_id ], unique: true
Generated
+108 -115
View File
@@ -11,20 +11,20 @@
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.2].define(version: 2025_11_06_154151) do
create_table "accesses", force: :cascade do |t|
create_table "accesses", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.datetime "accessed_at"
t.integer "board_id", null: false
t.bigint "board_id", null: false
t.datetime "created_at", null: false
t.string "involvement", default: "access_only", null: false
t.datetime "updated_at", null: false
t.integer "user_id", null: false
t.bigint "user_id", null: false
t.index ["accessed_at"], name: "index_accesses_on_accessed_at", order: :desc
t.index ["board_id", "user_id"], name: "index_accesses_on_board_id_and_user_id", unique: true
t.index ["board_id"], name: "index_accesses_on_board_id"
t.index ["user_id"], name: "index_accesses_on_user_id"
end
create_table "account_join_codes", force: :cascade do |t|
create_table "account_join_codes", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "code", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
@@ -33,7 +33,7 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_06_154151) do
t.index ["code"], name: "index_account_join_codes_on_code", unique: true
end
create_table "accounts", force: :cascade do |t|
create_table "accounts", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.datetime "created_at", null: false
t.integer "external_account_id"
t.string "name", null: false
@@ -41,8 +41,8 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_06_154151) do
t.index ["external_account_id"], name: "index_accounts_on_external_account_id", unique: true
end
create_table "action_text_rich_texts", force: :cascade do |t|
t.text "body"
create_table "action_text_rich_texts", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.text "body", size: :long
t.datetime "created_at", null: false
t.string "name", null: false
t.bigint "record_id", null: false
@@ -51,19 +51,17 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_06_154151) do
t.index ["record_type", "record_id", "name"], name: "index_action_text_rich_texts_uniqueness", unique: true
end
create_table "active_storage_attachments", force: :cascade do |t|
create_table "active_storage_attachments", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.bigint "blob_id", null: false
t.datetime "created_at", null: false
t.string "name", null: false
t.bigint "record_id", null: false
t.string "record_type", null: false
t.string "slug"
t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
t.index ["slug"], name: "index_active_storage_attachments_on_slug", unique: true
end
create_table "active_storage_blobs", force: :cascade do |t|
create_table "active_storage_blobs", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.bigint "byte_size", null: false
t.string "checksum"
t.string "content_type"
@@ -75,38 +73,38 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_06_154151) do
t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
end
create_table "active_storage_variant_records", force: :cascade do |t|
create_table "active_storage_variant_records", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.bigint "blob_id", null: false
t.string "variation_digest", null: false
t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
end
create_table "assignees_filters", id: false, force: :cascade do |t|
t.integer "assignee_id", null: false
t.integer "filter_id", null: false
create_table "assignees_filters", id: false, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.bigint "assignee_id", null: false
t.bigint "filter_id", null: false
t.index ["assignee_id"], name: "index_assignees_filters_on_assignee_id"
t.index ["filter_id"], name: "index_assignees_filters_on_filter_id"
end
create_table "assigners_filters", id: false, force: :cascade do |t|
t.integer "assigner_id", null: false
t.integer "filter_id", null: false
create_table "assigners_filters", id: false, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.bigint "assigner_id", null: false
t.bigint "filter_id", null: false
t.index ["assigner_id"], name: "index_assigners_filters_on_assigner_id"
t.index ["filter_id"], name: "index_assigners_filters_on_filter_id"
end
create_table "assignments", force: :cascade do |t|
t.integer "assignee_id", null: false
create_table "assignments", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.bigint "assignee_id", null: false
t.integer "assigner_id", null: false
t.integer "card_id", null: false
t.bigint "card_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["assignee_id", "card_id"], name: "index_assignments_on_assignee_id_and_card_id", unique: true
t.index ["card_id"], name: "index_assignments_on_card_id"
end
create_table "board_publications", force: :cascade do |t|
t.integer "board_id", null: false
create_table "board_publications", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.bigint "board_id", null: false
t.datetime "created_at", null: false
t.string "key"
t.datetime "updated_at", null: false
@@ -114,31 +112,31 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_06_154151) do
t.index ["key"], name: "index_board_publications_on_key", unique: true
end
create_table "boards", force: :cascade do |t|
create_table "boards", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.boolean "all_access", default: false, null: false
t.datetime "created_at", null: false
t.integer "creator_id", null: false
t.bigint "creator_id", null: false
t.string "name", null: false
t.datetime "updated_at", null: false
t.index ["creator_id"], name: "index_boards_on_creator_id"
end
create_table "boards_filters", id: false, force: :cascade do |t|
t.integer "board_id", null: false
t.integer "filter_id", null: false
create_table "boards_filters", id: false, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.bigint "board_id", null: false
t.bigint "filter_id", null: false
t.index ["board_id"], name: "index_boards_filters_on_board_id"
t.index ["filter_id"], name: "index_boards_filters_on_filter_id"
end
create_table "card_activity_spikes", force: :cascade do |t|
t.integer "card_id", null: false
create_table "card_activity_spikes", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.bigint "card_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["card_id"], name: "index_card_activity_spikes_on_card_id"
end
create_table "card_engagements", force: :cascade do |t|
t.integer "card_id"
create_table "card_engagements", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.bigint "card_id"
t.datetime "created_at", null: false
t.string "status", default: "doing", null: false
t.datetime "updated_at", null: false
@@ -146,30 +144,30 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_06_154151) do
t.index ["status"], name: "index_card_engagements_on_status"
end
create_table "card_goldnesses", force: :cascade do |t|
t.integer "card_id", null: false
create_table "card_goldnesses", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.bigint "card_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["card_id"], name: "index_card_goldnesses_on_card_id", unique: true
end
create_table "card_not_nows", force: :cascade do |t|
t.integer "card_id", null: false
create_table "card_not_nows", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.bigint "card_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
t.bigint "user_id"
t.index ["card_id"], name: "index_card_not_nows_on_card_id", unique: true
t.index ["user_id"], name: "index_card_not_nows_on_user_id"
end
create_table "cards", force: :cascade do |t|
t.integer "board_id", null: false
t.integer "column_id"
create_table "cards", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.bigint "board_id", null: false
t.bigint "column_id"
t.datetime "created_at", null: false
t.integer "creator_id", null: false
t.date "due_on"
t.datetime "last_active_at", null: false
t.text "status", default: "drafted", null: false
t.string "status", default: "drafted", null: false
t.string "title"
t.datetime "updated_at", null: false
t.index ["board_id"], name: "index_cards_on_board_id"
@@ -177,25 +175,25 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_06_154151) do
t.index ["last_active_at", "status"], name: "index_cards_on_last_active_at_and_status"
end
create_table "closers_filters", id: false, force: :cascade do |t|
create_table "closers_filters", id: false, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.integer "closer_id", null: false
t.integer "filter_id", null: false
t.index ["closer_id"], name: "index_closers_filters_on_closer_id"
t.index ["filter_id"], name: "index_closers_filters_on_filter_id"
end
create_table "closures", force: :cascade do |t|
t.integer "card_id", null: false
create_table "closures", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.bigint "card_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
t.bigint "user_id"
t.index ["card_id", "created_at"], name: "index_closures_on_card_id_and_created_at"
t.index ["card_id"], name: "index_closures_on_card_id", unique: true
t.index ["user_id"], name: "index_closures_on_user_id"
end
create_table "columns", force: :cascade do |t|
t.integer "board_id", null: false
create_table "columns", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.bigint "board_id", null: false
t.string "color", null: false
t.datetime "created_at", null: false
t.string "name", null: false
@@ -205,24 +203,24 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_06_154151) do
t.index ["board_id"], name: "index_columns_on_board_id"
end
create_table "comments", force: :cascade do |t|
t.integer "card_id", null: false
create_table "comments", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.bigint "card_id", null: false
t.datetime "created_at", null: false
t.integer "creator_id", null: false
t.datetime "updated_at", null: false
t.index ["card_id"], name: "index_comments_on_card_id"
end
create_table "creators_filters", id: false, force: :cascade do |t|
create_table "creators_filters", id: false, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.integer "creator_id", null: false
t.integer "filter_id", null: false
t.index ["creator_id"], name: "index_creators_filters_on_creator_id"
t.index ["filter_id"], name: "index_creators_filters_on_filter_id"
end
create_table "entropies", force: :cascade do |t|
create_table "entropies", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.bigint "auto_postpone_period", default: 2592000, null: false
t.integer "container_id", null: false
t.bigint "container_id", null: false
t.string "container_type", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
@@ -230,14 +228,14 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_06_154151) do
t.index ["container_type", "container_id"], name: "index_entropy_configurations_on_container", unique: true
end
create_table "events", force: :cascade do |t|
create_table "events", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "action", null: false
t.integer "board_id", null: false
t.bigint "board_id", null: false
t.datetime "created_at", null: false
t.integer "creator_id", null: false
t.integer "eventable_id", null: false
t.bigint "creator_id", null: false
t.bigint "eventable_id", null: false
t.string "eventable_type", null: false
t.json "particulars", default: {}
t.json "particulars", default: -> { "(json_object())" }
t.datetime "updated_at", null: false
t.index ["action"], name: "index_events_on_summary_id_and_action"
t.index ["board_id", "action", "created_at"], name: "index_events_on_board_id_and_action_and_created_at"
@@ -246,43 +244,43 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_06_154151) do
t.index ["eventable_type", "eventable_id"], name: "index_events_on_eventable"
end
create_table "filters", force: :cascade do |t|
create_table "filters", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.datetime "created_at", null: false
t.integer "creator_id", null: false
t.json "fields", default: {}, null: false
t.bigint "creator_id", null: false
t.json "fields", default: -> { "(json_object())" }, null: false
t.string "params_digest", null: false
t.datetime "updated_at", null: false
t.index ["creator_id", "params_digest"], name: "index_filters_on_creator_id_and_params_digest", unique: true
end
create_table "filters_tags", id: false, force: :cascade do |t|
t.integer "filter_id", null: false
t.integer "tag_id", null: false
create_table "filters_tags", id: false, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.bigint "filter_id", null: false
t.bigint "tag_id", null: false
t.index ["filter_id"], name: "index_filters_tags_on_filter_id"
t.index ["tag_id"], name: "index_filters_tags_on_tag_id"
end
create_table "identities", force: :cascade do |t|
create_table "identities", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.datetime "created_at", null: false
t.string "email_address", null: false
t.datetime "updated_at", null: false
t.index ["email_address"], name: "index_identities_on_email_address", unique: true
end
create_table "magic_links", force: :cascade do |t|
create_table "magic_links", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "code", null: false
t.datetime "created_at", null: false
t.datetime "expires_at", null: false
t.integer "identity_id"
t.bigint "identity_id"
t.datetime "updated_at", null: false
t.index ["code"], name: "index_magic_links_on_code", unique: true
t.index ["expires_at"], name: "index_magic_links_on_expires_at"
t.index ["identity_id"], name: "index_magic_links_on_identity_id"
end
create_table "memberships", force: :cascade do |t|
create_table "memberships", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.datetime "created_at", null: false
t.integer "identity_id", null: false
t.bigint "identity_id", null: false
t.string "join_code"
t.string "tenant", null: false
t.datetime "updated_at", null: false
@@ -291,11 +289,11 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_06_154151) do
t.index ["tenant"], name: "index_memberships_on_user_tenant_and_user_id"
end
create_table "mentions", force: :cascade do |t|
create_table "mentions", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.datetime "created_at", null: false
t.integer "mentionee_id", null: false
t.integer "mentioner_id", null: false
t.integer "source_id", null: false
t.bigint "mentionee_id", null: false
t.bigint "mentioner_id", null: false
t.bigint "source_id", null: false
t.string "source_type", null: false
t.datetime "updated_at", null: false
t.index ["mentionee_id"], name: "index_mentions_on_mentionee_id"
@@ -303,50 +301,50 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_06_154151) do
t.index ["source_type", "source_id"], name: "index_mentions_on_source"
end
create_table "notification_bundles", force: :cascade do |t|
create_table "notification_bundles", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "ends_at", null: false
t.datetime "starts_at", null: false
t.integer "status", default: 0, null: false
t.datetime "updated_at", null: false
t.integer "user_id", null: false
t.bigint "user_id", null: false
t.index ["ends_at", "status"], name: "index_notification_bundles_on_ends_at_and_status"
t.index ["user_id", "starts_at", "ends_at"], name: "idx_on_user_id_starts_at_ends_at_7eae5d3ac5"
t.index ["user_id", "status"], name: "index_notification_bundles_on_user_id_and_status"
end
create_table "notifications", force: :cascade do |t|
create_table "notifications", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.datetime "created_at", null: false
t.integer "creator_id"
t.bigint "creator_id"
t.datetime "read_at"
t.integer "source_id", null: false
t.bigint "source_id", null: false
t.string "source_type", null: false
t.datetime "updated_at", null: false
t.integer "user_id", null: false
t.bigint "user_id", null: false
t.index ["creator_id"], name: "index_notifications_on_creator_id"
t.index ["source_type", "source_id"], name: "index_notifications_on_source"
t.index ["user_id", "read_at", "created_at"], name: "index_notifications_on_user_id_and_read_at_and_created_at", order: { read_at: :desc, created_at: :desc }
t.index ["user_id"], name: "index_notifications_on_user_id"
end
create_table "pins", force: :cascade do |t|
t.integer "card_id", null: false
create_table "pins", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.bigint "card_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id", null: false
t.bigint "user_id", null: false
t.index ["card_id", "user_id"], name: "index_pins_on_card_id_and_user_id", unique: true
t.index ["card_id"], name: "index_pins_on_card_id"
t.index ["user_id"], name: "index_pins_on_user_id"
end
create_table "push_subscriptions", force: :cascade do |t|
create_table "push_subscriptions", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "auth_key"
t.datetime "created_at", null: false
t.string "endpoint"
t.string "p256dh_key"
t.datetime "updated_at", null: false
t.string "user_agent"
t.integer "user_id", null: false
t.bigint "user_id", null: false
t.index ["endpoint", "p256dh_key", "auth_key"], name: "idx_on_endpoint_p256dh_key_auth_key_7553014576"
t.index ["endpoint"], name: "index_push_subscriptions_on_endpoint"
t.index ["user_agent"], name: "index_push_subscriptions_on_user_agent"
@@ -354,7 +352,7 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_06_154151) do
t.index ["user_id"], name: "index_push_subscriptions_on_user_id"
end
create_table "reactions", force: :cascade do |t|
create_table "reactions", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.integer "comment_id", null: false
t.string "content", limit: 16, null: false
t.datetime "created_at", null: false
@@ -364,32 +362,32 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_06_154151) do
t.index ["reacter_id"], name: "index_reactions_on_reacter_id"
end
create_table "search_queries", force: :cascade do |t|
create_table "search_queries", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.datetime "created_at", null: false
t.string "terms", limit: 2000, null: false
t.datetime "updated_at", null: false
t.integer "user_id", null: false
t.index ["user_id", "terms"], name: "index_search_queries_on_user_id_and_terms"
t.bigint "user_id", null: false
t.index ["user_id", "terms"], name: "index_search_queries_on_user_id_and_terms", length: { terms: 255 }
t.index ["user_id", "updated_at"], name: "index_search_queries_on_user_id_and_updated_at", unique: true
t.index ["user_id"], name: "index_search_queries_on_user_id"
end
create_table "search_results", force: :cascade do |t|
create_table "search_results", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "sessions", force: :cascade do |t|
create_table "sessions", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.datetime "created_at", null: false
t.integer "identity_id", null: false
t.bigint "identity_id", null: false
t.string "ip_address"
t.datetime "updated_at", null: false
t.string "user_agent"
t.index ["identity_id"], name: "index_sessions_on_identity_id"
end
create_table "steps", force: :cascade do |t|
t.integer "card_id", null: false
create_table "steps", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.bigint "card_id", null: false
t.boolean "completed", default: false, null: false
t.text "content", null: false
t.datetime "created_at", null: false
@@ -398,33 +396,33 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_06_154151) do
t.index ["card_id"], name: "index_steps_on_card_id"
end
create_table "taggings", force: :cascade do |t|
t.integer "card_id", null: false
create_table "taggings", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.bigint "card_id", null: false
t.datetime "created_at", null: false
t.integer "tag_id", null: false
t.bigint "tag_id", null: false
t.datetime "updated_at", null: false
t.index ["card_id", "tag_id"], name: "index_taggings_on_card_id_and_tag_id", unique: true
t.index ["tag_id"], name: "index_taggings_on_tag_id"
end
create_table "tags", force: :cascade do |t|
create_table "tags", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.datetime "created_at", null: false
t.string "title"
t.datetime "updated_at", null: false
t.index ["title"], name: "index_tags_on_title", unique: true
end
create_table "user_settings", force: :cascade do |t|
create_table "user_settings", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.integer "bundle_email_frequency", default: 0, null: false
t.datetime "created_at", null: false
t.string "timezone_name"
t.datetime "updated_at", null: false
t.integer "user_id", null: false
t.bigint "user_id", null: false
t.index ["user_id", "bundle_email_frequency"], name: "index_user_settings_on_user_id_and_bundle_email_frequency"
t.index ["user_id"], name: "index_user_settings_on_user_id"
end
create_table "users", force: :cascade do |t|
create_table "users", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.boolean "active", default: true, null: false
t.datetime "created_at", null: false
t.integer "membership_id"
@@ -435,41 +433,41 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_06_154151) do
t.index ["role"], name: "index_users_on_role"
end
create_table "watches", force: :cascade do |t|
t.integer "card_id", null: false
create_table "watches", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.bigint "card_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id", null: false
t.bigint "user_id", null: false
t.boolean "watching", default: true, null: false
t.index ["card_id"], name: "index_watches_on_card_id"
t.index ["user_id", "card_id"], name: "index_watches_on_user_id_and_card_id"
t.index ["user_id"], name: "index_watches_on_user_id"
end
create_table "webhook_delinquency_trackers", force: :cascade do |t|
create_table "webhook_delinquency_trackers", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.integer "consecutive_failures_count", default: 0
t.datetime "created_at", null: false
t.datetime "first_failure_at"
t.datetime "updated_at", null: false
t.integer "webhook_id", null: false
t.bigint "webhook_id", null: false
t.index ["webhook_id"], name: "index_webhook_delinquency_trackers_on_webhook_id"
end
create_table "webhook_deliveries", force: :cascade do |t|
create_table "webhook_deliveries", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.datetime "created_at", null: false
t.integer "event_id", null: false
t.bigint "event_id", null: false
t.text "request"
t.text "response"
t.string "state", null: false
t.datetime "updated_at", null: false
t.integer "webhook_id", null: false
t.bigint "webhook_id", null: false
t.index ["event_id"], name: "index_webhook_deliveries_on_event_id"
t.index ["webhook_id"], name: "index_webhook_deliveries_on_webhook_id"
end
create_table "webhooks", force: :cascade do |t|
create_table "webhooks", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.boolean "active", default: true, null: false
t.integer "board_id", null: false
t.bigint "board_id", null: false
t.datetime "created_at", null: false
t.string "name"
t.string "signing_secret", null: false
@@ -477,7 +475,7 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_06_154151) do
t.datetime "updated_at", null: false
t.text "url", null: false
t.index ["board_id"], name: "index_webhooks_on_board_id"
t.index ["subscribed_actions"], name: "index_webhooks_on_subscribed_actions"
t.index ["subscribed_actions"], name: "index_webhooks_on_subscribed_actions", length: 255
end
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
@@ -515,9 +513,4 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_06_154151) do
add_foreign_key "webhook_deliveries", "events"
add_foreign_key "webhook_deliveries", "webhooks"
add_foreign_key "webhooks", "boards"
# Virtual tables defined in this database.
# Note that virtual tables may not work with other database engines. Be careful if changing database.
create_virtual_table "cards_search_index", "fts5", ["title", "description", "tokenize='porter'"]
create_virtual_table "comments_search_index", "fts5", ["body", "tokenize='porter'"]
end