mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-05-13 17:22:37 +02:00
ddd refactor - projects (#208)
This commit is contained in:
parent
4b095d16cc
commit
580ecc7f98
72 changed files with 2460 additions and 624 deletions
|
|
@ -1,8 +1,147 @@
|
|||
import { z } from "zod";
|
||||
import { Project } from "@/src/entities/models/project";
|
||||
import { ComposioConnectedAccount, CustomMcpServer, Project } from "@/src/entities/models/project";
|
||||
import { Workflow } from "@/app/lib/types/workflow_types";
|
||||
import { PaginatedList } from "@/src/entities/common/paginated-list";
|
||||
|
||||
/**
|
||||
* Schema for creating a new project. Includes name, creator, and optional workflows and secret.
|
||||
*/
|
||||
export const CreateSchema = Project
|
||||
.pick({
|
||||
name: true,
|
||||
createdByUserId: true,
|
||||
secret: true,
|
||||
})
|
||||
.extend({
|
||||
workflow: Workflow.omit({ lastUpdatedAt: true }),
|
||||
});
|
||||
|
||||
/**
|
||||
* Schema for adding a Composio connected account to a project.
|
||||
* Contains the toolkit slug and account data.
|
||||
*/
|
||||
export const AddComposioConnectedAccountSchema = z.object({
|
||||
toolkitSlug: z.string(),
|
||||
data: ComposioConnectedAccount,
|
||||
});
|
||||
|
||||
/**
|
||||
* Schema for adding a custom MCP server to a project.
|
||||
* Contains the server name and server data.
|
||||
*/
|
||||
export const AddCustomMcpServerSchema = z.object({
|
||||
name: z.string(),
|
||||
data: CustomMcpServer,
|
||||
});
|
||||
|
||||
/**
|
||||
* Repository interface for managing projects and their integrations.
|
||||
*/
|
||||
export interface IProjectsRepository {
|
||||
/**
|
||||
* Creates a new project.
|
||||
* @param data - The project creation data matching CreateSchema.
|
||||
* @returns The created Project object.
|
||||
*/
|
||||
create(data: z.infer<typeof CreateSchema>): Promise<z.infer<typeof Project>>;
|
||||
|
||||
/**
|
||||
* Fetches a project by its ID.
|
||||
* @param id - The project ID.
|
||||
* @returns The Project object if found, otherwise null.
|
||||
*/
|
||||
fetch(id: string): Promise<z.infer<typeof Project> | null>;
|
||||
|
||||
/**
|
||||
* Count projects created by user
|
||||
* @param createdByUserId - The creator user ID.
|
||||
* @returns The number of projects created by the user.
|
||||
*/
|
||||
countCreatedProjects(createdByUserId: string): Promise<number>;
|
||||
|
||||
/**
|
||||
* Lists projects for a user.
|
||||
* @param userId - The user ID.
|
||||
* @returns The list of projects.
|
||||
*/
|
||||
listProjects(userId: string, cursor?: string, limit?: number): Promise<z.infer<ReturnType<typeof PaginatedList<typeof Project>>>>;
|
||||
|
||||
/**
|
||||
* Adds a Composio connected account to a project.
|
||||
* @param projectId - The project ID.
|
||||
* @param data - The connected account data.
|
||||
* @returns The updated Project object.
|
||||
*/
|
||||
addComposioConnectedAccount(projectId: string, data: z.infer<typeof AddComposioConnectedAccountSchema>): Promise<z.infer<typeof Project>>;
|
||||
|
||||
/**
|
||||
* Deletes a Composio connected account from a project.
|
||||
* @param projectId - The project ID.
|
||||
* @param toolkitSlug - The toolkit slug to remove.
|
||||
* @returns True if the account was deleted, false otherwise.
|
||||
*/
|
||||
deleteComposioConnectedAccount(projectId: string, toolkitSlug: string): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Adds a custom MCP server to a project.
|
||||
* @param projectId - The project ID.
|
||||
* @param data - The custom MCP server data.
|
||||
* @returns The updated Project object.
|
||||
*/
|
||||
addCustomMcpServer(projectId: string, data: z.infer<typeof AddCustomMcpServerSchema>): Promise<z.infer<typeof Project>>;
|
||||
|
||||
/**
|
||||
* Deletes a custom MCP server from a project.
|
||||
* @param projectId - The project ID.
|
||||
* @param name - The name of the custom MCP server to remove.
|
||||
* @returns True if the server was deleted, false otherwise.
|
||||
*/
|
||||
deleteCustomMcpServer(projectId: string, name: string): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Updates the secret for a project.
|
||||
* @param projectId - The project ID.
|
||||
* @param secret - The new secret value.
|
||||
* @returns The updated Project object.
|
||||
*/
|
||||
updateSecret(projectId: string, secret: string): Promise<z.infer<typeof Project>>;
|
||||
|
||||
/**
|
||||
* Updates the webhook URL for a project.
|
||||
* @param projectId - The project ID.
|
||||
* @param url - The new webhook URL.
|
||||
* @returns The updated Project object.
|
||||
*/
|
||||
updateWebhookUrl(projectId: string, url: string): Promise<z.infer<typeof Project>>;
|
||||
|
||||
/**
|
||||
* Updates the name of a project.
|
||||
* @param projectId - The project ID.
|
||||
* @param name - The new project name.
|
||||
* @returns The updated Project object.
|
||||
*/
|
||||
updateName(projectId: string, name: string): Promise<z.infer<typeof Project>>;
|
||||
|
||||
/**
|
||||
* Updates the draft workflow for a project.
|
||||
* @param projectId - The project ID.
|
||||
* @param workflow - The new draft workflow.
|
||||
* @returns The updated Project object.
|
||||
*/
|
||||
updateDraftWorkflow(projectId: string, workflow: z.infer<typeof Workflow>): Promise<z.infer<typeof Project>>;
|
||||
|
||||
/**
|
||||
* Updates the live workflow for a project.
|
||||
* @param projectId - The project ID.
|
||||
* @param workflow - The new live workflow.
|
||||
* @returns The updated Project object.
|
||||
*/
|
||||
updateLiveWorkflow(projectId: string, workflow: z.infer<typeof Workflow>): Promise<z.infer<typeof Project>>;
|
||||
|
||||
/**
|
||||
* Deletes a project by its ID.
|
||||
* @param projectId - The project ID.
|
||||
* @returns True if the project was deleted, false otherwise.
|
||||
*/
|
||||
delete(projectId: string): Promise<boolean>;
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
import { BadRequestError, NotFoundError } from '@/src/entities/errors/common';
|
||||
import { projectsCollection } from "@/app/lib/mongodb";
|
||||
import { IConversationsRepository } from "@/src/application/repositories/conversations.repository.interface";
|
||||
import { z } from "zod";
|
||||
import { Conversation } from "@/src/entities/models/conversation";
|
||||
|
|
@ -7,6 +6,7 @@ import { Workflow } from "@/app/lib/types/workflow_types";
|
|||
import { IUsageQuotaPolicy } from '../../policies/usage-quota.policy.interface';
|
||||
import { IProjectActionAuthorizationPolicy } from '../../policies/project-action-authorization.policy';
|
||||
import { Reason } from '@/src/entities/models/turn';
|
||||
import { IProjectsRepository } from '../../repositories/projects.repository.interface';
|
||||
|
||||
const inputSchema = z.object({
|
||||
caller: z.enum(["user", "api", "job_worker"]),
|
||||
|
|
@ -26,19 +26,23 @@ export class CreateConversationUseCase implements ICreateConversationUseCase {
|
|||
private readonly conversationsRepository: IConversationsRepository;
|
||||
private readonly usageQuotaPolicy: IUsageQuotaPolicy;
|
||||
private readonly projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy;
|
||||
private readonly projectsRepository: IProjectsRepository;
|
||||
|
||||
constructor({
|
||||
conversationsRepository,
|
||||
usageQuotaPolicy,
|
||||
projectActionAuthorizationPolicy,
|
||||
projectsRepository,
|
||||
}: {
|
||||
conversationsRepository: IConversationsRepository,
|
||||
usageQuotaPolicy: IUsageQuotaPolicy,
|
||||
projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy,
|
||||
projectsRepository: IProjectsRepository,
|
||||
}) {
|
||||
this.conversationsRepository = conversationsRepository;
|
||||
this.usageQuotaPolicy = usageQuotaPolicy;
|
||||
this.projectActionAuthorizationPolicy = projectActionAuthorizationPolicy;
|
||||
this.projectsRepository = projectsRepository;
|
||||
}
|
||||
|
||||
async execute(data: z.infer<typeof inputSchema>): Promise<z.infer<typeof Conversation>> {
|
||||
|
|
@ -61,9 +65,7 @@ export class CreateConversationUseCase implements ICreateConversationUseCase {
|
|||
|
||||
// if workflow is not provided, fetch workflow
|
||||
if (!workflow) {
|
||||
const project = await projectsCollection.findOne({
|
||||
_id: projectId,
|
||||
});
|
||||
const project = await this.projectsRepository.fetch(projectId);
|
||||
if (!project) {
|
||||
throw new NotFoundError('Project not found');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
import { z } from "zod";
|
||||
import { IProjectsRepository } from "../../repositories/projects.repository.interface";
|
||||
import { IProjectActionAuthorizationPolicy } from "../../policies/project-action-authorization.policy";
|
||||
import { IUsageQuotaPolicy } from "../../policies/usage-quota.policy.interface";
|
||||
import { CustomMcpServer } from "@/src/entities/models/project";
|
||||
|
||||
export const InputSchema = z.object({
|
||||
caller: z.enum(["user", "api"]),
|
||||
userId: z.string().optional(),
|
||||
apiKey: z.string().optional(),
|
||||
projectId: z.string(),
|
||||
name: z.string(),
|
||||
server: CustomMcpServer,
|
||||
});
|
||||
|
||||
export interface IAddCustomMcpServerUseCase {
|
||||
execute(request: z.infer<typeof InputSchema>): Promise<void>;
|
||||
}
|
||||
|
||||
function validateHttpHttpsUrl(url: string): string {
|
||||
const parsedUrl = new URL(url);
|
||||
if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {
|
||||
throw new Error('Invalid protocol');
|
||||
}
|
||||
return parsedUrl.toString();
|
||||
}
|
||||
|
||||
export class AddCustomMcpServerUseCase implements IAddCustomMcpServerUseCase {
|
||||
private readonly projectsRepository: IProjectsRepository;
|
||||
private readonly projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy;
|
||||
private readonly usageQuotaPolicy: IUsageQuotaPolicy;
|
||||
|
||||
constructor({
|
||||
projectsRepository,
|
||||
projectActionAuthorizationPolicy,
|
||||
usageQuotaPolicy,
|
||||
}: {
|
||||
projectsRepository: IProjectsRepository,
|
||||
projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy,
|
||||
usageQuotaPolicy: IUsageQuotaPolicy,
|
||||
}) {
|
||||
this.projectsRepository = projectsRepository;
|
||||
this.projectActionAuthorizationPolicy = projectActionAuthorizationPolicy;
|
||||
this.usageQuotaPolicy = usageQuotaPolicy;
|
||||
}
|
||||
|
||||
async execute(request: z.infer<typeof InputSchema>): Promise<void> {
|
||||
const { caller, userId, apiKey, projectId, name } = request;
|
||||
|
||||
await this.projectActionAuthorizationPolicy.authorize({ caller, userId, apiKey, projectId });
|
||||
await this.usageQuotaPolicy.assertAndConsume(projectId);
|
||||
|
||||
// Validate server URL
|
||||
const serverUrl = validateHttpHttpsUrl(request.server.serverUrl);
|
||||
|
||||
await this.projectsRepository.addCustomMcpServer(projectId, {
|
||||
name,
|
||||
data: { serverUrl },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
import { z } from "zod";
|
||||
import { IProjectsRepository } from "../../repositories/projects.repository.interface";
|
||||
import { IProjectActionAuthorizationPolicy } from "../../policies/project-action-authorization.policy";
|
||||
import { IUsageQuotaPolicy } from "../../policies/usage-quota.policy.interface";
|
||||
import { ComposioConnectedAccount } from "@/src/entities/models/project";
|
||||
import { ZAuthScheme, ZCreateAuthConfigResponse, ZCreateConnectedAccountResponse, listAuthConfigs, createAuthConfig, createConnectedAccount } from "@/app/lib/composio/composio";
|
||||
|
||||
export const InputSchema = z.object({
|
||||
caller: z.enum(["user", "api"]),
|
||||
userId: z.string().optional(),
|
||||
apiKey: z.string().optional(),
|
||||
projectId: z.string(),
|
||||
toolkitSlug: z.string(),
|
||||
callbackUrl: z.string(),
|
||||
});
|
||||
|
||||
export interface ICreateComposioManagedConnectedAccountUseCase {
|
||||
execute(request: z.infer<typeof InputSchema>): Promise<z.infer<typeof ZCreateConnectedAccountResponse>>;
|
||||
}
|
||||
|
||||
export class CreateComposioManagedConnectedAccountUseCase implements ICreateComposioManagedConnectedAccountUseCase {
|
||||
private readonly projectsRepository: IProjectsRepository;
|
||||
private readonly projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy;
|
||||
private readonly usageQuotaPolicy: IUsageQuotaPolicy;
|
||||
|
||||
constructor({
|
||||
projectsRepository,
|
||||
projectActionAuthorizationPolicy,
|
||||
usageQuotaPolicy,
|
||||
}: {
|
||||
projectsRepository: IProjectsRepository,
|
||||
projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy,
|
||||
usageQuotaPolicy: IUsageQuotaPolicy,
|
||||
}) {
|
||||
this.projectsRepository = projectsRepository;
|
||||
this.projectActionAuthorizationPolicy = projectActionAuthorizationPolicy;
|
||||
this.usageQuotaPolicy = usageQuotaPolicy;
|
||||
}
|
||||
|
||||
async execute(request: z.infer<typeof InputSchema>): Promise<z.infer<typeof ZCreateConnectedAccountResponse>> {
|
||||
const { caller, userId, apiKey, projectId, toolkitSlug, callbackUrl } = request;
|
||||
|
||||
await this.projectActionAuthorizationPolicy.authorize({ caller, userId, apiKey, projectId });
|
||||
await this.usageQuotaPolicy.assertAndConsume(projectId);
|
||||
|
||||
// fetch managed auth configs
|
||||
const configs = await listAuthConfigs(toolkitSlug, null, true);
|
||||
|
||||
// check if managed oauth2 config exists or create one
|
||||
let authConfigId: string | undefined = undefined;
|
||||
const managedOauth2 = configs.items.find(cfg => cfg.auth_scheme === 'OAUTH2' && cfg.is_composio_managed);
|
||||
if (managedOauth2) {
|
||||
authConfigId = managedOauth2.id;
|
||||
} else {
|
||||
const created: z.infer<typeof ZCreateAuthConfigResponse> = await createAuthConfig({
|
||||
toolkit: { slug: toolkitSlug },
|
||||
auth_config: {
|
||||
type: 'use_composio_managed_auth',
|
||||
name: 'composio-managed-oauth2',
|
||||
},
|
||||
});
|
||||
authConfigId = created.auth_config.id;
|
||||
}
|
||||
|
||||
if (!authConfigId) {
|
||||
throw new Error(`No managed oauth2 auth config found for toolkit ${toolkitSlug}`);
|
||||
}
|
||||
|
||||
// create connected account
|
||||
const response = await createConnectedAccount({
|
||||
auth_config: { id: authConfigId },
|
||||
connection: { user_id: projectId, callback_url: callbackUrl },
|
||||
});
|
||||
|
||||
// persist to project
|
||||
const now = new Date().toISOString();
|
||||
const account: z.infer<typeof ComposioConnectedAccount> = {
|
||||
id: response.id,
|
||||
authConfigId,
|
||||
status: 'INITIATED',
|
||||
createdAt: now,
|
||||
lastUpdatedAt: now,
|
||||
};
|
||||
|
||||
await this.projectsRepository.addComposioConnectedAccount(projectId, {
|
||||
toolkitSlug,
|
||||
data: account,
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
import { z } from "zod";
|
||||
import { IProjectsRepository } from "../../repositories/projects.repository.interface";
|
||||
import { IProjectActionAuthorizationPolicy } from "../../policies/project-action-authorization.policy";
|
||||
import { IUsageQuotaPolicy } from "../../policies/usage-quota.policy.interface";
|
||||
import { ComposioConnectedAccount } from "@/src/entities/models/project";
|
||||
import { ZAuthScheme, ZCredentials, ZCreateAuthConfigResponse, ZCreateConnectedAccountResponse, ZCreateConnectedAccountRequest, createAuthConfig, createConnectedAccount } from "@/app/lib/composio/composio";
|
||||
|
||||
export const InputSchema = z.object({
|
||||
caller: z.enum(["user", "api"]),
|
||||
userId: z.string().optional(),
|
||||
apiKey: z.string().optional(),
|
||||
projectId: z.string(),
|
||||
toolkitSlug: z.string(),
|
||||
authConfig: z.object({
|
||||
authScheme: ZAuthScheme,
|
||||
credentials: ZCredentials,
|
||||
}),
|
||||
callbackUrl: z.string(),
|
||||
});
|
||||
|
||||
export interface ICreateCustomConnectedAccountUseCase {
|
||||
execute(request: z.infer<typeof InputSchema>): Promise<z.infer<typeof ZCreateConnectedAccountResponse>>;
|
||||
}
|
||||
|
||||
export class CreateCustomConnectedAccountUseCase implements ICreateCustomConnectedAccountUseCase {
|
||||
private readonly projectsRepository: IProjectsRepository;
|
||||
private readonly projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy;
|
||||
private readonly usageQuotaPolicy: IUsageQuotaPolicy;
|
||||
|
||||
constructor({
|
||||
projectsRepository,
|
||||
projectActionAuthorizationPolicy,
|
||||
usageQuotaPolicy,
|
||||
}: {
|
||||
projectsRepository: IProjectsRepository,
|
||||
projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy,
|
||||
usageQuotaPolicy: IUsageQuotaPolicy,
|
||||
}) {
|
||||
this.projectsRepository = projectsRepository;
|
||||
this.projectActionAuthorizationPolicy = projectActionAuthorizationPolicy;
|
||||
this.usageQuotaPolicy = usageQuotaPolicy;
|
||||
}
|
||||
|
||||
async execute(request: z.infer<typeof InputSchema>): Promise<z.infer<typeof ZCreateConnectedAccountResponse>> {
|
||||
const { caller, userId, apiKey, projectId, toolkitSlug, authConfig, callbackUrl } = request;
|
||||
|
||||
await this.projectActionAuthorizationPolicy.authorize({ caller, userId, apiKey, projectId });
|
||||
await this.usageQuotaPolicy.assertAndConsume(projectId);
|
||||
|
||||
// create custom auth config
|
||||
const created: z.infer<typeof ZCreateAuthConfigResponse> = await createAuthConfig({
|
||||
toolkit: { slug: toolkitSlug },
|
||||
auth_config: {
|
||||
type: 'use_custom_auth',
|
||||
authScheme: authConfig.authScheme,
|
||||
credentials: authConfig.credentials,
|
||||
name: `pid-${projectId}-${Date.now()}`,
|
||||
},
|
||||
});
|
||||
|
||||
// initiate connected account
|
||||
let state: z.infer<typeof ZCreateConnectedAccountRequest>["connection"]["state"] = undefined;
|
||||
if (authConfig.authScheme !== 'OAUTH2') {
|
||||
state = {
|
||||
authScheme: authConfig.authScheme,
|
||||
val: { status: 'ACTIVE', ...authConfig.credentials },
|
||||
} as any;
|
||||
}
|
||||
|
||||
const response = await createConnectedAccount({
|
||||
auth_config: { id: created.auth_config.id },
|
||||
connection: {
|
||||
state,
|
||||
user_id: projectId,
|
||||
callback_url: callbackUrl,
|
||||
},
|
||||
});
|
||||
|
||||
// persist to project
|
||||
const now = new Date().toISOString();
|
||||
const account: z.infer<typeof ComposioConnectedAccount> = {
|
||||
id: response.id,
|
||||
authConfigId: created.auth_config.id,
|
||||
status: 'INITIATED',
|
||||
createdAt: now,
|
||||
lastUpdatedAt: now,
|
||||
};
|
||||
|
||||
await this.projectsRepository.addComposioConnectedAccount(projectId, {
|
||||
toolkitSlug,
|
||||
data: account,
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
import { z } from "zod";
|
||||
import crypto from 'crypto';
|
||||
import { IProjectsRepository } from "../../repositories/projects.repository.interface";
|
||||
import { IUsageQuotaPolicy } from "../../policies/usage-quota.policy.interface";
|
||||
import { BadRequestError, BillingError } from "@/src/entities/errors/common";
|
||||
import { IProjectMembersRepository } from "../../repositories/project-members.repository.interface";
|
||||
import { authorize, getCustomerForUserId } from "@/app/lib/billing";
|
||||
import { USE_BILLING } from "@/app/lib/feature_flags";
|
||||
import { Project } from "@/src/entities/models/project";
|
||||
import { Workflow } from "@/app/lib/types/workflow_types";
|
||||
import { templates } from "@/app/lib/project_templates";
|
||||
|
||||
export const Mode = z.union([
|
||||
z.object({
|
||||
template: z.string(),
|
||||
}),
|
||||
z.object({
|
||||
workflowJson: z.string(),
|
||||
}),
|
||||
])
|
||||
|
||||
export const InputSchema = z.object({
|
||||
userId: z.string(),
|
||||
data: z.object({
|
||||
name: z.string().optional(),
|
||||
mode: Mode,
|
||||
}),
|
||||
});
|
||||
|
||||
const workflowSchema = Workflow.omit({ lastUpdatedAt: true });
|
||||
|
||||
export interface ICreateProjectUseCase {
|
||||
execute(request: z.infer<typeof InputSchema>): Promise<z.infer<typeof Project>>;
|
||||
}
|
||||
|
||||
export class CreateProjectUseCase implements ICreateProjectUseCase {
|
||||
private readonly projectsRepository: IProjectsRepository;
|
||||
private readonly projectMembersRepository: IProjectMembersRepository;
|
||||
private readonly usageQuotaPolicy: IUsageQuotaPolicy;
|
||||
|
||||
constructor({
|
||||
projectsRepository,
|
||||
projectMembersRepository,
|
||||
usageQuotaPolicy,
|
||||
}: {
|
||||
projectsRepository: IProjectsRepository,
|
||||
projectMembersRepository: IProjectMembersRepository,
|
||||
usageQuotaPolicy: IUsageQuotaPolicy,
|
||||
}) {
|
||||
this.projectsRepository = projectsRepository;
|
||||
this.projectMembersRepository = projectMembersRepository;
|
||||
this.usageQuotaPolicy = usageQuotaPolicy;
|
||||
}
|
||||
|
||||
async execute(request: z.infer<typeof InputSchema>): Promise<z.infer<typeof Project>> {
|
||||
// fetch current project count for this user
|
||||
const count = await this.projectsRepository.countCreatedProjects(request.userId);
|
||||
|
||||
// Check billing auth
|
||||
if (USE_BILLING) {
|
||||
// get billing customer id for project
|
||||
const customer = await getCustomerForUserId(request.userId);
|
||||
if (!customer) {
|
||||
throw new BillingError("User has no billing customer id");
|
||||
}
|
||||
|
||||
// validate enough credits
|
||||
const result = await authorize(customer._id, {
|
||||
type: "create_project",
|
||||
data: {
|
||||
existingProjectCount: count,
|
||||
},
|
||||
});
|
||||
if (!result.success) {
|
||||
throw new BillingError(result.error || 'Billing error');
|
||||
}
|
||||
}
|
||||
|
||||
// generate workflow based on input
|
||||
let workflow: z.infer<typeof workflowSchema>;
|
||||
if ('template' in request.data.mode) {
|
||||
const template = templates[request.data.mode.template] || templates.default;
|
||||
workflow = {
|
||||
agents: template.agents,
|
||||
prompts: template.prompts,
|
||||
tools: template.tools,
|
||||
startAgent: template.startAgent,
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
workflow = workflowSchema.parse(JSON.parse(request.data.mode.workflowJson));
|
||||
} catch (error) {
|
||||
throw new BadRequestError('Invalid workflow JSON');
|
||||
}
|
||||
}
|
||||
|
||||
// create project secret
|
||||
const secret = crypto.randomBytes(32).toString('hex');
|
||||
|
||||
// create project
|
||||
const project = await this.projectsRepository.create({
|
||||
...request.data,
|
||||
workflow,
|
||||
createdByUserId: request.userId,
|
||||
name: request.data.name || `Assistant ${count + 1}`,
|
||||
secret,
|
||||
});
|
||||
|
||||
// create membership
|
||||
await this.projectMembersRepository.create({
|
||||
projectId: project.id,
|
||||
userId: request.userId,
|
||||
});
|
||||
|
||||
// assert and consume quota
|
||||
await this.usageQuotaPolicy.assertAndConsume(project.id);
|
||||
|
||||
return project;
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,6 @@ const inputSchema = z.object({
|
|||
apiKey: z.string().optional(),
|
||||
projectId: z.string(),
|
||||
toolkitSlug: z.string(),
|
||||
connectedAccountId: z.string(),
|
||||
});
|
||||
|
||||
export interface IDeleteComposioConnectedAccountUseCase {
|
||||
|
|
@ -67,19 +66,19 @@ export class DeleteComposioConnectedAccountUseCase implements IDeleteComposioCon
|
|||
|
||||
// ensure connected account exists
|
||||
const account = project.composioConnectedAccounts?.[request.toolkitSlug];
|
||||
if (!account || account.id !== request.connectedAccountId) {
|
||||
if (!account) {
|
||||
throw new BadRequestError('Invalid connected account');
|
||||
}
|
||||
|
||||
// delete the connected account from composio
|
||||
// this will also delete any trigger instances associated with the connected account
|
||||
const result = await deleteConnectedAccount(request.connectedAccountId);
|
||||
const result = await deleteConnectedAccount(account.id);
|
||||
if (!result.success) {
|
||||
throw new Error(`Failed to delete connected account ${request.connectedAccountId}`);
|
||||
throw new Error(`Failed to delete connected account ${account.id}`);
|
||||
}
|
||||
|
||||
// delete trigger deployments data from db
|
||||
await this.composioTriggerDeploymentsRepository.deleteByConnectedAccountId(request.connectedAccountId);
|
||||
await this.composioTriggerDeploymentsRepository.deleteByConnectedAccountId(account.id);
|
||||
|
||||
// get auth config data
|
||||
const authConfig = await getAuthConfig(account.authConfigId);
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
import { z } from "zod";
|
||||
import { IProjectsRepository } from "../../repositories/projects.repository.interface";
|
||||
import { IProjectMembersRepository } from "../../repositories/project-members.repository.interface";
|
||||
import { IProjectActionAuthorizationPolicy } from "../../policies/project-action-authorization.policy";
|
||||
import { IApiKeysRepository } from "../../repositories/api-keys.repository.interface";
|
||||
import { IDataSourceDocsRepository } from "../../repositories/data-source-docs.repository.interface";
|
||||
import { IDataSourcesRepository } from "../../repositories/data-sources.repository.interface";
|
||||
import { qdrantClient } from "@/app/lib/qdrant";
|
||||
|
||||
export const InputSchema = z.object({
|
||||
projectId: z.string(),
|
||||
userId: z.string(),
|
||||
caller: z.enum(["user", "api"]),
|
||||
apiKey: z.string().optional(),
|
||||
});
|
||||
|
||||
export interface IDeleteProjectUseCase {
|
||||
execute(request: z.infer<typeof InputSchema>): Promise<void>;
|
||||
}
|
||||
|
||||
export class DeleteProjectUseCase implements IDeleteProjectUseCase {
|
||||
private readonly projectsRepository: IProjectsRepository;
|
||||
private readonly projectMembersRepository: IProjectMembersRepository;
|
||||
private readonly projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy;
|
||||
private readonly apiKeysRepository: IApiKeysRepository;
|
||||
private readonly dataSourceDocsRepository: IDataSourceDocsRepository;
|
||||
private readonly dataSourcesRepository: IDataSourcesRepository;
|
||||
|
||||
constructor({ projectsRepository, projectMembersRepository, projectActionAuthorizationPolicy, apiKeysRepository, dataSourceDocsRepository, dataSourcesRepository}: {
|
||||
projectsRepository: IProjectsRepository,
|
||||
projectMembersRepository: IProjectMembersRepository,
|
||||
projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy,
|
||||
apiKeysRepository: IApiKeysRepository,
|
||||
dataSourceDocsRepository: IDataSourceDocsRepository,
|
||||
dataSourcesRepository: IDataSourcesRepository,
|
||||
}) {
|
||||
this.projectsRepository = projectsRepository;
|
||||
this.projectMembersRepository = projectMembersRepository;
|
||||
this.projectActionAuthorizationPolicy = projectActionAuthorizationPolicy;
|
||||
this.apiKeysRepository = apiKeysRepository;
|
||||
this.dataSourceDocsRepository = dataSourceDocsRepository;
|
||||
this.dataSourcesRepository = dataSourcesRepository;
|
||||
}
|
||||
|
||||
async execute(request: z.infer<typeof InputSchema>): Promise<void> {
|
||||
const { projectId, userId, caller, apiKey } = request;
|
||||
await this.projectActionAuthorizationPolicy.authorize({
|
||||
caller,
|
||||
userId,
|
||||
apiKey,
|
||||
projectId,
|
||||
});
|
||||
|
||||
// delete memberships
|
||||
await this.projectMembersRepository.deleteByProjectId(projectId);
|
||||
|
||||
// delete api keys
|
||||
await this.apiKeysRepository.deleteAll(projectId);
|
||||
|
||||
// delete data sources data
|
||||
await this.dataSourceDocsRepository.deleteByProjectId(projectId);
|
||||
await this.dataSourcesRepository.deleteByProjectId(projectId);
|
||||
await qdrantClient.delete("embeddings", {
|
||||
filter: {
|
||||
must: [
|
||||
{ key: "projectId", match: { value: projectId } },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
// delete project members
|
||||
await this.projectMembersRepository.deleteByProjectId(projectId);
|
||||
|
||||
// delete project
|
||||
await this.projectsRepository.delete(projectId);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
import z from "zod";
|
||||
import { Project } from "@/src/entities/models/project";
|
||||
import { IProjectsRepository } from "../../repositories/projects.repository.interface";
|
||||
import { IProjectMembersRepository } from "../../repositories/project-members.repository.interface";
|
||||
import { IProjectActionAuthorizationPolicy } from "../../policies/project-action-authorization.policy";
|
||||
import { IUsageQuotaPolicy } from "../../policies/usage-quota.policy.interface";
|
||||
|
||||
const inputSchema = z.object({
|
||||
caller: z.enum(["user", "api"]),
|
||||
userId: z.string().optional(),
|
||||
apiKey: z.string().optional(),
|
||||
projectId: z.string(),
|
||||
});
|
||||
|
||||
export interface IFetchProjectUseCase {
|
||||
execute(request: z.infer<typeof inputSchema>): Promise<z.infer<typeof Project> | null>;
|
||||
}
|
||||
|
||||
export class FetchProjectUseCase implements IFetchProjectUseCase {
|
||||
private readonly projectsRepository: IProjectsRepository;
|
||||
private readonly projectMembersRepository: IProjectMembersRepository;
|
||||
private readonly projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy;
|
||||
private readonly usageQuotaPolicy: IUsageQuotaPolicy;
|
||||
|
||||
constructor({
|
||||
projectsRepository,
|
||||
projectMembersRepository,
|
||||
projectActionAuthorizationPolicy,
|
||||
usageQuotaPolicy,
|
||||
}: {
|
||||
projectsRepository: IProjectsRepository,
|
||||
projectMembersRepository: IProjectMembersRepository,
|
||||
projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy,
|
||||
usageQuotaPolicy: IUsageQuotaPolicy,
|
||||
}) {
|
||||
this.projectsRepository = projectsRepository;
|
||||
this.projectMembersRepository = projectMembersRepository;
|
||||
this.projectActionAuthorizationPolicy = projectActionAuthorizationPolicy;
|
||||
this.usageQuotaPolicy = usageQuotaPolicy;
|
||||
}
|
||||
|
||||
async execute(request: z.infer<typeof inputSchema>): Promise<z.infer<typeof Project> | null> {
|
||||
// extract projectid from conversation
|
||||
const { projectId } = request;
|
||||
|
||||
// authz check
|
||||
await this.projectActionAuthorizationPolicy.authorize({
|
||||
caller: request.caller,
|
||||
userId: request.userId,
|
||||
apiKey: request.apiKey,
|
||||
projectId,
|
||||
});
|
||||
|
||||
// assert and consume quota
|
||||
await this.usageQuotaPolicy.assertAndConsume(projectId);
|
||||
|
||||
return await this.projectsRepository.fetch(projectId);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import { z } from "zod";
|
||||
import { IProjectActionAuthorizationPolicy } from "../../policies/project-action-authorization.policy";
|
||||
import { IUsageQuotaPolicy } from "../../policies/usage-quota.policy.interface";
|
||||
import { ZGetToolkitResponse, getToolkit } from "@/app/lib/composio/composio";
|
||||
|
||||
export const InputSchema = z.object({
|
||||
caller: z.enum(["user", "api"]),
|
||||
userId: z.string().optional(),
|
||||
apiKey: z.string().optional(),
|
||||
projectId: z.string(),
|
||||
toolkitSlug: z.string(),
|
||||
});
|
||||
|
||||
export interface IGetComposioToolkitUseCase {
|
||||
execute(request: z.infer<typeof InputSchema>): Promise<z.infer<typeof ZGetToolkitResponse>>;
|
||||
}
|
||||
|
||||
export class GetComposioToolkitUseCase implements IGetComposioToolkitUseCase {
|
||||
private readonly projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy;
|
||||
private readonly usageQuotaPolicy: IUsageQuotaPolicy;
|
||||
|
||||
constructor({ projectActionAuthorizationPolicy, usageQuotaPolicy }: { projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy, usageQuotaPolicy: IUsageQuotaPolicy }) {
|
||||
this.projectActionAuthorizationPolicy = projectActionAuthorizationPolicy;
|
||||
this.usageQuotaPolicy = usageQuotaPolicy;
|
||||
}
|
||||
|
||||
async execute(request: z.infer<typeof InputSchema>): Promise<z.infer<typeof ZGetToolkitResponse>> {
|
||||
const { caller, userId, apiKey, projectId, toolkitSlug } = request;
|
||||
await this.projectActionAuthorizationPolicy.authorize({ caller, userId, apiKey, projectId });
|
||||
await this.usageQuotaPolicy.assertAndConsume(projectId);
|
||||
return await getToolkit(toolkitSlug);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import { z } from "zod";
|
||||
import { IProjectActionAuthorizationPolicy } from "../../policies/project-action-authorization.policy";
|
||||
import { IUsageQuotaPolicy } from "../../policies/usage-quota.policy.interface";
|
||||
import { ZToolkit, ZListResponse, listToolkits } from "@/app/lib/composio/composio";
|
||||
|
||||
export const InputSchema = z.object({
|
||||
caller: z.enum(["user", "api"]),
|
||||
userId: z.string().optional(),
|
||||
apiKey: z.string().optional(),
|
||||
projectId: z.string(),
|
||||
cursor: z.string().nullable().optional(),
|
||||
});
|
||||
|
||||
export interface IListComposioToolkitsUseCase {
|
||||
execute(request: z.infer<typeof InputSchema>): Promise<z.infer<ReturnType<typeof ZListResponse<typeof ZToolkit>>>>;
|
||||
}
|
||||
|
||||
export class ListComposioToolkitsUseCase implements IListComposioToolkitsUseCase {
|
||||
private readonly projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy;
|
||||
private readonly usageQuotaPolicy: IUsageQuotaPolicy;
|
||||
|
||||
constructor({ projectActionAuthorizationPolicy, usageQuotaPolicy }: { projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy, usageQuotaPolicy: IUsageQuotaPolicy }) {
|
||||
this.projectActionAuthorizationPolicy = projectActionAuthorizationPolicy;
|
||||
this.usageQuotaPolicy = usageQuotaPolicy;
|
||||
}
|
||||
|
||||
async execute(request: z.infer<typeof InputSchema>): Promise<z.infer<ReturnType<typeof ZListResponse<typeof ZToolkit>>>> {
|
||||
const { caller, userId, apiKey, projectId, cursor } = request;
|
||||
await this.projectActionAuthorizationPolicy.authorize({ caller, userId, apiKey, projectId });
|
||||
await this.usageQuotaPolicy.assertAndConsume(projectId);
|
||||
return await listToolkits(cursor ?? null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
import { z } from "zod";
|
||||
import { IProjectActionAuthorizationPolicy } from "../../policies/project-action-authorization.policy";
|
||||
import { IUsageQuotaPolicy } from "../../policies/usage-quota.policy.interface";
|
||||
import { ZTool, ZListResponse, listTools } from "@/app/lib/composio/composio";
|
||||
|
||||
export const InputSchema = z.object({
|
||||
caller: z.enum(["user", "api"]),
|
||||
userId: z.string().optional(),
|
||||
apiKey: z.string().optional(),
|
||||
projectId: z.string(),
|
||||
toolkitSlug: z.string(),
|
||||
searchQuery: z.string().nullable().optional(),
|
||||
cursor: z.string().nullable().optional(),
|
||||
});
|
||||
|
||||
export interface IListComposioToolsUseCase {
|
||||
execute(request: z.infer<typeof InputSchema>): Promise<z.infer<ReturnType<typeof ZListResponse<typeof ZTool>>>>;
|
||||
}
|
||||
|
||||
export class ListComposioToolsUseCase implements IListComposioToolsUseCase {
|
||||
private readonly projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy;
|
||||
private readonly usageQuotaPolicy: IUsageQuotaPolicy;
|
||||
|
||||
constructor({ projectActionAuthorizationPolicy, usageQuotaPolicy }: { projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy, usageQuotaPolicy: IUsageQuotaPolicy }) {
|
||||
this.projectActionAuthorizationPolicy = projectActionAuthorizationPolicy;
|
||||
this.usageQuotaPolicy = usageQuotaPolicy;
|
||||
}
|
||||
|
||||
async execute(request: z.infer<typeof InputSchema>): Promise<z.infer<ReturnType<typeof ZListResponse<typeof ZTool>>>> {
|
||||
const { caller, userId, apiKey, projectId, toolkitSlug, searchQuery, cursor } = request;
|
||||
await this.projectActionAuthorizationPolicy.authorize({ caller, userId, apiKey, projectId });
|
||||
await this.usageQuotaPolicy.assertAndConsume(projectId);
|
||||
return await listTools(toolkitSlug, searchQuery ?? null, cursor ?? null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
import { z } from "zod";
|
||||
import { IProjectsRepository } from "../../repositories/projects.repository.interface";
|
||||
import { Project } from "@/src/entities/models/project";
|
||||
import { PaginatedList } from "@/src/entities/common/paginated-list";
|
||||
|
||||
export const InputSchema = z.object({
|
||||
userId: z.string(),
|
||||
cursor: z.string().optional(),
|
||||
limit: z.number().optional(),
|
||||
});
|
||||
|
||||
export interface IListProjectsUseCase {
|
||||
execute(request: z.infer<typeof InputSchema>): Promise<z.infer<ReturnType<typeof PaginatedList<typeof Project>>>>;
|
||||
}
|
||||
|
||||
export class ListProjectsUseCase implements IListProjectsUseCase {
|
||||
private readonly projectsRepository: IProjectsRepository;
|
||||
|
||||
constructor({
|
||||
projectsRepository,
|
||||
}: {
|
||||
projectsRepository: IProjectsRepository,
|
||||
}) {
|
||||
this.projectsRepository = projectsRepository;
|
||||
}
|
||||
|
||||
async execute(request: z.infer<typeof InputSchema>): Promise<z.infer<ReturnType<typeof PaginatedList<typeof Project>>>> {
|
||||
const { userId, cursor, limit } = request;
|
||||
|
||||
// fetch projects for user
|
||||
return await this.projectsRepository.listProjects(userId, cursor, limit);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
import { z } from "zod";
|
||||
import { IProjectsRepository } from "../../repositories/projects.repository.interface";
|
||||
import { IProjectActionAuthorizationPolicy } from "../../policies/project-action-authorization.policy";
|
||||
import { IUsageQuotaPolicy } from "../../policies/usage-quota.policy.interface";
|
||||
|
||||
export const InputSchema = z.object({
|
||||
caller: z.enum(["user", "api"]),
|
||||
userId: z.string().optional(),
|
||||
apiKey: z.string().optional(),
|
||||
projectId: z.string(),
|
||||
name: z.string(),
|
||||
});
|
||||
|
||||
export interface IRemoveCustomMcpServerUseCase {
|
||||
execute(request: z.infer<typeof InputSchema>): Promise<void>;
|
||||
}
|
||||
|
||||
export class RemoveCustomMcpServerUseCase implements IRemoveCustomMcpServerUseCase {
|
||||
private readonly projectsRepository: IProjectsRepository;
|
||||
private readonly projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy;
|
||||
private readonly usageQuotaPolicy: IUsageQuotaPolicy;
|
||||
|
||||
constructor({
|
||||
projectsRepository,
|
||||
projectActionAuthorizationPolicy,
|
||||
usageQuotaPolicy,
|
||||
}: {
|
||||
projectsRepository: IProjectsRepository,
|
||||
projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy,
|
||||
usageQuotaPolicy: IUsageQuotaPolicy,
|
||||
}) {
|
||||
this.projectsRepository = projectsRepository;
|
||||
this.projectActionAuthorizationPolicy = projectActionAuthorizationPolicy;
|
||||
this.usageQuotaPolicy = usageQuotaPolicy;
|
||||
}
|
||||
|
||||
async execute(request: z.infer<typeof InputSchema>): Promise<void> {
|
||||
const { caller, userId, apiKey, projectId, name } = request;
|
||||
await this.projectActionAuthorizationPolicy.authorize({ caller, userId, apiKey, projectId });
|
||||
await this.usageQuotaPolicy.assertAndConsume(projectId);
|
||||
await this.projectsRepository.deleteCustomMcpServer(projectId, name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
import { z } from "zod";
|
||||
import { IProjectsRepository } from "../../repositories/projects.repository.interface";
|
||||
import { IProjectActionAuthorizationPolicy } from "../../policies/project-action-authorization.policy";
|
||||
import { IUsageQuotaPolicy } from "../../policies/usage-quota.policy.interface";
|
||||
import { NotFoundError, BadRequestError } from "@/src/entities/errors/common";
|
||||
|
||||
export const InputSchema = z.object({
|
||||
caller: z.enum(["user", "api"]),
|
||||
userId: z.string().optional(),
|
||||
apiKey: z.string().optional(),
|
||||
projectId: z.string(),
|
||||
});
|
||||
|
||||
export interface IRevertToLiveWorkflowUseCase {
|
||||
execute(request: z.infer<typeof InputSchema>): Promise<void>;
|
||||
}
|
||||
|
||||
export class RevertToLiveWorkflowUseCase implements IRevertToLiveWorkflowUseCase {
|
||||
private readonly projectsRepository: IProjectsRepository;
|
||||
private readonly projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy;
|
||||
private readonly usageQuotaPolicy: IUsageQuotaPolicy;
|
||||
|
||||
constructor({
|
||||
projectsRepository,
|
||||
projectActionAuthorizationPolicy,
|
||||
usageQuotaPolicy,
|
||||
}: {
|
||||
projectsRepository: IProjectsRepository,
|
||||
projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy,
|
||||
usageQuotaPolicy: IUsageQuotaPolicy,
|
||||
}) {
|
||||
this.projectsRepository = projectsRepository;
|
||||
this.projectActionAuthorizationPolicy = projectActionAuthorizationPolicy;
|
||||
this.usageQuotaPolicy = usageQuotaPolicy;
|
||||
}
|
||||
|
||||
async execute(request: z.infer<typeof InputSchema>): Promise<void> {
|
||||
const { projectId } = request;
|
||||
await this.projectActionAuthorizationPolicy.authorize({
|
||||
caller: request.caller,
|
||||
userId: request.userId,
|
||||
apiKey: request.apiKey,
|
||||
projectId,
|
||||
});
|
||||
await this.usageQuotaPolicy.assertAndConsume(projectId);
|
||||
|
||||
const project = await this.projectsRepository.fetch(projectId);
|
||||
if (!project) {
|
||||
throw new NotFoundError("Project not found");
|
||||
}
|
||||
const live = project.liveWorkflow;
|
||||
if (!live) {
|
||||
throw new BadRequestError("No live workflow found");
|
||||
}
|
||||
const draft = { ...live, lastUpdatedAt: new Date().toISOString() };
|
||||
await this.projectsRepository.updateDraftWorkflow(projectId, draft);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import { z } from "zod";
|
||||
import crypto from "crypto";
|
||||
import { IProjectsRepository } from "../../repositories/projects.repository.interface";
|
||||
import { IProjectActionAuthorizationPolicy } from "../../policies/project-action-authorization.policy";
|
||||
import { IUsageQuotaPolicy } from "../../policies/usage-quota.policy.interface";
|
||||
|
||||
export const InputSchema = z.object({
|
||||
projectId: z.string(),
|
||||
userId: z.string(),
|
||||
caller: z.enum(["user", "api"]),
|
||||
apiKey: z.string().optional(),
|
||||
});
|
||||
|
||||
export interface IRotateSecretUseCase {
|
||||
execute(request: z.infer<typeof InputSchema>): Promise<string>;
|
||||
}
|
||||
|
||||
export class RotateSecretUseCase implements IRotateSecretUseCase {
|
||||
private readonly projectsRepository: IProjectsRepository;
|
||||
private readonly projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy;
|
||||
private readonly usageQuotaPolicy: IUsageQuotaPolicy;
|
||||
|
||||
constructor({ projectsRepository, projectActionAuthorizationPolicy, usageQuotaPolicy }: { projectsRepository: IProjectsRepository, projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy, usageQuotaPolicy: IUsageQuotaPolicy }) {
|
||||
this.projectsRepository = projectsRepository;
|
||||
this.projectActionAuthorizationPolicy = projectActionAuthorizationPolicy;
|
||||
this.usageQuotaPolicy = usageQuotaPolicy;
|
||||
}
|
||||
|
||||
async execute(request: z.infer<typeof InputSchema>): Promise<string> {
|
||||
const { projectId, userId, caller, apiKey } = request;
|
||||
// project-level authz check
|
||||
await this.projectActionAuthorizationPolicy.authorize({
|
||||
caller,
|
||||
userId,
|
||||
apiKey,
|
||||
projectId,
|
||||
});
|
||||
|
||||
// assert and consume quota
|
||||
await this.usageQuotaPolicy.assertAndConsume(projectId);
|
||||
|
||||
const secret = crypto.randomBytes(32).toString("hex");
|
||||
await this.projectsRepository.updateSecret(projectId, secret);
|
||||
return secret;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
import { z } from "zod";
|
||||
import { IProjectsRepository } from "../../repositories/projects.repository.interface";
|
||||
import { IProjectActionAuthorizationPolicy } from "../../policies/project-action-authorization.policy";
|
||||
import { IUsageQuotaPolicy } from "../../policies/usage-quota.policy.interface";
|
||||
import { ComposioConnectedAccount } from "@/src/entities/models/project";
|
||||
import { ZConnectedAccount, getConnectedAccount } from "@/app/lib/composio/composio";
|
||||
|
||||
export const InputSchema = z.object({
|
||||
caller: z.enum(["user", "api"]),
|
||||
userId: z.string().optional(),
|
||||
apiKey: z.string().optional(),
|
||||
projectId: z.string(),
|
||||
toolkitSlug: z.string(),
|
||||
connectedAccountId: z.string(),
|
||||
});
|
||||
|
||||
export interface ISyncConnectedAccountUseCase {
|
||||
execute(request: z.infer<typeof InputSchema>): Promise<z.infer<typeof ComposioConnectedAccount>>;
|
||||
}
|
||||
|
||||
export class SyncConnectedAccountUseCase implements ISyncConnectedAccountUseCase {
|
||||
private readonly projectsRepository: IProjectsRepository;
|
||||
private readonly projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy;
|
||||
private readonly usageQuotaPolicy: IUsageQuotaPolicy;
|
||||
|
||||
constructor({
|
||||
projectsRepository,
|
||||
projectActionAuthorizationPolicy,
|
||||
usageQuotaPolicy,
|
||||
}: {
|
||||
projectsRepository: IProjectsRepository,
|
||||
projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy,
|
||||
usageQuotaPolicy: IUsageQuotaPolicy,
|
||||
}) {
|
||||
this.projectsRepository = projectsRepository;
|
||||
this.projectActionAuthorizationPolicy = projectActionAuthorizationPolicy;
|
||||
this.usageQuotaPolicy = usageQuotaPolicy;
|
||||
}
|
||||
|
||||
async execute(request: z.infer<typeof InputSchema>): Promise<z.infer<typeof ComposioConnectedAccount>> {
|
||||
const { caller, userId, apiKey, projectId, toolkitSlug, connectedAccountId } = request;
|
||||
|
||||
await this.projectActionAuthorizationPolicy.authorize({ caller, userId, apiKey, projectId });
|
||||
await this.usageQuotaPolicy.assertAndConsume(projectId);
|
||||
|
||||
// fetch project & account to verify
|
||||
const project = await this.projectsRepository.fetch(projectId);
|
||||
if (!project) {
|
||||
throw new Error('Project not found');
|
||||
}
|
||||
const account = project.composioConnectedAccounts?.[toolkitSlug];
|
||||
if (!account || account.id !== connectedAccountId) {
|
||||
throw new Error(`Connected account ${connectedAccountId} not found in project ${projectId}`);
|
||||
}
|
||||
|
||||
if (account.status === 'ACTIVE') {
|
||||
return account;
|
||||
}
|
||||
|
||||
// get latest status from Composio
|
||||
const response = await getConnectedAccount(connectedAccountId);
|
||||
|
||||
const updated: z.infer<typeof ComposioConnectedAccount> = {
|
||||
...account,
|
||||
status: (() => {
|
||||
switch (response.status) {
|
||||
case 'INITIALIZING':
|
||||
case 'INITIATED':
|
||||
return 'INITIATED' as const;
|
||||
case 'ACTIVE':
|
||||
return 'ACTIVE' as const;
|
||||
default:
|
||||
return 'FAILED' as const;
|
||||
}
|
||||
})(),
|
||||
lastUpdatedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
await this.projectsRepository.addComposioConnectedAccount(projectId, {
|
||||
toolkitSlug,
|
||||
data: updated,
|
||||
});
|
||||
|
||||
return updated;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
import { z } from "zod";
|
||||
import { IProjectsRepository } from "../../repositories/projects.repository.interface";
|
||||
import { IProjectActionAuthorizationPolicy } from "../../policies/project-action-authorization.policy";
|
||||
import { IUsageQuotaPolicy } from "../../policies/usage-quota.policy.interface";
|
||||
import { Workflow } from "@/app/lib/types/workflow_types";
|
||||
|
||||
export const InputSchema = z.object({
|
||||
caller: z.enum(["user", "api"]),
|
||||
userId: z.string().optional(),
|
||||
apiKey: z.string().optional(),
|
||||
projectId: z.string(),
|
||||
workflow: Workflow,
|
||||
});
|
||||
|
||||
export interface IUpdateDraftWorkflowUseCase {
|
||||
execute(request: z.infer<typeof InputSchema>): Promise<void>;
|
||||
}
|
||||
|
||||
export class UpdateDraftWorkflowUseCase implements IUpdateDraftWorkflowUseCase {
|
||||
private readonly projectsRepository: IProjectsRepository;
|
||||
private readonly projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy;
|
||||
private readonly usageQuotaPolicy: IUsageQuotaPolicy;
|
||||
|
||||
constructor({
|
||||
projectsRepository,
|
||||
projectActionAuthorizationPolicy,
|
||||
usageQuotaPolicy,
|
||||
}: {
|
||||
projectsRepository: IProjectsRepository,
|
||||
projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy,
|
||||
usageQuotaPolicy: IUsageQuotaPolicy,
|
||||
}) {
|
||||
this.projectsRepository = projectsRepository;
|
||||
this.projectActionAuthorizationPolicy = projectActionAuthorizationPolicy;
|
||||
this.usageQuotaPolicy = usageQuotaPolicy;
|
||||
}
|
||||
|
||||
async execute(request: z.infer<typeof InputSchema>): Promise<void> {
|
||||
const { projectId } = request;
|
||||
await this.projectActionAuthorizationPolicy.authorize({
|
||||
caller: request.caller,
|
||||
userId: request.userId,
|
||||
apiKey: request.apiKey,
|
||||
projectId,
|
||||
});
|
||||
await this.usageQuotaPolicy.assertAndConsume(projectId);
|
||||
|
||||
const workflow = { ...request.workflow, lastUpdatedAt: new Date().toISOString() } as z.infer<typeof Workflow>;
|
||||
await this.projectsRepository.updateDraftWorkflow(projectId, workflow);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
import { z } from "zod";
|
||||
import { IProjectsRepository } from "../../repositories/projects.repository.interface";
|
||||
import { IProjectActionAuthorizationPolicy } from "../../policies/project-action-authorization.policy";
|
||||
import { IUsageQuotaPolicy } from "../../policies/usage-quota.policy.interface";
|
||||
import { Workflow } from "@/app/lib/types/workflow_types";
|
||||
|
||||
export const InputSchema = z.object({
|
||||
caller: z.enum(["user", "api"]),
|
||||
userId: z.string().optional(),
|
||||
apiKey: z.string().optional(),
|
||||
projectId: z.string(),
|
||||
workflow: Workflow,
|
||||
});
|
||||
|
||||
export interface IUpdateLiveWorkflowUseCase {
|
||||
execute(request: z.infer<typeof InputSchema>): Promise<void>;
|
||||
}
|
||||
|
||||
export class UpdateLiveWorkflowUseCase implements IUpdateLiveWorkflowUseCase {
|
||||
private readonly projectsRepository: IProjectsRepository;
|
||||
private readonly projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy;
|
||||
private readonly usageQuotaPolicy: IUsageQuotaPolicy;
|
||||
|
||||
constructor({
|
||||
projectsRepository,
|
||||
projectActionAuthorizationPolicy,
|
||||
usageQuotaPolicy,
|
||||
}: {
|
||||
projectsRepository: IProjectsRepository,
|
||||
projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy,
|
||||
usageQuotaPolicy: IUsageQuotaPolicy,
|
||||
}) {
|
||||
this.projectsRepository = projectsRepository;
|
||||
this.projectActionAuthorizationPolicy = projectActionAuthorizationPolicy;
|
||||
this.usageQuotaPolicy = usageQuotaPolicy;
|
||||
}
|
||||
|
||||
async execute(request: z.infer<typeof InputSchema>): Promise<void> {
|
||||
const { projectId } = request;
|
||||
await this.projectActionAuthorizationPolicy.authorize({
|
||||
caller: request.caller,
|
||||
userId: request.userId,
|
||||
apiKey: request.apiKey,
|
||||
projectId,
|
||||
});
|
||||
await this.usageQuotaPolicy.assertAndConsume(projectId);
|
||||
|
||||
const workflow = { ...request.workflow, lastUpdatedAt: new Date().toISOString() } as z.infer<typeof Workflow>;
|
||||
await this.projectsRepository.updateLiveWorkflow(projectId, workflow);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
import { z } from "zod";
|
||||
import { IProjectsRepository } from "../../repositories/projects.repository.interface";
|
||||
import { IProjectActionAuthorizationPolicy } from "../../policies/project-action-authorization.policy";
|
||||
import { IUsageQuotaPolicy } from "../../policies/usage-quota.policy.interface";
|
||||
|
||||
export const InputSchema = z.object({
|
||||
projectId: z.string(),
|
||||
userId: z.string(),
|
||||
caller: z.enum(["user", "api"]),
|
||||
apiKey: z.string().optional(),
|
||||
name: z.string(),
|
||||
});
|
||||
|
||||
export interface IUpdateProjectNameUseCase {
|
||||
execute(request: z.infer<typeof InputSchema>): Promise<void>;
|
||||
}
|
||||
|
||||
export class UpdateProjectNameUseCase implements IUpdateProjectNameUseCase {
|
||||
private readonly projectsRepository: IProjectsRepository;
|
||||
private readonly projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy;
|
||||
private readonly usageQuotaPolicy: IUsageQuotaPolicy;
|
||||
|
||||
constructor({ projectsRepository, projectActionAuthorizationPolicy, usageQuotaPolicy }: { projectsRepository: IProjectsRepository, projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy, usageQuotaPolicy: IUsageQuotaPolicy }) {
|
||||
this.projectsRepository = projectsRepository;
|
||||
this.projectActionAuthorizationPolicy = projectActionAuthorizationPolicy;
|
||||
this.usageQuotaPolicy = usageQuotaPolicy;
|
||||
}
|
||||
|
||||
async execute(request: z.infer<typeof InputSchema>): Promise<void> {
|
||||
const { projectId, userId, caller, apiKey, name } = request;
|
||||
await this.projectActionAuthorizationPolicy.authorize({
|
||||
caller,
|
||||
userId,
|
||||
apiKey,
|
||||
projectId,
|
||||
});
|
||||
|
||||
// assert and consume quota
|
||||
await this.usageQuotaPolicy.assertAndConsume(projectId);
|
||||
|
||||
await this.projectsRepository.updateName(projectId, name);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
import { z } from "zod";
|
||||
import { IProjectsRepository } from "../../repositories/projects.repository.interface";
|
||||
import { IProjectActionAuthorizationPolicy } from "../../policies/project-action-authorization.policy";
|
||||
import { IUsageQuotaPolicy } from "../../policies/usage-quota.policy.interface";
|
||||
|
||||
export const InputSchema = z.object({
|
||||
projectId: z.string(),
|
||||
userId: z.string(),
|
||||
caller: z.enum(["user", "api"]),
|
||||
apiKey: z.string().optional(),
|
||||
url: z.string(),
|
||||
});
|
||||
|
||||
export interface IUpdateWebhookUrlUseCase {
|
||||
execute(request: z.infer<typeof InputSchema>): Promise<void>;
|
||||
}
|
||||
|
||||
export class UpdateWebhookUrlUseCase implements IUpdateWebhookUrlUseCase {
|
||||
private readonly projectsRepository: IProjectsRepository;
|
||||
private readonly projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy;
|
||||
private readonly usageQuotaPolicy: IUsageQuotaPolicy;
|
||||
|
||||
constructor({ projectsRepository, projectActionAuthorizationPolicy, usageQuotaPolicy }: { projectsRepository: IProjectsRepository, projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy, usageQuotaPolicy: IUsageQuotaPolicy }) {
|
||||
this.projectsRepository = projectsRepository;
|
||||
this.projectActionAuthorizationPolicy = projectActionAuthorizationPolicy;
|
||||
this.usageQuotaPolicy = usageQuotaPolicy;
|
||||
}
|
||||
|
||||
async execute(request: z.infer<typeof InputSchema>): Promise<void> {
|
||||
const { projectId, userId, caller, apiKey, url } = request;
|
||||
await this.projectActionAuthorizationPolicy.authorize({
|
||||
caller,
|
||||
userId,
|
||||
apiKey,
|
||||
projectId,
|
||||
});
|
||||
|
||||
// assert and consume quota
|
||||
await this.usageQuotaPolicy.assertAndConsume(projectId);
|
||||
|
||||
await this.projectsRepository.updateWebhookUrl(projectId, url);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue