diff --git a/packages/cli/src/databases/dsl/Column.ts b/packages/cli/src/databases/dsl/Column.ts index 48d2191478977..2ea2be7afdbe7 100644 --- a/packages/cli/src/databases/dsl/Column.ts +++ b/packages/cli/src/databases/dsl/Column.ts @@ -93,9 +93,11 @@ export class Column { options.type = isPostgres ? 'timestamptz' : 'datetime'; } else if (type === 'json' && isSqlite) { options.type = 'text'; - } else if (type === 'uuid' && isMysql) { + } else if (type === 'uuid') { // mysql does not support uuid type - options.type = 'varchar(36)'; + if (isMysql) options.type = 'varchar(36)'; + // we haven't been defining length on "uuid" varchar on sqlite + if (isSqlite) options.type = 'varchar'; } if ((type === 'varchar' || type === 'timestamp') && length !== 'auto') { diff --git a/packages/cli/src/databases/entities/Project.ts b/packages/cli/src/databases/entities/Project.ts new file mode 100644 index 0000000000000..90d25384640ff --- /dev/null +++ b/packages/cli/src/databases/entities/Project.ts @@ -0,0 +1,17 @@ +import { Column, Entity, OneToMany } from 'typeorm'; +import { WithTimestampsAndStringId } from './AbstractEntity'; +import type { ProjectRelation } from './ProjectRelation'; + +export type ProjectType = 'personal' | 'team' | 'public'; + +@Entity() +export class Project extends WithTimestampsAndStringId { + @Column({ length: 255 }) + name: string; + + @Column({ length: 36 }) + type: ProjectType; + + @OneToMany('ProjectRelation', 'project') + projectRelations: ProjectRelation[]; +} diff --git a/packages/cli/src/databases/entities/ProjectRelation.ts b/packages/cli/src/databases/entities/ProjectRelation.ts new file mode 100644 index 0000000000000..b60579c6289ba --- /dev/null +++ b/packages/cli/src/databases/entities/ProjectRelation.ts @@ -0,0 +1,28 @@ +import { Column, Entity, ManyToOne, PrimaryColumn } from 'typeorm'; +import { User } from './User'; +import { WithTimestamps } from './AbstractEntity'; +import { Project } from './Project'; + +export type ProjectRole = + | 'project:admin' + | 'project:editor' + | 'project:workflowEditor' + | 'project:viewer'; + +@Entity() +export class ProjectRelation extends WithTimestamps { + @Column() + role: ProjectRole; + + @ManyToOne('User', 'projectRelations') + user: User; + + @PrimaryColumn('uuid') + userId: string; + + @ManyToOne('Project', 'projectRelations') + project: Project; + + @PrimaryColumn() + projectId: string; +} diff --git a/packages/cli/src/databases/entities/User.ts b/packages/cli/src/databases/entities/User.ts index ac5e0d5a7950c..52cf376ebf3af 100644 --- a/packages/cli/src/databases/entities/User.ts +++ b/packages/cli/src/databases/entities/User.ts @@ -20,6 +20,7 @@ import type { IPersonalizationSurveyAnswers } from '@/Interfaces'; import type { AuthIdentity } from './AuthIdentity'; import { ownerPermissions, memberPermissions, adminPermissions } from '@/permissions/roles'; import { hasScope, type ScopeOptions, type Scope } from '@n8n/permissions'; +import type { ProjectRelation } from './ProjectRelation'; export type GlobalRole = 'global:owner' | 'global:admin' | 'global:member'; export type AssignableRole = Exclude; @@ -85,6 +86,9 @@ export class User extends WithTimestamps implements IUser { @OneToMany('SharedCredentials', 'user') sharedCredentials: SharedCredentials[]; + @OneToMany('ProjectRelation', 'user') + projectRelations: ProjectRelation[]; + @Column({ type: Boolean, default: false }) disabled: boolean; diff --git a/packages/cli/src/databases/entities/index.ts b/packages/cli/src/databases/entities/index.ts index db1f5a5ce713c..71be3c07bb49c 100644 --- a/packages/cli/src/databases/entities/index.ts +++ b/packages/cli/src/databases/entities/index.ts @@ -19,6 +19,8 @@ import { WorkflowStatistics } from './WorkflowStatistics'; import { ExecutionMetadata } from './ExecutionMetadata'; import { ExecutionData } from './ExecutionData'; import { WorkflowHistory } from './WorkflowHistory'; +import { Project } from './Project'; +import { ProjectRelation } from './ProjectRelation'; export const entities = { AuthIdentity, @@ -41,4 +43,6 @@ export const entities = { ExecutionMetadata, ExecutionData, WorkflowHistory, + Project, + ProjectRelation, }; diff --git a/packages/cli/src/databases/migrations/common/1705928727784-CreateProject.ts b/packages/cli/src/databases/migrations/common/1705928727784-CreateProject.ts new file mode 100644 index 0000000000000..9d43ee78f4495 --- /dev/null +++ b/packages/cli/src/databases/migrations/common/1705928727784-CreateProject.ts @@ -0,0 +1,38 @@ +import type { MigrationContext, ReversibleMigration } from '@db/types'; + +const tableName = 'project'; +const relationTableName = 'project_relation'; + +export class CreateProject1705928727784 implements ReversibleMigration { + async up({ schemaBuilder: { createTable, column } }: MigrationContext) { + await createTable(tableName).withColumns( + column('id').varchar(36).primary.notNull, + column('name').varchar(255), + column('type').varchar(36), + ).withTimestamps; + + await createTable(relationTableName) + .withColumns( + column('projectId').varchar(36).primary.notNull, + column('userId').uuid.primary.notNull, + column('role').varchar().notNull, + ) + .withIndexOn('projectId') + .withIndexOn('userId') + .withForeignKey('projectId', { + tableName, + columnName: 'id', + onDelete: 'CASCADE', + }) + .withForeignKey('userId', { + tableName: 'user', + columnName: 'id', + onDelete: 'CASCADE', + }).withTimestamps; + } + + async down({ schemaBuilder: { dropTable } }: MigrationContext) { + await dropTable(relationTableName); + await dropTable(tableName); + } +} diff --git a/packages/cli/src/databases/migrations/mysqldb/index.ts b/packages/cli/src/databases/migrations/mysqldb/index.ts index 8ce92158cf5df..779b108130373 100644 --- a/packages/cli/src/databases/migrations/mysqldb/index.ts +++ b/packages/cli/src/databases/migrations/mysqldb/index.ts @@ -51,6 +51,7 @@ import { ExecutionSoftDelete1693491613982 } from '../common/1693491613982-Execut import { AddWorkflowMetadata1695128658538 } from '../common/1695128658538-AddWorkflowMetadata'; import { ModifyWorkflowHistoryNodesAndConnections1695829275184 } from '../common/1695829275184-ModifyWorkflowHistoryNodesAndConnections'; import { AddGlobalAdminRole1700571993961 } from '../common/1700571993961-AddGlobalAdminRole'; +import { CreateProject1705928727784 } from '../common/1705928727784-CreateProject'; import { DropRoleMapping1705429061930 } from '../common/1705429061930-DropRoleMapping'; export const mysqlMigrations: Migration[] = [ @@ -106,5 +107,6 @@ export const mysqlMigrations: Migration[] = [ AddWorkflowMetadata1695128658538, ModifyWorkflowHistoryNodesAndConnections1695829275184, AddGlobalAdminRole1700571993961, + CreateProject1705928727784, DropRoleMapping1705429061930, ]; diff --git a/packages/cli/src/databases/migrations/postgresdb/index.ts b/packages/cli/src/databases/migrations/postgresdb/index.ts index 8ae373397682d..596108bbab1c1 100644 --- a/packages/cli/src/databases/migrations/postgresdb/index.ts +++ b/packages/cli/src/databases/migrations/postgresdb/index.ts @@ -50,6 +50,7 @@ import { AddWorkflowMetadata1695128658538 } from '../common/1695128658538-AddWor import { MigrateToTimestampTz1694091729095 } from './1694091729095-MigrateToTimestampTz'; import { ModifyWorkflowHistoryNodesAndConnections1695829275184 } from '../common/1695829275184-ModifyWorkflowHistoryNodesAndConnections'; import { AddGlobalAdminRole1700571993961 } from '../common/1700571993961-AddGlobalAdminRole'; +import { CreateProject1705928727784 } from '../common/1705928727784-CreateProject'; import { DropRoleMapping1705429061930 } from '../common/1705429061930-DropRoleMapping'; export const postgresMigrations: Migration[] = [ @@ -104,5 +105,6 @@ export const postgresMigrations: Migration[] = [ MigrateToTimestampTz1694091729095, ModifyWorkflowHistoryNodesAndConnections1695829275184, AddGlobalAdminRole1700571993961, + CreateProject1705928727784, DropRoleMapping1705429061930, ]; diff --git a/packages/cli/src/databases/migrations/sqlite/index.ts b/packages/cli/src/databases/migrations/sqlite/index.ts index 7db45788ac7b6..80e00426e4c1b 100644 --- a/packages/cli/src/databases/migrations/sqlite/index.ts +++ b/packages/cli/src/databases/migrations/sqlite/index.ts @@ -48,6 +48,7 @@ import { ExecutionSoftDelete1693491613982 } from './1693491613982-ExecutionSoftD import { AddWorkflowMetadata1695128658538 } from '../common/1695128658538-AddWorkflowMetadata'; import { ModifyWorkflowHistoryNodesAndConnections1695829275184 } from '../common/1695829275184-ModifyWorkflowHistoryNodesAndConnections'; import { AddGlobalAdminRole1700571993961 } from '../common/1700571993961-AddGlobalAdminRole'; +import { CreateProject1705928727784 } from '../common/1705928727784-CreateProject'; import { DropRoleMapping1705429061930 } from './1705429061930-DropRoleMapping'; const sqliteMigrations: Migration[] = [ @@ -100,6 +101,7 @@ const sqliteMigrations: Migration[] = [ AddWorkflowMetadata1695128658538, ModifyWorkflowHistoryNodesAndConnections1695829275184, AddGlobalAdminRole1700571993961, + CreateProject1705928727784, DropRoleMapping1705429061930, ]; diff --git a/packages/cli/src/databases/repositories/project.repository.ts b/packages/cli/src/databases/repositories/project.repository.ts new file mode 100644 index 0000000000000..9b8ed44d87e90 --- /dev/null +++ b/packages/cli/src/databases/repositories/project.repository.ts @@ -0,0 +1,10 @@ +import { Service } from 'typedi'; +import { DataSource, Repository } from 'typeorm'; +import { Project } from '../entities/Project'; + +@Service() +export class ProjectRepository extends Repository { + constructor(dataSource: DataSource) { + super(Project, dataSource.manager); + } +} diff --git a/packages/cli/src/databases/repositories/projectRelation.repository.ts b/packages/cli/src/databases/repositories/projectRelation.repository.ts new file mode 100644 index 0000000000000..83f8a3a56e708 --- /dev/null +++ b/packages/cli/src/databases/repositories/projectRelation.repository.ts @@ -0,0 +1,10 @@ +import { Service } from 'typedi'; +import { DataSource, Repository } from 'typeorm'; +import { ProjectRelation } from '../entities/ProjectRelation'; + +@Service() +export class ProjectRelationRepository extends Repository { + constructor(dataSource: DataSource) { + super(ProjectRelation, dataSource.manager); + } +}