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
@@ -1,7 +1,6 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined, isPlainObject } from 'twenty-shared/utils';
|
||||
|
||||
import { type CompositeFieldGroupByDefinition } from 'src/engine/api/common/common-args-processors/group-by-arg-processor/types/composite-field-group-by-definition.type';
|
||||
import { isGroupByDateFieldDefinition } from 'src/engine/api/common/common-args-processors/group-by-arg-processor/utils/is-group-by-date-field-definition.util';
|
||||
import { isRelationNestedFieldSupportedInGroupBy } from 'src/engine/api/common/common-args-processors/group-by-arg-processor/utils/is-relation-nested-field-supported-in-group-by.util';
|
||||
import { validateSingleKeyForGroupByOrThrow } from 'src/engine/api/common/common-args-processors/group-by-arg-processor/utils/validate-single-key-for-group-by-or-throw.util';
|
||||
|
||||
@@ -11,10 +11,6 @@ import { CreateApiKeyInput } from 'src/engine/core-modules/api-key/dtos/create-a
|
||||
import { GetApiKeyInput } from 'src/engine/core-modules/api-key/dtos/get-api-key.input';
|
||||
import { RevokeApiKeyInput } from 'src/engine/core-modules/api-key/dtos/revoke-api-key.input';
|
||||
import { UpdateApiKeyInput } from 'src/engine/core-modules/api-key/dtos/update-api-key.input';
|
||||
import {
|
||||
ApiKeyException,
|
||||
ApiKeyExceptionCode,
|
||||
} from 'src/engine/core-modules/api-key/exceptions/api-key.exception';
|
||||
import { apiKeyGraphqlApiExceptionHandler } from 'src/engine/core-modules/api-key/utils/api-key-graphql-api-exception-handler.util';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
|
||||
-10
@@ -16,7 +16,6 @@ import { ApplicationService } from 'src/engine/core-modules/application/applicat
|
||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
import { buildFromToAllUniversalFlatEntityMaps } from 'src/engine/core-modules/application/application-manifest/utils/build-from-to-all-universal-flat-entity-maps.util';
|
||||
import { getApplicationSubAllFlatEntityMaps } from 'src/engine/core-modules/application/application-manifest/utils/get-application-sub-all-flat-entity-maps.util';
|
||||
import { ApplicationVariableEntityService } from 'src/engine/core-modules/application/application-variable/application-variable.service';
|
||||
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||
import { createEmptyAllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-all-flat-entity-maps.constant';
|
||||
import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util';
|
||||
@@ -32,7 +31,6 @@ export class ApplicationSyncService {
|
||||
|
||||
constructor(
|
||||
private readonly applicationService: ApplicationService,
|
||||
private readonly applicationVariableService: ApplicationVariableEntityService,
|
||||
private readonly applicationManifestMigrationService: ApplicationManifestMigrationService,
|
||||
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
|
||||
private readonly workspaceCacheService: WorkspaceCacheService,
|
||||
@@ -145,14 +143,6 @@ export class ApplicationSyncService {
|
||||
);
|
||||
}
|
||||
|
||||
await this.applicationVariableService.upsertManyApplicationVariableEntities(
|
||||
{
|
||||
applicationVariables: manifest.application.applicationVariables,
|
||||
applicationId: application.id,
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
const resolvedRegistrationId =
|
||||
applicationRegistrationId ?? application.applicationRegistrationId;
|
||||
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
import { type UniversalFlatApplicationVariable } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-application-variable.type';
|
||||
|
||||
export const fromApplicationVariableManifestToUniversalFlatApplicationVariable =
|
||||
({
|
||||
key,
|
||||
universalIdentifier,
|
||||
description,
|
||||
value,
|
||||
isSecret,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}: {
|
||||
key: string;
|
||||
universalIdentifier: string;
|
||||
description?: string;
|
||||
value?: string;
|
||||
isSecret?: boolean;
|
||||
applicationUniversalIdentifier: string;
|
||||
now: string;
|
||||
}): UniversalFlatApplicationVariable => {
|
||||
return {
|
||||
universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
key,
|
||||
value: isSecret ? '' : (value ?? ''), // We protect secret variable by not syncing its value at all
|
||||
description: description ?? '',
|
||||
isSecret: isSecret ?? false,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
};
|
||||
+23
@@ -4,6 +4,7 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { generateIndexForFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/generate-index-for-flat-field-metadata.util';
|
||||
|
||||
import { fromApplicationVariableManifestToUniversalFlatApplicationVariable } from 'src/engine/core-modules/application/application-manifest/converters/from-application-variable-manifest-to-universal-flat-application-variable.util';
|
||||
import { fromCommandMenuItemManifestToUniversalFlatCommandMenuItem } from 'src/engine/core-modules/application/application-manifest/converters/from-command-menu-item-manifest-to-universal-flat-command-menu-item.util';
|
||||
import { fromConnectionProviderManifestToUniversalFlatConnectionProvider } from 'src/engine/core-modules/application/application-manifest/converters/from-connection-provider-manifest-to-universal-flat-connection-provider.util';
|
||||
import { fromFieldManifestToUniversalFlatFieldMetadata } from 'src/engine/core-modules/application/application-manifest/converters/from-field-manifest-to-universal-flat-field-metadata.util';
|
||||
@@ -431,6 +432,28 @@ export const computeApplicationManifestAllUniversalFlatEntityMaps = ({
|
||||
}
|
||||
}
|
||||
|
||||
for (const [key, applicationVariableManifest] of Object.entries(
|
||||
manifest.application.applicationVariables ?? {},
|
||||
)) {
|
||||
addUniversalFlatEntityToUniversalFlatEntityMapsThroughMutationOrThrow({
|
||||
universalFlatEntity:
|
||||
fromApplicationVariableManifestToUniversalFlatApplicationVariable({
|
||||
key,
|
||||
universalIdentifier: applicationVariableManifest.universalIdentifier,
|
||||
value:
|
||||
'value' in applicationVariableManifest
|
||||
? applicationVariableManifest.value
|
||||
: undefined,
|
||||
description: applicationVariableManifest.description,
|
||||
isSecret: applicationVariableManifest.isSecret,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityMapsToMutate:
|
||||
allUniversalFlatEntityMaps.flatApplicationVariableMaps,
|
||||
});
|
||||
}
|
||||
|
||||
for (const commandMenuItemManifest of manifest.commandMenuItems ?? []) {
|
||||
if (!isDefined(commandMenuItemManifest.frontComponentUniversalIdentifier)) {
|
||||
throw new Error(
|
||||
|
||||
-1
@@ -32,7 +32,6 @@ import { RotateClientSecretDTO } from 'src/engine/core-modules/application/appli
|
||||
import { TransferApplicationRegistrationOwnershipInput } from 'src/engine/core-modules/application/application-registration/dtos/transfer-application-registration-ownership.input';
|
||||
import { UpdateApplicationRegistrationInput } from 'src/engine/core-modules/application/application-registration/dtos/update-application-registration.input';
|
||||
import { ApplicationRegistrationSourceType } from 'src/engine/core-modules/application/application-registration/enums/application-registration-source-type.enum';
|
||||
import { DomainServerConfigService } from 'src/engine/core-modules/domain/domain-server-config/services/domain-server-config.service';
|
||||
import { AuthGraphqlApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-graphql-api-exception.filter';
|
||||
import { FileUrlService } from 'src/engine/core-modules/file/file-url/file-url.service';
|
||||
import { PreventNestToAutoLogGraphqlErrorsFilter } from 'src/engine/core-modules/graphql/filters/prevent-nest-to-auto-log-graphql-errors.filter';
|
||||
|
||||
-214
@@ -159,220 +159,6 @@ describe('ApplicationVariableEntityService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('upsertManyApplicationVariableEntities', () => {
|
||||
it('should encrypt secret values when creating new variables', async () => {
|
||||
repository.find.mockResolvedValue([]);
|
||||
repository.save.mockResolvedValue({} as any);
|
||||
repository.delete.mockResolvedValue({ affected: 0 } as any);
|
||||
|
||||
await service.upsertManyApplicationVariableEntities({
|
||||
applicationVariables: {
|
||||
SECRET_KEY: {
|
||||
universalIdentifier: 'secret-key-123',
|
||||
value: 'my-secret',
|
||||
description: 'A secret key',
|
||||
isSecret: true,
|
||||
},
|
||||
},
|
||||
applicationId: mockApplicationId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
});
|
||||
|
||||
expect(secretEncryptionService.encrypt).toHaveBeenCalledWith('my-secret');
|
||||
expect(repository.save).toHaveBeenCalledWith([
|
||||
{
|
||||
key: 'SECRET_KEY',
|
||||
value: 'encrypted_my-secret',
|
||||
description: 'A secret key',
|
||||
isSecret: true,
|
||||
applicationId: mockApplicationId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not encrypt non-secret values when creating new variables', async () => {
|
||||
repository.find.mockResolvedValue([]);
|
||||
repository.save.mockResolvedValue({} as any);
|
||||
repository.delete.mockResolvedValue({ affected: 0 } as any);
|
||||
|
||||
await service.upsertManyApplicationVariableEntities({
|
||||
applicationVariables: {
|
||||
PUBLIC_URL: {
|
||||
universalIdentifier: 'public-url-123',
|
||||
value: 'https://example.com',
|
||||
description: 'Public URL',
|
||||
isSecret: false,
|
||||
},
|
||||
},
|
||||
applicationId: mockApplicationId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
});
|
||||
|
||||
expect(secretEncryptionService.encrypt).not.toHaveBeenCalled();
|
||||
expect(repository.save).toHaveBeenCalledWith([
|
||||
{
|
||||
key: 'PUBLIC_URL',
|
||||
value: 'https://example.com',
|
||||
description: 'Public URL',
|
||||
isSecret: false,
|
||||
applicationId: mockApplicationId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle undefined isSecret as false', async () => {
|
||||
repository.find.mockResolvedValue([]);
|
||||
repository.save.mockResolvedValue({} as any);
|
||||
repository.delete.mockResolvedValue({ affected: 0 } as any);
|
||||
|
||||
await service.upsertManyApplicationVariableEntities({
|
||||
applicationVariables: {
|
||||
SOME_VAR: {
|
||||
universalIdentifier: 'some-var-123',
|
||||
value: 'some-value',
|
||||
description: 'Some variable',
|
||||
},
|
||||
},
|
||||
applicationId: mockApplicationId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
});
|
||||
|
||||
expect(secretEncryptionService.encrypt).not.toHaveBeenCalled();
|
||||
expect(repository.save).toHaveBeenCalledWith([
|
||||
expect.objectContaining({
|
||||
isSecret: false,
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should update existing variables without changing values', async () => {
|
||||
const existingVariable = {
|
||||
id: '1',
|
||||
key: 'EXISTING_VAR',
|
||||
value: 'existing-encrypted-value',
|
||||
isSecret: true,
|
||||
applicationId: mockApplicationId,
|
||||
} as ApplicationVariableEntity;
|
||||
|
||||
repository.find.mockResolvedValue([existingVariable]);
|
||||
repository.save.mockResolvedValue({} as any);
|
||||
repository.delete.mockResolvedValue({ affected: 0 } as any);
|
||||
|
||||
await service.upsertManyApplicationVariableEntities({
|
||||
applicationVariables: {
|
||||
EXISTING_VAR: {
|
||||
universalIdentifier: 'existing-var-123',
|
||||
value: 'new-value',
|
||||
description: 'Updated description',
|
||||
isSecret: true,
|
||||
},
|
||||
},
|
||||
applicationId: mockApplicationId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
});
|
||||
|
||||
expect(repository.save).toHaveBeenCalledWith([
|
||||
{
|
||||
id: '1',
|
||||
description: 'Updated description',
|
||||
isSecret: true,
|
||||
},
|
||||
]);
|
||||
expect(repository.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should update existing value when shouldUpdateValue is true', async () => {
|
||||
const existingVariable = {
|
||||
id: '1',
|
||||
key: 'EXISTING_VAR',
|
||||
value: 'existing-encrypted-value',
|
||||
isSecret: true,
|
||||
applicationId: mockApplicationId,
|
||||
} as ApplicationVariableEntity;
|
||||
|
||||
repository.find.mockResolvedValue([existingVariable]);
|
||||
repository.save.mockResolvedValue({} as any);
|
||||
repository.delete.mockResolvedValue({ affected: 0 } as any);
|
||||
|
||||
await service.upsertManyApplicationVariableEntities({
|
||||
applicationVariables: {
|
||||
EXISTING_VAR: {
|
||||
universalIdentifier: 'existing-var-123',
|
||||
value: 'new-value',
|
||||
description: 'Updated description',
|
||||
isSecret: true,
|
||||
},
|
||||
},
|
||||
applicationId: mockApplicationId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
shouldUpdateValue: true,
|
||||
});
|
||||
|
||||
expect(repository.save).toHaveBeenCalledWith([
|
||||
{
|
||||
id: '1',
|
||||
description: 'Updated description',
|
||||
value: 'encrypted_new-value',
|
||||
isSecret: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should update existing value if isSecret changes', async () => {
|
||||
const existingVariable = {
|
||||
id: '1',
|
||||
key: 'EXISTING_VAR',
|
||||
value: 'existing-encrypted-value',
|
||||
isSecret: true,
|
||||
applicationId: mockApplicationId,
|
||||
} as ApplicationVariableEntity;
|
||||
|
||||
repository.find.mockResolvedValue([existingVariable]);
|
||||
repository.save.mockResolvedValue({} as any);
|
||||
repository.delete.mockResolvedValue({ affected: 0 } as any);
|
||||
|
||||
await service.upsertManyApplicationVariableEntities({
|
||||
applicationVariables: {
|
||||
EXISTING_VAR: {
|
||||
universalIdentifier: 'existing-var-123',
|
||||
value: 'new-value',
|
||||
description: 'Updated description',
|
||||
isSecret: false,
|
||||
},
|
||||
},
|
||||
applicationId: mockApplicationId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
});
|
||||
|
||||
expect(repository.save).toHaveBeenCalledWith([
|
||||
{
|
||||
id: '1',
|
||||
description: 'Updated description',
|
||||
value: 'new-value',
|
||||
isSecret: false,
|
||||
},
|
||||
]);
|
||||
expect(repository.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle undefined applicationVariables', async () => {
|
||||
await service.upsertManyApplicationVariableEntities({
|
||||
applicationVariables: undefined,
|
||||
applicationId: mockApplicationId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
});
|
||||
|
||||
expect(repository.find).not.toHaveBeenCalled();
|
||||
expect(repository.save).not.toHaveBeenCalled();
|
||||
expect(repository.update).not.toHaveBeenCalled();
|
||||
expect(
|
||||
workspaceCacheService.invalidateAndRecompute,
|
||||
).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDisplayValue', () => {
|
||||
it('should return plain value for non-secret variables', () => {
|
||||
const variable = {
|
||||
|
||||
+6
-1
@@ -6,7 +6,10 @@ import {
|
||||
ApplicationVariableEntityException,
|
||||
ApplicationVariableEntityExceptionCode,
|
||||
} from 'src/engine/core-modules/application/application-variable/application-variable.exception';
|
||||
import { NotFoundError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
||||
import {
|
||||
NotFoundError,
|
||||
UserInputError,
|
||||
} from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
||||
|
||||
@Catch(ApplicationVariableEntityException)
|
||||
export class ApplicationVariableEntityExceptionFilter
|
||||
@@ -16,6 +19,8 @@ export class ApplicationVariableEntityExceptionFilter
|
||||
switch (exception.code) {
|
||||
case ApplicationVariableEntityExceptionCode.APPLICATION_VARIABLE_NOT_FOUND:
|
||||
throw new NotFoundError(exception);
|
||||
case ApplicationVariableEntityExceptionCode.INVALID_APPLICATION_VARIABLE_INPUT:
|
||||
throw new UserInputError(exception);
|
||||
default:
|
||||
assertUnreachable(exception.code);
|
||||
}
|
||||
|
||||
+2
-34
@@ -5,41 +5,23 @@ import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
Index,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
Unique,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import type { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { EntityRelation } from 'src/engine/workspace-manager/workspace-migration/types/entity-relation.interface';
|
||||
import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface';
|
||||
|
||||
@Entity({
|
||||
name: 'applicationVariable',
|
||||
schema: 'core',
|
||||
})
|
||||
@ObjectType('ApplicationVariable')
|
||||
@Unique('IDX_APPLICATION_VARIABLE_KEY_APPLICATION_ID_UNIQUE', [
|
||||
'key',
|
||||
'applicationId',
|
||||
])
|
||||
export class ApplicationVariableEntity {
|
||||
export class ApplicationVariableEntity extends SyncableEntity {
|
||||
@IDField(() => UUIDScalarType)
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ nullable: false, type: 'uuid' })
|
||||
@Index()
|
||||
workspaceId: string;
|
||||
|
||||
@ManyToOne('WorkspaceEntity', { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'workspaceId' })
|
||||
workspace: EntityRelation<WorkspaceEntity>;
|
||||
|
||||
@Column({ nullable: false, type: 'text' })
|
||||
key: string;
|
||||
|
||||
@@ -52,20 +34,6 @@ export class ApplicationVariableEntity {
|
||||
@Column({ nullable: false, type: 'boolean', default: false })
|
||||
isSecret: boolean;
|
||||
|
||||
@Column({ nullable: true, type: 'uuid' })
|
||||
applicationId?: string;
|
||||
|
||||
@ManyToOne(
|
||||
() => ApplicationEntity,
|
||||
(application) => application.applicationVariables,
|
||||
{
|
||||
onDelete: 'CASCADE',
|
||||
nullable: true,
|
||||
},
|
||||
)
|
||||
@JoinColumn({ name: 'applicationId' })
|
||||
application: EntityRelation<ApplicationEntity> | null;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamptz' })
|
||||
createdAt: Date;
|
||||
|
||||
|
||||
+3
@@ -6,6 +6,7 @@ import { CustomException } from 'src/utils/custom-exception';
|
||||
|
||||
export enum ApplicationVariableEntityExceptionCode {
|
||||
APPLICATION_VARIABLE_NOT_FOUND = 'APPLICATION_VARIABLE_NOT_FOUND',
|
||||
INVALID_APPLICATION_VARIABLE_INPUT = 'INVALID_APPLICATION_VARIABLE_INPUT',
|
||||
}
|
||||
|
||||
const getApplicationVariableEntityExceptionUserFriendlyMessage = (
|
||||
@@ -14,6 +15,8 @@ const getApplicationVariableEntityExceptionUserFriendlyMessage = (
|
||||
switch (code) {
|
||||
case ApplicationVariableEntityExceptionCode.APPLICATION_VARIABLE_NOT_FOUND:
|
||||
return msg`Application variable not found.`;
|
||||
case ApplicationVariableEntityExceptionCode.INVALID_APPLICATION_VARIABLE_INPUT:
|
||||
return msg`Invalid application variable input.`;
|
||||
default:
|
||||
assertUnreachable(code);
|
||||
}
|
||||
|
||||
+4
-1
@@ -3,21 +3,24 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
|
||||
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { ApplicationVariableEntity } from 'src/engine/core-modules/application/application-variable/application-variable.entity';
|
||||
import { ApplicationVariableEntityResolver } from 'src/engine/core-modules/application/application-variable/application-variable.resolver';
|
||||
import { ApplicationVariableEntityService } from 'src/engine/core-modules/application/application-variable/application-variable.service';
|
||||
import { WorkspaceApplicationVariableMapCacheService } from 'src/engine/core-modules/application/application-variable/workspace-application-variable-map-cache.service';
|
||||
import { SecretEncryptionModule } from 'src/engine/core-modules/secret-encryption/secret-encryption.module';
|
||||
import { FlatApplicationVariableModule } from 'src/engine/metadata-modules/flat-application-variable/flat-application-variable.module';
|
||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
NestjsQueryTypeOrmModule.forFeature([ApplicationVariableEntity]),
|
||||
TypeOrmModule.forFeature([ApplicationVariableEntity]),
|
||||
TypeOrmModule.forFeature([ApplicationVariableEntity, ApplicationEntity]),
|
||||
PermissionsModule,
|
||||
WorkspaceCacheModule,
|
||||
SecretEncryptionModule,
|
||||
FlatApplicationVariableModule,
|
||||
],
|
||||
providers: [
|
||||
ApplicationVariableEntityService,
|
||||
|
||||
+4
-131
@@ -1,9 +1,8 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { ApplicationVariables } from 'twenty-shared/application';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { In, Not, Repository } from 'typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { ApplicationVariableEntity } from 'src/engine/core-modules/application/application-variable/application-variable.entity';
|
||||
import {
|
||||
@@ -23,14 +22,6 @@ export class ApplicationVariableEntityService {
|
||||
private readonly secretEncryptionService: SecretEncryptionService,
|
||||
) {}
|
||||
|
||||
private encryptSecretValue(value: string, isSecret: boolean): string {
|
||||
if (!isSecret) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return this.secretEncryptionService.encrypt(value);
|
||||
}
|
||||
|
||||
getDisplayValue(applicationVariable: ApplicationVariableEntity): string {
|
||||
if (!applicationVariable.isSecret) {
|
||||
return applicationVariable.value;
|
||||
@@ -42,48 +33,6 @@ export class ApplicationVariableEntityService {
|
||||
});
|
||||
}
|
||||
|
||||
// Decrypted plaintext value. Server-side only — never expose via GraphQL.
|
||||
// Used by trusted server flows that need the raw secret (e.g. exchanging an
|
||||
// OAuth client secret with a third-party provider).
|
||||
getRawValue(applicationVariable: ApplicationVariableEntity): string {
|
||||
if (!applicationVariable.isSecret) {
|
||||
return applicationVariable.value;
|
||||
}
|
||||
|
||||
return this.secretEncryptionService.decrypt(applicationVariable.value);
|
||||
}
|
||||
|
||||
async findOneByKey({
|
||||
applicationId,
|
||||
key,
|
||||
}: {
|
||||
applicationId: string;
|
||||
key: string;
|
||||
}): Promise<ApplicationVariableEntity | null> {
|
||||
return this.applicationVariableRepository.findOne({
|
||||
where: { applicationId, key },
|
||||
});
|
||||
}
|
||||
|
||||
async getRawValueByKeyOrThrow({
|
||||
applicationId,
|
||||
key,
|
||||
}: {
|
||||
applicationId: string;
|
||||
key: string;
|
||||
}): Promise<string> {
|
||||
const variable = await this.findOneByKey({ applicationId, key });
|
||||
|
||||
if (!isDefined(variable)) {
|
||||
throw new ApplicationVariableEntityException(
|
||||
`Application variable "${key}" not found for application ${applicationId}`,
|
||||
ApplicationVariableEntityExceptionCode.APPLICATION_VARIABLE_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return this.getRawValue(variable);
|
||||
}
|
||||
|
||||
async update({
|
||||
key,
|
||||
plainTextValue,
|
||||
@@ -105,10 +54,9 @@ export class ApplicationVariableEntityService {
|
||||
);
|
||||
}
|
||||
|
||||
const encryptedValue = this.encryptSecretValue(
|
||||
plainTextValue,
|
||||
existingVariable.isSecret,
|
||||
);
|
||||
const encryptedValue = existingVariable.isSecret
|
||||
? this.secretEncryptionService.encrypt(plainTextValue)
|
||||
: plainTextValue;
|
||||
|
||||
await this.applicationVariableRepository.update(
|
||||
{ key, applicationId },
|
||||
@@ -121,79 +69,4 @@ export class ApplicationVariableEntityService {
|
||||
'applicationVariableMaps',
|
||||
]);
|
||||
}
|
||||
|
||||
async upsertManyApplicationVariableEntities({
|
||||
applicationVariables,
|
||||
applicationId,
|
||||
workspaceId,
|
||||
shouldUpdateValue = false,
|
||||
}: {
|
||||
applicationVariables?: ApplicationVariables;
|
||||
applicationId: string;
|
||||
workspaceId: string;
|
||||
shouldUpdateValue?: boolean;
|
||||
}) {
|
||||
if (!isDefined(applicationVariables)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const keys = Object.keys(applicationVariables);
|
||||
|
||||
const existingVariables = await this.applicationVariableRepository.find({
|
||||
where: {
|
||||
applicationId,
|
||||
key: In(keys),
|
||||
},
|
||||
});
|
||||
|
||||
const existingVariablesByKey = new Map(
|
||||
existingVariables.map((variable) => [variable.key, variable]),
|
||||
);
|
||||
|
||||
const entitiesToSave: Partial<ApplicationVariableEntity>[] = [];
|
||||
|
||||
for (const [key, { value, description, isSecret }] of Object.entries(
|
||||
applicationVariables,
|
||||
)) {
|
||||
const existingVariable = existingVariablesByKey.get(key);
|
||||
const isSecretValue = isSecret ?? false;
|
||||
const encryptedValue = this.encryptSecretValue(
|
||||
value ?? '',
|
||||
isSecretValue,
|
||||
);
|
||||
|
||||
if (isDefined(existingVariable)) {
|
||||
entitiesToSave.push({
|
||||
id: existingVariable.id,
|
||||
description: description ?? '',
|
||||
isSecret: isSecretValue,
|
||||
...(shouldUpdateValue || existingVariable.isSecret !== isSecretValue
|
||||
? { value: encryptedValue }
|
||||
: {}),
|
||||
});
|
||||
} else {
|
||||
entitiesToSave.push({
|
||||
key,
|
||||
value: encryptedValue,
|
||||
description: description ?? '',
|
||||
isSecret: isSecretValue,
|
||||
applicationId,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (entitiesToSave.length > 0) {
|
||||
await this.applicationVariableRepository.save(entitiesToSave);
|
||||
}
|
||||
|
||||
await this.applicationVariableRepository.delete({
|
||||
applicationId,
|
||||
key: Not(In(keys)),
|
||||
});
|
||||
|
||||
await this.workspaceCacheService.invalidateAndRecompute(workspaceId, [
|
||||
'applicationVariableMaps',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
+4
-5
@@ -1,6 +1,5 @@
|
||||
import { type FlatApplicationVariable } from 'src/engine/core-modules/application/application-variable/types/flat-application-variable.type';
|
||||
import { type FlatApplicationVariable } from 'src/engine/metadata-modules/flat-application-variable/types/flat-application-variable.type';
|
||||
import type { FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
|
||||
export type ApplicationVariableCacheMaps = {
|
||||
byId: Partial<Record<string, FlatApplicationVariable>>;
|
||||
byApplicationId: Partial<Record<string, FlatApplicationVariable[]>>;
|
||||
};
|
||||
export type ApplicationVariableCacheMaps =
|
||||
FlatEntityMaps<FlatApplicationVariable>;
|
||||
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
import { type ApplicationVariableEntity } from 'src/engine/core-modules/application/application-variable/application-variable.entity';
|
||||
import { type FlatApplicationVariable } from 'src/engine/core-modules/application/application-variable/types/flat-application-variable.type';
|
||||
|
||||
export const fromApplicationVariableEntityToFlatApplicationVariable = (
|
||||
entity: ApplicationVariableEntity,
|
||||
): FlatApplicationVariable => ({
|
||||
id: entity.id,
|
||||
key: entity.key,
|
||||
value: entity.value,
|
||||
description: entity.description,
|
||||
isSecret: entity.isSecret,
|
||||
applicationId: entity.applicationId,
|
||||
workspaceId: entity.workspaceId,
|
||||
createdAt: entity.createdAt.toISOString(),
|
||||
updatedAt: entity.updatedAt.toISOString(),
|
||||
});
|
||||
+28
-34
@@ -2,14 +2,17 @@ import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { WorkspaceCacheProvider } from 'src/engine/workspace-cache/interfaces/workspace-cache-provider.service';
|
||||
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { ApplicationVariableEntity } from 'src/engine/core-modules/application/application-variable/application-variable.entity';
|
||||
import { type ApplicationVariableCacheMaps } from 'src/engine/core-modules/application/application-variable/types/application-variable-cache-maps.type';
|
||||
import { fromApplicationVariableEntityToFlatApplicationVariable } from 'src/engine/core-modules/application/application-variable/utils/from-application-variable-entity-to-flat-application-variable.util';
|
||||
import { fromApplicationVariableEntityToFlatApplicationVariable } from 'src/engine/metadata-modules/flat-application-variable/utils/from-application-variable-entity-to-flat-application-variable.util';
|
||||
import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator';
|
||||
import { createIdToUniversalIdentifierMap } from 'src/engine/workspace-cache/utils/create-id-to-universal-identifier-map.util';
|
||||
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
|
||||
import { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util';
|
||||
|
||||
@Injectable()
|
||||
@WorkspaceCache('applicationVariableMaps')
|
||||
@@ -17,6 +20,8 @@ export class WorkspaceApplicationVariableMapCacheService extends WorkspaceCacheP
|
||||
constructor(
|
||||
@InjectRepository(ApplicationVariableEntity)
|
||||
private readonly applicationVariableRepository: Repository<ApplicationVariableEntity>,
|
||||
@InjectRepository(ApplicationEntity)
|
||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -24,43 +29,32 @@ export class WorkspaceApplicationVariableMapCacheService extends WorkspaceCacheP
|
||||
async computeForCache(
|
||||
workspaceId: string,
|
||||
): Promise<ApplicationVariableCacheMaps> {
|
||||
const applicationVariableEntities = await this.applicationVariableRepository
|
||||
.createQueryBuilder('applicationVariable')
|
||||
.innerJoin('applicationVariable.application', 'application')
|
||||
.where('application.workspaceId = :workspaceId', { workspaceId })
|
||||
.getMany();
|
||||
const [applicationVariableEntities, applications] = await Promise.all([
|
||||
this.applicationVariableRepository.find({
|
||||
where: { workspaceId },
|
||||
}),
|
||||
this.applicationRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
}),
|
||||
]);
|
||||
|
||||
const applicationVariableMaps: ApplicationVariableCacheMaps = {
|
||||
byId: {},
|
||||
byApplicationId: {},
|
||||
};
|
||||
const applicationIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(applications);
|
||||
|
||||
const applicationVariableMaps = createEmptyFlatEntityMaps();
|
||||
|
||||
for (const entity of applicationVariableEntities) {
|
||||
const flatApplicationVariable =
|
||||
fromApplicationVariableEntityToFlatApplicationVariable(entity);
|
||||
fromApplicationVariableEntityToFlatApplicationVariable({
|
||||
entity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
});
|
||||
|
||||
applicationVariableMaps.byId[flatApplicationVariable.id] =
|
||||
flatApplicationVariable;
|
||||
|
||||
if (!isDefined(flatApplicationVariable.applicationId)) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
!isDefined(
|
||||
applicationVariableMaps.byApplicationId[
|
||||
flatApplicationVariable.applicationId
|
||||
],
|
||||
)
|
||||
) {
|
||||
applicationVariableMaps.byApplicationId[
|
||||
flatApplicationVariable.applicationId
|
||||
] = [flatApplicationVariable];
|
||||
continue;
|
||||
}
|
||||
|
||||
applicationVariableMaps.byApplicationId[
|
||||
flatApplicationVariable.applicationId
|
||||
]?.push(flatApplicationVariable);
|
||||
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
|
||||
flatEntity: flatApplicationVariable,
|
||||
flatEntityMapsToMutate: applicationVariableMaps,
|
||||
});
|
||||
}
|
||||
|
||||
return applicationVariableMaps;
|
||||
|
||||
+12
-3
@@ -19,7 +19,7 @@ import {
|
||||
import { ApplicationLogsService } from 'src/engine/core-modules/application-logs/application-logs.service';
|
||||
import { parseApplicationLogLines } from 'src/engine/core-modules/application-logs/utils/parse-application-log-lines';
|
||||
import { ApplicationRegistrationVariableEntity } from 'src/engine/core-modules/application/application-registration-variable/application-registration-variable.entity';
|
||||
import type { FlatApplicationVariable } from 'src/engine/core-modules/application/application-variable/types/flat-application-variable.type';
|
||||
import type { FlatApplicationVariable } from 'src/engine/metadata-modules/flat-application-variable/types/flat-application-variable.type';
|
||||
import { FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
import { AuditService } from 'src/engine/core-modules/audit/services/audit.service';
|
||||
import { LOGIC_FUNCTION_EXECUTED_EVENT } from 'src/engine/core-modules/audit/utils/events/workspace-event/logic-function/logic-function-executed';
|
||||
@@ -205,8 +205,17 @@ export class LogicFunctionExecutorService {
|
||||
);
|
||||
}
|
||||
|
||||
const flatApplicationVariables =
|
||||
applicationVariableMaps.byApplicationId[flatApplication.id] ?? [];
|
||||
const flatApplicationVariableUniversalIdentifiers =
|
||||
applicationVariableMaps.universalIdentifiersByApplicationId[
|
||||
flatApplication.id
|
||||
] ?? [];
|
||||
|
||||
const flatApplicationVariables = flatApplicationVariableUniversalIdentifiers
|
||||
.map(
|
||||
(universalIdentifier) =>
|
||||
applicationVariableMaps.byUniversalIdentifier[universalIdentifier],
|
||||
)
|
||||
.filter(isDefined);
|
||||
|
||||
return { flatApplication, flatLogicFunction, flatApplicationVariables };
|
||||
}
|
||||
|
||||
+13
-1
@@ -1,4 +1,4 @@
|
||||
import { type FlatApplicationVariable } from 'src/engine/core-modules/application/application-variable/types/flat-application-variable.type';
|
||||
import { type FlatApplicationVariable } from 'src/engine/metadata-modules/flat-application-variable/types/flat-application-variable.type';
|
||||
import { type SecretEncryptionService } from 'src/engine/core-modules/secret-encryption/secret-encryption.service';
|
||||
import { buildEnvVar } from 'src/engine/core-modules/logic-function/logic-function-executor/utils/build-env-var';
|
||||
|
||||
@@ -28,6 +28,8 @@ describe('buildEnvVar', () => {
|
||||
isSecret: false,
|
||||
applicationId: 'app-1',
|
||||
workspaceId: '00000000-0000-0000-0000-000000000000',
|
||||
universalIdentifier: '00000000-0000-0000-0000-000000000000',
|
||||
applicationUniversalIdentifier: '00000000-0000-0000-0000-000000000000',
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
updatedAt: '2024-01-01T00:00:00.000Z',
|
||||
},
|
||||
@@ -39,6 +41,8 @@ describe('buildEnvVar', () => {
|
||||
isSecret: true,
|
||||
applicationId: 'app-1',
|
||||
workspaceId: '00000000-0000-0000-0000-000000000000',
|
||||
universalIdentifier: '00000000-0000-0000-0000-000000000000',
|
||||
applicationUniversalIdentifier: '00000000-0000-0000-0000-000000000000',
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
updatedAt: '2024-01-01T00:00:00.000Z',
|
||||
},
|
||||
@@ -50,6 +54,8 @@ describe('buildEnvVar', () => {
|
||||
isSecret: false,
|
||||
applicationId: 'app-1',
|
||||
workspaceId: '00000000-0000-0000-0000-000000000000',
|
||||
universalIdentifier: '00000000-0000-0000-0000-000000000000',
|
||||
applicationUniversalIdentifier: '00000000-0000-0000-0000-000000000000',
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
updatedAt: '2024-01-01T00:00:00.000Z',
|
||||
},
|
||||
@@ -78,6 +84,8 @@ describe('buildEnvVar', () => {
|
||||
isSecret: false,
|
||||
applicationId: 'app-1',
|
||||
workspaceId: '00000000-0000-0000-0000-000000000000',
|
||||
universalIdentifier: '00000000-0000-0000-0000-000000000000',
|
||||
applicationUniversalIdentifier: '00000000-0000-0000-0000-000000000000',
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
updatedAt: '2024-01-01T00:00:00.000Z',
|
||||
},
|
||||
@@ -89,6 +97,8 @@ describe('buildEnvVar', () => {
|
||||
isSecret: false,
|
||||
applicationId: 'app-1',
|
||||
workspaceId: '00000000-0000-0000-0000-000000000000',
|
||||
universalIdentifier: '00000000-0000-0000-0000-000000000000',
|
||||
applicationUniversalIdentifier: '00000000-0000-0000-0000-000000000000',
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
updatedAt: '2024-01-01T00:00:00.000Z',
|
||||
},
|
||||
@@ -112,6 +122,8 @@ describe('buildEnvVar', () => {
|
||||
isSecret: false,
|
||||
applicationId: 'app-1',
|
||||
workspaceId: '00000000-0000-0000-0000-000000000000',
|
||||
universalIdentifier: '00000000-0000-0000-0000-000000000000',
|
||||
applicationUniversalIdentifier: '00000000-0000-0000-0000-000000000000',
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
updatedAt: '2024-01-01T00:00:00.000Z',
|
||||
},
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { type FlatApplicationVariable } from 'src/engine/core-modules/application/application-variable/types/flat-application-variable.type';
|
||||
import { type FlatApplicationVariable } from 'src/engine/metadata-modules/flat-application-variable/types/flat-application-variable.type';
|
||||
import { type SecretEncryptionService } from 'src/engine/core-modules/secret-encryption/secret-encryption.service';
|
||||
|
||||
export const buildEnvVar = (
|
||||
|
||||
+1
@@ -102,6 +102,7 @@ export class UpgradeStatusCommand extends CommandRunner {
|
||||
...this.formatSummary(instanceStatus, groupedWorkspaceUpgradeStatuses),
|
||||
);
|
||||
|
||||
// oxlint-disable-next-line no-console
|
||||
console.log(lines.join('\n'));
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import { type MetadataEntityPropertyName } from 'src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant';
|
||||
|
||||
export const FLAT_APPLICATION_VARIABLE_EDITABLE_PROPERTIES = [
|
||||
'key',
|
||||
'description',
|
||||
'isSecret',
|
||||
] as const satisfies MetadataEntityPropertyName<'applicationVariable'>[];
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { ApplicationVariableEntity } from 'src/engine/core-modules/application/application-variable/application-variable.entity';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
|
||||
import { WorkspaceFlatApplicationVariableMapCacheService } from 'src/engine/metadata-modules/flat-application-variable/services/workspace-flat-application-variable-map-cache.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([ApplicationEntity, ApplicationVariableEntity]),
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheModule,
|
||||
],
|
||||
providers: [WorkspaceFlatApplicationVariableMapCacheService],
|
||||
exports: [WorkspaceFlatApplicationVariableMapCacheService],
|
||||
})
|
||||
export class FlatApplicationVariableModule {}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { WorkspaceCacheProvider } from 'src/engine/workspace-cache/interfaces/workspace-cache-provider.service';
|
||||
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { ApplicationVariableEntity } from 'src/engine/core-modules/application/application-variable/application-variable.entity';
|
||||
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
|
||||
import { type FlatApplicationVariableMaps } from 'src/engine/metadata-modules/flat-application-variable/types/flat-application-variable-maps.type';
|
||||
import { fromApplicationVariableEntityToFlatApplicationVariable } from 'src/engine/metadata-modules/flat-application-variable/utils/from-application-variable-entity-to-flat-application-variable.util';
|
||||
import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator';
|
||||
import { createIdToUniversalIdentifierMap } from 'src/engine/workspace-cache/utils/create-id-to-universal-identifier-map.util';
|
||||
import { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util';
|
||||
|
||||
@Injectable()
|
||||
@WorkspaceCache('flatApplicationVariableMaps')
|
||||
export class WorkspaceFlatApplicationVariableMapCacheService extends WorkspaceCacheProvider<FlatApplicationVariableMaps> {
|
||||
constructor(
|
||||
@InjectRepository(ApplicationVariableEntity)
|
||||
private readonly applicationVariableRepository: Repository<ApplicationVariableEntity>,
|
||||
@InjectRepository(ApplicationEntity)
|
||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async computeForCache(
|
||||
workspaceId: string,
|
||||
): Promise<FlatApplicationVariableMaps> {
|
||||
const [applicationVariables, applications] = await Promise.all([
|
||||
this.applicationVariableRepository.find({
|
||||
where: { workspaceId },
|
||||
}),
|
||||
this.applicationRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
}),
|
||||
]);
|
||||
|
||||
const applicationIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(applications);
|
||||
|
||||
const flatApplicationVariableMaps = createEmptyFlatEntityMaps();
|
||||
|
||||
for (const applicationVariableEntity of applicationVariables) {
|
||||
const flatApplicationVariable =
|
||||
fromApplicationVariableEntityToFlatApplicationVariable({
|
||||
entity: applicationVariableEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
});
|
||||
|
||||
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
|
||||
flatEntity: flatApplicationVariable,
|
||||
flatEntityMapsToMutate: flatApplicationVariableMaps,
|
||||
});
|
||||
}
|
||||
|
||||
return flatApplicationVariableMaps;
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type FlatApplicationVariable } from 'src/engine/metadata-modules/flat-application-variable/types/flat-application-variable.type';
|
||||
|
||||
export type FlatApplicationVariableMaps =
|
||||
FlatEntityMaps<FlatApplicationVariable>;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { type ApplicationVariableEntity } from 'src/engine/core-modules/application/application-variable/application-variable.entity';
|
||||
import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from.type';
|
||||
import { type ApplicationVariableEntity } from 'src/engine/core-modules/application/application-variable/application-variable.entity';
|
||||
|
||||
export type FlatApplicationVariable = FlatEntityFrom<ApplicationVariableEntity>;
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
FlatEntityMapsException,
|
||||
FlatEntityMapsExceptionCode,
|
||||
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
|
||||
import { type FlatApplicationVariable } from 'src/engine/metadata-modules/flat-application-variable/types/flat-application-variable.type';
|
||||
import { type FromEntityToFlatEntityArgs } from 'src/engine/workspace-cache/types/from-entity-to-flat-entity-args.type';
|
||||
|
||||
export const fromApplicationVariableEntityToFlatApplicationVariable = ({
|
||||
entity: applicationVariableEntity,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
}: FromEntityToFlatEntityArgs<'applicationVariable'>): FlatApplicationVariable => {
|
||||
const applicationUniversalIdentifier =
|
||||
applicationIdToUniversalIdentifierMap.get(
|
||||
applicationVariableEntity.applicationId,
|
||||
);
|
||||
|
||||
if (!isDefined(applicationUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`Application with id ${applicationVariableEntity.applicationId} not found for applicationVariable ${applicationVariableEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
id: applicationVariableEntity.id,
|
||||
key: applicationVariableEntity.key,
|
||||
value: applicationVariableEntity.value,
|
||||
description: applicationVariableEntity.description,
|
||||
isSecret: applicationVariableEntity.isSecret,
|
||||
workspaceId: applicationVariableEntity.workspaceId,
|
||||
universalIdentifier: applicationVariableEntity.universalIdentifier,
|
||||
applicationId: applicationVariableEntity.applicationId,
|
||||
createdAt: applicationVariableEntity.createdAt.toISOString(),
|
||||
updatedAt: applicationVariableEntity.updatedAt.toISOString(),
|
||||
applicationUniversalIdentifier,
|
||||
};
|
||||
};
|
||||
+8
@@ -20,6 +20,14 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY should ma
|
||||
"evaluationInputs",
|
||||
],
|
||||
},
|
||||
"applicationVariable": {
|
||||
"propertiesToCompare": [
|
||||
"key",
|
||||
"description",
|
||||
"isSecret",
|
||||
],
|
||||
"propertiesToStringify": [],
|
||||
},
|
||||
"commandMenuItem": {
|
||||
"propertiesToCompare": [
|
||||
"label",
|
||||
|
||||
+32
@@ -1542,6 +1542,38 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = {
|
||||
universalProperty: undefined,
|
||||
},
|
||||
},
|
||||
applicationVariable: {
|
||||
key: {
|
||||
toCompare: true,
|
||||
toStringify: false,
|
||||
universalProperty: undefined,
|
||||
},
|
||||
value: {
|
||||
toCompare: false,
|
||||
toStringify: false,
|
||||
universalProperty: undefined,
|
||||
},
|
||||
description: {
|
||||
toCompare: true,
|
||||
toStringify: false,
|
||||
universalProperty: undefined,
|
||||
},
|
||||
isSecret: {
|
||||
toCompare: true,
|
||||
toStringify: false,
|
||||
universalProperty: undefined,
|
||||
},
|
||||
createdAt: {
|
||||
toCompare: false,
|
||||
toStringify: false,
|
||||
universalProperty: undefined,
|
||||
},
|
||||
updatedAt: {
|
||||
toCompare: false,
|
||||
toStringify: false,
|
||||
universalProperty: undefined,
|
||||
},
|
||||
},
|
||||
connectionProvider: {
|
||||
name: { toCompare: true, toStringify: false, universalProperty: undefined },
|
||||
displayName: {
|
||||
|
||||
+4
@@ -275,6 +275,10 @@ export const ALL_MANY_TO_ONE_METADATA_FOREIGN_KEY = {
|
||||
workspace: null,
|
||||
application: null,
|
||||
},
|
||||
applicationVariable: {
|
||||
workspace: null,
|
||||
application: null,
|
||||
},
|
||||
viewSort: {
|
||||
application: null,
|
||||
workspace: null,
|
||||
|
||||
+4
@@ -487,6 +487,10 @@ export const ALL_MANY_TO_ONE_METADATA_RELATIONS = {
|
||||
workspace: null,
|
||||
application: null,
|
||||
},
|
||||
applicationVariable: {
|
||||
workspace: null,
|
||||
application: null,
|
||||
},
|
||||
viewSort: {
|
||||
application: null,
|
||||
workspace: null,
|
||||
|
||||
+2
@@ -3,6 +3,7 @@ import { type EntityTarget, type ObjectLiteral } from 'typeorm';
|
||||
|
||||
import { ConnectionProviderEntity } from 'src/engine/core-modules/application/connection-provider/connection-provider.entity';
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
import { ApplicationVariableEntity } from 'src/engine/core-modules/application/application-variable/application-variable.entity';
|
||||
import { CommandMenuItemEntity } from 'src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { FrontComponentEntity } from 'src/engine/metadata-modules/front-component/entities/front-component.entity';
|
||||
@@ -57,6 +58,7 @@ export const ALL_METADATA_ENTITY_BY_METADATA_NAME = {
|
||||
navigationMenuItem: NavigationMenuItemEntity,
|
||||
permissionFlag: PermissionFlagEntity,
|
||||
webhook: WebhookEntity,
|
||||
applicationVariable: ApplicationVariableEntity,
|
||||
viewSort: ViewSortEntity,
|
||||
connectionProvider: ConnectionProviderEntity,
|
||||
} as const satisfies Record<AllMetadataName, EntityTarget<ObjectLiteral>>;
|
||||
|
||||
+1
@@ -112,5 +112,6 @@ export const ALL_METADATA_REQUIRED_METADATA_FOR_VALIDATION = {
|
||||
},
|
||||
frontComponent: {},
|
||||
webhook: {},
|
||||
applicationVariable: {},
|
||||
connectionProvider: {},
|
||||
} as const satisfies MetadataRequiredForValidation;
|
||||
|
||||
+1
@@ -50,6 +50,7 @@ export const ALL_METADATA_SERIALIZED_RELATION = {
|
||||
viewSort: {},
|
||||
frontComponent: {},
|
||||
webhook: {},
|
||||
applicationVariable: {},
|
||||
connectionProvider: {},
|
||||
} as const satisfies MetadataSerializedRelationProperties;
|
||||
|
||||
|
||||
+1
@@ -243,6 +243,7 @@ export const ALL_ONE_TO_MANY_METADATA_RELATIONS = {
|
||||
},
|
||||
frontComponent: {},
|
||||
webhook: {},
|
||||
applicationVariable: {},
|
||||
viewSort: {},
|
||||
connectionProvider: {},
|
||||
} as const satisfies OneToManyMetadataRelationsProperties;
|
||||
|
||||
+27
@@ -45,6 +45,8 @@ import { type FlatViewSortMaps } from 'src/engine/metadata-modules/flat-view-sor
|
||||
import { type FlatViewSort } from 'src/engine/metadata-modules/flat-view-sort/types/flat-view-sort.type';
|
||||
import { type FlatViewMaps } from 'src/engine/metadata-modules/flat-view/types/flat-view-maps.type';
|
||||
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
|
||||
import { type FlatApplicationVariableMaps } from 'src/engine/metadata-modules/flat-application-variable/types/flat-application-variable-maps.type';
|
||||
import { type FlatApplicationVariable } from 'src/engine/metadata-modules/flat-application-variable/types/flat-application-variable.type';
|
||||
import { type FlatWebhookMaps } from 'src/engine/metadata-modules/flat-webhook/types/flat-webhook-maps.type';
|
||||
import { type FlatWebhook } from 'src/engine/metadata-modules/flat-webhook/types/flat-webhook.type';
|
||||
import { type FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type';
|
||||
@@ -80,6 +82,7 @@ import { type UniversalFlatViewFilter } from 'src/engine/workspace-manager/works
|
||||
import { type UniversalFlatViewGroup } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view-group.type';
|
||||
import { type UniversalFlatViewSort } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view-sort.type';
|
||||
import { type UniversalFlatView } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view.type';
|
||||
import { type UniversalFlatApplicationVariable } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-application-variable.type';
|
||||
import { type UniversalFlatWebhook } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-webhook.type';
|
||||
import {
|
||||
type FlatCreateAgentAction,
|
||||
@@ -297,6 +300,14 @@ import {
|
||||
type UniversalDeleteViewAction,
|
||||
type UniversalUpdateViewAction,
|
||||
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view/types/workspace-migration-view-action.type';
|
||||
import {
|
||||
type FlatCreateApplicationVariableAction,
|
||||
type FlatDeleteApplicationVariableAction,
|
||||
type FlatUpdateApplicationVariableAction,
|
||||
type UniversalCreateApplicationVariableAction,
|
||||
type UniversalDeleteApplicationVariableAction,
|
||||
type UniversalUpdateApplicationVariableAction,
|
||||
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/application-variable/types/workspace-migration-application-variable-action.type';
|
||||
import {
|
||||
type FlatCreateWebhookAction,
|
||||
type FlatDeleteWebhookAction,
|
||||
@@ -742,6 +753,22 @@ export type AllFlatEntityTypesByMetadataName = {
|
||||
universalFlatEntity: UniversalFlatWebhook;
|
||||
entity: MetadataEntity<'webhook'>;
|
||||
};
|
||||
applicationVariable: {
|
||||
flatEntityMaps: FlatApplicationVariableMaps;
|
||||
universalActions: {
|
||||
create: UniversalCreateApplicationVariableAction;
|
||||
update: UniversalUpdateApplicationVariableAction;
|
||||
delete: UniversalDeleteApplicationVariableAction;
|
||||
};
|
||||
flatActions: {
|
||||
create: FlatCreateApplicationVariableAction;
|
||||
update: FlatUpdateApplicationVariableAction;
|
||||
delete: FlatDeleteApplicationVariableAction;
|
||||
};
|
||||
flatEntity: FlatApplicationVariable;
|
||||
universalFlatEntity: UniversalFlatApplicationVariable;
|
||||
entity: MetadataEntity<'applicationVariable'>;
|
||||
};
|
||||
connectionProvider: {
|
||||
flatEntityMaps: FlatConnectionProviderMaps;
|
||||
universalActions: {
|
||||
|
||||
+2
@@ -2,6 +2,8 @@
|
||||
|
||||
exports[`getMetadataRelatedMetadataNames should return related metadata names for agent 1`] = `[]`;
|
||||
|
||||
exports[`getMetadataRelatedMetadataNames should return related metadata names for applicationVariable 1`] = `[]`;
|
||||
|
||||
exports[`getMetadataRelatedMetadataNames should return related metadata names for commandMenuItem 1`] = `
|
||||
[
|
||||
"objectMetadata",
|
||||
|
||||
+2
-1
@@ -1,4 +1,4 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`sortMetadataNamesChildrenFirst should return metadata names sorted with children first (most manyToOne relations first) 1`] = `
|
||||
[
|
||||
@@ -18,6 +18,7 @@ exports[`sortMetadataNamesChildrenFirst should return metadata names sorted with
|
||||
"rowLevelPermissionPredicateGroup",
|
||||
"viewGroup",
|
||||
"agent",
|
||||
"applicationVariable",
|
||||
"connectionProvider",
|
||||
"frontComponent",
|
||||
"logicFunction",
|
||||
|
||||
@@ -53,6 +53,7 @@ export const WORKSPACE_CACHE_KEYS_V2 = {
|
||||
'flat-maps:row-level-permission-predicate-group',
|
||||
flatFrontComponentMaps: 'flat-maps:front-component',
|
||||
flatWebhookMaps: 'flat-maps:webhook',
|
||||
flatApplicationVariableMaps: 'flat-maps:application-variable',
|
||||
flatConnectionProviderMaps: 'flat-maps:connection-provider',
|
||||
flatWorkspaceMemberMaps: 'flat-maps:workspace-member',
|
||||
apiKeyMap: 'cache:api-key-map',
|
||||
|
||||
+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