From d9ece38753707d99bfb2648491188ff0789a3e55 Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Tue, 4 Mar 2025 08:45:41 -0500 Subject: [PATCH] Make the application multi-tenant --- app/channels/application_cable/connection.rb | 2 +- app/models/application_record.rb | 1 + bin/dev | 3 +- config/database.yml | 9 +- config/initializers/tenanting.rb | 3 + config/storage.yml | 6 +- db/schema_cache.yml | 2007 ++++++++++++++++++ test/controllers/uploads_controller_test.rb | 2 +- 8 files changed, 2024 insertions(+), 9 deletions(-) create mode 100644 config/initializers/tenanting.rb create mode 100644 db/schema_cache.yml diff --git a/app/channels/application_cable/connection.rb b/app/channels/application_cable/connection.rb index d7618af5e..b39e7a216 100644 --- a/app/channels/application_cable/connection.rb +++ b/app/channels/application_cable/connection.rb @@ -2,7 +2,7 @@ module ApplicationCable class Connection < ActionCable::Connection::Base identified_by :current_user - def connect + tenanted_connection do set_current_user || reject_unauthorized_connection end diff --git a/app/models/application_record.rb b/app/models/application_record.rb index b63caeb8a..cb75f5d41 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -1,3 +1,4 @@ class ApplicationRecord < ActiveRecord::Base primary_abstract_class + tenanted end diff --git a/bin/dev b/bin/dev index b16192883..1b7f220b2 100755 --- a/bin/dev +++ b/bin/dev @@ -1,6 +1,5 @@ #!/usr/bin/env sh -echo "Starting Fizzy on http://localhost:3006" -echo "Login with david@37signals.com using secret123456" +echo "Starting Fizzy on http://development-tenant.fizzy.localhost:3006" SOLID_QUEUE_IN_PUMA=true exec ./bin/rails server -p 3006 diff --git a/config/database.yml b/config/database.yml index a77427e19..d0aa1fb18 100644 --- a/config/database.yml +++ b/config/database.yml @@ -12,7 +12,8 @@ default: &default development: primary: <<: *default - database: storage/development.sqlite3 + database: storage/development-tenanted/%{tenant}/main.sqlite3 + tenanted: true cable: <<: *default database: storage/development_cable.sqlite3 @@ -31,12 +32,14 @@ development: # Do not set this db to the same as development or production. test: <<: *default - database: storage/test.sqlite3 + database: storage/test-tenanted/%{tenant}/main.sqlite3 + tenanted: true production: primary: <<: *default - database: storage/production.sqlite3 + database: storage/production-tenanted/%{tenant}/main.sqlite3 + tenanted: true cable: <<: *default database: storage/production_cable.sqlite3 diff --git a/config/initializers/tenanting.rb b/config/initializers/tenanting.rb new file mode 100644 index 000000000..b05e90632 --- /dev/null +++ b/config/initializers/tenanting.rb @@ -0,0 +1,3 @@ +Rails.application.configure do |config| + config.middleware.use ActiveRecord::Tenanted::TenantSelector, "ApplicationRecord", ->(request) { request.subdomain } +end diff --git a/config/storage.yml b/config/storage.yml index badb11b67..bf004cc3c 100644 --- a/config/storage.yml +++ b/config/storage.yml @@ -1,10 +1,12 @@ test: service: Disk - root: <%= Rails.root.join("tmp/storage") %> + root: <%= Rails.root.join("tmp/storage/%{tenant}") %> + tenanted: true local: service: Disk - root: <%= Rails.root.join("storage", "files") %> + root: <%= Rails.root.join("storage", "files", "%{tenant}") %> + tenanted: true # Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) # amazon: diff --git a/db/schema_cache.yml b/db/schema_cache.yml new file mode 100644 index 000000000..040d4132f --- /dev/null +++ b/db/schema_cache.yml @@ -0,0 +1,2007 @@ +--- !ruby/object:ActiveRecord::ConnectionAdapters::SchemaCache +columns: + accesses: + - &5 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: true + name: id + cast_type: &1 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3Adapter::SQLite3Integer + precision: + scale: + limit: + max: 9223372036854775808 + min: -9223372036854775808 + sql_type_metadata: &2 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type: INTEGER + type: :integer + limit: + precision: + scale: + 'null': false + default: + default_function: + collation: + comment: + - &21 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: bucket_id + cast_type: *1 + sql_type_metadata: *2 + 'null': false + default: + default_function: + collation: + comment: + - &30 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: user_id + cast_type: *1 + sql_type_metadata: *2 + 'null': false + default: + default_function: + collation: + comment: + - &6 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: created_at + cast_type: &3 !ruby/object:ActiveRecord::Type::DateTime + precision: 6 + scale: + limit: + timezone: + sql_type_metadata: &4 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type: datetime(6) + type: :datetime + limit: + precision: 6 + scale: + 'null': false + default: + default_function: + collation: + comment: + - &7 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: updated_at + cast_type: *3 + sql_type_metadata: *4 + 'null': false + default: + default_function: + collation: + comment: + accounts: + - *5 + - &10 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: name + cast_type: &8 !ruby/object:ActiveModel::Type::String + true: t + false: f + precision: + scale: + limit: + sql_type_metadata: &9 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type: varchar + type: :string + limit: + precision: + scale: + 'null': false + default: + default_function: + collation: + comment: + - *6 + - *7 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: join_code + cast_type: *8 + sql_type_metadata: *9 + 'null': true + default: + default_function: + collation: + comment: + action_text_markdowns: + - *5 + - &11 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: record_type + cast_type: *8 + sql_type_metadata: *9 + 'null': false + default: + default_function: + collation: + comment: + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: record_id + cast_type: *1 + sql_type_metadata: *2 + 'null': false + default: + default_function: + collation: + comment: + - *10 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: content + cast_type: &14 !ruby/object:ActiveRecord::Type::Text + true: t + false: f + precision: + scale: + limit: + sql_type_metadata: &15 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type: TEXT + type: :text + limit: + precision: + scale: + 'null': false + default: '' + default_function: + collation: + comment: + - *6 + - *7 + active_storage_attachments: + - *5 + - *10 + - *11 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: record_id + cast_type: &12 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3Adapter::SQLite3Integer + precision: + scale: + limit: + max: 9223372036854775808 + min: -9223372036854775808 + sql_type_metadata: &13 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type: bigint + type: :integer + limit: + precision: + scale: + 'null': false + default: + default_function: + collation: + comment: + - &16 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: blob_id + cast_type: *12 + sql_type_metadata: *13 + 'null': false + default: + default_function: + collation: + comment: + - *6 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: slug + cast_type: *8 + sql_type_metadata: *9 + 'null': true + default: + default_function: + collation: + comment: + active_storage_blobs: + - *5 + - &17 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: key + cast_type: *8 + sql_type_metadata: *9 + 'null': false + default: + default_function: + collation: + comment: + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: filename + cast_type: *8 + sql_type_metadata: *9 + 'null': false + default: + default_function: + collation: + comment: + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: content_type + cast_type: *8 + sql_type_metadata: *9 + 'null': true + default: + default_function: + collation: + comment: + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: metadata + cast_type: *14 + sql_type_metadata: *15 + 'null': true + default: + default_function: + collation: + comment: + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: service_name + cast_type: *8 + sql_type_metadata: *9 + 'null': false + default: + default_function: + collation: + comment: + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: byte_size + cast_type: *12 + sql_type_metadata: *13 + 'null': false + default: + default_function: + collation: + comment: + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: checksum + cast_type: *8 + sql_type_metadata: *9 + 'null': true + default: + default_function: + collation: + comment: + - *6 + active_storage_variant_records: + - *5 + - *16 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: variation_digest + cast_type: *8 + sql_type_metadata: *9 + 'null': false + default: + default_function: + collation: + comment: + ar_internal_metadata: + - *17 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: value + cast_type: *8 + sql_type_metadata: *9 + 'null': true + default: + default_function: + collation: + comment: + - *6 + - *7 + assignees_filters: + - &18 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: filter_id + cast_type: *1 + sql_type_metadata: *2 + 'null': false + default: + default_function: + collation: + comment: + - &19 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: assignee_id + cast_type: *1 + sql_type_metadata: *2 + 'null': false + default: + default_function: + collation: + comment: + assigners_filters: + - *18 + - &20 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: assigner_id + cast_type: *1 + sql_type_metadata: *2 + 'null': false + default: + default_function: + collation: + comment: + assignments: + - *5 + - *19 + - &25 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: bubble_id + cast_type: *1 + sql_type_metadata: *2 + 'null': false + default: + default_function: + collation: + comment: + - *6 + - *7 + - *20 + bubbles: + - *5 + - &32 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: title + cast_type: *8 + sql_type_metadata: *9 + 'null': true + default: + default_function: + collation: + comment: + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: color + cast_type: *8 + sql_type_metadata: *9 + 'null': true + default: + default_function: + collation: + comment: + - *6 + - *7 + - &24 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: creator_id + cast_type: *1 + sql_type_metadata: *2 + 'null': false + default: + default_function: + collation: + comment: + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: due_on + cast_type: &26 !ruby/object:ActiveRecord::Type::Date + precision: + scale: + limit: + timezone: + sql_type_metadata: &27 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type: date + type: :date + limit: + precision: + scale: + 'null': true + default: + default_function: + collation: + comment: + - *21 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: boosts_count + cast_type: *1 + sql_type_metadata: *2 + 'null': false + default: 0 + default_function: + collation: + comment: + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: stage_id + cast_type: *1 + sql_type_metadata: *2 + 'null': true + default: + default_function: + collation: + comment: + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: comments_count + cast_type: *1 + sql_type_metadata: *2 + 'null': false + default: 0 + default_function: + collation: + comment: + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: activity_score + cast_type: &22 !ruby/object:ActiveModel::Type::Float + precision: + scale: + limit: + sql_type_metadata: &23 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type: float + type: :float + limit: + precision: + scale: + 'null': false + default: 0.0 + default_function: + collation: + comment: + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: status + cast_type: *14 + sql_type_metadata: *15 + 'null': false + default: creating + default_function: + collation: + comment: + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: auto_pop_at + cast_type: *3 + sql_type_metadata: *4 + 'null': false + default: + default_function: + collation: + comment: + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: activity_score_at + cast_type: *3 + sql_type_metadata: *4 + 'null': true + default: + default_function: + collation: + comment: + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: activity_score_order + cast_type: *22 + sql_type_metadata: *23 + 'null': false + default: 0.0 + default_function: + collation: + comment: + buckets: + - *5 + - &33 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: account_id + cast_type: *1 + sql_type_metadata: *2 + 'null': false + default: + default_function: + collation: + comment: + - *24 + - *10 + - *6 + - *7 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: all_access + cast_type: &34 !ruby/object:ActiveModel::Type::Boolean + precision: + scale: + limit: + sql_type_metadata: &35 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type: boolean + type: :boolean + limit: + precision: + scale: + 'null': false + default: false + default_function: + collation: + comment: + buckets_filters: + - *18 + - *21 + comments: + - *5 + - *24 + - *6 + - *7 + creators_filters: + - *18 + - *24 + event_summaries: + - *5 + - *6 + - *7 + events: + - *5 + - *24 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: particulars + cast_type: &28 !ruby/object:ActiveRecord::Type::Json + precision: + scale: + limit: + sql_type_metadata: &29 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type: json + type: :json + limit: + precision: + scale: + 'null': true + default: "{}" + default_function: + collation: + comment: + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: action + cast_type: *8 + sql_type_metadata: *9 + 'null': false + default: + default_function: + collation: + comment: + - *6 + - *7 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: summary_id + cast_type: *1 + sql_type_metadata: *2 + 'null': false + default: + default_function: + collation: + comment: + - *25 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: due_date + cast_type: *26 + sql_type_metadata: *27 + 'null': true + default: + default_function: + collation: + comment: + filters: + - *5 + - *24 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: params_digest + cast_type: *8 + sql_type_metadata: *9 + 'null': false + default: + default_function: + collation: + comment: + - *6 + - *7 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: fields + cast_type: *28 + sql_type_metadata: *29 + 'null': false + default: "{}" + default_function: + collation: + comment: + filters_stages: + - *18 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: stage_id + cast_type: *1 + sql_type_metadata: *2 + 'null': false + default: + default_function: + collation: + comment: + filters_tags: + - *18 + - &31 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: tag_id + cast_type: *1 + sql_type_metadata: *2 + 'null': false + default: + default_function: + collation: + comment: + messages: + - *5 + - *25 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: messageable_type + cast_type: *8 + sql_type_metadata: *9 + 'null': false + default: + default_function: + collation: + comment: + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: messageable_id + cast_type: *1 + sql_type_metadata: *2 + 'null': false + default: + default_function: + collation: + comment: + - *6 + - *7 + notifications: + - *5 + - *30 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: event_id + cast_type: *1 + sql_type_metadata: *2 + 'null': false + default: + default_function: + collation: + comment: + - *25 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: resource_type + cast_type: *8 + sql_type_metadata: *9 + 'null': false + default: + default_function: + collation: + comment: + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: resource_id + cast_type: *1 + sql_type_metadata: *2 + 'null': false + default: + default_function: + collation: + comment: + - *6 + - *7 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: read_at + cast_type: *3 + sql_type_metadata: *4 + 'null': true + default: + default_function: + collation: + comment: + pops: + - *5 + - *25 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: user_id + cast_type: *1 + sql_type_metadata: *2 + 'null': true + default: + default_function: + collation: + comment: + - *6 + - *7 + reactions: + - *5 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: comment_id + cast_type: *1 + sql_type_metadata: *2 + 'null': false + default: + default_function: + collation: + comment: + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: reacter_id + cast_type: *1 + sql_type_metadata: *2 + 'null': false + default: + default_function: + collation: + comment: + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: content + cast_type: !ruby/object:ActiveModel::Type::String + true: t + false: f + precision: + scale: + limit: 16 + sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type: varchar(16) + type: :string + limit: 16 + precision: + scale: + 'null': false + default: + default_function: + collation: + comment: + - *6 + - *7 + schema_migrations: + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: version + cast_type: *8 + sql_type_metadata: *9 + 'null': false + default: + default_function: + collation: + comment: + sessions: + - *5 + - *30 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: ip_address + cast_type: *8 + sql_type_metadata: *9 + 'null': true + default: + default_function: + collation: + comment: + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: user_agent + cast_type: *8 + sql_type_metadata: *9 + 'null': true + default: + default_function: + collation: + comment: + - *6 + - *7 + subscriptions: + - *5 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: subscribable_type + cast_type: *8 + sql_type_metadata: *9 + 'null': false + default: + default_function: + collation: + comment: + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: subscribable_id + cast_type: *1 + sql_type_metadata: *2 + 'null': false + default: + default_function: + collation: + comment: + - *30 + - *6 + - *7 + taggings: + - *5 + - *25 + - *31 + - *6 + - *7 + tags: + - *5 + - *32 + - *6 + - *7 + - *33 + users: + - *5 + - *33 + - *10 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: email_address + cast_type: *8 + sql_type_metadata: *9 + 'null': true + default: + default_function: + collation: + comment: + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: password_digest + cast_type: *8 + sql_type_metadata: *9 + 'null': true + default: + default_function: + collation: + comment: + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: active + cast_type: *34 + sql_type_metadata: *35 + 'null': false + default: true + default_function: + collation: + comment: + - *6 + - *7 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: role + cast_type: *8 + sql_type_metadata: *9 + 'null': false + default: member + default_function: + collation: + comment: + watches: + - *5 + - *30 + - *25 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: watching + cast_type: *34 + sql_type_metadata: *35 + 'null': false + default: true + default_function: + collation: + comment: + - *6 + - *7 + workflow_stages: + - *5 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: workflow_id + cast_type: *1 + sql_type_metadata: *2 + 'null': false + default: + default_function: + collation: + comment: + - *10 + - *6 + - *7 + workflows: + - *5 + - *33 + - *10 + - *6 + - *7 +primary_keys: + accesses: id + accounts: id + action_text_markdowns: id + active_storage_attachments: id + active_storage_blobs: id + active_storage_variant_records: id + ar_internal_metadata: key + assignees_filters: + assigners_filters: + assignments: id + bubbles: id + buckets: id + buckets_filters: + comments: id + creators_filters: + event_summaries: id + events: id + filters: id + filters_stages: + filters_tags: + messages: id + notifications: id + pops: id + reactions: id + schema_migrations: version + sessions: id + subscriptions: id + taggings: id + tags: id + users: id + watches: id + workflow_stages: id + workflows: id +data_sources: + accesses: true + accounts: true + action_text_markdowns: true + active_storage_attachments: true + active_storage_blobs: true + active_storage_variant_records: true + ar_internal_metadata: true + assignees_filters: true + assigners_filters: true + assignments: true + bubbles: true + buckets: true + buckets_filters: true + comments: true + creators_filters: true + event_summaries: true + events: true + filters: true + filters_stages: true + filters_tags: true + messages: true + notifications: true + pops: true + reactions: true + schema_migrations: true + sessions: true + subscriptions: true + taggings: true + tags: true + users: true + watches: true + workflow_stages: true + workflows: true +indexes: + accesses: + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: accesses + name: index_accesses_on_user_id + unique: false + columns: + - user_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: accesses + name: index_accesses_on_bucket_id + unique: false + columns: + - bucket_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: accesses + name: index_accesses_on_bucket_id_and_user_id + unique: true + columns: + - bucket_id + - user_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + accounts: + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: accounts + name: index_accounts_on_name + unique: true + columns: + - name + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: accounts + name: index_accounts_on_join_code + unique: true + columns: + - join_code + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + action_text_markdowns: + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: action_text_markdowns + name: index_action_text_markdowns_on_record + unique: false + columns: + - record_type + - record_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + active_storage_attachments: + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: active_storage_attachments + name: index_active_storage_attachments_on_slug + unique: true + columns: + - slug + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: active_storage_attachments + name: index_active_storage_attachments_uniqueness + unique: true + columns: + - record_type + - record_id + - name + - blob_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: active_storage_attachments + name: index_active_storage_attachments_on_blob_id + unique: false + columns: + - blob_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + active_storage_blobs: + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: active_storage_blobs + name: index_active_storage_blobs_on_key + unique: true + columns: + - key + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + active_storage_variant_records: + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: active_storage_variant_records + name: index_active_storage_variant_records_uniqueness + unique: true + columns: + - blob_id + - variation_digest + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + ar_internal_metadata: [] + assignees_filters: + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: assignees_filters + name: index_assignees_filters_on_filter_id + unique: false + columns: + - filter_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: assignees_filters + name: index_assignees_filters_on_assignee_id + unique: false + columns: + - assignee_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + assigners_filters: + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: assigners_filters + name: index_assigners_filters_on_filter_id + unique: false + columns: + - filter_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: assigners_filters + name: index_assigners_filters_on_assigner_id + unique: false + columns: + - assigner_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + assignments: + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: assignments + name: index_assignments_on_bubble_id + unique: false + columns: + - bubble_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: assignments + name: index_assignments_on_assignee_id_and_bubble_id + unique: true + columns: + - assignee_id + - bubble_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + bubbles: + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: bubbles + name: index_bubbles_on_activity_score_order + unique: false + columns: + - activity_score_order + lengths: {} + orders: :desc + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: bubbles + name: index_bubbles_on_stage_id + unique: false + columns: + - stage_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: bubbles + name: index_bubbles_on_bucket_id + unique: false + columns: + - bucket_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: bubbles + name: index_bubbles_on_auto_pop_at + unique: false + columns: + - auto_pop_at + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + buckets: + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: buckets + name: index_buckets_on_creator_id + unique: false + columns: + - creator_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: buckets + name: index_buckets_on_account_id + unique: false + columns: + - account_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + buckets_filters: + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: buckets_filters + name: index_buckets_filters_on_filter_id + unique: false + columns: + - filter_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: buckets_filters + name: index_buckets_filters_on_bucket_id + unique: false + columns: + - bucket_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + comments: [] + creators_filters: + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: creators_filters + name: index_creators_filters_on_filter_id + unique: false + columns: + - filter_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: creators_filters + name: index_creators_filters_on_creator_id + unique: false + columns: + - creator_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + event_summaries: [] + events: + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: events + name: index_events_on_summary_id_and_action + unique: false + columns: + - summary_id + - action + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: events + name: index_events_on_creator_id + unique: false + columns: + - creator_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: events + name: index_events_on_bubble_id + unique: false + columns: + - bubble_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + filters: + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: filters + name: index_filters_on_creator_id_and_params_digest + unique: true + columns: + - creator_id + - params_digest + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + filters_stages: + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: filters_stages + name: index_filters_stages_on_stage_id + unique: false + columns: + - stage_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: filters_stages + name: index_filters_stages_on_filter_id + unique: false + columns: + - filter_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + filters_tags: + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: filters_tags + name: index_filters_tags_on_tag_id + unique: false + columns: + - tag_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: filters_tags + name: index_filters_tags_on_filter_id + unique: false + columns: + - filter_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + messages: + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: messages + name: index_messages_on_messageable + unique: true + columns: + - messageable_type + - messageable_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: messages + name: index_messages_on_bubble_id + unique: false + columns: + - bubble_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + notifications: + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: notifications + name: index_notifications_on_user_id + unique: false + columns: + - user_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: notifications + name: index_notifications_on_user_id_and_read_at_and_created_at + unique: false + columns: + - user_id + - read_at + - created_at + lengths: {} + orders: + read_at: :desc + created_at: :desc + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: notifications + name: index_notifications_on_resource + unique: false + columns: + - resource_type + - resource_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: notifications + name: index_notifications_on_event_id + unique: false + columns: + - event_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: notifications + name: index_notifications_on_bubble_id + unique: false + columns: + - bubble_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + pops: + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: pops + name: index_pops_on_user_id + unique: false + columns: + - user_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: pops + name: index_pops_on_bubble_id + unique: true + columns: + - bubble_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + reactions: + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: reactions + name: index_reactions_on_reacter_id + unique: false + columns: + - reacter_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: reactions + name: index_reactions_on_comment_id + unique: false + columns: + - comment_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + schema_migrations: [] + sessions: + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: sessions + name: index_sessions_on_user_id + unique: false + columns: + - user_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + subscriptions: + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: subscriptions + name: index_subscriptions_on_user_id + unique: false + columns: + - user_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: subscriptions + name: index_subscriptions_on_subscribable + unique: false + columns: + - subscribable_type + - subscribable_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: subscriptions + name: idx_on_subscribable_type_subscribable_id_user_id_81936d569b + unique: true + columns: + - subscribable_type + - subscribable_id + - user_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + taggings: + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: taggings + name: index_taggings_on_tag_id + unique: false + columns: + - tag_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: taggings + name: index_taggings_on_bubble_id_and_tag_id + unique: true + columns: + - bubble_id + - tag_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + tags: + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: tags + name: index_tags_on_account_id + unique: false + columns: + - account_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + users: + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: users + name: index_users_on_role + unique: false + columns: + - role + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: users + name: index_users_on_email_address + unique: true + columns: + - email_address + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: users + name: index_users_on_account_id + unique: false + columns: + - account_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + watches: + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: watches + name: index_watches_on_user_id + unique: false + columns: + - user_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: watches + name: index_watches_on_bubble_id + unique: false + columns: + - bubble_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + workflow_stages: + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: workflow_stages + name: index_workflow_stages_on_workflow_id + unique: false + columns: + - workflow_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + workflows: + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: workflows + name: index_workflows_on_account_id + unique: false + columns: + - account_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true +version: 20250304140641 diff --git a/test/controllers/uploads_controller_test.rb b/test/controllers/uploads_controller_test.rb index 64a092171..3cbdb3250 100644 --- a/test/controllers/uploads_controller_test.rb +++ b/test/controllers/uploads_controller_test.rb @@ -11,7 +11,7 @@ class UploadsControllerTest < ActionDispatch::IntegrationTest end assert_response :success - assert_equal ActiveStorage::Attachment.last.slug_url(host: "www.example.com", port: nil), response.parsed_body["fileUrl"] + assert_equal ActiveStorage::Attachment.last.slug_url(host: "#{ApplicationRecord.current_tenant}.example.com", port: nil), response.parsed_body["fileUrl"] assert_equal "image/jpeg", response.parsed_body["mimetype"] assert_equal "moon.jpg", response.parsed_body["fileName"] end