Fix schema incomatibilities with production

This commit is contained in:
Stanko K.R.
2025-11-17 20:31:27 +01:00
parent a7ff18c437
commit b7404087bd
4 changed files with 93 additions and 35 deletions
@@ -0,0 +1,14 @@
class ChangeEndpointToTextInPushSubscriptions < ActiveRecord::Migration[8.2]
def change
# Remove foreign key first, then the index
remove_foreign_key :push_subscriptions, :users
remove_index :push_subscriptions, column: [:user_id, :endpoint]
# Change the column type
change_column :push_subscriptions, :endpoint, :text
# Re-add the index and foreign key
add_index :push_subscriptions, [:user_id, :endpoint], unique: true, length: { endpoint: 255 }
add_foreign_key :push_subscriptions, :users
end
end
@@ -0,0 +1,5 @@
class ChangeExternalAccountIdToBigintInAccounts < ActiveRecord::Migration[8.2]
def change
change_column :accounts, :external_account_id, :bigint
end
end
Generated
+4 -4
View File
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.2].define(version: 2025_11_14_183203) do
ActiveRecord::Schema[8.2].define(version: 2025_11_17_192434) do
create_table "accesses", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.datetime "accessed_at"
t.uuid "account_id", null: false
@@ -38,7 +38,7 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_14_183203) do
create_table "accounts", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.bigint "cards_count", default: 0, null: false
t.datetime "created_at", null: false
t.integer "external_account_id"
t.bigint "external_account_id"
t.string "name", null: false
t.datetime "updated_at", null: false
t.index ["external_account_id"], name: "index_accounts_on_external_account_id", unique: true
@@ -375,13 +375,13 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_14_183203) do
t.uuid "account_id", null: false
t.string "auth_key"
t.datetime "created_at", null: false
t.string "endpoint"
t.text "endpoint"
t.string "p256dh_key"
t.datetime "updated_at", null: false
t.string "user_agent"
t.uuid "user_id", null: false
t.index ["account_id"], name: "index_push_subscriptions_on_account_id"
t.index ["user_id", "endpoint"], name: "index_push_subscriptions_on_user_id_and_endpoint", unique: true
t.index ["user_id", "endpoint"], name: "index_push_subscriptions_on_user_id_and_endpoint", unique: true, length: { endpoint: 255 }
end
create_table "reactions", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+70 -31
View File
@@ -4,6 +4,8 @@ require_relative "../config/environment"
require "pathname"
require "optparse"
class AccountExistsError < StandardError; end
class Import
FIX_LINK_HOSTS = {
"fizzy.37signals.com" => "app.fizzy.do",
@@ -11,12 +13,13 @@ class Import
"app.box-car.com" => "app.fizzy.do"
}.freeze
attr_reader :db_path, :untenanted_db_path
attr_reader :db_path, :untenanted_db_path, :skip_already_imported
attr_reader :account, :tenant, :mapping
def initialize(db_path, untenanted_db_path)
def initialize(db_path, untenanted_db_path, skip_already_imported: false)
@db_path = Pathname(db_path)
@untenanted_db_path = Pathname(untenanted_db_path)
@skip_already_imported = skip_already_imported
@mapping = nil
end
@@ -31,21 +34,25 @@ class Import
ActiveRecord::Base.no_touching do
Current.with(account: account) do
Webhook.skip_callback(:create, :after, :create_delinquency_tracker!)
Comment.skip_callback(:commit, :after, :watch_card_by_creator)
Comment.skip_callback(:commit, :after, :track_creation)
Mention.skip_callback(:commit, :after, :watch_source_by_mentionee)
Notification.skip_callback(:commit, :after, :broadcast_unread)
Notification.skip_callback(:create, :after, :bundle)
Reaction.skip_callback(:create, :after, :register_card_activity)
Card.skip_callback(:save, :before, :set_default_title)
Card.skip_callback(:update, :after, :handle_board_change)
ActiveStorage::Blob.skip_callback(:update, :after, :touch_attachments)
ActiveStorage::Blob.skip_callback(:commit, :after, :update_service_metadata)
ActiveStorage::Attachment.skip_callback(:commit, :after, :mirror_blob_later)
ActiveStorage::Attachment.skip_callback(:commit, :after, :analyze_blob_later)
ActiveStorage::Attachment.skip_callback(:commit, :after, :transform_variants_later)
ActiveStorage::Attachment.skip_callback(:commit, :after, :purge_dependent_blob_later)
begin
Webhook.skip_callback(:create, :after, :create_delinquency_tracker!)
Comment.skip_callback(:commit, :after, :watch_card_by_creator)
Comment.skip_callback(:commit, :after, :track_creation)
Mention.skip_callback(:commit, :after, :watch_source_by_mentionee)
Notification.skip_callback(:commit, :after, :broadcast_unread)
Notification.skip_callback(:create, :after, :bundle)
Reaction.skip_callback(:create, :after, :register_card_activity)
Card.skip_callback(:save, :before, :set_default_title)
Card.skip_callback(:update, :after, :handle_board_change)
ActiveStorage::Blob.skip_callback(:update, :after, :touch_attachments)
ActiveStorage::Blob.skip_callback(:commit, :after, :update_service_metadata)
ActiveStorage::Attachment.skip_callback(:commit, :after, :mirror_blob_later)
ActiveStorage::Attachment.skip_callback(:commit, :after, :analyze_blob_later)
ActiveStorage::Attachment.skip_callback(:commit, :after, :transform_variants_later)
ActiveStorage::Attachment.skip_callback(:commit, :after, :purge_dependent_blob_later)
rescue => e
puts "⚠️ Warning: Could not skip some callbacks: #{e.message}"
end
Event.suppress do
copy_users
@@ -81,6 +88,8 @@ class Import
end
puts "🎉 Import complete! (#{duration.round(2)}s)"
rescue AccountExistsError => e
raise e unless skip_already_imported
end
private
@@ -115,7 +124,7 @@ class Import
new_identity = Identity.find_or_create_by!(email_address: membership.identity.email_address)
if Account.all.exists?(external_account_id: account.external_account_id)
raise "Account already exists"
raise AccountExistsError, "Account already exists"
else
@account = Account.create_with_admin_user(
account: {
@@ -542,8 +551,8 @@ class Import
end
Entropy.find_or_create_by!(account_id: account.id, container_type: old_entropy.container_type, container_id: container_id) do |entropy|
entropy.auto_postpone_period = old_entropy.auto_postpone_period,
entropy.created_at = old_entropy.created_at,
entropy.auto_postpone_period = old_entropy.auto_postpone_period || 0
entropy.created_at = old_entropy.created_at
entropy.updated_at = old_entropy.updated_at
end
end
@@ -1290,10 +1299,20 @@ class Models
end
end
options = {}
options = {
skip_already_imported: false
}
parser = OptionParser.new do |parser|
parser.banner = "Usage: #{$PROGRAM_NAME} <db_path> <untenanted_db_path>"
parser.banner = "Usage: #{$PROGRAM_NAME} [options] <tenanted_db_path>..."
parser.on("--untenanted-db-path PATH", "Path to the untenanted database") do |path|
options[:untenanted_db_path] = path
end
parser.on("--skip-already-imported", "Skip import if account already exists") do
options[:skip_already_imported] = true
end
parser.on("-h", "--help", "Show this help message") do
puts parser
@@ -1303,20 +1322,40 @@ end
parser.parse!
db_path = ARGV[0]
untenanted_db_path = ARGV[1]
untenanted_db_path = options[:untenanted_db_path]
tenanted_db_paths = ARGV
if db_path.nil? || untenanted_db_path.nil?
$stderr.puts "Error: both db_path and untenanted_db_path are required"
if untenanted_db_path.nil?
$stderr.puts "Error: --untenanted-db-path is required"
$stderr.puts
$stderr.puts parser
exit 1
end
begin
Import.new(db_path, untenanted_db_path).import_database
rescue => e
$stderr.puts "Error: #{e.message}"
$stderr.puts e.backtrace.join("\n") if ENV["DEBUG"]
if tenanted_db_paths.empty?
$stderr.puts "Error: at least one tenanted database path is required"
$stderr.puts
$stderr.puts parser
exit 1
end
total_imported = 0
duration = ActiveSupport::Benchmark.realtime do
tenanted_db_paths.each_with_index do |db_path, index|
puts
puts "="*80
puts "Processing database #{index + 1}/#{tenanted_db_paths.size}: #{db_path}"
puts "="*80
Import.new(db_path, untenanted_db_path, skip_already_imported: options[:skip_already_imported]).import_database
total_imported += 1
end
end
puts
puts "="*80
puts "Summary:"
puts " Imported: #{total_imported}"
puts " Total time: #{duration.round(2)} seconds"
puts "="*80