Fix schema incomatibilities with production
This commit is contained in:
@@ -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
@@ -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|
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user