Clean up the /scripts directory
Not removing anything, just organizing scripts into subdirectories to make it a bit more readable.
This commit is contained in:
Executable
+117
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require_relative "../config/environment"
|
||||
|
||||
ActiveRecord::Base.logger = Logger.new(File::NULL)
|
||||
|
||||
class BootstrapSignalId
|
||||
def initialize(dry_run: false)
|
||||
@dry_run = dry_run
|
||||
end
|
||||
|
||||
def run
|
||||
SignalId::Database.on_master do
|
||||
ApplicationRecord.with_each_tenant do |tenant|
|
||||
puts "\n# tenant: #{tenant}"
|
||||
|
||||
next unless check_account_preconditions
|
||||
create_signal_id_account if Account.sole.queenbee_id.nil?
|
||||
|
||||
create_signal_id_users
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def check_account_preconditions
|
||||
unless Account.count == 1
|
||||
puts "There are #{Account.count} accounts, but exactly one is expected."
|
||||
false
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def create_signal_id_account
|
||||
owner = SignalId::Identity.find_by_email_address!("kevin@37signals.com")
|
||||
print_identity("New owner is:", owner)
|
||||
|
||||
unless @dry_run
|
||||
qbattr = queenbee_account_attributes(owner)
|
||||
queenbee_account = Queenbee::Remote::Account.create!(qbattr)
|
||||
|
||||
signal_id_account = SignalId::Account.find_by!(queenbee_id: queenbee_account.id)
|
||||
signal_id_account.update_column :subdomain, ApplicationRecord.current_tenant
|
||||
|
||||
account = Account.sole
|
||||
account.queenbee_id = queenbee_account.id
|
||||
account.name = ApplicationRecord.current_tenant
|
||||
account.save!
|
||||
end
|
||||
end
|
||||
|
||||
def create_signal_id_users
|
||||
signal_account = Account.sole.signal_account
|
||||
|
||||
User.find_each do |user|
|
||||
if !user.system? && user.signal_user_id.nil?
|
||||
signal_identities = SignalId::Identity.where(email_address: user.email_address)
|
||||
if signal_identities.length > 1
|
||||
puts "Multiple identities found for #{user.email_address}:"
|
||||
signal_identities.each { |identity| print_identity(" - ", identity) }
|
||||
signal_identity = signal_identities.first
|
||||
elsif signal_identities.length == 1
|
||||
signal_identity = signal_identities.first
|
||||
print_identity("Identity for #{user.email_address}:", signal_identity)
|
||||
else
|
||||
puts "No identity found for #{user.name} (#{user.email_address})"
|
||||
signal_identity = nil
|
||||
end
|
||||
|
||||
if signal_identity
|
||||
unless @dry_run
|
||||
signal_user = SignalId::User.find_or_create_by!(identity: signal_identity, account: signal_account)
|
||||
|
||||
user.signal_user_id = signal_user.id
|
||||
user.save!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def print_identity(message = "Identity:", identity)
|
||||
pad = " " * message.length
|
||||
puts "#{message} #{identity.name} (#{identity.email_address})"
|
||||
puts "#{pad } ID: #{identity.id}"
|
||||
puts "#{pad } Username: #{identity.username}"
|
||||
end
|
||||
|
||||
def queenbee_account_attributes(signal_identity)
|
||||
{
|
||||
skip_remote: true, # Fizzy creates its own local account
|
||||
product_name: "fizzy",
|
||||
name: account_name,
|
||||
owner_identity_id: signal_identity.id,
|
||||
trial: false,
|
||||
subscription: subscription_attributes,
|
||||
remote_request: request_attributes
|
||||
}
|
||||
end
|
||||
|
||||
def subscription_attributes
|
||||
subscription = FreeV1Subscription
|
||||
{ name: subscription.to_param, price: subscription.price }
|
||||
end
|
||||
|
||||
def request_attributes
|
||||
{ user_agent: "script/bootstrap-signal-id.rb" }
|
||||
end
|
||||
|
||||
def account_name
|
||||
name = ApplicationRecord.current_tenant
|
||||
name += " (Beta)" if Rails.env.beta?
|
||||
name
|
||||
end
|
||||
end
|
||||
|
||||
BootstrapSignalId.new(dry_run: false).run
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require_relative "../config/environment"
|
||||
require "redcarpet"
|
||||
require "nokogiri"
|
||||
|
||||
class ActionText::Markdown < ApplicationRecord
|
||||
belongs_to :record, polymorphic: true
|
||||
end
|
||||
|
||||
class MarkdownToActionTextConverter
|
||||
ATTACHMENT_URL_REGEX = %r{/u/(?<slug>[^\/\s\)]+)}
|
||||
|
||||
def initialize(html)
|
||||
@doc = Nokogiri::HTML::DocumentFragment.parse(html)
|
||||
@attachments = []
|
||||
end
|
||||
|
||||
def convert
|
||||
process_images
|
||||
process_links
|
||||
[ @doc.to_html, @attachments ]
|
||||
end
|
||||
|
||||
private
|
||||
def process_images
|
||||
@doc.css("img").each do |img|
|
||||
src = img["src"].presence
|
||||
if src && match = src.match(ATTACHMENT_URL_REGEX)
|
||||
if (attachment = find_attachment(match[:slug]))
|
||||
img.replace(build_attachment_node(attachment))
|
||||
@attachments << attachment
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def process_links
|
||||
@doc.css("a").each do |link|
|
||||
href = link["href"].presence
|
||||
|
||||
if href && match = href.match(ATTACHMENT_URL_REGEX)
|
||||
if (attachment = find_attachment(match[:slug]))
|
||||
link.replace(build_attachment_node(attachment))
|
||||
@attachments << attachment
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def build_attachment_node(attachment)
|
||||
html = ActionText::Attachment.from_attachable(attachment).to_html
|
||||
fragment = Nokogiri::HTML::DocumentFragment.parse(html)
|
||||
|
||||
node = fragment.at_css("action-text-attachment")
|
||||
node["url"] = Rails.application.routes.url_helpers.rails_blob_path(attachment.blob, only_path: true)
|
||||
|
||||
fragment
|
||||
end
|
||||
|
||||
def find_attachment(slug)
|
||||
ActiveStorage::Attachment.find_by(slug: slug)
|
||||
end
|
||||
end
|
||||
|
||||
class RedcarpetRenderer
|
||||
def self.render(markdown)
|
||||
renderer = Redcarpet::Render::HTML.new
|
||||
markdowner = Redcarpet::Markdown.new(renderer,
|
||||
autolink: true,
|
||||
tables: true,
|
||||
fenced_code_blocks: true,
|
||||
strikethrough: true,
|
||||
superscript: true,
|
||||
)
|
||||
markdowner.render(markdown.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
def process_all(klass, field)
|
||||
klass.find_each do |record|
|
||||
markdown = ActionText::Markdown.find_by(record: record, name: field)
|
||||
next unless markdown
|
||||
|
||||
puts "markdown.id=#{markdown.id}"
|
||||
next unless markdown.record
|
||||
|
||||
html = RedcarpetRenderer.render(markdown.content.to_s)
|
||||
converter = MarkdownToActionTextConverter.new(html)
|
||||
rich_text_html, attachments = converter.convert
|
||||
|
||||
rich_text = ActionText::RichText.create!(
|
||||
name: markdown.name,
|
||||
record: markdown.record,
|
||||
body: rich_text_html
|
||||
)
|
||||
|
||||
attachments.each do |attachment|
|
||||
attachment.update!(record: rich_text)
|
||||
end
|
||||
|
||||
puts "✓ Created rich text for #{markdown.record_type}##{markdown.record_id} (#{markdown.name})"
|
||||
rescue => e
|
||||
warn "✗ Failed to process markdown ##{markdown.id}: #{e.class} - #{e.message}"
|
||||
end
|
||||
end
|
||||
|
||||
ApplicationRecord.with_each_tenant do |tenant|
|
||||
puts "Processing tenant: #{tenant}"
|
||||
|
||||
ActionText::RichText.delete_all
|
||||
|
||||
process_all(Card, :description)
|
||||
process_all(Comment, :body)
|
||||
end
|
||||
Executable
+47
@@ -0,0 +1,47 @@
|
||||
#! /usr/bin/env ruby
|
||||
|
||||
require_relative "../config/environment"
|
||||
|
||||
def migrate(source_service_name, target_service_name)
|
||||
ApplicationRecord.with_each_tenant do |tenant|
|
||||
puts "\n## #{tenant}"
|
||||
report = { updated: 0, skipped: 0, errors: 0 }
|
||||
|
||||
if ActiveStorage::Blob.count == 0
|
||||
puts "No blobs found, skipping."
|
||||
next
|
||||
end
|
||||
|
||||
ActiveStorage::Blob.service = source_service = ActiveStorage::Blob.services.fetch(source_service_name)
|
||||
target_service = ActiveStorage::Blob.services.fetch(target_service_name)
|
||||
|
||||
ActiveStorage::Blob.find_each do |blob|
|
||||
if target_service.name.to_sym == blob.service_name.to_sym
|
||||
report[:skipped] += 1
|
||||
putc "-"
|
||||
elsif target_service.exist?(blob.key)
|
||||
report[:skipped] += 1
|
||||
putc "S"
|
||||
else
|
||||
begin
|
||||
blob.open do |stream|
|
||||
target_service.upload(blob.key, stream, checksum: blob.checksum)
|
||||
end
|
||||
report[:updated] += 1
|
||||
putc "."
|
||||
rescue ActiveStorage::FileNotFoundError
|
||||
report[:errors] += 1
|
||||
putc "E"
|
||||
end
|
||||
end
|
||||
|
||||
# Update the service name of the blob.
|
||||
blob.update_column :service_name, target_service_name
|
||||
end
|
||||
|
||||
puts
|
||||
pp report
|
||||
end
|
||||
end
|
||||
|
||||
migrate :local, :purestorage
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require_relative "../config/environment"
|
||||
|
||||
ApplicationRecord.with_each_tenant do |tenant|
|
||||
account = Account.sole
|
||||
signal_account = account.signal_account
|
||||
|
||||
signal_users = SignalId::User.where(account_id: signal_account.id)
|
||||
|
||||
signal_users.each do |signal_user|
|
||||
unless User.find_by(signal_user_id: signal_user.id)
|
||||
User.create!(
|
||||
name: signal_user.identity.name,
|
||||
email_address: signal_user.identity.email_address,
|
||||
signal_user_id: signal_user.id,
|
||||
password: SecureRandom.hex(36) # TODO: remove password column?
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require_relative "../config/environment"
|
||||
|
||||
ApplicationRecord.with_each_tenant do |tenant|
|
||||
Account.find_each do |account|
|
||||
account.send(:create_default_closure_reasons)
|
||||
end
|
||||
end
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require_relative "../config/environment"
|
||||
|
||||
ApplicationRecord.with_each_tenant do |tenant|
|
||||
Card.find_each do |card|
|
||||
card.events.find_each do |event|
|
||||
Card::Eventable::SystemCommenter.new(card.reload, event).comment
|
||||
end
|
||||
end
|
||||
end
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require_relative "../config/environment"
|
||||
|
||||
domains = {
|
||||
"production" => "fizzy.37signals.com",
|
||||
"beta" => "fizzy-beta.37signals.com",
|
||||
"staging" => "fizzy.37signals-staging.com"
|
||||
}
|
||||
|
||||
def fix_attachments(rich_text)
|
||||
if rich_text.body
|
||||
rich_text.body.send(:attachment_nodes).each do |node|
|
||||
sgid = SignedGlobalID.parse(node["sgid"], for: ActionText::Attachable::LOCATOR_NAME)
|
||||
if sgid
|
||||
puts "Fixing attachment node: #{node.to_html}"
|
||||
model = sgid.model_class.find(sgid.model_id)
|
||||
node["sgid"] = model.attachable_sgid
|
||||
else
|
||||
puts "Skipping attachment node without valid sgid: #{node.to_html}"
|
||||
end
|
||||
end
|
||||
rich_text.save!
|
||||
end
|
||||
end
|
||||
|
||||
ApplicationRecord.with_each_tenant do |tenant|
|
||||
account_id = Account.sole.queenbee_id
|
||||
|
||||
unless account_id
|
||||
puts "Skipping URL fixup for tenant: #{tenant}"
|
||||
next
|
||||
end
|
||||
|
||||
puts "\n## Processing tenant: #{tenant}\n"
|
||||
|
||||
domain = domains[Rails.env] || domains["production"]
|
||||
regex = %r{://\w+\.#{domain}/}
|
||||
|
||||
pp [ Account.sole.name, account_id, domain, regex ]
|
||||
puts
|
||||
|
||||
Card.find_each do |card|
|
||||
puts "### Processing card #{card.id} in #{Rails.application.routes.url_helpers.collection_card_path(card.collection, card)}"
|
||||
fix_attachments(card.description)
|
||||
card.reload
|
||||
|
||||
old_body = card.description.body.to_s
|
||||
if match = regex.match(old_body)
|
||||
puts "URL found in card #{card.id} in #{Rails.application.routes.url_helpers.collection_card_path(card.collection, card)}"
|
||||
new_body = old_body.gsub(regex, "://#{domain}/#{account_id}/")
|
||||
|
||||
card.description.update(body: new_body) || raise("Failed to update card description for card #{card.id}")
|
||||
end
|
||||
end
|
||||
|
||||
Comment.find_each do |comment|
|
||||
puts "### Processing comment #{comment.id} in #{Rails.application.routes.url_helpers.collection_card_path(comment.card.collection, comment.card)}"
|
||||
fix_attachments(comment.body)
|
||||
comment.reload
|
||||
|
||||
old_body = comment.body.body.to_s
|
||||
if match = regex.match(old_body)
|
||||
puts "URL found in comment #{comment.id} in #{Rails.application.routes.url_helpers.collection_card_path(comment.card.collection, comment.card)}"
|
||||
new_body = old_body.gsub(regex, "://#{domain}/#{account_id}/")
|
||||
|
||||
comment.body.update(body: new_body) || raise("Failed to update comment body for comment #{comment.id}")
|
||||
end
|
||||
end
|
||||
end
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
#! /usr/bin/env ruby
|
||||
|
||||
require_relative "../config/environment"
|
||||
|
||||
ApplicationRecord.with_each_tenant do |tenant|
|
||||
puts "\n## #{tenant}"
|
||||
report = { updated: 0, skipped: 0 }
|
||||
|
||||
ActiveStorage::Blob.find_each do |blob|
|
||||
if blob.key.start_with?("#{tenant}/")
|
||||
report[:skipped] += 1
|
||||
else
|
||||
blob.update_column :key, "#{tenant}/#{blob.key}"
|
||||
report[:updated] += 1
|
||||
end
|
||||
end
|
||||
pp report
|
||||
|
||||
disk_service = ActiveStorage::Blob.services.fetch(:local)
|
||||
new_root = File.join(disk_service.root, tenant)
|
||||
old_root = File.join("storage", "tenants", Rails.env, tenant, "files")
|
||||
|
||||
FileUtils.mkdir_p(new_root, verbose: true) unless File.directory?(new_root)
|
||||
|
||||
Dir.glob(File.join(old_root, "??")).each_slice(20) do |blob_dirs|
|
||||
FileUtils.mv(blob_dirs, new_root, verbose: true)
|
||||
end
|
||||
end
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require_relative "../config/environment"
|
||||
|
||||
def replace_url(string)
|
||||
string.gsub(%r{/buckets/(\d+)/bubbles/(\d+)}) do
|
||||
"/collections/#{$1}/cards/#{$2}"
|
||||
end
|
||||
end
|
||||
|
||||
ApplicationRecord.with_each_tenant do |tenant|
|
||||
Account.find_each do |account|
|
||||
Comment.find_each do |comment|
|
||||
comment.update!(body: replace_url(comment.body.content.to_s))
|
||||
end
|
||||
end
|
||||
end
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require_relative "../config/environment"
|
||||
|
||||
tenant_names = []
|
||||
|
||||
ApplicationRecord.with_each_tenant do |tenant|
|
||||
next if tenant == "#{Rails.env}-tenant"
|
||||
|
||||
account = Account.sole
|
||||
queenbee_id = account.queenbee_id
|
||||
tenant_names << { from: tenant, to: queenbee_id }
|
||||
|
||||
ApplicationRecord.remove_connection
|
||||
end
|
||||
|
||||
pp [ "Tenant name changes:", tenant_names ]
|
||||
|
||||
root_config = ApplicationRecord.tenanted_root_config
|
||||
tenant_names.each do |name|
|
||||
from_db_path = root_config.database_path_for(name[:from])
|
||||
to_db_path = root_config.database_path_for(name[:to])
|
||||
|
||||
from_path = from_db_path.split("/").take(4).join("/")
|
||||
to_path = to_db_path.split("/").take(4).join("/")
|
||||
|
||||
unless from_path == to_path
|
||||
FileUtils.move from_path, to_path, verbose: true
|
||||
end
|
||||
end
|
||||
|
||||
puts
|
||||
pp [ "Tenants after renaming:", ApplicationRecord.tenants ]
|
||||
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require "find"
|
||||
require "active_support/core_ext/string" # for camelize
|
||||
|
||||
RENAME_RULES = {
|
||||
"bubble" => "card",
|
||||
"closed" => "closed",
|
||||
"poppable" => "closeable",
|
||||
"pop" => "closure",
|
||||
"bucket" => "collection"
|
||||
}
|
||||
|
||||
EXTENSIONS = %w[.rb .yml .html .js .css .erb]
|
||||
EXCLUDED_DIRS = %w[db .git script/renaming vendor/javascript]
|
||||
|
||||
# Helper to build replacement regex patterns respecting case and separators
|
||||
def build_patterns(from, to)
|
||||
boundary = "(?<=\\A|[^a-zA-Z0-9])#{from}(?=[^a-zA-Z0-9]|\\z)"
|
||||
camel = from.camelize
|
||||
camel_plural = camel.pluralize
|
||||
underscore_plural = from.pluralize.underscore
|
||||
dasherized_plural = underscore_plural.dasherize
|
||||
|
||||
[
|
||||
# Match lowercase boundary-delimited
|
||||
[ /#{boundary}/, to ],
|
||||
# Match capitalized version (e.g., Bubble => Card)
|
||||
[ /(?<![a-zA-Z0-9])#{from.capitalize}(?![a-z])/, to.capitalize ],
|
||||
# Match all-uppercase
|
||||
[ /(?<![a-zA-Z0-9])#{from.upcase}(?![A-Z])/, to.upcase ],
|
||||
# Match CamelCase and plural CamelCase
|
||||
[ /(?<![a-zA-Z0-9])#{camel}(?![a-z])/, to.camelize ],
|
||||
[ /(?<![a-zA-Z0-9])#{camel_plural}(?![a-z])/, to.camelize.pluralize ],
|
||||
# Match lowerCamelCase
|
||||
[ /(?<![a-zA-Z0-9])#{from.camelize(:lower)}(?![a-z])/, to.camelize(:lower) ],
|
||||
# Match underscore and dashed plural forms (e.g. bubbles(:logo) => cards(:logo))
|
||||
[ /(?<![a-zA-Z0-9])#{underscore_plural}(?![a-z])/, to.pluralize.underscore ],
|
||||
[ /(?<![a-zA-Z0-9])#{dasherized_plural}(?![a-z])/, to.pluralize.underscore.dasherize ]
|
||||
]
|
||||
end
|
||||
|
||||
patterns = []
|
||||
RENAME_RULES.each do |from, to|
|
||||
patterns.concat(build_patterns(from, to))
|
||||
end
|
||||
|
||||
Find.find(".") do |path|
|
||||
next if File.directory?(path)
|
||||
next unless EXTENSIONS.include?(File.extname(path))
|
||||
next if EXCLUDED_DIRS.any? { |dir| path.start_with?("./#{dir}/") }
|
||||
|
||||
content = File.read(path)
|
||||
original_content = content.dup
|
||||
|
||||
patterns.each do |regex, replacement|
|
||||
content.gsub!(regex, replacement)
|
||||
end
|
||||
|
||||
if content != original_content
|
||||
puts "Renaming in: #{path}"
|
||||
File.write(path, content)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require "fileutils"
|
||||
require "active_support/core_ext/string" # for camelize
|
||||
|
||||
# Configuration
|
||||
EXCLUDED_DIRS = [ "db", ".git", "script/renaming" ].freeze
|
||||
|
||||
RENAMES = {
|
||||
"bubble" => "card",
|
||||
"poppable" => "closeable",
|
||||
"closed" => "closed",
|
||||
"pop" => "closure",
|
||||
"bucket" => "collection"
|
||||
}.freeze
|
||||
|
||||
FILE_EXTENSIONS = %w[rb yml html css js jpg jpeg png gif svg erb].freeze
|
||||
|
||||
def excluded_path?(path)
|
||||
EXCLUDED_DIRS.any? { |excluded| path.split(File::SEPARATOR).include?(excluded) }
|
||||
end
|
||||
|
||||
def rename_path(path)
|
||||
new_path = path.dup
|
||||
|
||||
RENAMES.each do |from, to|
|
||||
# Replace snake_case, kebab-case, plain, and CamelCase versions
|
||||
patterns = [
|
||||
[ /(?<=\A|[^a-zA-Z0-9])#{from}(?=[^a-zA-Z0-9]|\z)/i, to ],
|
||||
[ from.camelize, to.camelize ],
|
||||
[ from.camelize(:lower), to.camelize(:lower) ],
|
||||
[ from.underscore.dasherize, to.underscore.dasherize ],
|
||||
[ from.underscore, to.underscore ]
|
||||
]
|
||||
|
||||
patterns.each do |pattern, replacement|
|
||||
new_path.gsub!(pattern, replacement)
|
||||
end
|
||||
end
|
||||
|
||||
new_path
|
||||
end
|
||||
|
||||
# Rename Directories First
|
||||
dirs = Dir.glob("**/*/").reject { |path| excluded_path?(path) }.sort_by { |dir| -dir.count("/") }
|
||||
|
||||
puts "Renaming directories..."
|
||||
dirs.each do |dir|
|
||||
clean_dir = dir.chomp("/")
|
||||
new_dir = rename_path(clean_dir)
|
||||
|
||||
next if clean_dir == new_dir
|
||||
next if File.exist?(new_dir)
|
||||
|
||||
puts "Renaming dir: #{clean_dir} => #{new_dir}"
|
||||
FileUtils.mkdir_p(File.dirname(new_dir))
|
||||
FileUtils.mv(clean_dir, new_dir)
|
||||
end
|
||||
|
||||
# Rename Files
|
||||
files = Dir.glob("**/*.{#{FILE_EXTENSIONS.join(",")}}").reject { |path| excluded_path?(path) }
|
||||
|
||||
puts "Renaming files..."
|
||||
files.each do |file|
|
||||
new_file = rename_path(file)
|
||||
|
||||
next if file == new_file
|
||||
next if File.exist?(new_file)
|
||||
|
||||
puts "Renaming file: #{file} => #{new_file}"
|
||||
FileUtils.mkdir_p(File.dirname(new_file))
|
||||
FileUtils.mv(file, new_file)
|
||||
end
|
||||
|
||||
puts "Renaming complete!"
|
||||
@@ -0,0 +1,165 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require_relative "../config/environment"
|
||||
|
||||
ApplicationRecord.with_each_tenant do |tenant|
|
||||
id_mapping = {}
|
||||
|
||||
puts "Processing tenant: #{tenant}"
|
||||
|
||||
# Disable foreign key constraints
|
||||
ApplicationRecord.connection.execute("PRAGMA foreign_keys = OFF;")
|
||||
|
||||
begin
|
||||
# Get all cards ordered by ID
|
||||
cards = Card.order(:id).to_a
|
||||
|
||||
# Create mapping of old IDs to new IDs
|
||||
cards.each_with_index do |card, index|
|
||||
id_mapping[card.id] = index + 1
|
||||
end
|
||||
|
||||
# Update foreign keys in related tables
|
||||
puts "Updating foreign keys in related tables..."
|
||||
|
||||
# Update assignments table
|
||||
Assignment.where.not(card_id: nil).find_each do |assignment|
|
||||
if id_mapping[assignment.card_id]
|
||||
assignment.update_column(:card_id, id_mapping[assignment.card_id])
|
||||
end
|
||||
end
|
||||
|
||||
# Update card_engagements table
|
||||
Card::Engagement.where.not(card_id: nil).find_each do |engagement|
|
||||
if id_mapping[engagement.card_id]
|
||||
engagement.update_column(:card_id, id_mapping[engagement.card_id])
|
||||
end
|
||||
end
|
||||
|
||||
# Update card_goldnesses table
|
||||
Card::Goldness.where.not(card_id: nil).find_each do |goldness|
|
||||
if id_mapping[goldness.card_id]
|
||||
goldness.update_column(:card_id, id_mapping[goldness.card_id])
|
||||
end
|
||||
end
|
||||
|
||||
# Update closures table
|
||||
Closure.where.not(card_id: nil).find_each do |closure|
|
||||
if id_mapping[closure.card_id]
|
||||
closure.update_column(:card_id, id_mapping[closure.card_id])
|
||||
end
|
||||
end
|
||||
|
||||
# Update comments table
|
||||
Comment.where.not(card_id: nil).find_each do |comment|
|
||||
if id_mapping[comment.card_id]
|
||||
comment.update_column(:card_id, id_mapping[comment.card_id])
|
||||
end
|
||||
end
|
||||
|
||||
# Update pins table
|
||||
Pin.where.not(card_id: nil).find_each do |pin|
|
||||
if id_mapping[pin.card_id]
|
||||
pin.update_column(:card_id, id_mapping[pin.card_id])
|
||||
end
|
||||
end
|
||||
|
||||
# Update taggings table
|
||||
Tagging.where.not(card_id: nil).find_each do |tagging|
|
||||
if id_mapping[tagging.card_id]
|
||||
tagging.update_column(:card_id, id_mapping[tagging.card_id])
|
||||
end
|
||||
end
|
||||
|
||||
# Update watches table
|
||||
Watch.where.not(card_id: nil).find_each do |watch|
|
||||
if id_mapping[watch.card_id]
|
||||
watch.update_column(:card_id, id_mapping[watch.card_id])
|
||||
end
|
||||
end
|
||||
|
||||
# Update events table (polymorphic relationship)
|
||||
Event.where(eventable_type: "Card").find_each do |event|
|
||||
if id_mapping[event.eventable_id]
|
||||
event.update_column(:eventable_id, id_mapping[event.eventable_id])
|
||||
end
|
||||
end
|
||||
|
||||
# Update mentions table (polymorphic relationship)
|
||||
Mention.where(source_type: "Card").find_each do |mention|
|
||||
if id_mapping[mention.source_id]
|
||||
mention.update_column(:source_id, id_mapping[mention.source_id])
|
||||
end
|
||||
end
|
||||
|
||||
# Update notifications table (polymorphic relationship)
|
||||
Notification.where(source_type: "Card").find_each do |notification|
|
||||
if id_mapping[notification.source_id]
|
||||
notification.update_column(:source_id, id_mapping[notification.source_id])
|
||||
end
|
||||
end
|
||||
|
||||
# Update action_text_markdowns table (polymorphic relationship)
|
||||
ActionText::RichText.where(record_type: "Card").find_each do |rich_text|
|
||||
if id_mapping[rich_text.record_id]
|
||||
rich_text.update_column(:record_id, id_mapping[rich_text.record_id])
|
||||
end
|
||||
end
|
||||
|
||||
# Reset the cards table IDs
|
||||
puts "Resetting card IDs..."
|
||||
cards.each do |card|
|
||||
new_id = id_mapping[card.id]
|
||||
# Use direct SQL to update the ID to avoid ActiveRecord validations
|
||||
ApplicationRecord.connection.execute("UPDATE cards SET id = #{new_id} WHERE id = #{card.id}")
|
||||
end
|
||||
|
||||
# Reset the SQLite sequence for the cards table
|
||||
ApplicationRecord.connection.execute("DELETE FROM sqlite_sequence WHERE name = 'cards'")
|
||||
max_id = Card.maximum(:id) || 0
|
||||
ApplicationRecord.connection.execute("INSERT INTO sqlite_sequence (name, seq) VALUES ('cards', #{max_id})")
|
||||
|
||||
puts "Card IDs have been reset successfully!"
|
||||
rescue => e
|
||||
puts "Error: #{e.message}"
|
||||
puts e.backtrace
|
||||
ensure
|
||||
# Re-enable foreign key constraints
|
||||
ApplicationRecord.connection.execute("PRAGMA foreign_keys = ON;")
|
||||
end
|
||||
|
||||
Card.find_each do |card|
|
||||
description = card.description.content.dup
|
||||
|
||||
description.gsub!(/cards\/(\d+)\)/) do |match|
|
||||
old_id = $1.to_i
|
||||
new_id = id_mapping[old_id]
|
||||
|
||||
new_id ? "cards/#{new_id})" : match
|
||||
end
|
||||
|
||||
if description != card.description.content
|
||||
puts "Updating links in card #{card.id}"
|
||||
card.update!(description: description)
|
||||
end
|
||||
end
|
||||
|
||||
Comment.find_each do |comment|
|
||||
body = comment.body.content.dup
|
||||
|
||||
body.gsub!(/cards\/(\d+)\)/) do |match|
|
||||
old_id = $1.to_i
|
||||
new_id = id_mapping[old_id]
|
||||
new_id ? "cards/#{new_id})" : match
|
||||
end
|
||||
|
||||
if body != comment.body.content
|
||||
puts "Updating links in comment #{comment.id}"
|
||||
comment.update!(body: body)
|
||||
end
|
||||
end
|
||||
|
||||
# Output the mapping of old IDs to new IDs
|
||||
puts "\nMapping of old IDs to new IDs:"
|
||||
puts id_mapping.inspect
|
||||
end
|
||||
@@ -0,0 +1,146 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require_relative "../config/environment"
|
||||
|
||||
ApplicationRecord.with_each_tenant do |tenant|
|
||||
id_mapping = {}
|
||||
|
||||
puts "Processing tenant: #{tenant}"
|
||||
|
||||
# Disable foreign key constraints
|
||||
ApplicationRecord.connection.execute("PRAGMA foreign_keys = OFF;")
|
||||
|
||||
begin
|
||||
# Get all collections ordered by ID
|
||||
collections = Collection.order(:id).to_a
|
||||
|
||||
# Create mapping of old IDs to new IDs
|
||||
collections.each_with_index do |collection, index|
|
||||
id_mapping[collection.id] = index + 1
|
||||
end
|
||||
|
||||
# Update foreign keys in related tables
|
||||
puts "Updating foreign keys in related tables..."
|
||||
|
||||
# Update accesses table
|
||||
Access.where.not(collection_id: nil).find_each do |access|
|
||||
if id_mapping[access.collection_id]
|
||||
access.update_column(:collection_id, id_mapping[access.collection_id])
|
||||
end
|
||||
end
|
||||
|
||||
# Update cards table
|
||||
Card.where.not(collection_id: nil).find_each do |card|
|
||||
if id_mapping[card.collection_id]
|
||||
card.update_column(:collection_id, id_mapping[card.collection_id])
|
||||
end
|
||||
end
|
||||
|
||||
# Update collections_filters table (join table)
|
||||
ApplicationRecord.connection.execute("SELECT collection_id FROM collections_filters").each do |row|
|
||||
old_id = row[0]
|
||||
if id_mapping[old_id]
|
||||
ApplicationRecord.connection.execute("UPDATE collections_filters SET collection_id = #{id_mapping[old_id]} WHERE collection_id = #{old_id}")
|
||||
end
|
||||
end
|
||||
|
||||
# Update events table
|
||||
Event.where.not(collection_id: nil).find_each do |event|
|
||||
if id_mapping[event.collection_id]
|
||||
event.update_column(:collection_id, id_mapping[event.collection_id])
|
||||
end
|
||||
end
|
||||
|
||||
# Update events table (polymorphic relationship)
|
||||
Event.where(eventable_type: "Collection").find_each do |event|
|
||||
if id_mapping[event.eventable_id]
|
||||
event.update_column(:eventable_id, id_mapping[event.eventable_id])
|
||||
end
|
||||
end
|
||||
|
||||
# Update mentions table (polymorphic relationship)
|
||||
Mention.where(source_type: "Collection").find_each do |mention|
|
||||
if id_mapping[mention.source_id]
|
||||
mention.update_column(:source_id, id_mapping[mention.source_id])
|
||||
end
|
||||
end
|
||||
|
||||
# Update notifications table (polymorphic relationship)
|
||||
Notification.where(source_type: "Collection").find_each do |notification|
|
||||
if id_mapping[notification.source_id]
|
||||
notification.update_column(:source_id, id_mapping[notification.source_id])
|
||||
end
|
||||
end
|
||||
|
||||
# Update action_text_markdowns table (polymorphic relationship)
|
||||
ActionText::RichText.where(record_type: "Collection").find_each do |rich_text|
|
||||
if id_mapping[rich_text.record_id]
|
||||
rich_text.update_column(:record_id, id_mapping[rich_text.record_id])
|
||||
end
|
||||
end
|
||||
|
||||
# Update active_storage_attachments table (polymorphic relationship)
|
||||
ActiveStorage::Attachment.where(record_type: "Collection").find_each do |attachment|
|
||||
if id_mapping[attachment.record_id]
|
||||
attachment.update_column(:record_id, id_mapping[attachment.record_id])
|
||||
end
|
||||
end
|
||||
|
||||
# Reset the collections table IDs
|
||||
puts "Resetting collection IDs..."
|
||||
collections.each do |collection|
|
||||
new_id = id_mapping[collection.id]
|
||||
# Use direct SQL to update the ID to avoid ActiveRecord validations
|
||||
ApplicationRecord.connection.execute("UPDATE collections SET id = #{new_id} WHERE id = #{collection.id}")
|
||||
end
|
||||
|
||||
# Reset the SQLite sequence for the collections table
|
||||
ApplicationRecord.connection.execute("DELETE FROM sqlite_sequence WHERE name = 'collections'")
|
||||
max_id = Collection.maximum(:id) || 0
|
||||
ApplicationRecord.connection.execute("INSERT INTO sqlite_sequence (name, seq) VALUES ('collections', #{max_id})")
|
||||
|
||||
puts "Collection IDs have been reset successfully!"
|
||||
rescue => e
|
||||
puts "Error: #{e.message}"
|
||||
puts e.backtrace
|
||||
ensure
|
||||
# Re-enable foreign key constraints
|
||||
ApplicationRecord.connection.execute("PRAGMA foreign_keys = ON;")
|
||||
end
|
||||
|
||||
# Update links in card descriptions and comment bodies
|
||||
Card.find_each do |card|
|
||||
description = card.description.content.dup
|
||||
|
||||
description.gsub!(/collections\/(\d+)\//) do |match|
|
||||
old_id = $1.to_i
|
||||
new_id = id_mapping[old_id]
|
||||
|
||||
new_id ? "collections/#{new_id}/" : match
|
||||
end
|
||||
|
||||
if description != card.description.content
|
||||
puts "Updating links in card #{card.id}"
|
||||
card.update!(description: description)
|
||||
end
|
||||
end
|
||||
|
||||
Comment.find_each do |comment|
|
||||
body = comment.body.content.dup
|
||||
|
||||
body.gsub!(/collections\/(\d+)\//) do |match|
|
||||
old_id = $1.to_i
|
||||
new_id = id_mapping[old_id]
|
||||
new_id ? "collections/#{new_id}/" : match
|
||||
end
|
||||
|
||||
if body != comment.body.content
|
||||
puts "Updating links in comment #{comment.id}"
|
||||
comment.update!(body: body)
|
||||
end
|
||||
end
|
||||
|
||||
# Output the mapping of old IDs to new IDs
|
||||
puts "\nMapping of old IDs to new IDs:"
|
||||
puts id_mapping.inspect
|
||||
end
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require_relative "../config/environment"
|
||||
|
||||
CHANGES = [
|
||||
{ from: "kevin@37signals.com", to: "kevin@basecamp.com" },
|
||||
{ from: "david@37signals.com", to: "david@hey.com" },
|
||||
{ from: "jay@37signals.com", to: "jay@basecamp.com" },
|
||||
{ from: "jeremy@37signals.com", to: "jeremy@basecamp.com" },
|
||||
{ from: "jillian@37signals.com", to: "jillian@basecamp.com" },
|
||||
{ from: "jorge@37signals.com", to: "jorge@basecamp.com" },
|
||||
{ from: "merissa@37signals.com", to: "merissa@basecamp.com" },
|
||||
{ from: "michelle@37signals.com", to: "michelle@basecamp.com" },
|
||||
{ from: "scott@37signals.com", to: "scott@basecamp.com" },
|
||||
{ from: "silvia@37signals.com", to: "silvia@basecamp.com" }
|
||||
]
|
||||
|
||||
ApplicationRecord.with_each_tenant do |tenant|
|
||||
CHANGES.each do |change|
|
||||
user = User.find_by(email_address: change[:from])
|
||||
if user
|
||||
puts "Updating user #{user.id} in tenant #{tenant}: #{change[:from]} -> #{change[:to]}"
|
||||
user.email_address = change[:to]
|
||||
user.save!
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user