From 5b9fba59bad5b59df24b57632e98ffce076cabf4 Mon Sep 17 00:00:00 2001
From: Alex Pooley <apooley@gitlab.com>
Date: Mon, 4 Sep 2023 14:35:34 +0000
Subject: [PATCH] Associate projects with organization

Changelog: added
---
 app/models/organizations/organization.rb            |  1 +
 app/models/project.rb                               |  1 +
 config/gitlab_loose_foreign_keys.yml                |  4 ++++
 ...20230822064649_add_organization_id_to_project.rb | 11 +++++++++++
 ...22064841_prepare_index_for_org_id_on_projects.rb | 13 +++++++++++++
 db/schema_migrations/20230822064649                 |  1 +
 db/schema_migrations/20230822064841                 |  1 +
 db/structure.sql                                    |  3 ++-
 spec/db/schema_spec.rb                              |  3 ++-
 spec/lib/gitlab/import_export/all_models.yml        |  1 +
 spec/models/organizations/organization_spec.rb      |  1 +
 spec/models/project_spec.rb                         |  8 ++++++++
 spec/requests/api/project_attributes.yml            |  1 +
 13 files changed, 47 insertions(+), 2 deletions(-)
 create mode 100644 db/migrate/20230822064649_add_organization_id_to_project.rb
 create mode 100644 db/post_migrate/20230822064841_prepare_index_for_org_id_on_projects.rb
 create mode 100644 db/schema_migrations/20230822064649
 create mode 100644 db/schema_migrations/20230822064841

diff --git a/app/models/organizations/organization.rb b/app/models/organizations/organization.rb
index 489fd6e0da7c..893b08d7872a 100644
--- a/app/models/organizations/organization.rb
+++ b/app/models/organizations/organization.rb
@@ -10,6 +10,7 @@ class Organization < ApplicationRecord
 
     has_many :namespaces
     has_many :groups
+    has_many :projects
 
     has_one :settings, class_name: "OrganizationSetting"
 
diff --git a/app/models/project.rb b/app/models/project.rb
index cb1468a8d0c7..4e69cdeff133 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -165,6 +165,7 @@ class Project < ApplicationRecord
   # Relations
   belongs_to :pool_repository
   belongs_to :creator, class_name: 'User'
+  belongs_to :organization, class_name: 'Organizations::Organization'
   belongs_to :group, -> { where(type: Group.sti_name) }, foreign_key: 'namespace_id'
   belongs_to :namespace
   # Sync deletion via DB Trigger to ensure we do not have
diff --git a/config/gitlab_loose_foreign_keys.yml b/config/gitlab_loose_foreign_keys.yml
index 6bd6f99a1344..c12a2dfda7bf 100644
--- a/config/gitlab_loose_foreign_keys.yml
+++ b/config/gitlab_loose_foreign_keys.yml
@@ -287,6 +287,10 @@ pages_deployments:
   - table: p_ci_builds
     column: ci_build_id
     on_delete: async_nullify
+projects:
+  - table: organizations
+    column: organization_id
+    on_delete: async_nullify
 requirements_management_test_reports:
   - table: ci_builds
     column: build_id
diff --git a/db/migrate/20230822064649_add_organization_id_to_project.rb b/db/migrate/20230822064649_add_organization_id_to_project.rb
new file mode 100644
index 000000000000..9607f711981b
--- /dev/null
+++ b/db/migrate/20230822064649_add_organization_id_to_project.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AddOrganizationIdToProject < Gitlab::Database::Migration[2.1]
+  DEFAULT_ORGANIZATION_ID = 1
+
+  enable_lock_retries!
+
+  def change
+    add_column :projects, :organization_id, :bigint, default: DEFAULT_ORGANIZATION_ID, null: true # rubocop:disable Migration/AddColumnsToWideTables
+  end
+end
diff --git a/db/post_migrate/20230822064841_prepare_index_for_org_id_on_projects.rb b/db/post_migrate/20230822064841_prepare_index_for_org_id_on_projects.rb
new file mode 100644
index 000000000000..1f822a440a40
--- /dev/null
+++ b/db/post_migrate/20230822064841_prepare_index_for_org_id_on_projects.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class PrepareIndexForOrgIdOnProjects < Gitlab::Database::Migration[2.1]
+  INDEX_NAME = 'index_projects_on_organization_id'
+
+  def up
+    prepare_async_index :projects, :organization_id, name: INDEX_NAME
+  end
+
+  def down
+    unprepare_async_index :projects, :organization_id, name: INDEX_NAME
+  end
+end
diff --git a/db/schema_migrations/20230822064649 b/db/schema_migrations/20230822064649
new file mode 100644
index 000000000000..449dd9844317
--- /dev/null
+++ b/db/schema_migrations/20230822064649
@@ -0,0 +1 @@
+b892940441125e854d08e24906e4b6287f8359b4ad374be5b141b43cfdcc1354
\ No newline at end of file
diff --git a/db/schema_migrations/20230822064841 b/db/schema_migrations/20230822064841
new file mode 100644
index 000000000000..2922af9c5733
--- /dev/null
+++ b/db/schema_migrations/20230822064841
@@ -0,0 +1 @@
+e025eb64ab8b9ece1a18c845024db272a1859757734948609c134f6dfee93884
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 9c1fc3b5500c..805745d44709 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -21836,7 +21836,8 @@ CREATE TABLE projects (
     autoclose_referenced_issues boolean,
     suggestion_commit_message character varying(255),
     project_namespace_id bigint,
-    hidden boolean DEFAULT false NOT NULL
+    hidden boolean DEFAULT false NOT NULL,
+    organization_id bigint DEFAULT 1
 );
 
 CREATE SEQUENCE projects_id_seq
diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb
index 04a83a1f6ab7..fea3c5b77a13 100644
--- a/spec/db/schema_spec.rb
+++ b/spec/db/schema_spec.rb
@@ -15,7 +15,8 @@
     search_namespace_index_assignments: [%w[search_index_id index_type]],
     slack_integrations_scopes: [%w[slack_api_scope_id]],
     notes: %w[namespace_id], # this index is added in an async manner, hence it needs to be ignored in the first phase.
-    vulnerabilities: [%w[finding_id]] # index will be created in https://gitlab.com/gitlab-org/gitlab/-/issues/423541
+    vulnerabilities: [%w[finding_id]], # index will be created in https://gitlab.com/gitlab-org/gitlab/-/issues/423541
+    projects: %w[organization_id] # this index is added in an async manner, hence it needs to be ignored in the first phase.
   }.with_indifferent_access.freeze
 
   TABLE_PARTITIONS = %w[ci_builds_metadata].freeze
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 99e871e52556..de3f28a5b906 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -823,6 +823,7 @@ project:
 - project_state
 - security_policy_bots
 - target_branch_rules
+- organization
 award_emoji:
 - awardable
 - user
diff --git a/spec/models/organizations/organization_spec.rb b/spec/models/organizations/organization_spec.rb
index 7838fc1c5a4f..2f9f04fd3e64 100644
--- a/spec/models/organizations/organization_spec.rb
+++ b/spec/models/organizations/organization_spec.rb
@@ -11,6 +11,7 @@
     it { is_expected.to have_many :groups }
     it { is_expected.to have_many(:users).through(:organization_users).inverse_of(:organizations) }
     it { is_expected.to have_many(:organization_users).inverse_of(:organization) }
+    it { is_expected.to have_many :projects }
   end
 
   describe 'validations' do
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 657c7d5dee8b..5312ca0ef702 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -17,6 +17,7 @@
   it_behaves_like 'ensures runners_token is prefixed', :project
 
   describe 'associations' do
+    it { is_expected.to belong_to(:organization) }
     it { is_expected.to belong_to(:group) }
     it { is_expected.to belong_to(:namespace) }
     it { is_expected.to belong_to(:project_namespace).class_name('Namespaces::ProjectNamespace').with_foreign_key('project_namespace_id').inverse_of(:project) }
@@ -9189,6 +9190,13 @@ def create_hook
     end
   end
 
+  context 'with loose foreign key on organization_id' do
+    it_behaves_like 'cleanup by a loose foreign key' do
+      let_it_be(:parent) { create(:organization) }
+      let_it_be(:model) { create(:project, organization: parent) }
+    end
+  end
+
   private
 
   def finish_job(export_job)
diff --git a/spec/requests/api/project_attributes.yml b/spec/requests/api/project_attributes.yml
index 1055c1a69686..677cb243a7ca 100644
--- a/spec/requests/api/project_attributes.yml
+++ b/spec/requests/api/project_attributes.yml
@@ -44,6 +44,7 @@ itself: # project
     - storage_version
     - topic_list
     - verification_checksum
+    - organization_id
   remapped_attributes:
     avatar: avatar_url
     build_allow_git_fetch: build_git_strategy
-- 
GitLab