1895 extensibility v1 application tokens 3 (#16504)
- moves applicationRoleId to application entity - add new `APPLICATION` FieldActorSource and `APPLICATION` JwtTokenTypeEnum value - create a new token with applicationId when executing a function - when applicationId is in token, check for application.defaultRole permissions -use twenty-shared types in `twenty-sdk/application` - create a new import from generate called "Twenty" that you can use directly without having to set TWENTY_API_KEY AND TWENTY_API_URL (keep metadata or core parameter only) - provide to serverless unique one time BEARER TOKEN to run it Result <img width="977" height="566" alt="image" src="https://github.com/user-attachments/assets/e78428a0-5b13-4975-aa13-58ee3b32450c" /> <img width="910" height="596" alt="image" src="https://github.com/user-attachments/assets/6ec72bf5-7655-4093-a45e-ad269595a324" /> <img width="741" height="568" alt="image" src="https://github.com/user-attachments/assets/7683944c-fd79-4417-8fb2-8e4815cc112f" />
This commit is contained in:
+2
-2
@@ -16,10 +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';
|
||||
import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-common.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -37,9 +37,9 @@ import { RoleModule } from 'src/engine/metadata-modules/role/role.module';
|
||||
WorkspaceMigrationV2Module,
|
||||
PermissionsModule,
|
||||
RoleModule,
|
||||
RoleTargetModule,
|
||||
ObjectPermissionModule,
|
||||
PermissionFlagModule,
|
||||
WorkflowCommonModule,
|
||||
],
|
||||
providers: [
|
||||
ApplicationResolver,
|
||||
|
||||
+145
-81
@@ -3,11 +3,12 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
import { parse } from 'path';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Sources } from 'twenty-shared/types';
|
||||
import { HTTPMethod, Sources } from 'twenty-shared/types';
|
||||
import {
|
||||
ApplicationManifest,
|
||||
FieldManifest,
|
||||
ObjectManifest,
|
||||
RoleManifest,
|
||||
ServerlessFunctionManifest,
|
||||
ServerlessFunctionTriggerManifest,
|
||||
} from 'twenty-shared/application';
|
||||
@@ -39,10 +40,10 @@ import { FlatServerlessFunction } from 'src/engine/metadata-modules/serverless-f
|
||||
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';
|
||||
import { buildObjectIdByNameMaps } from 'src/engine/metadata-modules/flat-object-metadata/utils/build-object-id-by-name-maps.util';
|
||||
import { FieldPermissionService } from 'src/engine/metadata-modules/object-permission/field-permission/field-permission.service';
|
||||
|
||||
@Injectable()
|
||||
export class ApplicationSyncService {
|
||||
@@ -62,7 +63,6 @@ export class ApplicationSyncService {
|
||||
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,
|
||||
@@ -106,6 +106,12 @@ export class ApplicationSyncService {
|
||||
});
|
||||
}
|
||||
|
||||
await this.syncRoles({
|
||||
manifest,
|
||||
workspaceId,
|
||||
applicationId: application.id,
|
||||
});
|
||||
|
||||
this.logger.log('✅ Application sync from manifest completed');
|
||||
}
|
||||
|
||||
@@ -130,17 +136,12 @@ export class ApplicationSyncService {
|
||||
version: packageJson.version,
|
||||
sourcePath: 'cli-sync', // Placeholder for CLI-synced apps
|
||||
serverlessFunctionLayerId: null,
|
||||
defaultServerlessFunctionRoleId: null,
|
||||
workspaceId,
|
||||
}));
|
||||
|
||||
let serverlessFunctionLayerId = application.serverlessFunctionLayerId;
|
||||
|
||||
await this.syncApplicationRole({
|
||||
applicationId: application.id,
|
||||
manifest,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
if (manifest.serverlessFunctions.length > 0) {
|
||||
if (!isDefined(serverlessFunctionLayerId)) {
|
||||
serverlessFunctionLayerId = (
|
||||
@@ -175,114 +176,177 @@ export class ApplicationSyncService {
|
||||
description: manifest.application.description,
|
||||
version: packageJson.version,
|
||||
serverlessFunctionLayerId,
|
||||
defaultServerlessFunctionRoleId: null,
|
||||
});
|
||||
}
|
||||
|
||||
private async syncApplicationRole({
|
||||
private async syncRoles({
|
||||
manifest,
|
||||
applicationId,
|
||||
workspaceId,
|
||||
applicationId,
|
||||
}: {
|
||||
manifest: ApplicationManifest;
|
||||
applicationId: string;
|
||||
workspaceId: string;
|
||||
applicationId: string;
|
||||
}) {
|
||||
const applicationRole = manifest.application.applicationRole;
|
||||
let defaultServerlessFunctionRoleId: string | null = null;
|
||||
|
||||
if (
|
||||
isDefined(applicationRole) &&
|
||||
isDefined(applicationRole.universalIdentifier)
|
||||
) {
|
||||
let role = await this.roleService.getRoleByUniversalIdentifier({
|
||||
universalIdentifier: applicationRole.universalIdentifier,
|
||||
for (const role of manifest.roles ?? []) {
|
||||
let existingRole = await this.roleService.getRoleByUniversalIdentifier({
|
||||
universalIdentifier: role.universalIdentifier,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
if (role) {
|
||||
if (existingRole) {
|
||||
await this.roleService.updateRole({
|
||||
input: {
|
||||
id: role.id,
|
||||
update: {
|
||||
...applicationRole,
|
||||
canAccessAllTools: false,
|
||||
canUpdateAllSettings: false,
|
||||
canBeAssignedToAgents: false,
|
||||
canBeAssignedToUsers: false,
|
||||
canBeAssignedToApiKeys: false,
|
||||
canBeAssignedToApplications: true,
|
||||
},
|
||||
id: existingRole.id,
|
||||
update: role,
|
||||
},
|
||||
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,
|
||||
},
|
||||
existingRole = await this.roleService.createRole({
|
||||
input: role,
|
||||
workspaceId,
|
||||
applicationId,
|
||||
});
|
||||
}
|
||||
|
||||
await this.syncApplicationRolePermissions({
|
||||
role,
|
||||
workspaceId,
|
||||
roleId: existingRole.id,
|
||||
});
|
||||
|
||||
if (
|
||||
isDefined(applicationRole.objectPermissions) &&
|
||||
applicationRole.objectPermissions.length > 0
|
||||
existingRole.universalIdentifier ===
|
||||
manifest.application.functionRoleUniversalIdentifier
|
||||
) {
|
||||
defaultServerlessFunctionRoleId = existingRole.id;
|
||||
}
|
||||
}
|
||||
|
||||
if (isDefined(defaultServerlessFunctionRoleId)) {
|
||||
await this.applicationService.update(applicationId, {
|
||||
defaultServerlessFunctionRoleId: defaultServerlessFunctionRoleId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async syncApplicationRolePermissions({
|
||||
role,
|
||||
workspaceId,
|
||||
roleId,
|
||||
}: {
|
||||
role: RoleManifest;
|
||||
workspaceId: string;
|
||||
roleId: string;
|
||||
}) {
|
||||
if (
|
||||
(role.objectPermissions ?? []).length > 0 ||
|
||||
(role.fieldPermissions ?? []).length > 0
|
||||
) {
|
||||
const { flatObjectMetadataMaps, flatFieldMetadataMaps } =
|
||||
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatObjectMetadataMaps', 'flatFieldMetadataMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const { idByNameSingular: objectIdByNameSingular } =
|
||||
buildObjectIdByNameMaps(flatObjectMetadataMaps);
|
||||
|
||||
const formattedObjectPermissions = role.objectPermissions
|
||||
?.map((perm) => ({
|
||||
...perm,
|
||||
objectMetadataId: isDefined(perm.objectNameSingular)
|
||||
? objectIdByNameSingular[perm.objectNameSingular]
|
||||
: isDefined(perm.objectUniversalIdentifier)
|
||||
? flatObjectMetadataMaps.idByUniversalIdentifier[
|
||||
perm.objectUniversalIdentifier
|
||||
]
|
||||
: undefined,
|
||||
}))
|
||||
.filter((perm): perm is typeof perm & { objectMetadataId: string } =>
|
||||
isDefined(perm.objectMetadataId),
|
||||
);
|
||||
|
||||
if (isDefined(formattedObjectPermissions)) {
|
||||
await this.objectPermissionService.upsertObjectPermissions({
|
||||
workspaceId,
|
||||
input: {
|
||||
roleId: role.id,
|
||||
objectPermissions: applicationRole.objectPermissions,
|
||||
roleId,
|
||||
objectPermissions: formattedObjectPermissions,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
isDefined(applicationRole.fieldPermissions) &&
|
||||
applicationRole.fieldPermissions.length > 0
|
||||
) {
|
||||
const formattedFieldPermissions = role?.fieldPermissions
|
||||
?.map((perm) => {
|
||||
const objectMetadataId = isDefined(perm.objectNameSingular)
|
||||
? objectIdByNameSingular[perm.objectNameSingular]
|
||||
: isDefined(perm.objectUniversalIdentifier)
|
||||
? flatObjectMetadataMaps.idByUniversalIdentifier[
|
||||
perm.objectUniversalIdentifier
|
||||
]
|
||||
: undefined;
|
||||
|
||||
const fieldMetadataId = isDefined(objectMetadataId)
|
||||
? isDefined(perm.fieldName)
|
||||
? Object.values(flatFieldMetadataMaps.byId).find(
|
||||
(flatField) =>
|
||||
isDefined(flatField) &&
|
||||
flatField.objectMetadataId === objectMetadataId &&
|
||||
flatField.name === perm.fieldName,
|
||||
)?.id
|
||||
: isDefined(perm.fieldUniversalIdentifier)
|
||||
? Object.values(flatFieldMetadataMaps.byId).find(
|
||||
(flatField) =>
|
||||
isDefined(flatField) &&
|
||||
flatField.objectMetadataId === objectMetadataId &&
|
||||
flatField.universalIdentifier ===
|
||||
perm.fieldUniversalIdentifier,
|
||||
)?.id
|
||||
: undefined
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
...perm,
|
||||
objectMetadataId,
|
||||
fieldMetadataId,
|
||||
};
|
||||
})
|
||||
.filter(
|
||||
(
|
||||
perm,
|
||||
): perm is typeof perm & {
|
||||
objectMetadataId: string;
|
||||
fieldMetadataId: string;
|
||||
} =>
|
||||
isDefined(perm.objectMetadataId) && isDefined(perm.fieldMetadataId),
|
||||
);
|
||||
|
||||
if (isDefined(formattedFieldPermissions)) {
|
||||
await this.fieldPermissionService.upsertFieldPermissions({
|
||||
workspaceId,
|
||||
input: {
|
||||
roleId: role.id,
|
||||
fieldPermissions: applicationRole.fieldPermissions,
|
||||
roleId,
|
||||
fieldPermissions: formattedFieldPermissions,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
isDefined(applicationRole.permissionFlags) &&
|
||||
applicationRole.permissionFlags.length > 0
|
||||
) {
|
||||
await this.permissionService.upsertPermissionFlags({
|
||||
workspaceId,
|
||||
input: {
|
||||
roleId: role.id,
|
||||
permissionFlagKeys: applicationRole.permissionFlags,
|
||||
},
|
||||
});
|
||||
}
|
||||
if (isDefined(role?.permissionFlags) && role.permissionFlags.length > 0) {
|
||||
await this.permissionService.upsertPermissionFlags({
|
||||
workspaceId,
|
||||
input: {
|
||||
roleId,
|
||||
permissionFlagKeys: role.permissionFlags,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1036,7 +1100,7 @@ export class ApplicationSyncService {
|
||||
id: triggerToUpdate.id,
|
||||
update: {
|
||||
path: triggerToSync.path,
|
||||
httpMethod: triggerToSync.httpMethod,
|
||||
httpMethod: triggerToSync.httpMethod as HTTPMethod,
|
||||
isAuthRequired: triggerToSync.isAuthRequired,
|
||||
},
|
||||
};
|
||||
@@ -1054,7 +1118,7 @@ export class ApplicationSyncService {
|
||||
|
||||
const createRouteTriggerInput = {
|
||||
path: triggerToCreate.path,
|
||||
httpMethod: triggerToCreate.httpMethod,
|
||||
httpMethod: triggerToCreate.httpMethod as HTTPMethod,
|
||||
isAuthRequired: triggerToCreate.isAuthRequired,
|
||||
serverlessFunctionId,
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ObjectType } from '@nestjs/graphql';
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import {
|
||||
Column,
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
import { RoleDTO } from 'src/engine/metadata-modules/role/dtos/role.dto';
|
||||
import { ApplicationVariableEntity } from 'src/engine/core-modules/applicationVariable/application-variable.entity';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
@@ -60,6 +61,12 @@ export class ApplicationEntity {
|
||||
@Column({ nullable: true, type: 'uuid' })
|
||||
serverlessFunctionLayerId: string | null;
|
||||
|
||||
@Column({ nullable: true, type: 'uuid' })
|
||||
defaultServerlessFunctionRoleId: string | null;
|
||||
|
||||
@Field(() => RoleDTO, { nullable: true })
|
||||
defaultServerlessFunctionRole: RoleDTO | null;
|
||||
|
||||
@Column({ nullable: false, type: 'boolean', default: true })
|
||||
canBeUninstalled: boolean;
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import { ApplicationVariableEntityDTO } from 'src/engine/core-modules/applicatio
|
||||
import { AgentDTO } from 'src/engine/metadata-modules/ai/ai-agent/dtos/agent.dto';
|
||||
import { ObjectMetadataDTO } from 'src/engine/metadata-modules/object-metadata/dtos/object-metadata.dto';
|
||||
import { ServerlessFunctionDTO } from 'src/engine/metadata-modules/serverless-function/dtos/serverless-function.dto';
|
||||
import { RoleDTO } from 'src/engine/metadata-modules/role/dtos/role.dto';
|
||||
|
||||
@ObjectType('Application')
|
||||
export class ApplicationDTO {
|
||||
@@ -43,6 +44,15 @@ export class ApplicationDTO {
|
||||
@IsBoolean()
|
||||
canBeUninstalled: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@Field({ nullable: true })
|
||||
defaultServerlessFunctionRoleId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@Field(() => RoleDTO, { nullable: true })
|
||||
defaultServerlessFunctionRole?: RoleDTO;
|
||||
|
||||
@Field(() => [AgentDTO])
|
||||
agents?: AgentDTO[];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user