20215 convert application variable to a syncable entity (#20269)
## Summary - Converts applicationVariable from a bespoke sync path to a proper SyncableEntity, unifying it with the workspace migration pipeline used by all other manifest-managed entities (agent, skill, frontComponent, webhook, etc.) - Removes the upsertManyApplicationVariableEntities method and its direct-DB-mutation approach in favor of the standard validate → build → run action handler pipeline - Adds universalIdentifier, deletedAt columns and makes applicationId NOT NULL via an instance command migration ## Motivation Before this change, applicationVariable was the only manifest-managed entity that bypassed ApplicationManifestMigrationService.syncMetadataFromManifest(). It used a bespoke service method called directly from syncApplication(), creating two mental models, two validation styles, and two cache invalidation patterns. Now there's one unified pipeline for all manifest entities. ## What changed ### Entity refactor: - ApplicationVariableEntity now extends SyncableEntity (gains universalIdentifier, non-nullable applicationId with CASCADE, soft-delete via deletedAt) ### New flat entity layer (flat-application-variable/): - Type, maps type, editable properties constant, entity-to-flat converter, cache service, module ### New migration pipeline wiring: - Manifest converter (fromApplicationVariableManifestToUniversalFlatApplicationVariable) - Validator service (FlatApplicationVariableValidatorService) - Builder service (WorkspaceMigrationApplicationVariableActionsBuilderService) - Create/Update/Delete action handlers with secret encryption hooks - Registered in orchestrator, builder module, runner module, and all type registries ### Removed bespoke path: - Deleted upsertManyApplicationVariableEntities from ApplicationVariableEntityService - Removed its call from ApplicationSyncService.syncApplication() - Kept update() (operator-set value at runtime) and getDisplayValue() (runtime display) ### Database migration: - Instance command to add columns, backfill universalIdentifier, enforce NOT NULL constraints, and update indexes ## Test plan - npx nx typecheck twenty-server passes (0 errors) - Unit tests pass (application-variable.service.spec.ts, build-env-var.spec.ts) - Install an app with applicationVariables in its manifest → variables appear with correct universalIdentifier - Update app manifest (add/remove/modify a variable) → migration pipeline handles diff correctly - Operator-set value via update endpoint persists correctly with encryption - Uninstall app → variables cascade-deleted - app dev --once on example app syncs without errors
This commit is contained in:
+1
@@ -171,6 +171,7 @@ export const enrichCreateWorkspaceMigrationActionsWithIds = ({
|
||||
case 'objectPermission':
|
||||
case 'fieldPermission':
|
||||
case 'webhook':
|
||||
case 'applicationVariable':
|
||||
case 'connectionProvider': {
|
||||
if (!isDefined(idByUniversalIdentifier)) {
|
||||
return action;
|
||||
|
||||
+37
@@ -41,6 +41,7 @@ import { WorkspaceMigrationViewFilterActionsBuilderService } from 'src/engine/wo
|
||||
import { WorkspaceMigrationViewGroupActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-group/workspace-migration-view-group-actions-builder.service';
|
||||
import { WorkspaceMigrationViewSortActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-sort/workspace-migration-view-sort-actions.builder.service';
|
||||
import { WorkspaceMigrationViewActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view/workspace-migration-view-actions-builder.service';
|
||||
import { WorkspaceMigrationApplicationVariableActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/application-variable/workspace-migration-application-variable-actions-builder.service';
|
||||
import { WorkspaceMigrationWebhookActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/webhook/workspace-migration-webhook-actions-builder.service';
|
||||
|
||||
@Injectable()
|
||||
@@ -73,6 +74,7 @@ export class WorkspaceMigrationBuildOrchestratorService {
|
||||
private readonly workspaceMigrationRowLevelPermissionPredicateGroupActionsBuilderService: WorkspaceMigrationRowLevelPermissionPredicateGroupActionsBuilderService,
|
||||
private readonly workspaceMigrationFrontComponentActionsBuilderService: WorkspaceMigrationFrontComponentActionsBuilderService,
|
||||
private readonly workspaceMigrationWebhookActionsBuilderService: WorkspaceMigrationWebhookActionsBuilderService,
|
||||
private readonly workspaceMigrationApplicationVariableActionsBuilderService: WorkspaceMigrationApplicationVariableActionsBuilderService,
|
||||
private readonly workspaceMigrationConnectionProviderActionsBuilderService: WorkspaceMigrationConnectionProviderActionsBuilderService,
|
||||
) {}
|
||||
|
||||
@@ -125,6 +127,7 @@ export class WorkspaceMigrationBuildOrchestratorService {
|
||||
const orchestratorActionsReport = structuredClone({
|
||||
...createEmptyOrchestratorActionsReport(),
|
||||
});
|
||||
|
||||
const orchestratorFailureReport = structuredClone(
|
||||
EMPTY_ORCHESTRATOR_FAILURE_REPORT(),
|
||||
);
|
||||
@@ -166,6 +169,7 @@ export class WorkspaceMigrationBuildOrchestratorService {
|
||||
flatPageLayoutTabMaps,
|
||||
flatFrontComponentMaps,
|
||||
flatWebhookMaps,
|
||||
flatApplicationVariableMaps,
|
||||
flatConnectionProviderMaps,
|
||||
} = fromToAllFlatEntityMaps;
|
||||
|
||||
@@ -826,6 +830,34 @@ export class WorkspaceMigrationBuildOrchestratorService {
|
||||
}
|
||||
}
|
||||
|
||||
if (isDefined(flatApplicationVariableMaps)) {
|
||||
const {
|
||||
from: fromFlatApplicationVariableMaps,
|
||||
to: toFlatApplicationVariableMaps,
|
||||
} = flatApplicationVariableMaps;
|
||||
|
||||
const applicationVariableResult =
|
||||
await this.workspaceMigrationApplicationVariableActionsBuilderService.validateAndBuild(
|
||||
{
|
||||
additionalCacheDataMaps,
|
||||
from: fromFlatApplicationVariableMaps,
|
||||
to: toFlatApplicationVariableMaps,
|
||||
buildOptions,
|
||||
dependencyOptimisticFlatEntityMaps: optimisticAllFlatEntityMaps,
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
if (applicationVariableResult.status === 'fail') {
|
||||
orchestratorFailureReport.applicationVariable.push(
|
||||
...applicationVariableResult.errors,
|
||||
);
|
||||
} else {
|
||||
orchestratorActionsReport.applicationVariable =
|
||||
applicationVariableResult.actions;
|
||||
}
|
||||
}
|
||||
|
||||
if (isDefined(flatConnectionProviderMaps)) {
|
||||
const {
|
||||
from: fromFlatConnectionProviderMaps,
|
||||
@@ -1031,6 +1063,11 @@ export class WorkspaceMigrationBuildOrchestratorService {
|
||||
...aggregatedOrchestratorActionsReport.webhook.update,
|
||||
///
|
||||
|
||||
// Application Variables
|
||||
...aggregatedOrchestratorActionsReport.applicationVariable.delete,
|
||||
...aggregatedOrchestratorActionsReport.applicationVariable.create,
|
||||
...aggregatedOrchestratorActionsReport.applicationVariable.update,
|
||||
|
||||
// Connection providers
|
||||
...aggregatedOrchestratorActionsReport.connectionProvider.delete,
|
||||
...aggregatedOrchestratorActionsReport.connectionProvider.create,
|
||||
|
||||
+1
@@ -3,6 +3,7 @@
|
||||
exports[`ALL_UNIVERSAL_FLAT_ENTITY_FOREIGN_KEY_AGGREGATOR_PROPERTIES should match snapshot 1`] = `
|
||||
{
|
||||
"agent": [],
|
||||
"applicationVariable": [],
|
||||
"commandMenuItem": [],
|
||||
"connectionProvider": [],
|
||||
"fieldMetadata": [
|
||||
|
||||
+1
@@ -42,6 +42,7 @@ export const ALL_JSONB_PROPERTIES_WITH_SERIALIZED_RELATION_BY_METADATA_NAME = {
|
||||
objectPermission: {},
|
||||
fieldPermission: {},
|
||||
frontComponent: {},
|
||||
applicationVariable: {},
|
||||
connectionProvider: {},
|
||||
} as const satisfies {
|
||||
[P in AllMetadataName]: Partial<{
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import { type ApplicationVariableEntity } from 'src/engine/core-modules/application/application-variable/application-variable.entity';
|
||||
import { type UniversalFlatEntityFrom } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-from.type';
|
||||
|
||||
export type UniversalFlatApplicationVariable = UniversalFlatEntityFrom<
|
||||
ApplicationVariableEntity,
|
||||
'applicationVariable'
|
||||
>;
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import { type BaseFlatCreateWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-flat-create-workspace-migration-action.type';
|
||||
import { type BaseFlatDeleteWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-flat-delete-workspace-migration-action.type';
|
||||
import { type BaseFlatUpdateWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-flat-update-workspace-migration-action.type';
|
||||
import { type BaseUniversalCreateWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-universal-create-workspace-migration-action.type';
|
||||
import { type BaseUniversalDeleteWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-universal-delete-workspace-migration-action.type';
|
||||
import { type BaseUniversalUpdateWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-universal-update-workspace-migration-action.type';
|
||||
|
||||
export type FlatCreateApplicationVariableAction =
|
||||
BaseFlatCreateWorkspaceMigrationAction<'applicationVariable'>;
|
||||
|
||||
export type UniversalCreateApplicationVariableAction =
|
||||
BaseUniversalCreateWorkspaceMigrationAction<'applicationVariable'>;
|
||||
|
||||
export type FlatUpdateApplicationVariableAction =
|
||||
BaseFlatUpdateWorkspaceMigrationAction<'applicationVariable'>;
|
||||
|
||||
export type UniversalUpdateApplicationVariableAction =
|
||||
BaseUniversalUpdateWorkspaceMigrationAction<'applicationVariable'>;
|
||||
|
||||
export type UniversalDeleteApplicationVariableAction =
|
||||
BaseUniversalDeleteWorkspaceMigrationAction<'applicationVariable'>;
|
||||
|
||||
export type FlatDeleteApplicationVariableAction =
|
||||
BaseFlatDeleteWorkspaceMigrationAction<'applicationVariable'>;
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
|
||||
|
||||
import { UniversalUpdateApplicationVariableAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/application-variable/types/workspace-migration-application-variable-action.type';
|
||||
import { WorkspaceEntityMigrationBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/services/workspace-entity-migration-builder.service';
|
||||
import { FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-update-validation-args.type';
|
||||
import { UniversalFlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-validation-args.type';
|
||||
import { UniversalFlatEntityValidationReturnType } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-validation-result.type';
|
||||
import { FlatApplicationVariableValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-application-variable-validator.service';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceMigrationApplicationVariableActionsBuilderService extends WorkspaceEntityMigrationBuilderService<
|
||||
typeof ALL_METADATA_NAME.applicationVariable
|
||||
> {
|
||||
constructor(
|
||||
private readonly flatApplicationVariableValidatorService: FlatApplicationVariableValidatorService,
|
||||
) {
|
||||
super(ALL_METADATA_NAME.applicationVariable);
|
||||
}
|
||||
|
||||
protected validateFlatEntityCreation(
|
||||
args: UniversalFlatEntityValidationArgs<
|
||||
typeof ALL_METADATA_NAME.applicationVariable
|
||||
>,
|
||||
): UniversalFlatEntityValidationReturnType<
|
||||
typeof ALL_METADATA_NAME.applicationVariable,
|
||||
'create'
|
||||
> {
|
||||
const validationResult =
|
||||
this.flatApplicationVariableValidatorService.validateFlatApplicationVariableCreation(
|
||||
args,
|
||||
);
|
||||
|
||||
if (validationResult.errors.length > 0) {
|
||||
return {
|
||||
status: 'fail',
|
||||
...validationResult,
|
||||
};
|
||||
}
|
||||
|
||||
const { flatEntityToValidate: flatApplicationVariableToValidate } = args;
|
||||
|
||||
return {
|
||||
status: 'success',
|
||||
action: {
|
||||
type: 'create',
|
||||
metadataName: 'applicationVariable',
|
||||
flatEntity: flatApplicationVariableToValidate,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected validateFlatEntityDeletion(
|
||||
args: UniversalFlatEntityValidationArgs<
|
||||
typeof ALL_METADATA_NAME.applicationVariable
|
||||
>,
|
||||
): UniversalFlatEntityValidationReturnType<
|
||||
typeof ALL_METADATA_NAME.applicationVariable,
|
||||
'delete'
|
||||
> {
|
||||
const validationResult =
|
||||
this.flatApplicationVariableValidatorService.validateFlatApplicationVariableDeletion(
|
||||
args,
|
||||
);
|
||||
|
||||
if (validationResult.errors.length > 0) {
|
||||
return {
|
||||
status: 'fail',
|
||||
...validationResult,
|
||||
};
|
||||
}
|
||||
|
||||
const { flatEntityToValidate: flatApplicationVariableToValidate } = args;
|
||||
|
||||
return {
|
||||
status: 'success',
|
||||
action: {
|
||||
type: 'delete',
|
||||
metadataName: 'applicationVariable',
|
||||
universalIdentifier:
|
||||
flatApplicationVariableToValidate.universalIdentifier,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected validateFlatEntityUpdate(
|
||||
args: FlatEntityUpdateValidationArgs<
|
||||
typeof ALL_METADATA_NAME.applicationVariable
|
||||
>,
|
||||
): UniversalFlatEntityValidationReturnType<
|
||||
typeof ALL_METADATA_NAME.applicationVariable,
|
||||
'update'
|
||||
> {
|
||||
const validationResult =
|
||||
this.flatApplicationVariableValidatorService.validateFlatApplicationVariableUpdate(
|
||||
args,
|
||||
);
|
||||
|
||||
if (validationResult.errors.length > 0) {
|
||||
return {
|
||||
status: 'fail',
|
||||
...validationResult,
|
||||
};
|
||||
}
|
||||
|
||||
const { universalIdentifier, flatEntityUpdate } = args;
|
||||
|
||||
const updateAction: UniversalUpdateApplicationVariableAction = {
|
||||
type: 'update',
|
||||
metadataName: 'applicationVariable',
|
||||
universalIdentifier,
|
||||
update: flatEntityUpdate,
|
||||
};
|
||||
|
||||
return {
|
||||
status: 'success',
|
||||
action: updateAction,
|
||||
};
|
||||
}
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { msg, t } from '@lingui/core/macro';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { ApplicationVariableEntityExceptionCode } from 'src/engine/core-modules/application/application-variable/application-variable.exception';
|
||||
import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util';
|
||||
import { type FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/types/failed-flat-entity-validation.type';
|
||||
import { getEmptyFlatEntityValidationError } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/utils/get-flat-entity-validation-error.util';
|
||||
import { type FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-update-validation-args.type';
|
||||
import { type UniversalFlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-validation-args.type';
|
||||
|
||||
@Injectable()
|
||||
export class FlatApplicationVariableValidatorService {
|
||||
public validateFlatApplicationVariableCreation({
|
||||
flatEntityToValidate: flatApplicationVariable,
|
||||
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
|
||||
flatApplicationVariableMaps: optimisticFlatApplicationVariableMaps,
|
||||
},
|
||||
}: UniversalFlatEntityValidationArgs<
|
||||
typeof ALL_METADATA_NAME.applicationVariable
|
||||
>): FailedFlatEntityValidation<'applicationVariable', 'create'> {
|
||||
const validationResult = getEmptyFlatEntityValidationError({
|
||||
flatEntityMinimalInformation: {
|
||||
universalIdentifier: flatApplicationVariable.universalIdentifier,
|
||||
key: flatApplicationVariable.key,
|
||||
},
|
||||
metadataName: 'applicationVariable',
|
||||
type: 'create',
|
||||
});
|
||||
|
||||
if (!isNonEmptyString(flatApplicationVariable.key)) {
|
||||
validationResult.errors.push({
|
||||
code: ApplicationVariableEntityExceptionCode.INVALID_APPLICATION_VARIABLE_INPUT,
|
||||
message: t`Application variable key is required`,
|
||||
userFriendlyMessage: msg`Application variable key is required`,
|
||||
});
|
||||
}
|
||||
|
||||
const existingVariableWithSameKey = Object.values(
|
||||
optimisticFlatApplicationVariableMaps.byUniversalIdentifier,
|
||||
).find(
|
||||
(variable) =>
|
||||
isDefined(variable) &&
|
||||
variable.key === flatApplicationVariable.key &&
|
||||
variable.universalIdentifier !==
|
||||
flatApplicationVariable.universalIdentifier,
|
||||
);
|
||||
|
||||
if (isDefined(existingVariableWithSameKey)) {
|
||||
validationResult.errors.push({
|
||||
code: ApplicationVariableEntityExceptionCode.INVALID_APPLICATION_VARIABLE_INPUT,
|
||||
message: t`Application variable key must be unique`,
|
||||
userFriendlyMessage: msg`Application variable key must be unique`,
|
||||
});
|
||||
}
|
||||
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
public validateFlatApplicationVariableDeletion({
|
||||
flatEntityToValidate,
|
||||
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
|
||||
flatApplicationVariableMaps: optimisticFlatApplicationVariableMaps,
|
||||
},
|
||||
}: UniversalFlatEntityValidationArgs<
|
||||
typeof ALL_METADATA_NAME.applicationVariable
|
||||
>): FailedFlatEntityValidation<'applicationVariable', 'delete'> {
|
||||
const validationResult = getEmptyFlatEntityValidationError({
|
||||
flatEntityMinimalInformation: {
|
||||
universalIdentifier: flatEntityToValidate.universalIdentifier,
|
||||
key: flatEntityToValidate.key,
|
||||
},
|
||||
metadataName: 'applicationVariable',
|
||||
type: 'delete',
|
||||
});
|
||||
|
||||
const existingVariable = findFlatEntityByUniversalIdentifier({
|
||||
universalIdentifier: flatEntityToValidate.universalIdentifier,
|
||||
flatEntityMaps: optimisticFlatApplicationVariableMaps,
|
||||
});
|
||||
|
||||
if (!isDefined(existingVariable)) {
|
||||
validationResult.errors.push({
|
||||
code: ApplicationVariableEntityExceptionCode.APPLICATION_VARIABLE_NOT_FOUND,
|
||||
message: t`Application variable not found`,
|
||||
userFriendlyMessage: msg`Application variable not found`,
|
||||
});
|
||||
}
|
||||
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
public validateFlatApplicationVariableUpdate({
|
||||
universalIdentifier,
|
||||
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
|
||||
flatApplicationVariableMaps: optimisticFlatApplicationVariableMaps,
|
||||
},
|
||||
}: FlatEntityUpdateValidationArgs<
|
||||
typeof ALL_METADATA_NAME.applicationVariable
|
||||
>): FailedFlatEntityValidation<'applicationVariable', 'update'> {
|
||||
const fromFlatApplicationVariable = findFlatEntityByUniversalIdentifier({
|
||||
universalIdentifier,
|
||||
flatEntityMaps: optimisticFlatApplicationVariableMaps,
|
||||
});
|
||||
|
||||
const validationResult = getEmptyFlatEntityValidationError({
|
||||
flatEntityMinimalInformation: {
|
||||
universalIdentifier,
|
||||
},
|
||||
metadataName: 'applicationVariable',
|
||||
type: 'update',
|
||||
});
|
||||
|
||||
if (!isDefined(fromFlatApplicationVariable)) {
|
||||
validationResult.errors.push({
|
||||
code: ApplicationVariableEntityExceptionCode.APPLICATION_VARIABLE_NOT_FOUND,
|
||||
message: t`Application variable not found`,
|
||||
userFriendlyMessage: msg`Application variable not found`,
|
||||
});
|
||||
}
|
||||
|
||||
return validationResult;
|
||||
}
|
||||
}
|
||||
+3
@@ -29,6 +29,7 @@ import { FlatViewFilterValidatorService } from 'src/engine/workspace-manager/wor
|
||||
import { FlatViewGroupValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-group-validator.service';
|
||||
import { FlatViewSortValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-sort-validator.service';
|
||||
import { FlatViewValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-validator.service';
|
||||
import { FlatApplicationVariableValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-application-variable-validator.service';
|
||||
import { FlatWebhookValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-webhook-validator.service';
|
||||
import { FlatConnectionProviderValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-connection-provider-validator.service';
|
||||
|
||||
@@ -64,6 +65,7 @@ import { FlatConnectionProviderValidatorService } from 'src/engine/workspace-man
|
||||
FlatRowLevelPermissionPredicateGroupValidatorService,
|
||||
FlatFrontComponentValidatorService,
|
||||
FlatWebhookValidatorService,
|
||||
FlatApplicationVariableValidatorService,
|
||||
FlatConnectionProviderValidatorService,
|
||||
],
|
||||
exports: [
|
||||
@@ -95,6 +97,7 @@ import { FlatConnectionProviderValidatorService } from 'src/engine/workspace-man
|
||||
FlatRowLevelPermissionPredicateGroupValidatorService,
|
||||
FlatFrontComponentValidatorService,
|
||||
FlatWebhookValidatorService,
|
||||
FlatApplicationVariableValidatorService,
|
||||
FlatConnectionProviderValidatorService,
|
||||
],
|
||||
})
|
||||
|
||||
+3
@@ -28,6 +28,7 @@ import { WorkspaceMigrationViewFilterActionsBuilderService } from 'src/engine/wo
|
||||
import { WorkspaceMigrationViewGroupActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-group/workspace-migration-view-group-actions-builder.service';
|
||||
import { WorkspaceMigrationViewSortActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-sort/workspace-migration-view-sort-actions.builder.service';
|
||||
import { WorkspaceMigrationViewActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view/workspace-migration-view-actions-builder.service';
|
||||
import { WorkspaceMigrationApplicationVariableActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/application-variable/workspace-migration-application-variable-actions-builder.service';
|
||||
import { WorkspaceMigrationWebhookActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/webhook/workspace-migration-webhook-actions-builder.service';
|
||||
import { WorkspaceMigrationConnectionProviderActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/connection-provider/workspace-migration-connection-provider-actions-builder.service';
|
||||
import { WorkspaceMigrationBuilderValidatorsModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/workspace-migration-builder-validators.module';
|
||||
@@ -63,6 +64,7 @@ import { WorkspaceMigrationBuilderValidatorsModule } from 'src/engine/workspace-
|
||||
WorkspaceMigrationRowLevelPermissionPredicateGroupActionsBuilderService,
|
||||
WorkspaceMigrationFrontComponentActionsBuilderService,
|
||||
WorkspaceMigrationWebhookActionsBuilderService,
|
||||
WorkspaceMigrationApplicationVariableActionsBuilderService,
|
||||
WorkspaceMigrationConnectionProviderActionsBuilderService,
|
||||
],
|
||||
exports: [
|
||||
@@ -94,6 +96,7 @@ import { WorkspaceMigrationBuilderValidatorsModule } from 'src/engine/workspace-
|
||||
FlatFieldMetadataTypeValidatorService,
|
||||
WorkspaceMigrationFrontComponentActionsBuilderService,
|
||||
WorkspaceMigrationWebhookActionsBuilderService,
|
||||
WorkspaceMigrationApplicationVariableActionsBuilderService,
|
||||
WorkspaceMigrationConnectionProviderActionsBuilderService,
|
||||
],
|
||||
})
|
||||
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||
|
||||
import { getUniversalFlatEntityEmptyForeignKeyAggregators } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/reset-universal-flat-entity-foreign-key-aggregators.util';
|
||||
import {
|
||||
FlatCreateApplicationVariableAction,
|
||||
UniversalCreateApplicationVariableAction,
|
||||
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/application-variable/types/workspace-migration-application-variable-action.type';
|
||||
import {
|
||||
WorkspaceMigrationActionRunnerArgs,
|
||||
WorkspaceMigrationActionRunnerContext,
|
||||
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type';
|
||||
|
||||
@Injectable()
|
||||
export class CreateApplicationVariableActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
|
||||
'create',
|
||||
'applicationVariable',
|
||||
) {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
override async transpileUniversalActionToFlatAction({
|
||||
action,
|
||||
flatApplication,
|
||||
workspaceId,
|
||||
}: WorkspaceMigrationActionRunnerArgs<UniversalCreateApplicationVariableAction>): Promise<FlatCreateApplicationVariableAction> {
|
||||
const emptyUniversalForeignKeyAggregators =
|
||||
getUniversalFlatEntityEmptyForeignKeyAggregators({
|
||||
metadataName: 'applicationVariable',
|
||||
});
|
||||
|
||||
return {
|
||||
...action,
|
||||
flatEntity: {
|
||||
...action.flatEntity,
|
||||
applicationId: flatApplication.id,
|
||||
id: action.id ?? v4(),
|
||||
workspaceId,
|
||||
...emptyUniversalForeignKeyAggregators,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async executeForMetadata(
|
||||
context: WorkspaceMigrationActionRunnerContext<FlatCreateApplicationVariableAction>,
|
||||
): Promise<void> {
|
||||
const { flatAction, queryRunner } = context;
|
||||
const { flatEntity } = flatAction;
|
||||
|
||||
await this.insertFlatEntitiesInRepository({
|
||||
queryRunner,
|
||||
flatEntities: [flatEntity],
|
||||
});
|
||||
}
|
||||
|
||||
async executeForWorkspaceSchema(
|
||||
_context: WorkspaceMigrationActionRunnerContext<FlatCreateApplicationVariableAction>,
|
||||
): Promise<void> {
|
||||
return;
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||
|
||||
import { ApplicationVariableEntity } from 'src/engine/core-modules/application/application-variable/application-variable.entity';
|
||||
import {
|
||||
FlatDeleteApplicationVariableAction,
|
||||
UniversalDeleteApplicationVariableAction,
|
||||
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/application-variable/types/workspace-migration-application-variable-action.type';
|
||||
import {
|
||||
WorkspaceMigrationActionRunnerArgs,
|
||||
WorkspaceMigrationActionRunnerContext,
|
||||
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type';
|
||||
|
||||
@Injectable()
|
||||
export class DeleteApplicationVariableActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
|
||||
'delete',
|
||||
'applicationVariable',
|
||||
) {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
override async transpileUniversalActionToFlatAction(
|
||||
context: WorkspaceMigrationActionRunnerArgs<UniversalDeleteApplicationVariableAction>,
|
||||
): Promise<FlatDeleteApplicationVariableAction> {
|
||||
return this.transpileUniversalDeleteActionToFlatDeleteAction(context);
|
||||
}
|
||||
|
||||
async executeForMetadata(
|
||||
context: WorkspaceMigrationActionRunnerContext<FlatDeleteApplicationVariableAction>,
|
||||
): Promise<void> {
|
||||
const { flatAction, queryRunner, workspaceId } = context;
|
||||
|
||||
const applicationVariableRepository =
|
||||
queryRunner.manager.getRepository<ApplicationVariableEntity>(
|
||||
ApplicationVariableEntity,
|
||||
);
|
||||
|
||||
await applicationVariableRepository.delete({
|
||||
id: flatAction.entityId,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
async executeForWorkspaceSchema(
|
||||
_context: WorkspaceMigrationActionRunnerContext<FlatDeleteApplicationVariableAction>,
|
||||
): Promise<void> {
|
||||
return;
|
||||
}
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||
|
||||
import { ApplicationVariableEntity } from 'src/engine/core-modules/application/application-variable/application-variable.entity';
|
||||
import { SecretEncryptionService } from 'src/engine/core-modules/secret-encryption/secret-encryption.service';
|
||||
import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util';
|
||||
import { resolveUniversalUpdateRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-update-relation-identifiers-to-ids.util';
|
||||
import {
|
||||
FlatUpdateApplicationVariableAction,
|
||||
UniversalUpdateApplicationVariableAction,
|
||||
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/application-variable/types/workspace-migration-application-variable-action.type';
|
||||
import {
|
||||
WorkspaceMigrationActionRunnerArgs,
|
||||
WorkspaceMigrationActionRunnerContext,
|
||||
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type';
|
||||
|
||||
@Injectable()
|
||||
export class UpdateApplicationVariableActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
|
||||
'update',
|
||||
'applicationVariable',
|
||||
) {
|
||||
constructor(
|
||||
private readonly secretEncryptionService: SecretEncryptionService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
override async transpileUniversalActionToFlatAction(
|
||||
context: WorkspaceMigrationActionRunnerArgs<UniversalUpdateApplicationVariableAction>,
|
||||
): Promise<FlatUpdateApplicationVariableAction> {
|
||||
const { action, allFlatEntityMaps } = context;
|
||||
|
||||
const flatApplicationVariable = findFlatEntityByUniversalIdentifierOrThrow({
|
||||
flatEntityMaps: allFlatEntityMaps.flatApplicationVariableMaps,
|
||||
universalIdentifier: action.universalIdentifier,
|
||||
});
|
||||
|
||||
const update = resolveUniversalUpdateRelationIdentifiersToIds({
|
||||
metadataName: 'applicationVariable',
|
||||
universalUpdate: action.update,
|
||||
allFlatEntityMaps,
|
||||
});
|
||||
|
||||
return {
|
||||
type: 'update',
|
||||
metadataName: 'applicationVariable',
|
||||
entityId: flatApplicationVariable.id,
|
||||
update,
|
||||
};
|
||||
}
|
||||
|
||||
async executeForMetadata(
|
||||
context: WorkspaceMigrationActionRunnerContext<FlatUpdateApplicationVariableAction>,
|
||||
): Promise<void> {
|
||||
const { flatAction, queryRunner, workspaceId } = context;
|
||||
const { entityId, update } = flatAction;
|
||||
const applicationVariableRepository =
|
||||
queryRunner.manager.getRepository<ApplicationVariableEntity>(
|
||||
ApplicationVariableEntity,
|
||||
);
|
||||
|
||||
const existing = await applicationVariableRepository.findOne({
|
||||
where: { id: entityId, workspaceId },
|
||||
});
|
||||
|
||||
if (
|
||||
update.isSecret !== undefined &&
|
||||
update.isSecret &&
|
||||
existing &&
|
||||
!existing.isSecret
|
||||
) {
|
||||
(update as Record<string, unknown>).value =
|
||||
this.secretEncryptionService.encrypt(existing.value);
|
||||
}
|
||||
|
||||
if (
|
||||
update.isSecret !== undefined &&
|
||||
!update.isSecret &&
|
||||
existing &&
|
||||
existing.isSecret
|
||||
) {
|
||||
(update as Record<string, unknown>).value =
|
||||
this.secretEncryptionService.decrypt(existing.value);
|
||||
}
|
||||
|
||||
await applicationVariableRepository.update(
|
||||
{ id: entityId, workspaceId },
|
||||
update,
|
||||
);
|
||||
}
|
||||
|
||||
async executeForWorkspaceSchema(
|
||||
_context: WorkspaceMigrationActionRunnerContext<FlatUpdateApplicationVariableAction>,
|
||||
): Promise<void> {
|
||||
return;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -15,8 +15,8 @@ import {
|
||||
import { fromUniversalFlatFieldMetadataToFlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/field/services/utils/from-universal-flat-field-metadata-to-flat-field-metadata.util';
|
||||
import { fromUniversalFlatObjectMetadataToFlatObjectMetadata } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/object/services/utils/from-universal-flat-object-metadata-to-flat-object-metadata.util';
|
||||
import {
|
||||
WorkspaceMigrationActionRunnerContext,
|
||||
type WorkspaceMigrationActionRunnerArgs,
|
||||
WorkspaceMigrationActionRunnerContext,
|
||||
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type';
|
||||
import { flatEntityToScalarFlatEntity } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/flat-entity-to-scalar-flat-entity.util';
|
||||
import { generateColumnDefinitions } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/generate-column-definitions.util';
|
||||
@@ -41,7 +41,7 @@ export class CreateObjectActionHandlerService extends WorkspaceMigrationRunnerAc
|
||||
override async transpileUniversalActionToFlatAction(
|
||||
context: WorkspaceMigrationActionRunnerArgs<UniversalCreateObjectAction>,
|
||||
): Promise<FlatCreateObjectAction> {
|
||||
const { action, workspaceId, allFlatEntityMaps } = context;
|
||||
const { action, allFlatEntityMaps } = context;
|
||||
const { fieldIdByUniversalIdentifier, id: providedObjectId } = action;
|
||||
|
||||
const allFieldIdToBeCreatedInActionByUniversalIdentifierMap = new Map<
|
||||
|
||||
+10
@@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { SecretEncryptionModule } from 'src/engine/core-modules/secret-encryption/secret-encryption.module';
|
||||
import { WorkspaceSchemaManagerModule } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.module';
|
||||
import { CreateAgentActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/agent/services/create-agent-action-handler.service';
|
||||
import { DeleteAgentActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/agent/services/delete-agent-action-handler.service';
|
||||
@@ -81,6 +82,9 @@ import { UpdateViewSortActionHandlerService } from 'src/engine/workspace-manager
|
||||
import { CreateViewActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view/services/create-view-action-handler.service';
|
||||
import { DeleteViewActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view/services/delete-view-action-handler.service';
|
||||
import { UpdateViewActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view/services/update-view-action-handler.service';
|
||||
import { CreateApplicationVariableActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/application-variable/services/create-application-variable-action-handler.service';
|
||||
import { DeleteApplicationVariableActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/application-variable/services/delete-application-variable-action-handler.service';
|
||||
import { UpdateApplicationVariableActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/application-variable/services/update-application-variable-action-handler.service';
|
||||
import { CreateWebhookActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/webhook/services/create-webhook-action-handler.service';
|
||||
import { DeleteWebhookActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/webhook/services/delete-webhook-action-handler.service';
|
||||
import { UpdateWebhookActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/webhook/services/update-webhook-action-handler.service';
|
||||
@@ -92,6 +96,7 @@ import { UpdateConnectionProviderActionHandlerService } from 'src/engine/workspa
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([ApplicationEntity]),
|
||||
WorkspaceSchemaManagerModule,
|
||||
SecretEncryptionModule,
|
||||
],
|
||||
providers: [
|
||||
CreateFieldActionHandlerService,
|
||||
@@ -201,6 +206,11 @@ import { UpdateConnectionProviderActionHandlerService } from 'src/engine/workspa
|
||||
CreateWebhookActionHandlerService,
|
||||
UpdateWebhookActionHandlerService,
|
||||
DeleteWebhookActionHandlerService,
|
||||
|
||||
CreateApplicationVariableActionHandlerService,
|
||||
UpdateApplicationVariableActionHandlerService,
|
||||
DeleteApplicationVariableActionHandlerService,
|
||||
|
||||
CreateConnectionProviderActionHandlerService,
|
||||
UpdateConnectionProviderActionHandlerService,
|
||||
DeleteConnectionProviderActionHandlerService,
|
||||
|
||||
+1
@@ -28,5 +28,6 @@ export const METADATA_EVENTS_TO_EMIT = {
|
||||
logicFunction: true,
|
||||
viewSort: true,
|
||||
webhook: false,
|
||||
applicationVariable: false,
|
||||
connectionProvider: true,
|
||||
} as const satisfies { [P in AllMetadataName]: boolean };
|
||||
|
||||
+3
@@ -69,6 +69,9 @@ const deriveAllMetadataEventsFromCreateAction = (
|
||||
|
||||
return [objectEvent, ...fieldEvents];
|
||||
}
|
||||
case 'applicationVariable': {
|
||||
return [];
|
||||
}
|
||||
case 'view':
|
||||
case 'viewField':
|
||||
case 'viewGroup':
|
||||
|
||||
+3
@@ -31,6 +31,9 @@ const deriveAllMetadataEventsFromDeleteAction = ({
|
||||
allFlatEntityMaps,
|
||||
}: DeriveMetadataEventsFromDeleteActionArgs): MetadataEvent[] => {
|
||||
switch (flatAction.metadataName) {
|
||||
case 'applicationVariable': {
|
||||
return [];
|
||||
}
|
||||
case 'fieldMetadata':
|
||||
case 'objectMetadata':
|
||||
case 'view':
|
||||
|
||||
+3
@@ -110,6 +110,9 @@ const deriveAllMetadataEventsFromUpdateAction = ({
|
||||
|
||||
return [deleteIndexMetadataEvent, createIndexMetadataEvent];
|
||||
}
|
||||
case 'applicationVariable': {
|
||||
return [];
|
||||
}
|
||||
case 'fieldMetadata':
|
||||
case 'objectMetadata':
|
||||
case 'view':
|
||||
|
||||
+4
@@ -26,5 +26,9 @@ export const flatEntityToScalarFlatEntity = <T extends AllMetadataName>({
|
||||
result.applicationId = flatEntityRecord.applicationId;
|
||||
result.universalIdentifier = flatEntityRecord.universalIdentifier;
|
||||
|
||||
if (metadataName === 'applicationVariable' && result.isSecret === true) {
|
||||
result.value = '********';
|
||||
}
|
||||
|
||||
return result as ScalarFlatEntity<MetadataEntity<T>>;
|
||||
};
|
||||
|
||||
+1
@@ -72,6 +72,7 @@ export const optimisticallyApplyCreateActionOnAllFlatEntityMaps = ({
|
||||
case 'objectPermission':
|
||||
case 'fieldPermission':
|
||||
case 'webhook':
|
||||
case 'applicationVariable':
|
||||
case 'connectionProvider': {
|
||||
addFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrow({
|
||||
flatEntity: flatAction.flatEntity,
|
||||
|
||||
+1
@@ -44,6 +44,7 @@ export const optimisticallyApplyDeleteActionOnAllFlatEntityMaps = ({
|
||||
case 'objectPermission':
|
||||
case 'fieldPermission':
|
||||
case 'webhook':
|
||||
case 'applicationVariable':
|
||||
case 'connectionProvider': {
|
||||
const flatEntityToDelete = findFlatEntityByIdInFlatEntityMapsOrThrow<
|
||||
MetadataFlatEntity<typeof flatAction.metadataName>
|
||||
|
||||
+1
@@ -65,6 +65,7 @@ export const optimisticallyApplyUpdateActionOnAllFlatEntityMaps = ({
|
||||
case 'objectPermission':
|
||||
case 'fieldPermission':
|
||||
case 'webhook':
|
||||
case 'applicationVariable':
|
||||
case 'connectionProvider': {
|
||||
const flatEntityMapsKey = getMetadataFlatEntityMapsKey(
|
||||
flatAction.metadataName,
|
||||
|
||||
Reference in New Issue
Block a user