From 2c3e2461ef157abea664d39d83d239539b70cb8c Mon Sep 17 00:00:00 2001 From: Jeremy Green Date: Tue, 19 Nov 2024 16:03:50 -0600 Subject: [PATCH 1/8] Setup the main bullet_train gem to use postgres for database and import a schema --- bullet_train/Gemfile.lock | 2 + bullet_train/bullet_train.gemspec | 1 + bullet_train/test/dummy/config/database.yml | 21 ++---- bullet_train/test/dummy/db/schema.rb | 79 +++++++++++++++++++++ 4 files changed, 86 insertions(+), 17 deletions(-) create mode 100644 bullet_train/test/dummy/db/schema.rb diff --git a/bullet_train/Gemfile.lock b/bullet_train/Gemfile.lock index cefbfb786..22d8711cf 100644 --- a/bullet_train/Gemfile.lock +++ b/bullet_train/Gemfile.lock @@ -347,6 +347,7 @@ GEM parallel (1.22.1) parser (3.1.3.0) ast (~> 2.4.1) + pg (1.5.9) phonelib (0.8.4) possessive (1.0.1) premailer (1.21.0) @@ -503,6 +504,7 @@ DEPENDENCIES bullet_train-themes! bullet_train-themes-light! minitest-reporters + pg (~> 1.3) pry pry-stack_explorer sprockets-rails diff --git a/bullet_train/bullet_train.gemspec b/bullet_train/bullet_train.gemspec index 15e9f2158..2269c7250 100644 --- a/bullet_train/bullet_train.gemspec +++ b/bullet_train/bullet_train.gemspec @@ -104,6 +104,7 @@ Gem::Specification.new do |spec| # We don't want to develop in a world where we don't have `binding.pry` or `object.pry` for debugging. spec.add_development_dependency "pry" spec.add_development_dependency "pry-stack_explorer" + spec.add_development_dependency "pg", "~> 1.3" # Password strength. spec.add_dependency "devise-pwned_password" diff --git a/bullet_train/test/dummy/config/database.yml b/bullet_train/test/dummy/config/database.yml index fcba57f19..4e130eaed 100644 --- a/bullet_train/test/dummy/config/database.yml +++ b/bullet_train/test/dummy/config/database.yml @@ -1,25 +1,12 @@ -# SQLite. Versions 3.8.0 and up are supported. -# gem install sqlite3 -# -# Ensure the SQLite 3 gem is defined in your Gemfile -# gem "sqlite3" -# default: &default - adapter: sqlite3 + adapter: postgresql + encoding: unicode pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> - timeout: 5000 development: <<: *default - database: db/development.sqlite3 + database: bullet_train-core_development -# Warning: The database defined as "test" will be erased and -# re-generated from your development database when you run "rake". -# Do not set this db to the same as development or production. test: <<: *default - database: db/test.sqlite3 - -production: - <<: *default - database: db/production.sqlite3 + database: bullet_train-core_test diff --git a/bullet_train/test/dummy/db/schema.rb b/bullet_train/test/dummy/db/schema.rb new file mode 100644 index 000000000..fd3290232 --- /dev/null +++ b/bullet_train/test/dummy/db/schema.rb @@ -0,0 +1,79 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema[7.0].define(version: 2022_03_02_235728) do + # These are extensions that must be enabled in order to support this database + enable_extension "plpgsql" + + create_table "documents", force: :cascade do |t| + t.bigint "membership_id", null: false + t.string "name" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["membership_id"], name: "index_documents_on_membership_id" + end + + create_table "memberships", force: :cascade do |t| + t.jsonb "role_ids" + t.bigint "user_id", null: false + t.bigint "team_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["team_id"], name: "index_memberships_on_team_id" + t.index ["user_id"], name: "index_memberships_on_user_id" + end + + create_table "scaffolding_absolutely_abstract_creative_concepts", force: :cascade do |t| + t.string "name" + t.text "description" + t.bigint "team_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["team_id"], name: "index_scaffold_absolutely_abstract_creative_concept_on_team_id" + end + + create_table "scaffolding_absolutely_abstract_creative_concepts_collaborators", force: :cascade do |t| + t.jsonb "role_ids" + t.bigint "creative_concept_id", null: false + t.bigint "membership_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["creative_concept_id"], name: "index_creative_concepts_collaborators_on_creative_concept_id" + t.index ["membership_id"], name: "index_creative_concepts_collaborators_on_membership_id" + end + + create_table "teams", force: :cascade do |t| + t.string "name" + t.string "slug" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.boolean "being_destroyed" + t.string "time_zone" + t.string "locale" + end + + create_table "users", force: :cascade do |t| + t.string "email" + t.string "first_name" + t.string "last_name" + t.integer "current_team_id" + t.jsonb "ability_cache" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_foreign_key "documents", "memberships" + add_foreign_key "memberships", "teams" + add_foreign_key "memberships", "users" + add_foreign_key "scaffolding_absolutely_abstract_creative_concepts", "teams" + add_foreign_key "scaffolding_absolutely_abstract_creative_concepts_collaborators", "scaffolding_absolutely_abstract_creative_concepts", column: "creative_concept_id" +end From 7816840c987ee5d1881ab34efe5682cd4967ea6d Mon Sep 17 00:00:00 2001 From: Jeremy Green Date: Tue, 19 Nov 2024 16:04:07 -0600 Subject: [PATCH 2/8] add a failing test for default time zone --- bullet_train/test/models/team_test.rb | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 bullet_train/test/models/team_test.rb diff --git a/bullet_train/test/models/team_test.rb b/bullet_train/test/models/team_test.rb new file mode 100644 index 000000000..3f0c3ccab --- /dev/null +++ b/bullet_train/test/models/team_test.rb @@ -0,0 +1,8 @@ +require "test_helper" + +class TeamTest < ActiveSupport::TestCase + test "a new team defaults time_zone to UTC" do + team = Team.new + assert_equal "UTC", team.time_zone + end +end From 745de7e14dc022d58907d21f54a2f95363f1ad2e Mon Sep 17 00:00:00 2001 From: Jeremy Green Date: Tue, 19 Nov 2024 16:34:46 -0600 Subject: [PATCH 3/8] get the failing test passing --- bullet_train/app/models/concerns/teams/base.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bullet_train/app/models/concerns/teams/base.rb b/bullet_train/app/models/concerns/teams/base.rb index bfafc8085..dcd077a71 100644 --- a/bullet_train/app/models/concerns/teams/base.rb +++ b/bullet_train/app/models/concerns/teams/base.rb @@ -25,6 +25,11 @@ module Teams::Base validates :time_zone, inclusion: {in: ActiveSupport::TimeZone.all.map(&:name)}, allow_nil: true end + def initialize(attributes = nil) + super + self.time_zone = "UTC" if time_zone.blank? + end + def platform_agent_access_tokens Platform::AccessToken.joins(:application).where(resource_owner_id: users.where.not(platform_agent_of_id: nil), application: {team: nil}) end From 8628ae6f8e3f0edbb9821544200739654873d758 Mon Sep 17 00:00:00 2001 From: Jeremy Green Date: Tue, 19 Nov 2024 16:35:22 -0600 Subject: [PATCH 4/8] A failing test for getting the time_zone from the first user --- bullet_train/config/models/roles.yml | 37 ++++++++++++++++ bullet_train/test/dummy/db/schema.rb | 63 ++++++++++++++++++++++----- bullet_train/test/models/team_test.rb | 8 ++++ 3 files changed, 97 insertions(+), 11 deletions(-) create mode 100644 bullet_train/config/models/roles.yml diff --git a/bullet_train/config/models/roles.yml b/bullet_train/config/models/roles.yml new file mode 100644 index 000000000..1b2825dc8 --- /dev/null +++ b/bullet_train/config/models/roles.yml @@ -0,0 +1,37 @@ +default: + models: + Team: read + Document: read + Membership: + - read + - search + +crud_role: + models: + Team: crud + +editor: + models: + Scaffolding::AbsolutelyAbstract::CreativeConcept: + - read + - update + +manager: + includes: + - editor + +supervisor: + includes: + - manager + +admin: + includes: + - editor + manageable_roles: + - admin + - editor + models: + Team: manage + Membership: manage + Document: manage + Scaffolding::AbsolutelyAbstract::CreativeConcept: manage diff --git a/bullet_train/test/dummy/db/schema.rb b/bullet_train/test/dummy/db/schema.rb index fd3290232..4a207d532 100644 --- a/bullet_train/test/dummy/db/schema.rb +++ b/bullet_train/test/dummy/db/schema.rb @@ -22,12 +22,23 @@ t.index ["membership_id"], name: "index_documents_on_membership_id" end - create_table "memberships", force: :cascade do |t| - t.jsonb "role_ids" - t.bigint "user_id", null: false - t.bigint "team_id", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + create_table "memberships", id: :serial, force: :cascade do |t| + t.integer "user_id" + t.integer "team_id" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.bigint "invitation_id" + t.string "user_first_name" + t.string "user_last_name" + t.string "user_profile_photo_id" + t.string "user_email" + t.bigint "added_by_id" + t.bigint "platform_agent_of_id" + t.jsonb "role_ids", default: [] + t.boolean "platform_agent", default: false + t.index ["added_by_id"], name: "index_memberships_on_added_by_id" + t.index ["invitation_id"], name: "index_memberships_on_invitation_id" + t.index ["platform_agent_of_id"], name: "index_memberships_on_platform_agent_of_id" t.index ["team_id"], name: "index_memberships_on_team_id" t.index ["user_id"], name: "index_memberships_on_user_id" end @@ -61,14 +72,44 @@ t.string "locale" end - create_table "users", force: :cascade do |t| - t.string "email" + create_table "users", id: :serial, force: :cascade do |t| + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false + t.string "reset_password_token" + t.datetime "reset_password_sent_at", precision: nil + t.datetime "remember_created_at", precision: nil + t.integer "sign_in_count", default: 0, null: false + t.datetime "current_sign_in_at", precision: nil + t.datetime "last_sign_in_at", precision: nil + t.inet "current_sign_in_ip" + t.inet "last_sign_in_ip" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.integer "current_team_id" t.string "first_name" t.string "last_name" - t.integer "current_team_id" + t.string "time_zone" + t.datetime "last_seen_at", precision: nil + t.string "profile_photo_id" t.jsonb "ability_cache" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "last_notification_email_sent_at", precision: nil + t.boolean "former_user", default: false, null: false + t.string "encrypted_otp_secret" + t.string "encrypted_otp_secret_iv" + t.string "encrypted_otp_secret_salt" + t.integer "consumed_timestep" + t.boolean "otp_required_for_login" + t.string "otp_backup_codes", array: true + t.string "locale" + t.bigint "platform_agent_of_id" + t.string "otp_secret" + t.integer "failed_attempts", default: 0, null: false + t.string "unlock_token" + t.datetime "locked_at" + t.index ["email"], name: "index_users_on_email", unique: true + t.index ["platform_agent_of_id"], name: "index_users_on_platform_agent_of_id" + t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true + t.index ["unlock_token"], name: "index_users_on_unlock_token", unique: true end add_foreign_key "documents", "memberships" diff --git a/bullet_train/test/models/team_test.rb b/bullet_train/test/models/team_test.rb index 3f0c3ccab..3058c3721 100644 --- a/bullet_train/test/models/team_test.rb +++ b/bullet_train/test/models/team_test.rb @@ -5,4 +5,12 @@ class TeamTest < ActiveSupport::TestCase team = Team.new assert_equal "UTC", team.time_zone end + + test "a new team gets the time_zone of the first user when they join" do + team = Team.create!(name: "new test team") + user = User.create!(email: "test@test.com", password: "password", password_confirmation: "password", time_zone: "Central Time (US & Canada)") + Membership.create!(team: team, user: user) + team.reload + assert_equal "Central Time (US & Canada)", team.time_zone + end end From 7c3e7255f2125f12c3cb2af349b0a35191960fab Mon Sep 17 00:00:00 2001 From: Jeremy Green Date: Tue, 19 Nov 2024 16:46:27 -0600 Subject: [PATCH 5/8] More tests and methods --- .../app/models/concerns/memberships/base.rb | 10 +++++++ .../app/models/concerns/teams/base.rb | 5 ++++ bullet_train/test/models/team_test.rb | 30 +++++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/bullet_train/app/models/concerns/memberships/base.rb b/bullet_train/app/models/concerns/memberships/base.rb index 8b30ac741..33751aab8 100644 --- a/bullet_train/app/models/concerns/memberships/base.rb +++ b/bullet_train/app/models/concerns/memberships/base.rb @@ -32,6 +32,8 @@ module Memberships::Base after_commit :publish_changed_quantity + after_create :set_team_time_zone, if: :is_first_membership? + after_validation :remove_user_profile_photo, if: :user_profile_photo_removal? scope :excluding_platform_agents, -> { where(platform_agent_of: nil) } @@ -157,5 +159,13 @@ def publish_changed_quantity ActiveSupport::Notifications.instrument("memberships.quantity-changed", {team:}) end + def set_team_time_zone + team.set_time_zone_from_user(user) + end + + def is_first_membership? + team.memberships.count == 1 && team.memberships.first.id == id + end + ActiveSupport.run_load_hooks :bullet_train_memberships_base, self end diff --git a/bullet_train/app/models/concerns/teams/base.rb b/bullet_train/app/models/concerns/teams/base.rb index dcd077a71..fa4410a6a 100644 --- a/bullet_train/app/models/concerns/teams/base.rb +++ b/bullet_train/app/models/concerns/teams/base.rb @@ -30,6 +30,11 @@ def initialize(attributes = nil) self.time_zone = "UTC" if time_zone.blank? end + def set_time_zone_from_user(user) + self.time_zone = user.time_zone if user.time_zone.present? + save + end + def platform_agent_access_tokens Platform::AccessToken.joins(:application).where(resource_owner_id: users.where.not(platform_agent_of_id: nil), application: {team: nil}) end diff --git a/bullet_train/test/models/team_test.rb b/bullet_train/test/models/team_test.rb index 3058c3721..d8ace514c 100644 --- a/bullet_train/test/models/team_test.rb +++ b/bullet_train/test/models/team_test.rb @@ -6,6 +6,14 @@ class TeamTest < ActiveSupport::TestCase assert_equal "UTC", team.time_zone end + test "explicitly set time_zone is not clobbered by first user" do + team = Team.create!(name: "new test team", time_zone: "Eastern Time (US & Canada)") + user = User.create!(email: "test@test.com", password: "password", password_confirmation: "password", time_zone: "Central Time (US & Canada)") + Membership.create!(team: team, user: user) + team.reload + assert_equal "Eastern Time (US & Canada)", team.time_zone + end + test "a new team gets the time_zone of the first user when they join" do team = Team.create!(name: "new test team") user = User.create!(email: "test@test.com", password: "password", password_confirmation: "password", time_zone: "Central Time (US & Canada)") @@ -13,4 +21,26 @@ class TeamTest < ActiveSupport::TestCase team.reload assert_equal "Central Time (US & Canada)", team.time_zone end + + test "default UTC time_zone is not clobbered if first user doesn't have a time zone set" do + team = Team.create!(name: "new test team") + user = User.create!(email: "test@test.com", password: "password", password_confirmation: "password", time_zone: nil) + Membership.create!(team: team, user: user) + team.reload + assert_equal "UTC", team.time_zone + end + + test "default UTC time_zone is overwritten once the first user sets a time zone" do + team = Team.create!(name: "new test team") + user = User.create!(email: "test@test.com", password: "password", password_confirmation: "password", time_zone: nil) + Membership.create!(team: team, user: user) + team.reload + assert_equal "UTC", team.time_zone + + user.time_zone = "Central Time (US & Canada)" + user.save + + team.reload + assert_equal "Central Time (US & Canada)", team.time_zone + end end From ead98a3de6626c0de2b1535d78dce50b1d41c864 Mon Sep 17 00:00:00 2001 From: Jeremy Green Date: Wed, 20 Nov 2024 09:14:45 -0600 Subject: [PATCH 6/8] fix a test and remove a test --- bullet_train/app/models/concerns/teams/base.rb | 1 + bullet_train/test/models/team_test.rb | 14 -------------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/bullet_train/app/models/concerns/teams/base.rb b/bullet_train/app/models/concerns/teams/base.rb index fa4410a6a..fc075ddf3 100644 --- a/bullet_train/app/models/concerns/teams/base.rb +++ b/bullet_train/app/models/concerns/teams/base.rb @@ -31,6 +31,7 @@ def initialize(attributes = nil) end def set_time_zone_from_user(user) + return if time_zone != "UTC" self.time_zone = user.time_zone if user.time_zone.present? save end diff --git a/bullet_train/test/models/team_test.rb b/bullet_train/test/models/team_test.rb index d8ace514c..1a8e95f80 100644 --- a/bullet_train/test/models/team_test.rb +++ b/bullet_train/test/models/team_test.rb @@ -29,18 +29,4 @@ class TeamTest < ActiveSupport::TestCase team.reload assert_equal "UTC", team.time_zone end - - test "default UTC time_zone is overwritten once the first user sets a time zone" do - team = Team.create!(name: "new test team") - user = User.create!(email: "test@test.com", password: "password", password_confirmation: "password", time_zone: nil) - Membership.create!(team: team, user: user) - team.reload - assert_equal "UTC", team.time_zone - - user.time_zone = "Central Time (US & Canada)" - user.save - - team.reload - assert_equal "Central Time (US & Canada)", team.time_zone - end end From cc654d99ba8ce97961a3af4713d9e1bacc7026c3 Mon Sep 17 00:00:00 2001 From: Jeremy Green Date: Wed, 20 Nov 2024 09:28:46 -0600 Subject: [PATCH 7/8] bring back that test and make it pass --- .../app/models/concerns/users/base.rb | 2 +- bullet_train/test/models/team_test.rb | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/bullet_train/app/models/concerns/users/base.rb b/bullet_train/app/models/concerns/users/base.rb index c75299c98..ddbf4d598 100644 --- a/bullet_train/app/models/concerns/users/base.rb +++ b/bullet_train/app/models/concerns/users/base.rb @@ -153,7 +153,7 @@ def developer? end def set_teams_time_zone - teams.where(time_zone: nil).each do |team| + teams.where(time_zone: [nil, "UTC"]).each do |team| team.update(time_zone: time_zone) if team.users.count == 1 end end diff --git a/bullet_train/test/models/team_test.rb b/bullet_train/test/models/team_test.rb index 1a8e95f80..8f111ad88 100644 --- a/bullet_train/test/models/team_test.rb +++ b/bullet_train/test/models/team_test.rb @@ -29,4 +29,34 @@ class TeamTest < ActiveSupport::TestCase team.reload assert_equal "UTC", team.time_zone end + + test "default UTC time_zone is overwritten once the first user sets a time zone" do + team = Team.create!(name: "new test team") + user = User.create!(email: "test@test.com", password: "password", password_confirmation: "password", time_zone: nil) + Membership.create!(team: team, user: user) + team.reload + assert_equal "UTC", team.time_zone + + user.time_zone = "Central Time (US & Canada)" + user.save + + team.reload + assert_equal "Central Time (US & Canada)", team.time_zone + end + + test "nil time_zone is overwritten once the first user sets a time zone" do + team = Team.create!(name: "new test team", time_zone: nil) + user = User.create!(email: "test@test.com", password: "password", password_confirmation: "password", time_zone: nil) + Membership.create!(team: team, user: user) + team.time_zone = nil + team.save + team.reload + assert_equal nil, team.time_zone + + user.time_zone = "Central Time (US & Canada)" + user.save + + team.reload + assert_equal "Central Time (US & Canada)", team.time_zone + end end From 989c3d74e8f4d1076759912046cf47f02be80b8e Mon Sep 17 00:00:00 2001 From: Jeremy Green Date: Wed, 20 Nov 2024 09:36:33 -0600 Subject: [PATCH 8/8] another test and more robustification --- bullet_train/app/models/concerns/teams/base.rb | 7 ++++--- bullet_train/test/models/team_test.rb | 13 +++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/bullet_train/app/models/concerns/teams/base.rb b/bullet_train/app/models/concerns/teams/base.rb index fc075ddf3..f732c375b 100644 --- a/bullet_train/app/models/concerns/teams/base.rb +++ b/bullet_train/app/models/concerns/teams/base.rb @@ -31,9 +31,10 @@ def initialize(attributes = nil) end def set_time_zone_from_user(user) - return if time_zone != "UTC" - self.time_zone = user.time_zone if user.time_zone.present? - save + if time_zone.blank? || time_zone == "UTC" + self.time_zone = user.time_zone if user.time_zone.present? + save + end end def platform_agent_access_tokens diff --git a/bullet_train/test/models/team_test.rb b/bullet_train/test/models/team_test.rb index 8f111ad88..a053f0ff7 100644 --- a/bullet_train/test/models/team_test.rb +++ b/bullet_train/test/models/team_test.rb @@ -22,6 +22,19 @@ class TeamTest < ActiveSupport::TestCase assert_equal "Central Time (US & Canada)", team.time_zone end + test "a team with a nil time_zone gets the time_zone of the first user when they join" do + team = Team.create!(name: "new test team") + team.time_zone = nil + team.save + team.reload + assert_equal nil, team.time_zone + + user = User.create!(email: "test@test.com", password: "password", password_confirmation: "password", time_zone: "Central Time (US & Canada)") + Membership.create!(team: team, user: user) + team.reload + assert_equal "Central Time (US & Canada)", team.time_zone + end + test "default UTC time_zone is not clobbered if first user doesn't have a time zone set" do team = Team.create!(name: "new test team") user = User.create!(email: "test@test.com", password: "password", password_confirmation: "password", time_zone: nil)