diff --git a/app/models/authentication_token.rb b/app/models/authentication_token.rb index 9de51c21..60a33137 100644 --- a/app/models/authentication_token.rb +++ b/app/models/authentication_token.rb @@ -4,6 +4,7 @@ require 'securerandom' class AuthenticationToken < ApplicationRecord include Creation + belongs_to :study_group, optional: true def self.generate!(user) create!( diff --git a/app/models/consumer.rb b/app/models/consumer.rb index 95868e5f..567d2217 100644 --- a/app/models/consumer.rb +++ b/app/models/consumer.rb @@ -2,6 +2,7 @@ class Consumer < ApplicationRecord has_many :users + has_many :study_groups, dependent: :destroy scope :with_internal_users, -> { where('id IN (SELECT DISTINCT consumer_id FROM internal_users)') } scope :with_external_users, -> { where('id IN (SELECT DISTINCT consumer_id FROM external_users)') } diff --git a/app/models/study_group.rb b/app/models/study_group.rb index e1a26174..7688c89d 100644 --- a/app/models/study_group.rb +++ b/app/models/study_group.rb @@ -6,6 +6,8 @@ class StudyGroup < ApplicationRecord has_many :internal_users, through: :study_group_memberships, source_type: 'InternalUser', source: :user has_many :submissions, dependent: :nullify has_many :remote_evaluation_mappings, dependent: :nullify + has_many :subscriptions, dependent: :nullify + has_many :authentication_tokens, dependent: :nullify belongs_to :consumer def users diff --git a/app/models/study_group_membership.rb b/app/models/study_group_membership.rb index efb9683f..5ff0e96e 100644 --- a/app/models/study_group_membership.rb +++ b/app/models/study_group_membership.rb @@ -4,5 +4,11 @@ class StudyGroupMembership < ApplicationRecord belongs_to :user, polymorphic: true belongs_to :study_group + enum role: { + learner: 0, + teacher: 1, + }, _default: :learner, _prefix: true + + validates :role, presence: true validates :user_id, uniqueness: {scope: %i[user_type study_group_id]} end diff --git a/app/models/subscription.rb b/app/models/subscription.rb index ae9477ae..027e4992 100644 --- a/app/models/subscription.rb +++ b/app/models/subscription.rb @@ -3,4 +3,5 @@ class Subscription < ApplicationRecord belongs_to :user, polymorphic: true belongs_to :request_for_comment + belongs_to :study_group, optional: true end diff --git a/db/migrate/20220906142041_add_study_group_authorization.rb b/db/migrate/20220906142041_add_study_group_authorization.rb new file mode 100644 index 00000000..b168dd6e --- /dev/null +++ b/db/migrate/20220906142041_add_study_group_authorization.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class AddStudyGroupAuthorization < ActiveRecord::Migration[6.1] + def change + add_column :external_users, :platform_admin, :boolean, default: false, nil: false + add_column :internal_users, :platform_admin, :boolean, default: false, nil: false + add_column :study_group_memberships, :role, :integer, limit: 1, null: false, default: 0, comment: 'Used as enum in Rails' + add_reference :subscriptions, :study_group, index: true, null: true, foreign_key: true + add_reference :authentication_tokens, :study_group, index: true, null: true, foreign_key: true + end +end diff --git a/db/migrate/20220906142550_migrate_permissions_to_study_group.rb b/db/migrate/20220906142550_migrate_permissions_to_study_group.rb new file mode 100644 index 00000000..7df8d273 --- /dev/null +++ b/db/migrate/20220906142550_migrate_permissions_to_study_group.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +class MigratePermissionsToStudyGroup < ActiveRecord::Migration[6.1] + # rubocop:disable Rails/SkipsModelValidations + def up + create_default_groups + migrate_internal_users + migrate_external_users + end + + def create_default_groups + Consumer.find_each do |consumer| + StudyGroup.find_or_create_by!(consumer: consumer, external_id: nil) do |new_group| + new_group.name = "Default Study Group for #{consumer.name}" + end + end + end + + def migrate_internal_users + # Internal users don't necessarily have a study group yet, which is needed for the teacher role + InternalUser.find_each do |user| + user.update_columns(platform_admin: true) if user.role == 'admin' + + study_group = StudyGroup.find_by!(consumer: user.consumer, external_id: nil) + + # All platform admins will "just" be a teacher in the study group + new_role = %w[admin teacher].include?(user.role) ? :teacher : :learner + membership = StudyGroupMembership.find_or_create_by!(study_group: study_group, user: user) + membership.update_columns(role: new_role) + end + end + + def migrate_external_users + # All external users are (or will be) in a study group once launched through LTI + # and therefore don't need a new StudyGroupMembership + ExternalUser.where(role: 'admin').update(platform_admin: true) + end + # rubocop:enable Rails/SkipsModelValidations +end diff --git a/db/migrate/20220906142603_remove_role_from_users.rb b/db/migrate/20220906142603_remove_role_from_users.rb new file mode 100644 index 00000000..401fbf89 --- /dev/null +++ b/db/migrate/20220906142603_remove_role_from_users.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class RemoveRoleFromUsers < ActiveRecord::Migration[6.1] + def change + remove_column :external_users, :role + remove_column :internal_users, :role + end +end diff --git a/db/schema.rb b/db/schema.rb index f0cba53b..b128800d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2022_07_21_131946) do +ActiveRecord::Schema.define(version: 2022_09_06_142603) do # These are extensions that must be enabled in order to support this database enable_extension "pg_trgm" @@ -37,7 +37,9 @@ ActiveRecord::Schema.define(version: 2022_07_21_131946) do t.datetime "expire_at", null: false t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false + t.bigint "study_group_id" t.index ["shared_secret"], name: "index_authentication_tokens_on_shared_secret", unique: true + t.index ["study_group_id"], name: "index_authentication_tokens_on_study_group_id" t.index ["user_type", "user_id"], name: "index_authentication_tokens_on_user" end @@ -238,7 +240,7 @@ ActiveRecord::Schema.define(version: 2022_07_21_131946) do t.string "name" t.datetime "created_at" t.datetime "updated_at" - t.string "role", default: "learner", null: false + t.boolean "platform_admin", default: false end create_table "file_templates", id: :serial, force: :cascade do |t| @@ -288,7 +290,6 @@ ActiveRecord::Schema.define(version: 2022_07_21_131946) do t.integer "consumer_id" t.string "email" t.string "name" - t.string "role" t.datetime "created_at" t.datetime "updated_at" t.string "crypted_password" @@ -304,6 +305,7 @@ ActiveRecord::Schema.define(version: 2022_07_21_131946) do t.string "activation_state" t.string "activation_token" t.datetime "activation_token_expires_at" + t.boolean "platform_admin", default: false t.index ["activation_token"], name: "index_internal_users_on_activation_token" t.index ["email"], name: "index_internal_users_on_email", unique: true t.index ["remember_me_token"], name: "index_internal_users_on_remember_me_token" @@ -429,6 +431,7 @@ ActiveRecord::Schema.define(version: 2022_07_21_131946) do t.bigint "study_group_id" t.string "user_type" t.bigint "user_id" + t.integer "role", limit: 2, default: 0, null: false, comment: "Used as enum in Rails" t.index ["study_group_id"], name: "index_study_group_memberships_on_study_group_id" t.index ["user_type", "user_id"], name: "index_study_group_memberships_on_user" end @@ -465,6 +468,8 @@ ActiveRecord::Schema.define(version: 2022_07_21_131946) do t.datetime "created_at", null: false t.datetime "updated_at", null: false t.boolean "deleted" + t.bigint "study_group_id" + t.index ["study_group_id"], name: "index_subscriptions_on_study_group_id" end create_table "tags", id: :serial, force: :cascade do |t| @@ -563,6 +568,7 @@ ActiveRecord::Schema.define(version: 2022_07_21_131946) do t.index ["user_type", "user_id"], name: "index_user_proxy_exercise_exercises_on_user" end + add_foreign_key "authentication_tokens", "study_groups" add_foreign_key "community_solution_contributions", "community_solution_locks" add_foreign_key "community_solution_contributions", "community_solutions" add_foreign_key "community_solution_contributions", "study_groups" @@ -573,6 +579,7 @@ ActiveRecord::Schema.define(version: 2022_07_21_131946) do add_foreign_key "exercise_tips", "tips" add_foreign_key "remote_evaluation_mappings", "study_groups" add_foreign_key "submissions", "study_groups" + add_foreign_key "subscriptions", "study_groups" add_foreign_key "testrun_execution_environments", "execution_environments" add_foreign_key "testrun_execution_environments", "testruns" add_foreign_key "testrun_messages", "testruns"