1895 extensibility v1 application tokens (#16365)
First PR to implement application tokens - add new application role in twenty-server - move duplicated constants and types to twenty-shared - will add role configuration utils into twenty-sdk in another PR
This commit is contained in:
@@ -16,6 +16,10 @@ import { ServerlessFunctionLayerModule } from 'src/engine/metadata-modules/serve
|
||||
import { ServerlessFunctionModule } from 'src/engine/metadata-modules/serverless-function/serverless-function.module';
|
||||
import { WorkspaceMigrationBuilderGraphqlApiExceptionInterceptor } from 'src/engine/workspace-manager/workspace-migration-v2/interceptors/workspace-migration-builder-graphql-api-exception.interceptor';
|
||||
import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-v2.module';
|
||||
import { RoleTargetModule } from 'src/engine/metadata-modules/role-target/role-target.module';
|
||||
import { ObjectPermissionModule } from 'src/engine/metadata-modules/object-permission/object-permission.module';
|
||||
import { PermissionFlagModule } from 'src/engine/metadata-modules/permission-flag/permission-flag.module';
|
||||
import { RoleModule } from 'src/engine/metadata-modules/role/role.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -32,6 +36,10 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa
|
||||
RouteTriggerModule,
|
||||
WorkspaceMigrationV2Module,
|
||||
PermissionsModule,
|
||||
RoleModule,
|
||||
RoleTargetModule,
|
||||
ObjectPermissionModule,
|
||||
PermissionFlagModule,
|
||||
],
|
||||
providers: [
|
||||
ApplicationResolver,
|
||||
|
||||
+132
-7
@@ -3,6 +3,14 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
import { parse } from 'path';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Sources } from 'twenty-shared/types';
|
||||
import {
|
||||
ApplicationManifest,
|
||||
FieldManifest,
|
||||
ObjectManifest,
|
||||
ServerlessFunctionManifest,
|
||||
ServerlessFunctionTriggerManifest,
|
||||
} from 'twenty-shared/application';
|
||||
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import {
|
||||
@@ -11,14 +19,7 @@ import {
|
||||
} from 'src/engine/core-modules/application/application.exception';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { ApplicationInput } from 'src/engine/core-modules/application/dtos/application.input';
|
||||
import {
|
||||
FieldManifest,
|
||||
ObjectManifest,
|
||||
ServerlessFunctionManifest,
|
||||
ServerlessFunctionTriggerManifest,
|
||||
} from 'src/engine/core-modules/application/types/application.types';
|
||||
import { ApplicationVariableEntityService } from 'src/engine/core-modules/applicationVariable/application-variable.service';
|
||||
import { Sources } from 'src/engine/core-modules/file-storage/types/source.type';
|
||||
import { CronTriggerV2Service } from 'src/engine/metadata-modules/cron-trigger/services/cron-trigger-v2.service';
|
||||
import { FlatCronTrigger } from 'src/engine/metadata-modules/cron-trigger/types/flat-cron-trigger.type';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
@@ -37,6 +38,11 @@ import { ServerlessFunctionV2Service } from 'src/engine/metadata-modules/serverl
|
||||
import { FlatServerlessFunction } from 'src/engine/metadata-modules/serverless-function/types/flat-serverless-function.type';
|
||||
import { computeMetadataNameFromLabelOrThrow } from 'src/engine/metadata-modules/utils/compute-metadata-name-from-label-or-throw.util';
|
||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service';
|
||||
import { RoleService } from 'src/engine/metadata-modules/role/role.service';
|
||||
import { RoleTargetService } from 'src/engine/metadata-modules/role-target/services/role-target.service';
|
||||
import { ObjectPermissionService } from 'src/engine/metadata-modules/object-permission/object-permission.service';
|
||||
import { FieldPermissionService } from 'src/engine/metadata-modules/object-permission/field-permission/field-permission.service';
|
||||
import { PermissionFlagService } from 'src/engine/metadata-modules/permission-flag/permission-flag.service';
|
||||
|
||||
@Injectable()
|
||||
export class ApplicationSyncService {
|
||||
@@ -55,6 +61,11 @@ export class ApplicationSyncService {
|
||||
private readonly cronTriggerV2Service: CronTriggerV2Service,
|
||||
private readonly routeTriggerV2Service: RouteTriggerV2Service,
|
||||
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
|
||||
private readonly roleService: RoleService,
|
||||
private readonly roleTargetService: RoleTargetService,
|
||||
private readonly objectPermissionService: ObjectPermissionService,
|
||||
private readonly fieldPermissionService: FieldPermissionService,
|
||||
private readonly permissionService: PermissionFlagService,
|
||||
) {}
|
||||
|
||||
public async synchronizeFromManifest({
|
||||
@@ -124,6 +135,12 @@ export class ApplicationSyncService {
|
||||
|
||||
let serverlessFunctionLayerId = application.serverlessFunctionLayerId;
|
||||
|
||||
await this.syncApplicationRole({
|
||||
applicationId: application.id,
|
||||
manifest,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
if (manifest.serverlessFunctions.length > 0) {
|
||||
if (!isDefined(serverlessFunctionLayerId)) {
|
||||
serverlessFunctionLayerId = (
|
||||
@@ -161,6 +178,114 @@ export class ApplicationSyncService {
|
||||
});
|
||||
}
|
||||
|
||||
private async syncApplicationRole({
|
||||
manifest,
|
||||
applicationId,
|
||||
workspaceId,
|
||||
}: {
|
||||
manifest: ApplicationManifest;
|
||||
applicationId: string;
|
||||
workspaceId: string;
|
||||
}) {
|
||||
const applicationRole = manifest.application.applicationRole;
|
||||
|
||||
if (
|
||||
isDefined(applicationRole) &&
|
||||
isDefined(applicationRole.universalIdentifier)
|
||||
) {
|
||||
let role = await this.roleService.getRoleByUniversalIdentifier({
|
||||
universalIdentifier: applicationRole.universalIdentifier,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
if (role) {
|
||||
await this.roleService.updateRole({
|
||||
input: {
|
||||
id: role.id,
|
||||
update: {
|
||||
...applicationRole,
|
||||
canAccessAllTools: false,
|
||||
canUpdateAllSettings: false,
|
||||
canBeAssignedToAgents: false,
|
||||
canBeAssignedToUsers: false,
|
||||
canBeAssignedToApiKeys: false,
|
||||
canBeAssignedToApplications: true,
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
});
|
||||
} else {
|
||||
role = await this.roleService.createRole({
|
||||
input: {
|
||||
canReadAllObjectRecords: true,
|
||||
canUpdateAllObjectRecords: false,
|
||||
canSoftDeleteAllObjectRecords: false,
|
||||
canDestroyAllObjectRecords: false,
|
||||
...applicationRole,
|
||||
canAccessAllTools: false,
|
||||
canUpdateAllSettings: false,
|
||||
canBeAssignedToAgents: false,
|
||||
canBeAssignedToUsers: false,
|
||||
canBeAssignedToApiKeys: false,
|
||||
canBeAssignedToApplications: true,
|
||||
},
|
||||
workspaceId,
|
||||
applicationId: applicationId,
|
||||
});
|
||||
|
||||
await this.roleTargetService.create({
|
||||
createRoleTargetInput: {
|
||||
roleId: role.id,
|
||||
applicationId: applicationId,
|
||||
universalIdentifier: role.universalIdentifier,
|
||||
targetMetadataForeignKey: 'targetApplicationId',
|
||||
targetId: applicationId,
|
||||
},
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
isDefined(applicationRole.objectPermissions) &&
|
||||
applicationRole.objectPermissions.length > 0
|
||||
) {
|
||||
await this.objectPermissionService.upsertObjectPermissions({
|
||||
workspaceId,
|
||||
input: {
|
||||
roleId: role.id,
|
||||
objectPermissions: applicationRole.objectPermissions,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
isDefined(applicationRole.fieldPermissions) &&
|
||||
applicationRole.fieldPermissions.length > 0
|
||||
) {
|
||||
await this.fieldPermissionService.upsertFieldPermissions({
|
||||
workspaceId,
|
||||
input: {
|
||||
roleId: role.id,
|
||||
fieldPermissions: applicationRole.fieldPermissions,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
isDefined(applicationRole.permissionFlags) &&
|
||||
applicationRole.permissionFlags.length > 0
|
||||
) {
|
||||
await this.permissionService.upsertPermissionFlags({
|
||||
workspaceId,
|
||||
input: {
|
||||
roleId: role.id,
|
||||
permissionFlagKeys: applicationRole.permissionFlags,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async syncFields({
|
||||
objectId,
|
||||
fieldsToSync,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { UseFilters, UseGuards, UseInterceptors } from '@nestjs/common';
|
||||
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
|
||||
|
||||
import { PermissionFlagType } from 'twenty-shared/constants';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { ApplicationExceptionFilter } from 'src/engine/core-modules/application/application-exception-filter';
|
||||
import { ApplicationSyncService } from 'src/engine/core-modules/application/application-sync.service';
|
||||
@@ -14,7 +16,6 @@ import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorat
|
||||
import { RequireFeatureFlag } from 'src/engine/guards/feature-flag.guard';
|
||||
import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
|
||||
import { WorkspaceMigrationBuilderGraphqlApiExceptionInterceptor } from 'src/engine/workspace-manager/workspace-migration-v2/interceptors/workspace-migration-builder-graphql-api-exception.interceptor';
|
||||
|
||||
@UseGuards(
|
||||
|
||||
+2
-6
@@ -1,16 +1,12 @@
|
||||
import { ArgsType, Field } from '@nestjs/graphql';
|
||||
|
||||
import GraphQLJSON from 'graphql-type-json';
|
||||
|
||||
import {
|
||||
AppManifest,
|
||||
PackageJson,
|
||||
} from 'src/engine/core-modules/application/types/application.types';
|
||||
import { ApplicationManifest, PackageJson } from 'twenty-shared/application';
|
||||
|
||||
@ArgsType()
|
||||
export class ApplicationInput {
|
||||
@Field(() => GraphQLJSON, { nullable: false })
|
||||
manifest: AppManifest;
|
||||
manifest: ApplicationManifest;
|
||||
|
||||
@Field(() => GraphQLJSON, { nullable: false })
|
||||
packageJson: PackageJson;
|
||||
|
||||
-113
@@ -1,113 +0,0 @@
|
||||
import { type FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { type HTTPMethod } from 'src/engine/metadata-modules/route-trigger/route-trigger.entity';
|
||||
|
||||
export type PackageJson = {
|
||||
name: string;
|
||||
license: string;
|
||||
engines: {
|
||||
node: string;
|
||||
npm: string;
|
||||
yarn: string;
|
||||
};
|
||||
packageManager: string;
|
||||
version: string;
|
||||
dependencies?: object;
|
||||
devDependencies?: object;
|
||||
};
|
||||
|
||||
type ApplicationVariable = {
|
||||
universalIdentifier: string;
|
||||
value?: string;
|
||||
description?: string;
|
||||
isSecret?: boolean;
|
||||
};
|
||||
|
||||
type Sources = { [key: string]: string | Sources };
|
||||
|
||||
type Application = {
|
||||
universalIdentifier: string;
|
||||
displayName?: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
applicationVariables?: Record<string, ApplicationVariable>;
|
||||
};
|
||||
export type AppManifest = {
|
||||
application: Application;
|
||||
objects: ObjectManifest[];
|
||||
serverlessFunctions: ServerlessFunctionManifest[];
|
||||
sources: Sources;
|
||||
};
|
||||
|
||||
export type ServerlessFunctionManifest = {
|
||||
universalIdentifier: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
timeoutSeconds?: number;
|
||||
handlerPath: string;
|
||||
handlerName: string;
|
||||
triggers: ServerlessFunctionTriggerManifest[];
|
||||
};
|
||||
|
||||
export type ServerlessFunctionTriggerManifest = (
|
||||
| {
|
||||
type: 'cron';
|
||||
pattern: string;
|
||||
}
|
||||
| {
|
||||
type: 'databaseEvent';
|
||||
eventName: string;
|
||||
}
|
||||
| {
|
||||
type: 'route';
|
||||
path: string;
|
||||
httpMethod: HTTPMethod;
|
||||
isAuthRequired: boolean;
|
||||
}
|
||||
) & {
|
||||
universalIdentifier: string;
|
||||
};
|
||||
|
||||
export type FieldManifest = {
|
||||
universalIdentifier: string;
|
||||
type: FieldMetadataType;
|
||||
label: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
defaultValue?: any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
options?: any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
settings?: any;
|
||||
isNullable?: boolean;
|
||||
isFieldUiReadOnly?: boolean;
|
||||
};
|
||||
|
||||
export type ObjectManifest = {
|
||||
universalIdentifier: string;
|
||||
nameSingular: string;
|
||||
namePlural: string;
|
||||
labelSingular: string;
|
||||
labelPlural: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
fields?: FieldManifest[];
|
||||
};
|
||||
|
||||
interface AgentResponseFormat {
|
||||
type: 'json' | 'text';
|
||||
schema?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export type AgentManifest = {
|
||||
$schema?: string;
|
||||
standardId: string;
|
||||
name: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
prompt: string;
|
||||
modelId: string;
|
||||
responseFormat?: AgentResponseFormat;
|
||||
};
|
||||
Reference in New Issue
Block a user