Decouple Send Email node from workflows (#13322)
- Renamed `WorkflowActionAdapter` to `ToolExecutorWorkflowAction` - Renamed `settingPermission` table to `permissionFlag` and `setting` column to `flag` - Decoupled the send email logic from workflows to tools - Add new `Tools Permission` section in FE --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com> Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
@@ -11,11 +11,16 @@ import { ToolAdapterService } from 'src/engine/core-modules/ai/services/tool-ada
|
||||
import { ToolService } from 'src/engine/core-modules/ai/services/tool.service';
|
||||
import { TokenModule } from 'src/engine/core-modules/auth/token/token.module';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { ToolRegistryService } from 'src/engine/core-modules/tool/services/tool-registry.service';
|
||||
import { SendEmailTool } from 'src/engine/core-modules/tool/tools/send-email-tool/send-email-tool';
|
||||
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module';
|
||||
import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.module';
|
||||
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
|
||||
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
||||
import { MessagingModule } from 'src/modules/messaging/messaging.module';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
@@ -27,6 +32,9 @@ import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/
|
||||
WorkspacePermissionsCacheModule,
|
||||
WorkspaceCacheStorageModule,
|
||||
UserRoleModule,
|
||||
TwentyORMModule,
|
||||
MessagingModule,
|
||||
PermissionsModule,
|
||||
],
|
||||
controllers: [AiController, McpController],
|
||||
providers: [
|
||||
@@ -34,8 +42,10 @@ import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/
|
||||
AiModelRegistryService,
|
||||
ToolService,
|
||||
ToolAdapterService,
|
||||
ToolRegistryService,
|
||||
AIBillingService,
|
||||
McpService,
|
||||
SendEmailTool,
|
||||
],
|
||||
exports: [
|
||||
AiService,
|
||||
@@ -43,7 +53,9 @@ import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/
|
||||
AIBillingService,
|
||||
ToolService,
|
||||
ToolAdapterService,
|
||||
ToolRegistryService,
|
||||
McpService,
|
||||
SendEmailTool,
|
||||
],
|
||||
})
|
||||
export class AiModule {}
|
||||
|
||||
+37
-16
@@ -2,28 +2,49 @@ import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { ToolSet } from 'ai';
|
||||
|
||||
import { TOOLS } from 'src/engine/core-modules/tool/constants/tools.const';
|
||||
import { ToolRegistryService } from 'src/engine/core-modules/tool/services/tool-registry.service';
|
||||
import { ToolInput } from 'src/engine/core-modules/tool/types/tool-input.type';
|
||||
import { Tool } from 'src/engine/core-modules/tool/types/tool.type';
|
||||
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
|
||||
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
|
||||
|
||||
@Injectable()
|
||||
export class ToolAdapterService {
|
||||
getCoreTools(): ToolSet {
|
||||
const tools = Array.from(TOOLS.entries()).reduce<ToolSet>(
|
||||
(acc, [toolType, tool]) => {
|
||||
acc[toolType.toLowerCase()] = {
|
||||
description: tool.description,
|
||||
parameters: tool.parameters,
|
||||
execute: async (parameters) => {
|
||||
const response = await tool.execute(parameters.input);
|
||||
constructor(
|
||||
private readonly toolRegistry: ToolRegistryService,
|
||||
private readonly permissionsService: PermissionsService,
|
||||
) {}
|
||||
|
||||
return response;
|
||||
},
|
||||
};
|
||||
async getTools(roleId?: string, workspaceId?: string): Promise<ToolSet> {
|
||||
const tools: ToolSet = {};
|
||||
|
||||
return acc;
|
||||
},
|
||||
{},
|
||||
);
|
||||
for (const toolType of this.toolRegistry.getAllToolTypes()) {
|
||||
const tool = this.toolRegistry.getTool(toolType);
|
||||
|
||||
if (!tool.flag) {
|
||||
tools[toolType.toLowerCase()] = this.createToolSet(tool);
|
||||
} else if (roleId && workspaceId) {
|
||||
const hasPermission = await this.permissionsService.hasToolPermission(
|
||||
roleId,
|
||||
workspaceId,
|
||||
tool.flag as PermissionFlagType,
|
||||
);
|
||||
|
||||
if (hasPermission) {
|
||||
tools[toolType.toLowerCase()] = this.createToolSet(tool);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tools;
|
||||
}
|
||||
|
||||
private createToolSet(tool: Tool) {
|
||||
return {
|
||||
description: tool.description,
|
||||
parameters: tool.parameters,
|
||||
execute: async (parameters: { input: ToolInput }) =>
|
||||
tool.execute(parameters.input),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
|
||||
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||
import { TwoFactorAuthenticationExceptionFilter } from 'src/engine/core-modules/two-factor-authentication/two-factor-authentication-exception.filter';
|
||||
|
||||
@@ -548,7 +548,7 @@ export class AuthResolver {
|
||||
|
||||
@UseGuards(
|
||||
WorkspaceAuthGuard,
|
||||
SettingsPermissionsGuard(SettingPermissionType.API_KEYS_AND_WEBHOOKS),
|
||||
SettingsPermissionsGuard(PermissionFlagType.API_KEYS_AND_WEBHOOKS),
|
||||
)
|
||||
@Mutation(() => ApiKeyToken)
|
||||
async generateApiKeyToken(
|
||||
|
||||
@@ -32,7 +32,7 @@ import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorat
|
||||
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
|
||||
import {
|
||||
PermissionsException,
|
||||
PermissionsExceptionCode,
|
||||
@@ -60,7 +60,7 @@ export class BillingResolver {
|
||||
@Query(() => BillingSessionOutput)
|
||||
@UseGuards(
|
||||
WorkspaceAuthGuard,
|
||||
SettingsPermissionsGuard(SettingPermissionType.WORKSPACE),
|
||||
SettingsPermissionsGuard(PermissionFlagType.WORKSPACE),
|
||||
)
|
||||
async billingPortalSession(
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@@ -124,7 +124,7 @@ export class BillingResolver {
|
||||
@Mutation(() => BillingUpdateOutput)
|
||||
@UseGuards(
|
||||
WorkspaceAuthGuard,
|
||||
SettingsPermissionsGuard(SettingPermissionType.WORKSPACE),
|
||||
SettingsPermissionsGuard(PermissionFlagType.WORKSPACE),
|
||||
)
|
||||
async switchToYearlyInterval(@AuthWorkspace() workspace: Workspace) {
|
||||
await this.billingSubscriptionService.switchToYearlyInterval(workspace);
|
||||
@@ -135,7 +135,7 @@ export class BillingResolver {
|
||||
@Mutation(() => BillingUpdateOutput)
|
||||
@UseGuards(
|
||||
WorkspaceAuthGuard,
|
||||
SettingsPermissionsGuard(SettingPermissionType.WORKSPACE),
|
||||
SettingsPermissionsGuard(PermissionFlagType.WORKSPACE),
|
||||
)
|
||||
async switchToEnterprisePlan(@AuthWorkspace() workspace: Workspace) {
|
||||
await this.billingSubscriptionService.switchToEnterprisePlan(workspace);
|
||||
@@ -154,7 +154,7 @@ export class BillingResolver {
|
||||
@Mutation(() => BillingEndTrialPeriodOutput)
|
||||
@UseGuards(
|
||||
WorkspaceAuthGuard,
|
||||
SettingsPermissionsGuard(SettingPermissionType.WORKSPACE),
|
||||
SettingsPermissionsGuard(PermissionFlagType.WORKSPACE),
|
||||
)
|
||||
async endSubscriptionTrialPeriod(
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@@ -165,7 +165,7 @@ export class BillingResolver {
|
||||
@Query(() => [BillingMeteredProductUsageOutput])
|
||||
@UseGuards(
|
||||
WorkspaceAuthGuard,
|
||||
SettingsPermissionsGuard(SettingPermissionType.WORKSPACE),
|
||||
SettingsPermissionsGuard(PermissionFlagType.WORKSPACE),
|
||||
)
|
||||
async getMeteredProductsUsage(
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@@ -199,7 +199,7 @@ export class BillingResolver {
|
||||
await this.permissionsService.userHasWorkspaceSettingPermission({
|
||||
userWorkspaceId,
|
||||
workspaceId,
|
||||
setting: SettingPermissionType.WORKSPACE,
|
||||
setting: PermissionFlagType.WORKSPACE,
|
||||
isExecutedByApiKey,
|
||||
});
|
||||
|
||||
|
||||
+2
-2
@@ -23,7 +23,7 @@ import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
|
||||
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { ImapSmtpCalDavAPIService } from 'src/modules/connected-account/services/imap-smtp-caldav-apis.service';
|
||||
@@ -32,7 +32,7 @@ import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/s
|
||||
@Resolver()
|
||||
@UsePipes(ResolverValidationPipe)
|
||||
@UseFilters(AuthGraphqlApiExceptionFilter, PermissionsGraphqlApiExceptionFilter)
|
||||
@UseGuards(SettingsPermissionsGuard(SettingPermissionType.WORKSPACE))
|
||||
@UseGuards(SettingsPermissionsGuard(PermissionFlagType.WORKSPACE))
|
||||
export class ImapSmtpCaldavResolver {
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
|
||||
@@ -13,7 +13,7 @@ import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
|
||||
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||
|
||||
@Resolver()
|
||||
@@ -23,7 +23,7 @@ import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-module
|
||||
PermissionsGraphqlApiExceptionFilter,
|
||||
PreventNestToAutoLogGraphqlErrorsFilter,
|
||||
)
|
||||
@UseGuards(SettingsPermissionsGuard(SettingPermissionType.WORKSPACE))
|
||||
@UseGuards(SettingsPermissionsGuard(PermissionFlagType.WORKSPACE))
|
||||
export class LabResolver {
|
||||
constructor(private featureFlagService: FeatureFlagService) {}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
|
||||
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||
|
||||
@Resolver()
|
||||
@@ -31,7 +31,7 @@ import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-module
|
||||
PreventNestToAutoLogGraphqlErrorsFilter,
|
||||
)
|
||||
@UsePipes(ResolverValidationPipe)
|
||||
@UseGuards(SettingsPermissionsGuard(SettingPermissionType.SECURITY))
|
||||
@UseGuards(SettingsPermissionsGuard(PermissionFlagType.SECURITY))
|
||||
export class SSOResolver {
|
||||
constructor(private readonly sSOService: SSOService) {}
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { ToolType } from 'src/engine/core-modules/tool/enums/tool-type.enum';
|
||||
import { HttpTool } from 'src/engine/core-modules/tool/tools/http-tool/http-tool';
|
||||
import { Tool } from 'src/engine/core-modules/tool/types/tool.type';
|
||||
|
||||
export const TOOLS: Map<ToolType, Tool> = new Map([
|
||||
[ToolType.HTTP_REQUEST, new HttpTool()],
|
||||
]);
|
||||
@@ -1,3 +1,4 @@
|
||||
export enum ToolType {
|
||||
HTTP_REQUEST = 'HTTP_REQUEST',
|
||||
SEND_EMAIL = 'SEND_EMAIL',
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { ToolType } from 'src/engine/core-modules/tool/enums/tool-type.enum';
|
||||
import { HttpTool } from 'src/engine/core-modules/tool/tools/http-tool/http-tool';
|
||||
import { SendEmailTool } from 'src/engine/core-modules/tool/tools/send-email-tool/send-email-tool';
|
||||
import { SendEmailInput } from 'src/engine/core-modules/tool/tools/send-email-tool/types/send-email-input.type';
|
||||
import { Tool } from 'src/engine/core-modules/tool/types/tool.type';
|
||||
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
|
||||
|
||||
@Injectable()
|
||||
export class ToolRegistryService {
|
||||
private readonly toolFactories: Map<ToolType, () => Tool>;
|
||||
|
||||
constructor(private readonly sendEmailTool: SendEmailTool) {
|
||||
this.toolFactories = new Map<ToolType, () => Tool>([
|
||||
[ToolType.HTTP_REQUEST, () => new HttpTool()],
|
||||
[
|
||||
ToolType.SEND_EMAIL,
|
||||
() => ({
|
||||
description: this.sendEmailTool.description,
|
||||
parameters: this.sendEmailTool.parameters,
|
||||
execute: (params) =>
|
||||
this.sendEmailTool.execute(params as SendEmailInput),
|
||||
flag: PermissionFlagType.SEND_EMAIL_TOOL,
|
||||
}),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
getTool(toolType: ToolType): Tool {
|
||||
const factory = this.toolFactories.get(toolType);
|
||||
|
||||
if (!factory) {
|
||||
throw new Error(`Unknown tool type: ${toolType}`);
|
||||
}
|
||||
|
||||
return factory();
|
||||
}
|
||||
|
||||
getAllToolTypes(): ToolType[] {
|
||||
return Array.from(this.toolFactories.keys());
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import { CustomException } from 'src/utils/custom-exception';
|
||||
|
||||
export class SendEmailToolException extends CustomException {
|
||||
constructor(message: string, code: SendEmailToolExceptionCode) {
|
||||
super(message, code);
|
||||
}
|
||||
}
|
||||
|
||||
export enum SendEmailToolExceptionCode {
|
||||
INVALID_CONNECTED_ACCOUNT_ID = 'INVALID_CONNECTED_ACCOUNT_ID',
|
||||
CONNECTED_ACCOUNT_NOT_FOUND = 'CONNECTED_ACCOUNT_NOT_FOUND',
|
||||
INVALID_EMAIL = 'INVALID_EMAIL',
|
||||
WORKSPACE_ID_NOT_FOUND = 'WORKSPACE_ID_NOT_FOUND',
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const SendEmailInputZodSchema = z.object({
|
||||
email: z.string().email().describe('The recipient email address'),
|
||||
subject: z.string().describe('The email subject line'),
|
||||
body: z.string().describe('The email body content (HTML or plain text)'),
|
||||
connectedAccountId: z
|
||||
.string()
|
||||
.uuid()
|
||||
.describe(
|
||||
'The UUID of the connected account to send the email from. Provide this only if you have it; otherwise, leave blank.',
|
||||
)
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export const SendEmailToolParametersZodSchema = z.object({
|
||||
toolDescription: z
|
||||
.string()
|
||||
.describe(
|
||||
"A clear, human-readable status message describing the email being sent. This will be shown to the user while the tool is being called, so phrase it as a present-tense status update (e.g., 'Sending email to customer about order status'). Explain what email you are sending and to whom in natural language.",
|
||||
),
|
||||
input: SendEmailInputZodSchema,
|
||||
});
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import DOMPurify from 'dompurify';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import { isDefined, isValidUuid } from 'twenty-shared/utils';
|
||||
import { z } from 'zod';
|
||||
|
||||
import {
|
||||
SendEmailToolException,
|
||||
SendEmailToolExceptionCode,
|
||||
} from 'src/engine/core-modules/tool/tools/send-email-tool/exceptions/send-email-tool.exception';
|
||||
import { SendEmailToolParametersZodSchema } from 'src/engine/core-modules/tool/tools/send-email-tool/send-email-tool.schema';
|
||||
import { SendEmailInput } from 'src/engine/core-modules/tool/tools/send-email-tool/types/send-email-input.type';
|
||||
import { ToolOutput } from 'src/engine/core-modules/tool/types/tool-output.type';
|
||||
import { Tool } from 'src/engine/core-modules/tool/types/tool.type';
|
||||
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { MessagingSendMessageService } from 'src/modules/messaging/message-import-manager/services/messaging-send-message.service';
|
||||
|
||||
@Injectable()
|
||||
export class SendEmailTool implements Tool {
|
||||
private readonly logger = new Logger(SendEmailTool.name);
|
||||
|
||||
description =
|
||||
'Send an email using a connected account. Requires SEND_EMAIL_TOOL permission.';
|
||||
parameters = SendEmailToolParametersZodSchema;
|
||||
|
||||
constructor(
|
||||
private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory,
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
private readonly sendMessageService: MessagingSendMessageService,
|
||||
) {}
|
||||
|
||||
private async getConnectedAccount(
|
||||
connectedAccountId: string,
|
||||
workspaceId: string,
|
||||
) {
|
||||
if (!isValidUuid(connectedAccountId)) {
|
||||
throw new SendEmailToolException(
|
||||
`Connected Account ID is not a valid UUID`,
|
||||
SendEmailToolExceptionCode.INVALID_CONNECTED_ACCOUNT_ID,
|
||||
);
|
||||
}
|
||||
|
||||
const connectedAccountRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ConnectedAccountWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'connectedAccount',
|
||||
);
|
||||
const connectedAccount = await connectedAccountRepository.findOneBy({
|
||||
id: connectedAccountId,
|
||||
});
|
||||
|
||||
if (!isDefined(connectedAccount)) {
|
||||
throw new SendEmailToolException(
|
||||
`Connected Account '${connectedAccountId}' not found`,
|
||||
SendEmailToolExceptionCode.CONNECTED_ACCOUNT_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return connectedAccount;
|
||||
}
|
||||
|
||||
private async getOrThrowFirstConnectedAccountId(
|
||||
workspaceId: string,
|
||||
): Promise<string> {
|
||||
const connectedAccountRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ConnectedAccountWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'connectedAccount',
|
||||
);
|
||||
const allAccounts = await connectedAccountRepository.find();
|
||||
|
||||
if (!allAccounts || allAccounts.length === 0) {
|
||||
throw new SendEmailToolException(
|
||||
'No connected accounts found for this workspace',
|
||||
SendEmailToolExceptionCode.CONNECTED_ACCOUNT_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return allAccounts[0].id;
|
||||
}
|
||||
|
||||
async execute(parameters: SendEmailInput): Promise<ToolOutput> {
|
||||
const { workspaceId } = this.scopedWorkspaceContextFactory.create();
|
||||
|
||||
const { email, subject, body } = parameters;
|
||||
let { connectedAccountId } = parameters;
|
||||
|
||||
try {
|
||||
const emailSchema = z.string().trim().email('Invalid email');
|
||||
const emailValidation = emailSchema.safeParse(email);
|
||||
|
||||
if (!emailValidation.success) {
|
||||
throw new SendEmailToolException(
|
||||
`Email '${email}' is invalid`,
|
||||
SendEmailToolExceptionCode.INVALID_EMAIL,
|
||||
);
|
||||
}
|
||||
|
||||
if (!workspaceId) {
|
||||
throw new SendEmailToolException(
|
||||
'Workspace ID not found',
|
||||
SendEmailToolExceptionCode.WORKSPACE_ID_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
if (!connectedAccountId) {
|
||||
connectedAccountId =
|
||||
await this.getOrThrowFirstConnectedAccountId(workspaceId);
|
||||
}
|
||||
|
||||
const connectedAccount = await this.getConnectedAccount(
|
||||
connectedAccountId,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const window = new JSDOM('').window;
|
||||
const purify = DOMPurify(window);
|
||||
const safeBody = purify.sanitize(body || '');
|
||||
const safeSubject = purify.sanitize(subject || '');
|
||||
|
||||
await this.sendMessageService.sendMessage(
|
||||
{
|
||||
to: email,
|
||||
subject: safeSubject,
|
||||
body: safeBody,
|
||||
},
|
||||
connectedAccount,
|
||||
);
|
||||
|
||||
this.logger.log(`Email sent successfully to ${email}`);
|
||||
|
||||
return {
|
||||
result: {
|
||||
success: true,
|
||||
message: `Email sent successfully to ${email}`,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof SendEmailToolException) {
|
||||
return {
|
||||
error: error.message,
|
||||
};
|
||||
}
|
||||
|
||||
this.logger.error(`Failed to send email: ${error}`);
|
||||
|
||||
return {
|
||||
error: error instanceof Error ? error.message : 'Failed to send email',
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { SendEmailInputZodSchema } from 'src/engine/core-modules/tool/tools/send-email-tool/send-email-tool.schema';
|
||||
|
||||
export type SendEmailInput = z.infer<typeof SendEmailInputZodSchema>;
|
||||
@@ -3,9 +3,11 @@ import { ZodType } from 'zod';
|
||||
|
||||
import { ToolInput } from 'src/engine/core-modules/tool/types/tool-input.type';
|
||||
import { ToolOutput } from 'src/engine/core-modules/tool/types/tool-output.type';
|
||||
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
|
||||
|
||||
export type Tool = {
|
||||
description: string;
|
||||
parameters: JSONSchema7 | ZodType;
|
||||
execute(input: ToolInput): Promise<ToolOutput>;
|
||||
flag?: PermissionFlagType;
|
||||
};
|
||||
|
||||
+7
-7
@@ -19,15 +19,15 @@ import {
|
||||
} from 'typeorm';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { TwoFactorAuthenticationMethodSummaryDto } from 'src/engine/core-modules/two-factor-authentication/dto/two-factor-authentication-method.dto';
|
||||
import { TwoFactorAuthenticationMethod } from 'src/engine/core-modules/two-factor-authentication/entities/two-factor-authentication-method.entity';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { ObjectPermissionDTO } from 'src/engine/metadata-modules/object-permission/dtos/object-permission.dto';
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
import { TwoFactorAuthenticationMethod } from 'src/engine/core-modules/two-factor-authentication/entities/two-factor-authentication-method.entity';
|
||||
import { TwoFactorAuthenticationMethodSummaryDto } from 'src/engine/core-modules/two-factor-authentication/dto/two-factor-authentication-method.dto';
|
||||
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
|
||||
|
||||
registerEnumType(SettingPermissionType, {
|
||||
name: 'SettingPermissionType',
|
||||
registerEnumType(PermissionFlagType, {
|
||||
name: 'PermissionFlagType',
|
||||
});
|
||||
|
||||
registerEnumType(PermissionsOnAllObjectRecords, {
|
||||
@@ -96,8 +96,8 @@ export class UserWorkspace {
|
||||
)
|
||||
twoFactorAuthenticationMethods: Relation<TwoFactorAuthenticationMethod[]>;
|
||||
|
||||
@Field(() => [SettingPermissionType], { nullable: true })
|
||||
settingsPermissions?: SettingPermissionType[];
|
||||
@Field(() => [PermissionFlagType], { nullable: true })
|
||||
settingsPermissions?: PermissionFlagType[];
|
||||
|
||||
@Field(() => [PermissionsOnAllObjectRecords], {
|
||||
nullable: true,
|
||||
|
||||
+2
-2
@@ -12,7 +12,7 @@ import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorat
|
||||
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
|
||||
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||
import { OutputSchema } from 'src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type';
|
||||
import { WorkflowSchemaWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.workspace-service';
|
||||
@@ -21,7 +21,7 @@ import { WorkflowSchemaWorkspaceService } from 'src/modules/workflow/workflow-bu
|
||||
@UseGuards(
|
||||
WorkspaceAuthGuard,
|
||||
UserAuthGuard,
|
||||
SettingsPermissionsGuard(SettingPermissionType.WORKFLOWS),
|
||||
SettingsPermissionsGuard(PermissionFlagType.WORKFLOWS),
|
||||
)
|
||||
@UsePipes(ResolverValidationPipe)
|
||||
@UseFilters(
|
||||
|
||||
+2
-2
@@ -16,7 +16,7 @@ import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorat
|
||||
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
|
||||
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||
import { WorkflowVersionStepWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-step/workflow-version-step.workspace-service';
|
||||
import { WorkflowActionType } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
|
||||
@@ -27,7 +27,7 @@ import { WorkflowRunWorkspaceService } from 'src/modules/workflow/workflow-runne
|
||||
@UseGuards(
|
||||
WorkspaceAuthGuard,
|
||||
UserAuthGuard,
|
||||
SettingsPermissionsGuard(SettingPermissionType.WORKFLOWS),
|
||||
SettingsPermissionsGuard(PermissionFlagType.WORKFLOWS),
|
||||
)
|
||||
@UseFilters(
|
||||
PermissionsGraphqlApiExceptionFilter,
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@ import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorat
|
||||
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
|
||||
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { WorkflowTriggerWorkspaceService } from 'src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service';
|
||||
@@ -24,7 +24,7 @@ import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/sta
|
||||
@UseGuards(
|
||||
WorkspaceAuthGuard,
|
||||
UserAuthGuard,
|
||||
SettingsPermissionsGuard(SettingPermissionType.WORKFLOWS),
|
||||
SettingsPermissionsGuard(PermissionFlagType.WORKFLOWS),
|
||||
)
|
||||
@UsePipes(ResolverValidationPipe)
|
||||
@UseFilters(
|
||||
|
||||
+2
-2
@@ -10,7 +10,7 @@ import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorat
|
||||
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
|
||||
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||
import { WorkflowVersionWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version/workflow-version.workspace-service';
|
||||
|
||||
@@ -19,7 +19,7 @@ import { WorkflowVersionWorkspaceService } from 'src/modules/workflow/workflow-b
|
||||
@UseGuards(
|
||||
WorkspaceAuthGuard,
|
||||
UserAuthGuard,
|
||||
SettingsPermissionsGuard(SettingPermissionType.WORKFLOWS),
|
||||
SettingsPermissionsGuard(PermissionFlagType.WORKFLOWS),
|
||||
)
|
||||
@UseFilters(
|
||||
PermissionsGraphqlApiExceptionFilter,
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@ import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorat
|
||||
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
|
||||
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
@@ -23,7 +23,7 @@ import { SendInvitationsInput } from './dtos/send-invitations.input';
|
||||
|
||||
@UseGuards(
|
||||
WorkspaceAuthGuard,
|
||||
SettingsPermissionsGuard(SettingPermissionType.WORKSPACE_MEMBERS),
|
||||
SettingsPermissionsGuard(PermissionFlagType.WORKSPACE_MEMBERS),
|
||||
)
|
||||
@UsePipes(ResolverValidationPipe)
|
||||
@UseFilters(
|
||||
|
||||
+3
-3
@@ -36,7 +36,7 @@ import {
|
||||
WorkspaceExceptionCode,
|
||||
} from 'src/engine/core-modules/workspace/workspace.exception';
|
||||
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
|
||||
import {
|
||||
PermissionsException,
|
||||
PermissionsExceptionCode,
|
||||
@@ -457,7 +457,7 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
||||
const userHasPermission =
|
||||
await this.permissionsService.userHasWorkspaceSettingPermission({
|
||||
userWorkspaceId,
|
||||
setting: SettingPermissionType.SECURITY,
|
||||
setting: PermissionFlagType.SECURITY,
|
||||
workspaceId: workspaceId,
|
||||
isExecutedByApiKey: isDefined(apiKey),
|
||||
});
|
||||
@@ -504,7 +504,7 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
||||
await this.permissionsService.userHasWorkspaceSettingPermission({
|
||||
userWorkspaceId,
|
||||
workspaceId,
|
||||
setting: SettingPermissionType.WORKSPACE,
|
||||
setting: PermissionFlagType.WORKSPACE,
|
||||
isExecutedByApiKey: isDefined(apiKey),
|
||||
});
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { AgentService } from 'src/engine/metadata-modules/agent/agent.service';
|
||||
import { AgentDTO } from 'src/engine/metadata-modules/agent/dtos/agent.dto';
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
|
||||
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||
import { RoleDTO } from 'src/engine/metadata-modules/role/dtos/role.dto';
|
||||
import { RoleService } from 'src/engine/metadata-modules/role/role.service';
|
||||
@@ -144,7 +144,7 @@ export class WorkspaceResolver {
|
||||
@Mutation(() => SignedFileDTO)
|
||||
@UseGuards(
|
||||
WorkspaceAuthGuard,
|
||||
SettingsPermissionsGuard(SettingPermissionType.WORKSPACE),
|
||||
SettingsPermissionsGuard(PermissionFlagType.WORKSPACE),
|
||||
)
|
||||
async uploadWorkspaceLogo(
|
||||
@AuthWorkspace() { id }: Workspace,
|
||||
@@ -190,7 +190,7 @@ export class WorkspaceResolver {
|
||||
@Mutation(() => Workspace)
|
||||
@UseGuards(
|
||||
WorkspaceAuthGuard,
|
||||
SettingsPermissionsGuard(SettingPermissionType.WORKSPACE),
|
||||
SettingsPermissionsGuard(PermissionFlagType.WORKSPACE),
|
||||
)
|
||||
async deleteCurrentWorkspace(@AuthWorkspace() { id }: Workspace) {
|
||||
return this.workspaceService.deleteWorkspace(id);
|
||||
|
||||
Reference in New Issue
Block a user