Files
Automata/packages/cli/src/environments/sourceControl/sourceControl.controller.ee.ts
Michael Auerswald c3ba0123ad feat: Migrate integer primary keys to nanoids (#6345)
* first commit for postgres migration

* (not working)

* sqlite migration

* quicksave

* fix tests

* fix pg test

* fix postgres

* fix variables import

* fix execution saving

* add user settings fix

* change migration to single lines

* patch preferences endpoint

* cleanup

* improve variable import

* cleanup unusued code

* Update packages/cli/src/PublicApi/v1/handlers/workflows/workflows.handler.ts

Co-authored-by: Omar Ajoue <krynble@gmail.com>

* address review notes

* fix var update/import

* refactor: Separate execution data to its own table (#6323)

* wip: Temporary migration process

* refactor: Create boilerplate repository methods for executions

* fix: Lint issues

* refactor: Added search endpoint to repository

* refactor: Make the execution list work again

* wip: Updating how we create and update executions everywhere

* fix: Lint issues and remove most of the direct access to execution model

* refactor: Remove includeWorkflowData flag and fix more tests

* fix: Lint issues

* fix: Fixed ordering of executions for FE, removed transaction when saving execution and removed unnecessary update

* refactor: Add comment about missing feature

* refactor: Refactor counting executions

* refactor: Add migration for other dbms and fix issues found

* refactor: Fix lint issues

* refactor: Remove unnecessary comment and auto inject repo to internal hooks

* refactor: remove type assertion

* fix: Fix broken tests

* fix: Remove unnecessary import

* Remove unnecessary toString() call

Co-authored-by: Iván Ovejero <ivov.src@gmail.com>

* fix: Address comments after review

* refactor: Remove unused import

* fix: Lint issues

* fix: Add correct migration files

---------

Co-authored-by: Iván Ovejero <ivov.src@gmail.com>

* remove null values from credential export

* fix: Fix an issue with queue mode where all running execution would be returned

* fix: Update n8n node to allow for workflow ids with letters

* set upstream on set branch

* remove typo

* add nodeAccess to credentials

* fix unsaved run check for undefined id

* fix(core): Rename version control feature to source control (#6480)

* rename versionControl to sourceControl

* fix source control tooltip wording

---------

Co-authored-by: Romain Minaud <romain.minaud@gmail.com>

* fix(editor): Pay 548 hide the set up version control button (#6485)

* feat(DebugHelper Node): Fix and include in main app (#6406)

* improve node a bit

* fixing continueOnFail() ton contain error in json

* improve pairedItem

* fix random data returning object results

* fix nanoId length typo

* update pnpm-lock file

---------

Co-authored-by: Marcus <marcus@n8n.io>

* fix(editor): Remove setup source control CTA button

* fix(editor): Remove setup source control CTA button

---------

Co-authored-by: Michael Auerswald <michael.auerswald@gmail.com>
Co-authored-by: Marcus <marcus@n8n.io>

* fix(editor): Update source control docs links (#6488)

* feat(DebugHelper Node): Fix and include in main app (#6406)

* improve node a bit

* fixing continueOnFail() ton contain error in json

* improve pairedItem

* fix random data returning object results

* fix nanoId length typo

* update pnpm-lock file

---------

Co-authored-by: Marcus <marcus@n8n.io>

* feat(editor): Replace root events with event bus events (no-changelog) (#6454)

* feat: replace root events with event bus events

* fix: prevent cypress from replacing global with globalThis in import path

* feat: remove emitter mixin

* fix: replace component events with event bus

* fix: fix linting issue

* fix: fix breaking expression switch

* chore: prettify ndv e2e suite code

* fix(editor): Update source control docs links

---------

Co-authored-by: Michael Auerswald <michael.auerswald@gmail.com>
Co-authored-by: Marcus <marcus@n8n.io>
Co-authored-by: Alex Grozav <alex@grozav.com>

* fix tag endpoint regex

---------

Co-authored-by: Omar Ajoue <krynble@gmail.com>
Co-authored-by: Iván Ovejero <ivov.src@gmail.com>
Co-authored-by: Romain Minaud <romain.minaud@gmail.com>
Co-authored-by: Csaba Tuncsik <csaba@n8n.io>
Co-authored-by: Marcus <marcus@n8n.io>
Co-authored-by: Alex Grozav <alex@grozav.com>
2023-06-20 19:13:18 +02:00

236 lines
8.0 KiB
TypeScript

import { Authorized, Get, Post, Patch, RestController } from '@/decorators';
import {
sourceControlLicensedMiddleware,
sourceControlLicensedAndEnabledMiddleware,
} from './middleware/sourceControlEnabledMiddleware.ee';
import { SourceControlService } from './sourceControl.service.ee';
import { SourceControlRequest } from './types/requests';
import type { SourceControlPreferences } from './types/sourceControlPreferences';
import { BadRequestError } from '@/ResponseHelper';
import type { PullResult, PushResult, StatusResult } from 'simple-git';
import express from 'express';
import type { ImportResult } from './types/importResult';
import { SourceControlPreferencesService } from './sourceControlPreferences.service.ee';
import type { SourceControlledFile } from './types/sourceControlledFile';
import { SOURCE_CONTROL_API_ROOT, SOURCE_CONTROL_DEFAULT_BRANCH } from './constants';
@RestController(`/${SOURCE_CONTROL_API_ROOT}`)
export class SourceControlController {
constructor(
private sourceControlService: SourceControlService,
private sourceControlPreferencesService: SourceControlPreferencesService,
) {}
@Authorized('any')
@Get('/preferences', { middlewares: [sourceControlLicensedMiddleware] })
async getPreferences(): Promise<SourceControlPreferences> {
// returns the settings with the privateKey property redacted
return this.sourceControlPreferencesService.getPreferences();
}
@Authorized(['global', 'owner'])
@Post('/preferences', { middlewares: [sourceControlLicensedMiddleware] })
async setPreferences(req: SourceControlRequest.UpdatePreferences) {
if (
req.body.branchReadOnly === undefined &&
this.sourceControlPreferencesService.isSourceControlConnected()
) {
throw new BadRequestError(
'Cannot change preferences while connected to a source control provider. Please disconnect first.',
);
}
try {
const sanitizedPreferences: Partial<SourceControlPreferences> = {
...req.body,
initRepo: req.body.initRepo ?? true, // default to true if not specified
connected: undefined,
publicKey: undefined,
};
await this.sourceControlPreferencesService.validateSourceControlPreferences(
sanitizedPreferences,
);
const updatedPreferences = await this.sourceControlPreferencesService.setPreferences(
sanitizedPreferences,
);
if (sanitizedPreferences.initRepo === true) {
try {
await this.sourceControlService.initializeRepository({
...updatedPreferences,
branchName:
updatedPreferences.branchName === ''
? SOURCE_CONTROL_DEFAULT_BRANCH
: updatedPreferences.branchName,
initRepo: true,
});
if (this.sourceControlPreferencesService.getPreferences().branchName !== '') {
await this.sourceControlPreferencesService.setPreferences({
connected: true,
});
}
} catch (error) {
// if initialization fails, run cleanup to remove any intermediate state and throw the error
await this.sourceControlService.disconnect({ keepKeyPair: true });
throw error;
}
}
await this.sourceControlService.init();
return this.sourceControlPreferencesService.getPreferences();
} catch (error) {
throw new BadRequestError((error as { message: string }).message);
}
}
@Authorized(['global', 'owner'])
@Patch('/preferences', { middlewares: [sourceControlLicensedMiddleware] })
async updatePreferences(req: SourceControlRequest.UpdatePreferences) {
try {
const sanitizedPreferences: Partial<SourceControlPreferences> = {
...req.body,
initRepo: false,
connected: undefined,
publicKey: undefined,
repositoryUrl: undefined,
authorName: undefined,
authorEmail: undefined,
};
const currentPreferences = this.sourceControlPreferencesService.getPreferences();
await this.sourceControlPreferencesService.validateSourceControlPreferences(
sanitizedPreferences,
);
if (
sanitizedPreferences.branchName &&
sanitizedPreferences.branchName !== currentPreferences.branchName
) {
await this.sourceControlService.setBranch(sanitizedPreferences.branchName);
}
if (sanitizedPreferences.branchColor || sanitizedPreferences.branchReadOnly !== undefined) {
await this.sourceControlPreferencesService.setPreferences(
{
branchColor: sanitizedPreferences.branchColor,
branchReadOnly: sanitizedPreferences.branchReadOnly,
},
true,
);
}
await this.sourceControlService.init();
return this.sourceControlPreferencesService.getPreferences();
} catch (error) {
throw new BadRequestError((error as { message: string }).message);
}
}
@Authorized(['global', 'owner'])
@Post('/disconnect', { middlewares: [sourceControlLicensedMiddleware] })
async disconnect(req: SourceControlRequest.Disconnect) {
try {
return await this.sourceControlService.disconnect(req.body);
} catch (error) {
throw new BadRequestError((error as { message: string }).message);
}
}
@Authorized('any')
@Get('/get-branches', { middlewares: [sourceControlLicensedMiddleware] })
async getBranches() {
try {
return await this.sourceControlService.getBranches();
} catch (error) {
throw new BadRequestError((error as { message: string }).message);
}
}
@Authorized(['global', 'owner'])
@Post('/push-workfolder', { middlewares: [sourceControlLicensedAndEnabledMiddleware] })
async pushWorkfolder(
req: SourceControlRequest.PushWorkFolder,
res: express.Response,
): Promise<PushResult | SourceControlledFile[]> {
if (this.sourceControlPreferencesService.isBranchReadOnly()) {
throw new BadRequestError('Cannot push onto read-only branch.');
}
try {
const result = await this.sourceControlService.pushWorkfolder(req.body);
if ((result as PushResult).pushed) {
res.statusCode = 200;
} else {
res.statusCode = 409;
}
return result;
} catch (error) {
throw new BadRequestError((error as { message: string }).message);
}
}
@Authorized(['global', 'owner'])
@Post('/pull-workfolder', { middlewares: [sourceControlLicensedAndEnabledMiddleware] })
async pullWorkfolder(
req: SourceControlRequest.PullWorkFolder,
res: express.Response,
): Promise<SourceControlledFile[] | ImportResult | PullResult | StatusResult | undefined> {
try {
const result = await this.sourceControlService.pullWorkfolder({
force: req.body.force,
variables: req.body.variables,
userId: req.user.id,
importAfterPull: req.body.importAfterPull ?? true,
});
if ((result as ImportResult)?.workflows) {
res.statusCode = 200;
} else {
res.statusCode = 409;
}
return result;
} catch (error) {
throw new BadRequestError((error as { message: string }).message);
}
}
@Authorized(['global', 'owner'])
@Get('/reset-workfolder', { middlewares: [sourceControlLicensedAndEnabledMiddleware] })
async resetWorkfolder(
req: SourceControlRequest.PullWorkFolder,
): Promise<ImportResult | undefined> {
try {
return await this.sourceControlService.resetWorkfolder({
force: req.body.force,
variables: req.body.variables,
userId: req.user.id,
importAfterPull: req.body.importAfterPull ?? true,
});
} catch (error) {
throw new BadRequestError((error as { message: string }).message);
}
}
@Authorized('any')
@Get('/get-status', { middlewares: [sourceControlLicensedAndEnabledMiddleware] })
async getStatus() {
try {
return await this.sourceControlService.getStatus();
} catch (error) {
throw new BadRequestError((error as { message: string }).message);
}
}
@Authorized('any')
@Get('/status', { middlewares: [sourceControlLicensedMiddleware] })
async status(): Promise<StatusResult> {
try {
return await this.sourceControlService.status();
} catch (error) {
throw new BadRequestError((error as { message: string }).message);
}
}
@Authorized(['global', 'owner'])
@Post('/generate-key-pair', { middlewares: [sourceControlLicensedMiddleware] })
async generateKeyPair(): Promise<SourceControlPreferences> {
try {
const result = await this.sourceControlPreferencesService.generateAndSaveKeyPair();
return result;
} catch (error) {
throw new BadRequestError((error as { message: string }).message);
}
}
}