diff --git a/packages/@n8n/api-types/src/dto/index.ts b/packages/@n8n/api-types/src/dto/index.ts index 41a55f050a..34ee681342 100644 --- a/packages/@n8n/api-types/src/dto/index.ts +++ b/packages/@n8n/api-types/src/dto/index.ts @@ -3,3 +3,4 @@ export { RoleChangeRequestDto } from './user/role-change-request.dto'; export { SettingsUpdateRequestDto } from './user/settings-update-request.dto'; export { UserUpdateRequestDto } from './user/user-update-request.dto'; export { CommunityRegisteredRequestDto } from './license/community-registered-request.dto'; +export { CreateVariableRequestDto } from './variables/create-variable-request.dto'; diff --git a/packages/cli/src/databases/entities/variables.ts b/packages/cli/src/databases/entities/variables.ts index d6bf10431e..12bf33c720 100644 --- a/packages/cli/src/databases/entities/variables.ts +++ b/packages/cli/src/databases/entities/variables.ts @@ -1,6 +1,7 @@ -import { Column, Entity } from '@n8n/typeorm'; +import { Column, Entity, ManyToOne } from '@n8n/typeorm'; import { WithStringId } from './abstract-entity'; +import type { Project } from './project'; @Entity() export class Variables extends WithStringId { @@ -12,4 +13,14 @@ export class Variables extends WithStringId { @Column('text') value: string; + + // If null, it's a global variable + @ManyToOne('Project', { + onDelete: 'CASCADE', + nullable: true, + }) + project: Project | null; + + @Column('text', { nullable: true }) + projectId: string | null; } diff --git a/packages/cli/src/databases/migrations/mysqldb/index.ts b/packages/cli/src/databases/migrations/mysqldb/index.ts index ff40fd9dc0..8ae7eaf488 100644 --- a/packages/cli/src/databases/migrations/mysqldb/index.ts +++ b/packages/cli/src/databases/migrations/mysqldb/index.ts @@ -68,6 +68,7 @@ import { CreateProcessedDataTable1726606152711 } from '../common/1726606152711-C import { SeparateExecutionCreationFromStart1727427440136 } from '../common/1727427440136-SeparateExecutionCreationFromStart'; import { AddMissingPrimaryKeyOnAnnotationTagMapping1728659839644 } from '../common/1728659839644-AddMissingPrimaryKeyOnAnnotationTagMapping'; import { UpdateProcessedDataValueColumnToText1729607673464 } from '../common/1729607673464-UpdateProcessedDataValueColumnToText'; +import { AddProjectToVariables1729695079000 } from '../common/1729695079000-AddProjectToVariables'; export const mysqlMigrations: Migration[] = [ InitialMigration1588157391238, @@ -138,4 +139,5 @@ export const mysqlMigrations: Migration[] = [ CreateProcessedDataTable1726606152711, AddMissingPrimaryKeyOnAnnotationTagMapping1728659839644, UpdateProcessedDataValueColumnToText1729607673464, + AddProjectToVariables1729695079000, ]; diff --git a/packages/cli/src/databases/migrations/postgresdb/index.ts b/packages/cli/src/databases/migrations/postgresdb/index.ts index f3ac7e0474..53894cbc9f 100644 --- a/packages/cli/src/databases/migrations/postgresdb/index.ts +++ b/packages/cli/src/databases/migrations/postgresdb/index.ts @@ -68,6 +68,7 @@ import { CreateProcessedDataTable1726606152711 } from '../common/1726606152711-C import { SeparateExecutionCreationFromStart1727427440136 } from '../common/1727427440136-SeparateExecutionCreationFromStart'; import { AddMissingPrimaryKeyOnAnnotationTagMapping1728659839644 } from '../common/1728659839644-AddMissingPrimaryKeyOnAnnotationTagMapping'; import { UpdateProcessedDataValueColumnToText1729607673464 } from '../common/1729607673464-UpdateProcessedDataValueColumnToText'; +import { AddProjectToVariables1729695079000 } from '../common/1729695079000-AddProjectToVariables'; export const postgresMigrations: Migration[] = [ InitialMigration1587669153312, @@ -138,4 +139,5 @@ export const postgresMigrations: Migration[] = [ CreateProcessedDataTable1726606152711, AddMissingPrimaryKeyOnAnnotationTagMapping1728659839644, UpdateProcessedDataValueColumnToText1729607673464, + AddProjectToVariables1729695079000, ]; diff --git a/packages/cli/src/databases/migrations/sqlite/index.ts b/packages/cli/src/databases/migrations/sqlite/index.ts index e53b5f43bd..b1625f2616 100644 --- a/packages/cli/src/databases/migrations/sqlite/index.ts +++ b/packages/cli/src/databases/migrations/sqlite/index.ts @@ -65,6 +65,7 @@ import { CreateAnnotationTables1724753530828 } from '../common/1724753530828-Cre import { CreateProcessedDataTable1726606152711 } from '../common/1726606152711-CreateProcessedDataTable'; import { SeparateExecutionCreationFromStart1727427440136 } from '../common/1727427440136-SeparateExecutionCreationFromStart'; import { UpdateProcessedDataValueColumnToText1729607673464 } from '../common/1729607673464-UpdateProcessedDataValueColumnToText'; +import { AddProjectToVariables1729695079000 } from '../common/1729695079000-AddProjectToVariables'; const sqliteMigrations: Migration[] = [ InitialMigration1588102412422, @@ -132,6 +133,7 @@ const sqliteMigrations: Migration[] = [ CreateProcessedDataTable1726606152711, AddMissingPrimaryKeyOnAnnotationTagMapping1728659839644, UpdateProcessedDataValueColumnToText1729607673464, + AddProjectToVariables1729695079000, ]; export { sqliteMigrations }; diff --git a/packages/cli/src/environments/variables/variables.controller.ee.ts b/packages/cli/src/environments/variables/variables.controller.ee.ts index 5da4221a3d..94cc543d75 100644 --- a/packages/cli/src/environments/variables/variables.controller.ee.ts +++ b/packages/cli/src/environments/variables/variables.controller.ee.ts @@ -1,9 +1,20 @@ -import { Delete, Get, GlobalScope, Licensed, Patch, Post, RestController } from '@/decorators'; +import { CreateVariableRequestDto } from '@n8n/api-types'; + +import { + Body, + Delete, + Get, + GlobalScope, + Licensed, + Patch, + Post, + RestController, +} from '@/decorators'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { NotFoundError } from '@/errors/response-errors/not-found.error'; import { VariableCountLimitReachedError } from '@/errors/variable-count-limit-reached.error'; import { VariableValidationError } from '@/errors/variable-validation.error'; -import { VariablesRequest } from '@/requests'; +import { AuthenticatedRequest, VariablesRequest } from '@/requests'; import { VariablesService } from './variables.service.ee'; @@ -20,11 +31,13 @@ export class VariablesController { @Post('/') @Licensed('feat:variables') @GlobalScope('variable:create') - async createVariable(req: VariablesRequest.Create) { - const variable = req.body; - delete variable.id; + async createVariable( + req: AuthenticatedRequest, + _res: Response, + @Body variable: CreateVariableRequestDto, + ) { try { - return await this.variablesService.create(variable); + return await this.variablesService.create(variable, req.user); } catch (error) { if (error instanceof VariableCountLimitReachedError) { throw new BadRequestError(error.message); diff --git a/packages/cli/src/environments/variables/variables.service.ee.ts b/packages/cli/src/environments/variables/variables.service.ee.ts index 31cf725099..e83c280ccb 100644 --- a/packages/cli/src/environments/variables/variables.service.ee.ts +++ b/packages/cli/src/environments/variables/variables.service.ee.ts @@ -1,3 +1,4 @@ +import type { CreateVariableRequestDto } from '@n8n/api-types'; import { Container, Service } from 'typedi'; import type { Variables } from '@/databases/entities/variables'; @@ -9,6 +10,7 @@ import { EventService } from '@/events/event.service'; import { CacheService } from '@/services/cache/cache.service'; import { canCreateNewVariable } from './environment-helpers'; +import { User } from '@/databases/entities/user'; @Service() export class VariablesService { @@ -67,11 +69,10 @@ export class VariablesService { } } - async create(variable: Omit): Promise { + async create(variable: CreateVariableRequestDto, user: User): Promise { if (!canCreateNewVariable(await this.getCount())) { throw new VariableCountLimitReachedError('Variables limit reached'); } - this.validateVariable(variable); this.eventService.emit('variable-created'); const saveResult = await this.variablesRepository.save( diff --git a/packages/cli/src/public-api/v1/handlers/variables/variables.handler.ts b/packages/cli/src/public-api/v1/handlers/variables/variables.handler.ts index 65fb1daab5..e71576cc06 100644 --- a/packages/cli/src/public-api/v1/handlers/variables/variables.handler.ts +++ b/packages/cli/src/public-api/v1/handlers/variables/variables.handler.ts @@ -3,6 +3,10 @@ import Container from 'typedi'; import { VariablesRepository } from '@/databases/repositories/variables.repository'; import { VariablesController } from '@/environments/variables/variables.controller.ee'; +import { VariablesService } from '@/environments/variables/variables.service.ee'; +import { BadRequestError } from '@/errors/response-errors/bad-request.error'; +import { VariableCountLimitReachedError } from '@/errors/variable-count-limit-reached.error'; +import { VariableValidationError } from '@/errors/variable-validation.error'; import type { PaginatedRequest } from '@/public-api/types'; import type { VariablesRequest } from '@/requests'; @@ -18,7 +22,18 @@ export = { isLicensed('feat:variables'), globalScope('variable:create'), async (req: Create, res: Response) => { - await Container.get(VariablesController).createVariable(req); + delete req.body.id; + try { + Container.get(VariablesService).validateVariable(req.body); + await Container.get(VariablesService).create(req.body, req.user); + } catch (error) { + if (error instanceof VariableCountLimitReachedError) { + throw new BadRequestError(error.message); + } else if (error instanceof VariableValidationError) { + throw new BadRequestError(error.message); + } + throw error; + } res.status(201).send(); }, diff --git a/packages/cli/src/requests.ts b/packages/cli/src/requests.ts index 4765ac1fad..f0f9540239 100644 --- a/packages/cli/src/requests.ts +++ b/packages/cli/src/requests.ts @@ -453,7 +453,7 @@ export type BinaryDataRequest = AuthenticatedRequest< // ---------------------------------- // export declare namespace VariablesRequest { - type CreateUpdatePayload = Omit & { id?: unknown }; + type CreateUpdatePayload = Omit & { id?: unknown; type: 'string' }; type GetAll = AuthenticatedRequest; type Get = AuthenticatedRequest<{ id: string }, {}, {}, {}>;