feat(core): Add project Git connection table (no-changelog) (#36549)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Irénée
2026-08-19 07:21:32 +00:00
committed by GitHub
parent 8c3223ef68
commit d42484b7f6
12 changed files with 427 additions and 4 deletions
+9
View File
@@ -64,6 +64,7 @@ Auto-generated from the PostgreSQL migrations in @n8n/db. Do not edit by hand.
| [public.folder](public.folder.md) | 6 | | BASE TABLE |
| [public.folder_tag](public.folder_tag.md) | 2 | | BASE TABLE |
| [public.git_connection](public.git_connection.md) | 13 | | BASE TABLE |
| [public.git_connection_project](public.git_connection_project.md) | 4 | | BASE TABLE |
| [public.insights_by_period](public.insights_by_period.md) | 6 | | BASE TABLE |
| [public.insights_metadata](public.insights_metadata.md) | 5 | | BASE TABLE |
| [public.insights_raw](public.insights_raw.md) | 5 | | BASE TABLE |
@@ -248,6 +249,8 @@ erDiagram
"public.folder" }o--o| "public.folder" : "FOREIGN KEY (#quot;parentFolderId#quot;) REFERENCES folder(id) ON DELETE CASCADE"
"public.folder_tag" }o--|| "public.tag_entity" : "FOREIGN KEY (#quot;tagId#quot;) REFERENCES tag_entity(id) ON DELETE CASCADE"
"public.folder_tag" }o--|| "public.folder" : "FOREIGN KEY (#quot;folderId#quot;) REFERENCES folder(id) ON DELETE CASCADE"
"public.git_connection_project" |o--|| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE"
"public.git_connection_project" }o--|| "public.git_connection" : "FOREIGN KEY (#quot;gitConnectionId#quot;) REFERENCES git_connection(id) ON DELETE CASCADE"
"public.insights_by_period" }o--|| "public.insights_metadata" : "FOREIGN KEY (#quot;metaId#quot;) REFERENCES insights_metadata(#quot;metaId#quot;) ON DELETE CASCADE"
"public.insights_metadata" }o--o| "public.workflow_entity" : "FOREIGN KEY (#quot;workflowId#quot;) REFERENCES workflow_entity(id) ON DELETE SET NULL"
"public.insights_metadata" }o--o| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE SET NULL"
@@ -925,6 +928,12 @@ erDiagram
text repositoryUrl
timestamp_3__with_time_zone updatedAt
}
"public.git_connection_project" {
timestamp_3__with_time_zone createdAt
varchar_36_ gitConnectionId FK
varchar_36_ projectId FK
timestamp_3__with_time_zone updatedAt
}
"public.insights_by_period" {
integer id
integer metaId FK
+8 -1
View File
@@ -11,7 +11,7 @@
| encryptedPassword | text | | true | | | Encrypted HTTPS password/token; set when connectionType is 'https', null for 'ssh'. |
| encryptedPrivateKey | text | | true | | | Encrypted SSH private key; set when connectionType is 'ssh', null for 'https'. |
| encryptedUsername | text | | true | | | Encrypted HTTPS username; set when connectionType is 'https', null for 'ssh'. |
| id | varchar(36) | | false | | | |
| id | varchar(36) | | false | [public.git_connection_project](public.git_connection_project.md) | | |
| keyGeneratorType | varchar(16) | | true | | | GitKeyGeneratorType enum: "ed25519", "rsa" |
| name | varchar(128) | | false | | | |
| publicKey | text | | true | | | SSH public key; set when connectionType is 'ssh', null for 'https'. |
@@ -45,6 +45,7 @@
```mermaid
erDiagram
"public.git_connection_project" }o--|| "public.git_connection" : "FOREIGN KEY (#quot;gitConnectionId#quot;) REFERENCES git_connection(id) ON DELETE CASCADE"
"public.git_connection" {
varchar_64_ baseCommit
@@ -61,6 +62,12 @@ erDiagram
text repositoryUrl
timestamp_3__with_time_zone updatedAt
}
"public.git_connection_project" {
timestamp_3__with_time_zone createdAt
varchar_36_ gitConnectionId FK
varchar_36_ projectId FK
timestamp_3__with_time_zone updatedAt
}
```
---
@@ -0,0 +1,75 @@
# public.git_connection_project
## Columns
| Name | Type | Default | Nullable | Children | Parents | Comment |
| ---- | ---- | ------- | -------- | -------- | ------- | ------- |
| createdAt | timestamp(3) with time zone | CURRENT_TIMESTAMP(3) | false | | | |
| gitConnectionId | varchar(36) | | false | | [public.git_connection](public.git_connection.md) | |
| projectId | varchar(36) | | false | | [public.project](public.project.md) | |
| updatedAt | timestamp(3) with time zone | CURRENT_TIMESTAMP(3) | false | | | |
## Constraints
| Name | Type | Definition |
| ---- | ---- | ---------- |
| FK_12a7fe192a1ee1d933e712e6b1c | FOREIGN KEY | FOREIGN KEY ("gitConnectionId") REFERENCES git_connection(id) ON DELETE CASCADE |
| FK_5affe1c6075bd7ac20297b7947c | FOREIGN KEY | FOREIGN KEY ("projectId") REFERENCES project(id) ON DELETE CASCADE |
| PK_5affe1c6075bd7ac20297b7947c | PRIMARY KEY | PRIMARY KEY ("projectId") |
| git_connection_project_createdAt_not_null | n | NOT NULL "createdAt" |
| git_connection_project_gitConnectionId_not_null | n | NOT NULL "gitConnectionId" |
| git_connection_project_projectId_not_null | n | NOT NULL "projectId" |
| git_connection_project_updatedAt_not_null | n | NOT NULL "updatedAt" |
## Indexes
| Name | Definition |
| ---- | ---------- |
| IDX_12a7fe192a1ee1d933e712e6b1 | CREATE INDEX "IDX_12a7fe192a1ee1d933e712e6b1" ON public.git_connection_project USING btree ("gitConnectionId") |
| PK_5affe1c6075bd7ac20297b7947c | CREATE UNIQUE INDEX "PK_5affe1c6075bd7ac20297b7947c" ON public.git_connection_project USING btree ("projectId") |
## Relations
```mermaid
erDiagram
"public.git_connection_project" }o--|| "public.git_connection" : "FOREIGN KEY (#quot;gitConnectionId#quot;) REFERENCES git_connection(id) ON DELETE CASCADE"
"public.git_connection_project" |o--|| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE"
"public.git_connection_project" {
timestamp_3__with_time_zone createdAt
varchar_36_ gitConnectionId FK
varchar_36_ projectId FK
timestamp_3__with_time_zone updatedAt
}
"public.git_connection" {
varchar_64_ baseCommit
varchar_255_ branchName
varchar_16_ connectionType
timestamp_3__with_time_zone createdAt
text encryptedPassword
text encryptedPrivateKey
text encryptedUsername
varchar_36_ id
varchar_16_ keyGeneratorType
varchar_128_ name
text publicKey
text repositoryUrl
timestamp_3__with_time_zone updatedAt
}
"public.project" {
timestamp_3__with_time_zone createdAt
uuid creatorId FK
json customTelemetryTags
varchar_512_ description
json icon
varchar_36_ id
varchar_255_ name
varchar_36_ type
timestamp_3__with_time_zone updatedAt
}
```
---
> Generated by [tbls](https://github.com/k1LoW/tbls)
+8 -1
View File
@@ -9,7 +9,7 @@
| customTelemetryTags | json | '[]'::json | false | | | |
| description | varchar(512) | | true | | | |
| icon | json | | true | | | |
| id | varchar(36) | | false | [public.agent_chat_attachments](public.agent_chat_attachments.md) [public.agent_execution_threads](public.agent_execution_threads.md) [public.agents](public.agents.md) [public.data_table](public.data_table.md) [public.folder](public.folder.md) [public.insights_metadata](public.insights_metadata.md) [public.instance_ai_threads](public.instance_ai_threads.md) [public.project_relation](public.project_relation.md) [public.project_secrets_provider_access](public.project_secrets_provider_access.md) [public.role_mapping_rule_project](public.role_mapping_rule_project.md) [public.shared_credentials](public.shared_credentials.md) [public.shared_workflow](public.shared_workflow.md) [public.variables](public.variables.md) [public.workflow_review_request](public.workflow_review_request.md) | | |
| id | varchar(36) | | false | [public.agent_chat_attachments](public.agent_chat_attachments.md) [public.agent_execution_threads](public.agent_execution_threads.md) [public.agents](public.agents.md) [public.data_table](public.data_table.md) [public.folder](public.folder.md) [public.git_connection_project](public.git_connection_project.md) [public.insights_metadata](public.insights_metadata.md) [public.instance_ai_threads](public.instance_ai_threads.md) [public.project_relation](public.project_relation.md) [public.project_secrets_provider_access](public.project_secrets_provider_access.md) [public.role_mapping_rule_project](public.role_mapping_rule_project.md) [public.shared_credentials](public.shared_credentials.md) [public.shared_workflow](public.shared_workflow.md) [public.variables](public.variables.md) [public.workflow_review_request](public.workflow_review_request.md) | | |
| name | varchar(255) | | false | | | |
| type | varchar(36) | | false | | | |
| updatedAt | timestamp(3) with time zone | CURRENT_TIMESTAMP(3) | false | | | |
@@ -44,6 +44,7 @@ erDiagram
"public.agents" }o--|| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE"
"public.data_table" }o--|| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE"
"public.folder" }o--|| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE"
"public.git_connection_project" |o--|| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE"
"public.insights_metadata" }o--o| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE SET NULL"
"public.instance_ai_threads" }o--|| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE"
"public.project_relation" }o--|| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE"
@@ -146,6 +147,12 @@ erDiagram
varchar_36_ projectId FK
timestamp_3__with_time_zone updatedAt
}
"public.git_connection_project" {
timestamp_3__with_time_zone createdAt
varchar_36_ gitConnectionId FK
varchar_36_ projectId FK
timestamp_3__with_time_zone updatedAt
}
"public.insights_metadata" {
integer metaId
varchar_36_ projectId FK
+9
View File
@@ -64,6 +64,7 @@ Auto-generated from the SQLite migrations in @n8n/db. Do not edit by hand.
| [folder](folder.md) | 6 | | table |
| [folder_tag](folder_tag.md) | 2 | | table |
| [git_connection](git_connection.md) | 13 | | table |
| [git_connection_project](git_connection_project.md) | 4 | | table |
| [insights_by_period](insights_by_period.md) | 6 | | table |
| [insights_metadata](insights_metadata.md) | 5 | | table |
| [insights_raw](insights_raw.md) | 5 | | table |
@@ -231,6 +232,8 @@ erDiagram
"folder" }o--|| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
"folder_tag" |o--|| "tag_entity" : "FOREIGN KEY (tagId) REFERENCES tag_entity (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
"folder_tag" |o--|| "folder" : "FOREIGN KEY (folderId) REFERENCES folder (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
"git_connection_project" }o--|| "git_connection" : "FOREIGN KEY (gitConnectionId) REFERENCES git_connection (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
"git_connection_project" |o--|| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
"insights_by_period" }o--|| "insights_metadata" : "FOREIGN KEY (metaId) REFERENCES insights_metadata (metaId) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
"insights_metadata" }o--o| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE"
"insights_metadata" }o--o| "workflow_entity" : "FOREIGN KEY (workflowId) REFERENCES workflow_entity (id) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE"
@@ -912,6 +915,12 @@ erDiagram
TEXT repositoryUrl
datetime_3_ updatedAt
}
"git_connection_project" {
datetime_3_ createdAt
varchar_36_ gitConnectionId FK
varchar_36_ projectId PK
datetime_3_ updatedAt
}
"insights_by_period" {
INTEGER id
INTEGER metaId FK
+8 -1
View File
@@ -22,7 +22,7 @@ CREATE TABLE "git_connection" ("id" varchar(36) PRIMARY KEY NOT NULL, "name" var
| encryptedPassword | TEXT | | true | | | |
| encryptedPrivateKey | TEXT | | true | | | |
| encryptedUsername | TEXT | | true | | | |
| id | varchar(36) | | false | | | |
| id | varchar(36) | | false | [git_connection_project](git_connection_project.md) | | |
| keyGeneratorType | varchar(16) | | true | | | |
| name | varchar(128) | | false | | | |
| publicKey | TEXT | | true | | | |
@@ -51,6 +51,7 @@ CREATE TABLE "git_connection" ("id" varchar(36) PRIMARY KEY NOT NULL, "name" var
```mermaid
erDiagram
"git_connection_project" }o--|| "git_connection" : "FOREIGN KEY (gitConnectionId) REFERENCES git_connection (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
"git_connection" {
varchar_64_ baseCommit
@@ -67,6 +68,12 @@ erDiagram
TEXT repositoryUrl
datetime_3_ updatedAt
}
"git_connection_project" {
datetime_3_ createdAt
varchar_36_ gitConnectionId FK
varchar_36_ projectId PK
datetime_3_ updatedAt
}
```
---
+83
View File
@@ -0,0 +1,83 @@
# git_connection_project
## Description
<details>
<summary><strong>Table Definition</strong></summary>
```sql
CREATE TABLE "git_connection_project" ("projectId" varchar(36) PRIMARY KEY NOT NULL, "gitConnectionId" varchar(36) NOT NULL, "createdAt" datetime(3) NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), "updatedAt" datetime(3) NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), CONSTRAINT "FK_5affe1c6075bd7ac20297b7947c" FOREIGN KEY ("projectId") REFERENCES "project" ("id") ON DELETE CASCADE, CONSTRAINT "FK_12a7fe192a1ee1d933e712e6b1c" FOREIGN KEY ("gitConnectionId") REFERENCES "git_connection" ("id") ON DELETE CASCADE)
```
</details>
## Columns
| Name | Type | Default | Nullable | Children | Parents | Comment |
| ---- | ---- | ------- | -------- | -------- | ------- | ------- |
| createdAt | datetime(3) | STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW') | false | | | |
| gitConnectionId | varchar(36) | | false | | [git_connection](git_connection.md) | |
| projectId | varchar(36) | | false | | [project](project.md) | |
| updatedAt | datetime(3) | STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW') | false | | | |
## Constraints
| Name | Type | Definition |
| ---- | ---- | ---------- |
| - (Foreign key ID: 0) | FOREIGN KEY | FOREIGN KEY (gitConnectionId) REFERENCES git_connection (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE |
| - (Foreign key ID: 1) | FOREIGN KEY | FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE |
| projectId | PRIMARY KEY | PRIMARY KEY (projectId) |
| sqlite_autoindex_git_connection_project_1 | PRIMARY KEY | PRIMARY KEY (projectId) |
## Indexes
| Name | Definition |
| ---- | ---------- |
| IDX_12a7fe192a1ee1d933e712e6b1 | CREATE INDEX "IDX_12a7fe192a1ee1d933e712e6b1" ON "git_connection_project" ("gitConnectionId") |
| sqlite_autoindex_git_connection_project_1 | PRIMARY KEY (projectId) |
## Relations
```mermaid
erDiagram
"git_connection_project" }o--|| "git_connection" : "FOREIGN KEY (gitConnectionId) REFERENCES git_connection (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
"git_connection_project" |o--|| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
"git_connection_project" {
datetime_3_ createdAt
varchar_36_ gitConnectionId FK
varchar_36_ projectId PK
datetime_3_ updatedAt
}
"git_connection" {
varchar_64_ baseCommit
varchar_255_ branchName
varchar_16_ connectionType
datetime_3_ createdAt
TEXT encryptedPassword
TEXT encryptedPrivateKey
TEXT encryptedUsername
varchar_36_ id PK
varchar_16_ keyGeneratorType
varchar_128_ name
TEXT publicKey
TEXT repositoryUrl
datetime_3_ updatedAt
}
"project" {
datetime_3_ createdAt
varchar creatorId FK
TEXT customTelemetryTags
varchar_512_ description
TEXT icon
varchar_36_ id PK
varchar_255_ name
varchar_36_ type
datetime_3_ updatedAt
}
```
---
> Generated by [tbls](https://github.com/k1LoW/tbls)
+8 -1
View File
@@ -20,7 +20,7 @@ CREATE TABLE "project" ("id" varchar(36) PRIMARY KEY NOT NULL, "name" varchar(25
| customTelemetryTags | TEXT | '[]' | false | | | |
| description | varchar(512) | | true | | | |
| icon | TEXT | | true | | | |
| id | varchar(36) | | false | [agent_chat_attachments](agent_chat_attachments.md) [agent_execution_threads](agent_execution_threads.md) [agents](agents.md) [data_table](data_table.md) [folder](folder.md) [insights_metadata](insights_metadata.md) [instance_ai_threads](instance_ai_threads.md) [project_relation](project_relation.md) [project_secrets_provider_access](project_secrets_provider_access.md) [role_mapping_rule_project](role_mapping_rule_project.md) [shared_credentials](shared_credentials.md) [shared_workflow](shared_workflow.md) [variables](variables.md) [workflow_review_request](workflow_review_request.md) | | |
| id | varchar(36) | | false | [agent_chat_attachments](agent_chat_attachments.md) [agent_execution_threads](agent_execution_threads.md) [agents](agents.md) [data_table](data_table.md) [folder](folder.md) [git_connection_project](git_connection_project.md) [insights_metadata](insights_metadata.md) [instance_ai_threads](instance_ai_threads.md) [project_relation](project_relation.md) [project_secrets_provider_access](project_secrets_provider_access.md) [role_mapping_rule_project](role_mapping_rule_project.md) [shared_credentials](shared_credentials.md) [shared_workflow](shared_workflow.md) [variables](variables.md) [workflow_review_request](workflow_review_request.md) | | |
| name | varchar(255) | | false | | | |
| type | varchar(36) | | false | | | |
| updatedAt | datetime(3) | STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW') | false | | | |
@@ -50,6 +50,7 @@ erDiagram
"agents" }o--|| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
"data_table" }o--|| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
"folder" }o--|| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
"git_connection_project" |o--|| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
"insights_metadata" }o--o| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE"
"instance_ai_threads" }o--|| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
"project_relation" |o--|| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
@@ -152,6 +153,12 @@ erDiagram
varchar_36_ projectId FK
datetime_3_ updatedAt
}
"git_connection_project" {
datetime_3_ createdAt
varchar_36_ gitConnectionId FK
varchar_36_ projectId PK
datetime_3_ updatedAt
}
"insights_metadata" {
INTEGER metaId
varchar_36_ projectId FK
@@ -0,0 +1,35 @@
import type { MigrationContext, ReversibleMigration } from '../migration-types';
const TABLE = 'git_connection_project';
const PROJECT_TABLE = 'project';
const GIT_CONNECTION_TABLE = 'git_connection';
export class CreateGitConnectionProjectTable1787089039726 implements ReversibleMigration {
async up({ schemaBuilder: { createTable, column } }: MigrationContext) {
// projectId is the primary key: a project can be linked to at most one
// connection, while a connection can hold many project links. Both FKs
// cascade so a link disappears when either side is deleted.
await createTable(TABLE)
.withColumns(
column('projectId').varchar(36).primary,
column('gitConnectionId').varchar(36).notNull,
)
.withForeignKey('projectId', {
tableName: PROJECT_TABLE,
columnName: 'id',
onDelete: 'CASCADE',
})
.withForeignKey('gitConnectionId', {
tableName: GIT_CONNECTION_TABLE,
columnName: 'id',
onDelete: 'CASCADE',
})
// A connection holds many projects; index the FK for connection-scoped
// reads ("list projects for this connection") and the cascade delete.
.withIndexOn(['gitConnectionId']).withTimestamps;
}
async down({ schemaBuilder: { dropTable } }: MigrationContext) {
await dropTable(TABLE);
}
}
@@ -0,0 +1,34 @@
import { WithTimestamps } from '@n8n/db';
import {
Column,
Entity,
Index,
JoinColumn,
ManyToOne,
PrimaryColumn,
type Relation,
} from '@n8n/typeorm';
import { GitConnection } from './git-connection.entity';
/**
* Links a project to a git connection. The `projectId` primary key enforces the
* one-connection-per-project rule; a connection can hold many project links.
*
* The FK to `project` (with cascade delete) is declared in the migration rather
* than as a `@ManyToOne`, to keep the module entity decoupled from the core
* `Project` entity — mirroring the workflow-reviews pattern.
*/
@Entity('git_connection_project')
export class GitConnectionProject extends WithTimestamps {
@PrimaryColumn({ type: 'varchar', length: 36 })
projectId: string;
@Index()
@Column({ type: 'varchar', length: 36 })
gitConnectionId: string;
@ManyToOne(() => GitConnection, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'gitConnectionId' })
gitConnection: Relation<GitConnection>;
}
@@ -68,6 +68,11 @@
"name": "WorkflowReviewRequest",
"path": "packages/@n8n/db/src/entities/workflow-review-request.ee.ts",
"reason": "Not transferred with the project. Open reviews are closed when their workflow is archived, moved to another project, or deleted (workflow lifecycle hooks); rows are removed via the projectId FK cascade when the source project itself is deleted, as in ownership-transfer flows. WorkflowReviewActivity and WorkflowReviewActivityComment ride this entity's cascade and have no projectId of their own, so they never appear in this manifest."
},
{
"name": "GitConnectionProject",
"path": "packages/cli/src/modules/git-connections.ee/database/entities/git-connection-project.entity.ts",
"reason": "Project-scoped git configuration (which connection the project is linked to), not user data. Dropped via the projectId FK cascade when the source project itself is deleted, as in ownership-transfer flows."
}
]
}
@@ -0,0 +1,145 @@
import {
createTestMigrationContext,
initDbUpToMigration,
runSingleMigration,
type TestMigrationContext,
} from '@n8n/backend-test-utils';
import { DbConnection } from '@n8n/db';
import { Container } from '@n8n/di';
import { DataSource } from '@n8n/typeorm';
import { randomUUID } from 'node:crypto';
const MIGRATION_NAME = 'CreateGitConnectionProjectTable1787089039726';
describe('CreateGitConnectionProjectTable migration', () => {
let dataSource: DataSource;
beforeAll(async () => {
const dbConnection = Container.get(DbConnection);
await dbConnection.init();
dataSource = Container.get(DataSource);
const context = createTestMigrationContext(dataSource);
await context.queryRunner.clearDatabase();
await context.queryRunner.release();
await initDbUpToMigration(MIGRATION_NAME);
await runSingleMigration(MIGRATION_NAME);
});
afterAll(async () => {
await Container.get(DbConnection).close();
});
async function insertProject(context: TestMigrationContext, id: string) {
const table = context.escape.tableName('project');
const now = new Date();
await context.runQuery(
`INSERT INTO ${table} ("id", "name", "type", "customTelemetryTags", "createdAt", "updatedAt")
VALUES (:id, :name, :type, :tags, :createdAt, :updatedAt)`,
{ id, name: 'Project', type: 'team', tags: '[]', createdAt: now, updatedAt: now },
);
}
async function insertConnection(context: TestMigrationContext, id: string) {
const table = context.escape.tableName('git_connection');
const now = new Date();
await context.runQuery(
`INSERT INTO ${table} ("id", "name", "repositoryUrl", "connectionType", "createdAt", "updatedAt")
VALUES (:id, :name, :repositoryUrl, :connectionType, :createdAt, :updatedAt)`,
{
id,
name: 'Connection',
repositoryUrl: 'https://example.com/org/repo.git',
connectionType: 'https',
createdAt: now,
updatedAt: now,
},
);
}
async function insertLink(
context: TestMigrationContext,
projectId: string,
connectionId: string,
) {
const table = context.escape.tableName('git_connection_project');
const now = new Date();
await context.runQuery(
`INSERT INTO ${table} ("projectId", "gitConnectionId", "createdAt", "updatedAt")
VALUES (:projectId, :gitConnectionId, :createdAt, :updatedAt)`,
{ projectId, gitConnectionId: connectionId, createdAt: now, updatedAt: now },
);
}
async function countLinks(context: TestMigrationContext, projectId: string) {
const table = context.escape.tableName('git_connection_project');
const rows = await context.runQuery<Array<{ c: number }>>(
`SELECT COUNT(*) as c FROM ${table} WHERE "projectId" = :projectId`,
{ projectId },
);
return Number(rows[0].c);
}
describe('Up migration', () => {
it('enforces one connection per project via the primary key', async () => {
const context = createTestMigrationContext(dataSource);
const projectId = randomUUID();
const connectionId = randomUUID();
await insertProject(context, projectId);
await insertConnection(context, connectionId);
await insertLink(context, projectId, connectionId);
await expect(insertLink(context, projectId, connectionId)).rejects.toThrow();
await context.queryRunner.release();
});
it('cascades the link away when the git connection is deleted', async () => {
const context = createTestMigrationContext(dataSource);
const projectId = randomUUID();
const connectionId = randomUUID();
await insertProject(context, projectId);
await insertConnection(context, connectionId);
await insertLink(context, projectId, connectionId);
await context.runQuery(
`DELETE FROM ${context.escape.tableName('git_connection')} WHERE "id" = :id`,
{ id: connectionId },
);
expect(await countLinks(context, projectId)).toBe(0);
await context.queryRunner.release();
});
it('cascades the link away when the project is deleted', async () => {
const context = createTestMigrationContext(dataSource);
const projectId = randomUUID();
const connectionId = randomUUID();
await insertProject(context, projectId);
await insertConnection(context, connectionId);
await insertLink(context, projectId, connectionId);
await context.runQuery(
`DELETE FROM ${context.escape.tableName('project')} WHERE "id" = :id`,
{ id: projectId },
);
expect(await countLinks(context, projectId)).toBe(0);
await context.queryRunner.release();
});
});
describe('Down migration', () => {
it('drops the table and can be re-applied', async () => {
await dataSource.undoLastMigration({ transaction: 'each' });
const context = createTestMigrationContext(dataSource);
const table = `${context.tablePrefix}git_connection_project`;
expect(await context.queryRunner.hasTable(table)).toBe(false);
await context.queryRunner.release();
await runSingleMigration(MIGRATION_NAME);
const context2 = createTestMigrationContext(dataSource);
expect(await context2.queryRunner.hasTable(table)).toBe(true);
await context2.queryRunner.release();
});
});
});