From 6bdb1dcb7c7a2851037bfbcc05833eff1779c59d Mon Sep 17 00:00:00 2001 From: Corinna Gogolok <cgogolok@gitlab.com> Date: Wed, 7 Jun 2023 13:57:38 +0000 Subject: [PATCH] Add database structure for add-ons This change adds the models and their database tables for add-ons. Changelog: added --- db/docs/subscription_add_on_purchases.yml | 10 +++ db/docs/subscription_add_ons.yml | 10 +++ ...30531134916_create_subscription_add_ons.rb | 12 ++++ ...01_create_subscription_add_on_purchases.rb | 18 ++++++ ..._on_id_on_subscription_add_on_purchases.rb | 18 ++++++ ...ace_id_on_subscription_add_on_purchases.rb | 15 +++++ db/schema_migrations/20230531134916 | 1 + db/schema_migrations/20230531135001 | 1 + db/schema_migrations/20230531142032 | 1 + db/schema_migrations/20230531142053 | 1 + db/structure.sql | 61 +++++++++++++++++++ ee/app/models/ee/namespace.rb | 1 + ee/app/models/gitlab_subscriptions/add_on.rb | 20 ++++++ .../gitlab_subscriptions/add_on_purchase.rb | 18 ++++++ ee/spec/factories/add_on.rb | 8 +++ ee/spec/factories/add_on_purchase.rb | 11 ++++ ee/spec/models/ee/namespace_spec.rb | 1 + .../add_on_purchase_spec.rb | 24 ++++++++ .../gitlab_subscriptions/add_on_spec.rb | 19 ++++++ 19 files changed, 250 insertions(+) create mode 100644 db/docs/subscription_add_on_purchases.yml create mode 100644 db/docs/subscription_add_ons.yml create mode 100644 db/migrate/20230531134916_create_subscription_add_ons.rb create mode 100644 db/migrate/20230531135001_create_subscription_add_on_purchases.rb create mode 100644 db/migrate/20230531142032_add_foreign_key_subscription_add_on_id_on_subscription_add_on_purchases.rb create mode 100644 db/migrate/20230531142053_add_foreign_key_namespace_id_on_subscription_add_on_purchases.rb create mode 100644 db/schema_migrations/20230531134916 create mode 100644 db/schema_migrations/20230531135001 create mode 100644 db/schema_migrations/20230531142032 create mode 100644 db/schema_migrations/20230531142053 create mode 100644 ee/app/models/gitlab_subscriptions/add_on.rb create mode 100644 ee/app/models/gitlab_subscriptions/add_on_purchase.rb create mode 100644 ee/spec/factories/add_on.rb create mode 100644 ee/spec/factories/add_on_purchase.rb create mode 100644 ee/spec/models/gitlab_subscriptions/add_on_purchase_spec.rb create mode 100644 ee/spec/models/gitlab_subscriptions/add_on_spec.rb diff --git a/db/docs/subscription_add_on_purchases.yml b/db/docs/subscription_add_on_purchases.yml new file mode 100644 index 0000000000000..21915cff54529 --- /dev/null +++ b/db/docs/subscription_add_on_purchases.yml @@ -0,0 +1,10 @@ +--- +table_name: subscription_add_on_purchases +description: Stores add-on purchase information +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122662 +milestone: '16.1' +feature_categories: +- subscription_management +classes: +- GitlabSubscriptions::AddOnPurchase +gitlab_schema: gitlab_main diff --git a/db/docs/subscription_add_ons.yml b/db/docs/subscription_add_ons.yml new file mode 100644 index 0000000000000..93730f80a999f --- /dev/null +++ b/db/docs/subscription_add_ons.yml @@ -0,0 +1,10 @@ +--- +table_name: subscription_add_ons +description: Stores available add-ons for which purchases are stored in `subscription_add_on_purchases`. +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122662 +milestone: '16.1' +feature_categories: +- subscription_management +classes: +- GitlabSubscriptions::AddOn +gitlab_schema: gitlab_main diff --git a/db/migrate/20230531134916_create_subscription_add_ons.rb b/db/migrate/20230531134916_create_subscription_add_ons.rb new file mode 100644 index 0000000000000..5faee04953403 --- /dev/null +++ b/db/migrate/20230531134916_create_subscription_add_ons.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class CreateSubscriptionAddOns < Gitlab::Database::Migration[2.1] + def change + create_table :subscription_add_ons, if_not_exists: true do |t| + t.timestamps_with_timezone null: false + + t.integer :name, limit: 2, null: false, index: { unique: true } + t.text :description, null: false, limit: 512 + end + end +end diff --git a/db/migrate/20230531135001_create_subscription_add_on_purchases.rb b/db/migrate/20230531135001_create_subscription_add_on_purchases.rb new file mode 100644 index 0000000000000..6fdf1fdd49545 --- /dev/null +++ b/db/migrate/20230531135001_create_subscription_add_on_purchases.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class CreateSubscriptionAddOnPurchases < Gitlab::Database::Migration[2.1] + def change + create_table :subscription_add_on_purchases, if_not_exists: true do |t| + t.timestamps_with_timezone null: false + + t.bigint :subscription_add_on_id, null: false + t.bigint :namespace_id, null: false + t.integer :quantity, null: false + t.date :expires_on, null: false + t.text :purchase_xid, null: false, limit: 255 + + t.index :subscription_add_on_id + t.index :namespace_id + end + end +end diff --git a/db/migrate/20230531142032_add_foreign_key_subscription_add_on_id_on_subscription_add_on_purchases.rb b/db/migrate/20230531142032_add_foreign_key_subscription_add_on_id_on_subscription_add_on_purchases.rb new file mode 100644 index 0000000000000..234cd2fa3af3e --- /dev/null +++ b/db/migrate/20230531142032_add_foreign_key_subscription_add_on_id_on_subscription_add_on_purchases.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class AddForeignKeySubscriptionAddOnIdOnSubscriptionAddOnPurchases < Gitlab::Database::Migration[2.1] + disable_ddl_transaction! + + def up + add_concurrent_foreign_key :subscription_add_on_purchases, + :subscription_add_ons, + column: :subscription_add_on_id, + on_delete: :cascade + end + + def down + with_lock_retries do + remove_foreign_key :subscription_add_on_purchases, column: :subscription_add_on_id + end + end +end diff --git a/db/migrate/20230531142053_add_foreign_key_namespace_id_on_subscription_add_on_purchases.rb b/db/migrate/20230531142053_add_foreign_key_namespace_id_on_subscription_add_on_purchases.rb new file mode 100644 index 0000000000000..7f7083a3a9c38 --- /dev/null +++ b/db/migrate/20230531142053_add_foreign_key_namespace_id_on_subscription_add_on_purchases.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class AddForeignKeyNamespaceIdOnSubscriptionAddOnPurchases < Gitlab::Database::Migration[2.1] + disable_ddl_transaction! + + def up + add_concurrent_foreign_key :subscription_add_on_purchases, :namespaces, column: :namespace_id, on_delete: :cascade + end + + def down + with_lock_retries do + remove_foreign_key :subscription_add_on_purchases, column: :namespace_id + end + end +end diff --git a/db/schema_migrations/20230531134916 b/db/schema_migrations/20230531134916 new file mode 100644 index 0000000000000..5cf00727101d3 --- /dev/null +++ b/db/schema_migrations/20230531134916 @@ -0,0 +1 @@ +fc2e3d8e6aca7b00569340b0468488a4b0545b39e67857a5b40824f6d0a62a97 \ No newline at end of file diff --git a/db/schema_migrations/20230531135001 b/db/schema_migrations/20230531135001 new file mode 100644 index 0000000000000..32850b297da14 --- /dev/null +++ b/db/schema_migrations/20230531135001 @@ -0,0 +1 @@ +1a672c9412b8ceeec35fd375bf86dde325781c9cb94340995d2cab4bb804e4bf \ No newline at end of file diff --git a/db/schema_migrations/20230531142032 b/db/schema_migrations/20230531142032 new file mode 100644 index 0000000000000..bae2817773a59 --- /dev/null +++ b/db/schema_migrations/20230531142032 @@ -0,0 +1 @@ +3e77f991a4daa9756b541255e3b8da9d8accb52a5a4b625613771982e3dff3b5 \ No newline at end of file diff --git a/db/schema_migrations/20230531142053 b/db/schema_migrations/20230531142053 new file mode 100644 index 0000000000000..55da4601012df --- /dev/null +++ b/db/schema_migrations/20230531142053 @@ -0,0 +1 @@ +0a4b3b8848f486e34e1f0426bae4e15f67e851447fc3fe397cf2039e03b185b5 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index cc22a017044e4..23e2df53c913f 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -23031,6 +23031,45 @@ CREATE SEQUENCE status_page_settings_project_id_seq ALTER SEQUENCE status_page_settings_project_id_seq OWNED BY status_page_settings.project_id; +CREATE TABLE subscription_add_on_purchases ( + id bigint NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + subscription_add_on_id bigint NOT NULL, + namespace_id bigint NOT NULL, + quantity integer NOT NULL, + expires_on date NOT NULL, + purchase_xid text NOT NULL, + CONSTRAINT check_3313c4d200 CHECK ((char_length(purchase_xid) <= 255)) +); + +CREATE SEQUENCE subscription_add_on_purchases_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER SEQUENCE subscription_add_on_purchases_id_seq OWNED BY subscription_add_on_purchases.id; + +CREATE TABLE subscription_add_ons ( + id bigint NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + name smallint NOT NULL, + description text NOT NULL, + CONSTRAINT check_4c39d15ada CHECK ((char_length(description) <= 512)) +); + +CREATE SEQUENCE subscription_add_ons_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER SEQUENCE subscription_add_ons_id_seq OWNED BY subscription_add_ons.id; + CREATE TABLE subscriptions ( id integer NOT NULL, user_id integer, @@ -25847,6 +25886,10 @@ ALTER TABLE ONLY status_page_published_incidents ALTER COLUMN id SET DEFAULT nex ALTER TABLE ONLY status_page_settings ALTER COLUMN project_id SET DEFAULT nextval('status_page_settings_project_id_seq'::regclass); +ALTER TABLE ONLY subscription_add_on_purchases ALTER COLUMN id SET DEFAULT nextval('subscription_add_on_purchases_id_seq'::regclass); + +ALTER TABLE ONLY subscription_add_ons ALTER COLUMN id SET DEFAULT nextval('subscription_add_ons_id_seq'::regclass); + ALTER TABLE ONLY subscriptions ALTER COLUMN id SET DEFAULT nextval('subscriptions_id_seq'::regclass); ALTER TABLE ONLY suggestions ALTER COLUMN id SET DEFAULT nextval('suggestions_id_seq'::regclass); @@ -28317,6 +28360,12 @@ ALTER TABLE ONLY status_page_published_incidents ALTER TABLE ONLY status_page_settings ADD CONSTRAINT status_page_settings_pkey PRIMARY KEY (project_id); +ALTER TABLE ONLY subscription_add_on_purchases + ADD CONSTRAINT subscription_add_on_purchases_pkey PRIMARY KEY (id); + +ALTER TABLE ONLY subscription_add_ons + ADD CONSTRAINT subscription_add_ons_pkey PRIMARY KEY (id); + ALTER TABLE ONLY subscriptions ADD CONSTRAINT subscriptions_pkey PRIMARY KEY (id); @@ -32871,6 +32920,12 @@ CREATE UNIQUE INDEX index_status_page_published_incidents_on_issue_id ON status_ CREATE INDEX index_status_page_settings_on_project_id ON status_page_settings USING btree (project_id); +CREATE INDEX index_subscription_add_on_purchases_on_namespace_id ON subscription_add_on_purchases USING btree (namespace_id); + +CREATE INDEX index_subscription_add_on_purchases_on_subscription_add_on_id ON subscription_add_on_purchases USING btree (subscription_add_on_id); + +CREATE UNIQUE INDEX index_subscription_add_ons_on_name ON subscription_add_ons USING btree (name); + CREATE INDEX index_subscriptions_on_project_id ON subscriptions USING btree (project_id); CREATE UNIQUE INDEX index_subscriptions_on_subscribable_and_user_id_and_project_id ON subscriptions USING btree (subscribable_id, subscribable_type, user_id, project_id); @@ -35420,6 +35475,9 @@ ALTER TABLE ONLY abuse_reports ALTER TABLE ONLY protected_environment_approval_rules ADD CONSTRAINT fk_405568b491 FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE; +ALTER TABLE ONLY subscription_add_on_purchases + ADD CONSTRAINT fk_410004d68b FOREIGN KEY (subscription_add_on_id) REFERENCES subscription_add_ons(id) ON DELETE CASCADE; + ALTER TABLE ONLY ci_pipeline_schedule_variables ADD CONSTRAINT fk_41c35fda51 FOREIGN KEY (pipeline_schedule_id) REFERENCES ci_pipeline_schedules(id) ON DELETE CASCADE; @@ -35768,6 +35826,9 @@ ALTER TABLE ONLY issues ALTER TABLE ONLY ml_candidates ADD CONSTRAINT fk_a1d5f1bc45 FOREIGN KEY (package_id) REFERENCES packages_packages(id) ON DELETE SET NULL; +ALTER TABLE ONLY subscription_add_on_purchases + ADD CONSTRAINT fk_a1db288990 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE; + ALTER TABLE p_ci_builds ADD CONSTRAINT fk_a2141b1522 FOREIGN KEY (auto_canceled_by_id) REFERENCES ci_pipelines(id) ON DELETE SET NULL; diff --git a/ee/app/models/ee/namespace.rb b/ee/app/models/ee/namespace.rb index 758f1f09e7ba3..a5f193fb6f40d 100644 --- a/ee/app/models/ee/namespace.rb +++ b/ee/app/models/ee/namespace.rb @@ -38,6 +38,7 @@ module Namespace has_many :ci_minutes_additional_packs, class_name: "Ci::Minutes::AdditionalPack" has_many :compliance_management_frameworks, class_name: "ComplianceManagement::Framework" has_many :member_roles + has_many :subscription_add_on_purchases, class_name: 'GitlabSubscriptions::AddOnPurchase' accepts_nested_attributes_for :gitlab_subscription, update_only: true accepts_nested_attributes_for :namespace_limit diff --git a/ee/app/models/gitlab_subscriptions/add_on.rb b/ee/app/models/gitlab_subscriptions/add_on.rb new file mode 100644 index 0000000000000..fa1a714382312 --- /dev/null +++ b/ee/app/models/gitlab_subscriptions/add_on.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module GitlabSubscriptions + class AddOn < ApplicationRecord + self.table_name = 'subscription_add_ons' + + has_many :add_on_purchases, foreign_key: :subscription_add_on_id, inverse_of: :add_on + + validates :name, + presence: true, + uniqueness: true + validates :description, + presence: true, + length: { maximum: 512 } + + enum name: { + code_suggestions: 1 + } + end +end diff --git a/ee/app/models/gitlab_subscriptions/add_on_purchase.rb b/ee/app/models/gitlab_subscriptions/add_on_purchase.rb new file mode 100644 index 0000000000000..567b0be4a65b6 --- /dev/null +++ b/ee/app/models/gitlab_subscriptions/add_on_purchase.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module GitlabSubscriptions + class AddOnPurchase < ApplicationRecord + self.table_name = 'subscription_add_on_purchases' + + belongs_to :add_on, foreign_key: :subscription_add_on_id, inverse_of: :add_on_purchases + belongs_to :namespace + + validates :add_on, :namespace, :expires_on, presence: true + validates :quantity, + presence: true, + numericality: { only_integer: true, greater_than_or_equal_to: 1 } + validates :purchase_xid, + presence: true, + length: { maximum: 255 } + end +end diff --git a/ee/spec/factories/add_on.rb b/ee/spec/factories/add_on.rb new file mode 100644 index 0000000000000..b612e2b03e623 --- /dev/null +++ b/ee/spec/factories/add_on.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :add_on, class: 'GitlabSubscriptions::AddOn' do + name { GitlabSubscriptions::AddOn.names[:code_suggestions] } + description { 'AddOn for code suggestion features' } + end +end diff --git a/ee/spec/factories/add_on_purchase.rb b/ee/spec/factories/add_on_purchase.rb new file mode 100644 index 0000000000000..e0846db85c92e --- /dev/null +++ b/ee/spec/factories/add_on_purchase.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :add_on_purchase, class: 'GitlabSubscriptions::AddOnPurchase' do + add_on + namespace { association(:group) } + quantity { 1 } + expires_on { 1.year.from_now.to_date } + purchase_xid { 'S-A00000001' } + end +end diff --git a/ee/spec/models/ee/namespace_spec.rb b/ee/spec/models/ee/namespace_spec.rb index 9cdbf4495d950..faf4c51113d9f 100644 --- a/ee/spec/models/ee/namespace_spec.rb +++ b/ee/spec/models/ee/namespace_spec.rb @@ -18,6 +18,7 @@ it { is_expected.to have_one(:storage_limit_exclusion) } it { is_expected.to have_many(:ci_minutes_additional_packs) } it { is_expected.to have_many(:member_roles) } + it { is_expected.to have_many(:subscription_add_on_purchases).class_name('GitlabSubscriptions::AddOnPurchase') } it { is_expected.to delegate_method(:trial?).to(:gitlab_subscription) } it { is_expected.to delegate_method(:trial_ends_on).to(:gitlab_subscription) } diff --git a/ee/spec/models/gitlab_subscriptions/add_on_purchase_spec.rb b/ee/spec/models/gitlab_subscriptions/add_on_purchase_spec.rb new file mode 100644 index 0000000000000..77c0a762d9c37 --- /dev/null +++ b/ee/spec/models/gitlab_subscriptions/add_on_purchase_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSubscriptions::AddOnPurchase, feature_category: :subscription_management do + subject { build(:add_on_purchase) } + + describe 'associations' do + it { is_expected.to belong_to(:add_on).with_foreign_key(:subscription_add_on_id).inverse_of(:add_on_purchases) } + it { is_expected.to belong_to(:namespace) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:add_on) } + it { is_expected.to validate_presence_of(:namespace) } + it { is_expected.to validate_presence_of(:expires_on) } + + it { is_expected.to validate_presence_of(:quantity) } + it { is_expected.to validate_numericality_of(:quantity).only_integer.is_greater_than_or_equal_to(1) } + + it { is_expected.to validate_presence_of(:purchase_xid) } + it { is_expected.to validate_length_of(:purchase_xid).is_at_most(255) } + end +end diff --git a/ee/spec/models/gitlab_subscriptions/add_on_spec.rb b/ee/spec/models/gitlab_subscriptions/add_on_spec.rb new file mode 100644 index 0000000000000..f5e1d78751b09 --- /dev/null +++ b/ee/spec/models/gitlab_subscriptions/add_on_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSubscriptions::AddOn, feature_category: :subscription_management do + subject { build(:add_on) } + + describe 'associations' do + it { is_expected.to have_many(:add_on_purchases).with_foreign_key(:subscription_add_on_id).inverse_of(:add_on) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:name) } + it { is_expected.to validate_uniqueness_of(:name).ignoring_case_sensitivity } + + it { is_expected.to validate_presence_of(:description) } + it { is_expected.to validate_length_of(:description).is_at_most(512) } + end +end -- GitLab