mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-05-08 06:42:39 +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,37 +1,32 @@
|
|||
"use server";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
listToolkits as libListToolkits,
|
||||
listTools as libListTools,
|
||||
getConnectedAccount as libGetConnectedAccount,
|
||||
listAuthConfigs as libListAuthConfigs,
|
||||
createAuthConfig as libCreateAuthConfig,
|
||||
getToolkit as libGetToolkit,
|
||||
createConnectedAccount as libCreateConnectedAccount,
|
||||
ZToolkit,
|
||||
ZGetToolkitResponse,
|
||||
ZTool,
|
||||
ZListResponse,
|
||||
ZCreateConnectedAccountResponse,
|
||||
ZAuthScheme,
|
||||
ZCredentials,
|
||||
} from "@/app/lib/composio/composio";
|
||||
import { ComposioConnectedAccount } from "@/app/lib/types/project_types";
|
||||
import { getProjectConfig, projectAuthCheck } from "./project.actions";
|
||||
import { projectsCollection } from "../lib/mongodb";
|
||||
import { ZToolkit, ZGetToolkitResponse, ZTool, ZListResponse, ZCreateConnectedAccountResponse, ZAuthScheme, ZCredentials } from "@/app/lib/composio/composio";
|
||||
import { ComposioConnectedAccount } from "@/src/entities/models/project";
|
||||
import { container } from "@/di/container";
|
||||
import { ICreateComposioTriggerDeploymentController } from "@/src/interface-adapters/controllers/composio-trigger-deployments/create-composio-trigger-deployment.controller";
|
||||
import { IListComposioTriggerDeploymentsController } from "@/src/interface-adapters/controllers/composio-trigger-deployments/list-composio-trigger-deployments.controller";
|
||||
import { IDeleteComposioTriggerDeploymentController } from "@/src/interface-adapters/controllers/composio-trigger-deployments/delete-composio-trigger-deployment.controller";
|
||||
import { IListComposioTriggerTypesController } from "@/src/interface-adapters/controllers/composio-trigger-deployments/list-composio-trigger-types.controller";
|
||||
import { IDeleteComposioConnectedAccountController } from "@/src/interface-adapters/controllers/composio/delete-composio-connected-account.controller";
|
||||
import { IDeleteComposioConnectedAccountController } from "@/src/interface-adapters/controllers/projects/delete-composio-connected-account.controller";
|
||||
import { authCheck } from "./auth.actions";
|
||||
import { ICreateComposioManagedConnectedAccountController } from "@/src/interface-adapters/controllers/projects/create-composio-managed-connected-account.controller";
|
||||
import { ICreateCustomConnectedAccountController } from "@/src/interface-adapters/controllers/projects/create-custom-connected-account.controller";
|
||||
import { ISyncConnectedAccountController } from "@/src/interface-adapters/controllers/projects/sync-connected-account.controller";
|
||||
import { IListComposioToolkitsController } from "@/src/interface-adapters/controllers/projects/list-composio-toolkits.controller";
|
||||
import { IGetComposioToolkitController } from "@/src/interface-adapters/controllers/projects/get-composio-toolkit.controller";
|
||||
import { IListComposioToolsController } from "@/src/interface-adapters/controllers/projects/list-composio-tools.controller";
|
||||
|
||||
const createComposioTriggerDeploymentController = container.resolve<ICreateComposioTriggerDeploymentController>("createComposioTriggerDeploymentController");
|
||||
const listComposioTriggerDeploymentsController = container.resolve<IListComposioTriggerDeploymentsController>("listComposioTriggerDeploymentsController");
|
||||
const deleteComposioTriggerDeploymentController = container.resolve<IDeleteComposioTriggerDeploymentController>("deleteComposioTriggerDeploymentController");
|
||||
const listComposioTriggerTypesController = container.resolve<IListComposioTriggerTypesController>("listComposioTriggerTypesController");
|
||||
const deleteComposioConnectedAccountController = container.resolve<IDeleteComposioConnectedAccountController>("deleteComposioConnectedAccountController");
|
||||
const createComposioManagedConnectedAccountController = container.resolve<ICreateComposioManagedConnectedAccountController>("createComposioManagedConnectedAccountController");
|
||||
const createCustomConnectedAccountController = container.resolve<ICreateCustomConnectedAccountController>("createCustomConnectedAccountController");
|
||||
const syncConnectedAccountController = container.resolve<ISyncConnectedAccountController>("syncConnectedAccountController");
|
||||
const listComposioToolkitsController = container.resolve<IListComposioToolkitsController>("listComposioToolkitsController");
|
||||
const getComposioToolkitController = container.resolve<IGetComposioToolkitController>("getComposioToolkitController");
|
||||
const listComposioToolsController = container.resolve<IListComposioToolsController>("listComposioToolsController");
|
||||
|
||||
const ZCreateCustomConnectedAccountRequest = z.object({
|
||||
toolkitSlug: z.string(),
|
||||
|
|
@ -43,164 +38,72 @@ const ZCreateCustomConnectedAccountRequest = z.object({
|
|||
});
|
||||
|
||||
export async function listToolkits(projectId: string, cursor: string | null = null): Promise<z.infer<ReturnType<typeof ZListResponse<typeof ZToolkit>>>> {
|
||||
await projectAuthCheck(projectId);
|
||||
return await libListToolkits(cursor);
|
||||
const user = await authCheck();
|
||||
return await listComposioToolkitsController.execute({
|
||||
caller: 'user',
|
||||
userId: user._id,
|
||||
projectId,
|
||||
cursor,
|
||||
});
|
||||
}
|
||||
|
||||
export async function getToolkit(projectId: string, toolkitSlug: string): Promise<z.infer<typeof ZGetToolkitResponse>> {
|
||||
await projectAuthCheck(projectId);
|
||||
return await libGetToolkit(toolkitSlug);
|
||||
const user = await authCheck();
|
||||
return await getComposioToolkitController.execute({
|
||||
caller: 'user',
|
||||
userId: user._id,
|
||||
projectId,
|
||||
toolkitSlug,
|
||||
});
|
||||
}
|
||||
|
||||
export async function listTools(projectId: string, toolkitSlug: string, searchQuery: string | null, cursor: string | null = null): Promise<z.infer<ReturnType<typeof ZListResponse<typeof ZTool>>>> {
|
||||
await projectAuthCheck(projectId);
|
||||
return await libListTools(toolkitSlug, searchQuery, cursor);
|
||||
const user = await authCheck();
|
||||
return await listComposioToolsController.execute({
|
||||
caller: 'user',
|
||||
userId: user._id,
|
||||
projectId,
|
||||
toolkitSlug,
|
||||
searchQuery,
|
||||
cursor,
|
||||
});
|
||||
}
|
||||
|
||||
export async function createComposioManagedOauth2ConnectedAccount(projectId: string, toolkitSlug: string, callbackUrl: string): Promise<z.infer<typeof ZCreateConnectedAccountResponse>> {
|
||||
await projectAuthCheck(projectId);
|
||||
|
||||
// fetch managed auth configs
|
||||
const configs = await libListAuthConfigs(toolkitSlug, null, true);
|
||||
|
||||
// check if managed oauth2 config exists
|
||||
let authConfigId: string | undefined = undefined;
|
||||
const authConfig = configs.items.find(config => config.auth_scheme === 'OAUTH2' && config.is_composio_managed);
|
||||
authConfigId = authConfig?.id;
|
||||
if (!authConfig) {
|
||||
// create a new managed oauth2 auth config
|
||||
const newAuthConfig = await libCreateAuthConfig({
|
||||
toolkit: {
|
||||
slug: toolkitSlug,
|
||||
},
|
||||
auth_config: {
|
||||
type: 'use_composio_managed_auth',
|
||||
name: 'composio-managed-oauth2',
|
||||
},
|
||||
});
|
||||
authConfigId = newAuthConfig.auth_config.id;
|
||||
}
|
||||
if (!authConfigId) {
|
||||
throw new Error(`No managed oauth2 auth config found for toolkit ${toolkitSlug}`);
|
||||
}
|
||||
|
||||
// create new connected account
|
||||
const response = await libCreateConnectedAccount({
|
||||
auth_config: {
|
||||
id: authConfigId,
|
||||
},
|
||||
connection: {
|
||||
user_id: projectId,
|
||||
callback_url: callbackUrl,
|
||||
},
|
||||
const user = await authCheck();
|
||||
return await createComposioManagedConnectedAccountController.execute({
|
||||
caller: 'user',
|
||||
userId: user._id,
|
||||
projectId,
|
||||
toolkitSlug,
|
||||
callbackUrl,
|
||||
});
|
||||
|
||||
// update project with new connected account
|
||||
const key = `composioConnectedAccounts.${toolkitSlug}`;
|
||||
const data: z.infer<typeof ComposioConnectedAccount> = {
|
||||
id: response.id,
|
||||
authConfigId: authConfigId,
|
||||
status: 'INITIATED',
|
||||
createdAt: new Date().toISOString(),
|
||||
lastUpdatedAt: new Date().toISOString(),
|
||||
}
|
||||
await projectsCollection.updateOne({ _id: projectId }, { $set: { [key]: data } });
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
export async function createCustomConnectedAccount(projectId: string, request: z.infer<typeof ZCreateCustomConnectedAccountRequest>): Promise<z.infer<typeof ZCreateConnectedAccountResponse>> {
|
||||
await projectAuthCheck(projectId);
|
||||
|
||||
// first, create the auth config
|
||||
const authConfig = await libCreateAuthConfig({
|
||||
toolkit: {
|
||||
slug: request.toolkitSlug,
|
||||
},
|
||||
auth_config: {
|
||||
type: 'use_custom_auth',
|
||||
authScheme: request.authConfig.authScheme,
|
||||
credentials: request.authConfig.credentials,
|
||||
name: `pid-${projectId}-${Date.now()}`,
|
||||
},
|
||||
const user = await authCheck();
|
||||
return await createCustomConnectedAccountController.execute({
|
||||
caller: 'user',
|
||||
userId: user._id,
|
||||
projectId,
|
||||
toolkitSlug: request.toolkitSlug,
|
||||
authConfig: request.authConfig,
|
||||
callbackUrl: request.callbackUrl,
|
||||
});
|
||||
|
||||
// then, create the connected account
|
||||
let state = undefined;
|
||||
if (request.authConfig.authScheme !== 'OAUTH2') {
|
||||
state = {
|
||||
authScheme: request.authConfig.authScheme,
|
||||
val: {
|
||||
status: 'ACTIVE' as const,
|
||||
...request.authConfig.credentials,
|
||||
},
|
||||
};
|
||||
}
|
||||
const response = await libCreateConnectedAccount({
|
||||
auth_config: {
|
||||
id: authConfig.auth_config.id,
|
||||
},
|
||||
connection: {
|
||||
state,
|
||||
user_id: projectId,
|
||||
callback_url: request.callbackUrl,
|
||||
},
|
||||
});
|
||||
|
||||
// update project with new connected account
|
||||
const key = `composioConnectedAccounts.${request.toolkitSlug}`;
|
||||
const data: z.infer<typeof ComposioConnectedAccount> = {
|
||||
id: response.id,
|
||||
authConfigId: authConfig.auth_config.id,
|
||||
status: 'INITIATED',
|
||||
createdAt: new Date().toISOString(),
|
||||
lastUpdatedAt: new Date().toISOString(),
|
||||
}
|
||||
await projectsCollection.updateOne({ _id: projectId }, { $set: { [key]: data } });
|
||||
|
||||
// return the connected account
|
||||
return response;
|
||||
}
|
||||
|
||||
export async function syncConnectedAccount(projectId: string, toolkitSlug: string, connectedAccountId: string): Promise<z.infer<typeof ComposioConnectedAccount>> {
|
||||
await projectAuthCheck(projectId);
|
||||
|
||||
// ensure that the connected account belongs to this project
|
||||
const project = await getProjectConfig(projectId);
|
||||
const account = project.composioConnectedAccounts?.[toolkitSlug];
|
||||
if (!account || account.id !== connectedAccountId) {
|
||||
throw new Error(`Connected account ${connectedAccountId} not found in project ${projectId}`);
|
||||
}
|
||||
|
||||
// if account is already active, nothing to sync
|
||||
if (account.status === 'ACTIVE') {
|
||||
return account;
|
||||
}
|
||||
|
||||
// get the connected account
|
||||
const response = await libGetConnectedAccount(connectedAccountId);
|
||||
|
||||
// update project with new connected account
|
||||
const key = `composioConnectedAccounts.${response.toolkit.slug}`;
|
||||
switch (response.status) {
|
||||
case 'INITIALIZING':
|
||||
case 'INITIATED':
|
||||
account.status = 'INITIATED';
|
||||
break;
|
||||
case 'ACTIVE':
|
||||
account.status = 'ACTIVE';
|
||||
break;
|
||||
default:
|
||||
account.status = 'FAILED';
|
||||
break;
|
||||
}
|
||||
account.lastUpdatedAt = new Date().toISOString();
|
||||
await projectsCollection.updateOne({ _id: projectId }, { $set: { [key]: account } });
|
||||
|
||||
return account;
|
||||
const user = await authCheck();
|
||||
return await syncConnectedAccountController.execute({
|
||||
caller: 'user',
|
||||
userId: user._id,
|
||||
projectId,
|
||||
toolkitSlug,
|
||||
connectedAccountId,
|
||||
});
|
||||
}
|
||||
|
||||
export async function deleteConnectedAccount(projectId: string, toolkitSlug: string, connectedAccountId: string): Promise<boolean> {
|
||||
export async function deleteConnectedAccount(projectId: string, toolkitSlug: string): Promise<boolean> {
|
||||
const user = await authCheck();
|
||||
|
||||
await deleteComposioConnectedAccountController.execute({
|
||||
|
|
@ -208,7 +111,6 @@ export async function deleteConnectedAccount(projectId: string, toolkitSlug: str
|
|||
userId: user._id,
|
||||
projectId,
|
||||
toolkitSlug,
|
||||
connectedAccountId,
|
||||
});
|
||||
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
'use server';
|
||||
|
||||
import { projectsCollection } from '../lib/mongodb';
|
||||
import { z } from 'zod';
|
||||
import { projectAuthCheck } from './project.actions';
|
||||
import { CustomMcpServer } from '../lib/types/project_types';
|
||||
import { CustomMcpServer } from "@/src/entities/models/project";
|
||||
import { getMcpClient } from '../lib/mcp';
|
||||
import { WorkflowTool } from '../lib/types/workflow_types';
|
||||
import { authCheck } from './auth.actions';
|
||||
import { container } from '@/di/container';
|
||||
import { IAddCustomMcpServerController } from '@/src/interface-adapters/controllers/projects/add-custom-mcp-server.controller';
|
||||
import { IRemoveCustomMcpServerController } from '@/src/interface-adapters/controllers/projects/remove-custom-mcp-server.controller';
|
||||
|
||||
type McpServerType = z.infer<typeof CustomMcpServer>;
|
||||
|
||||
|
|
@ -22,26 +23,30 @@ function validateUrl(url: string): string {
|
|||
}
|
||||
}
|
||||
|
||||
const addCustomMcpServerController = container.resolve<IAddCustomMcpServerController>('addCustomMcpServerController');
|
||||
const removeCustomMcpServerController = container.resolve<IRemoveCustomMcpServerController>('removeCustomMcpServerController');
|
||||
|
||||
export async function addServer(projectId: string, name: string, server: McpServerType): Promise<void> {
|
||||
await projectAuthCheck(projectId);
|
||||
|
||||
// Validate the server URL
|
||||
const user = await authCheck();
|
||||
// validate early for UX; use-case will validate again
|
||||
validateUrl(server.serverUrl);
|
||||
|
||||
// Update the customMcpServers record with the server
|
||||
await projectsCollection.updateOne(
|
||||
{ _id: projectId },
|
||||
{ $set: { [`customMcpServers.${name}`]: server } }
|
||||
);
|
||||
await addCustomMcpServerController.execute({
|
||||
caller: 'user',
|
||||
userId: user._id,
|
||||
projectId,
|
||||
name,
|
||||
server,
|
||||
});
|
||||
}
|
||||
|
||||
export async function removeServer(projectId: string, name: string): Promise<void> {
|
||||
await projectAuthCheck(projectId);
|
||||
|
||||
await projectsCollection.updateOne(
|
||||
{ _id: projectId },
|
||||
{ $unset: { [`customMcpServers.${name}`]: "" } }
|
||||
);
|
||||
const user = await authCheck();
|
||||
await removeCustomMcpServerController.execute({
|
||||
caller: 'user',
|
||||
userId: user._id,
|
||||
projectId,
|
||||
name,
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchTools(serverUrl: string, serverName: string): Promise<z.infer<typeof WorkflowTool>[]> {
|
||||
|
|
|
|||
|
|
@ -1,36 +1,43 @@
|
|||
'use server';
|
||||
import { redirect } from "next/navigation";
|
||||
import { db, projectsCollection } from "../lib/mongodb";
|
||||
import { z } from 'zod';
|
||||
import crypto from 'crypto';
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { container } from "@/di/container";
|
||||
import { redirect } from "next/navigation";
|
||||
import { templates } from "../lib/project_templates";
|
||||
import { authCheck } from "./auth.actions";
|
||||
import { User, WithStringId } from "../lib/types/types";
|
||||
import { ApiKey } from "@/src/entities/models/api-key";
|
||||
import { Project } from "../lib/types/project_types";
|
||||
import { Project } from "@/src/entities/models/project";
|
||||
import { USE_AUTH } from "../lib/feature_flags";
|
||||
import { authorizeUserAction } from "./billing.actions";
|
||||
import { Workflow } from "../lib/types/workflow_types";
|
||||
import { IProjectActionAuthorizationPolicy } from "@/src/application/policies/project-action-authorization.policy";
|
||||
import { ICreateApiKeyController } from "@/src/interface-adapters/controllers/api-keys/create-api-key.controller";
|
||||
import { IListApiKeysController } from "@/src/interface-adapters/controllers/api-keys/list-api-keys.controller";
|
||||
import { IDeleteApiKeyController } from "@/src/interface-adapters/controllers/api-keys/delete-api-key.controller";
|
||||
import { IApiKeysRepository } from "@/src/application/repositories/api-keys.repository.interface";
|
||||
import { IProjectMembersRepository } from "@/src/application/repositories/project-members.repository.interface";
|
||||
import { IDataSourcesRepository } from "@/src/application/repositories/data-sources.repository.interface";
|
||||
import { IDataSourceDocsRepository } from "@/src/application/repositories/data-source-docs.repository.interface";
|
||||
import { container } from "@/di/container";
|
||||
import { qdrantClient } from "../lib/qdrant";
|
||||
import { ICreateProjectController } from "@/src/interface-adapters/controllers/projects/create-project.controller";
|
||||
import { BillingError } from "@/src/entities/errors/common";
|
||||
import { IFetchProjectController } from "@/src/interface-adapters/controllers/projects/fetch-project.controller";
|
||||
import { IListProjectsController } from "@/src/interface-adapters/controllers/projects/list-projects.controller";
|
||||
import { IRotateSecretController } from "@/src/interface-adapters/controllers/projects/rotate-secret.controller";
|
||||
import { IUpdateWebhookUrlController } from "@/src/interface-adapters/controllers/projects/update-webhook-url.controller";
|
||||
import { IUpdateProjectNameController } from "@/src/interface-adapters/controllers/projects/update-project-name.controller";
|
||||
import { IDeleteProjectController } from "@/src/interface-adapters/controllers/projects/delete-project.controller";
|
||||
import { IUpdateDraftWorkflowController } from "@/src/interface-adapters/controllers/projects/update-draft-workflow.controller";
|
||||
import { IUpdateLiveWorkflowController } from "@/src/interface-adapters/controllers/projects/update-live-workflow.controller";
|
||||
import { IRevertToLiveWorkflowController } from "@/src/interface-adapters/controllers/projects/revert-to-live-workflow.controller";
|
||||
|
||||
const projectActionAuthorizationPolicy = container.resolve<IProjectActionAuthorizationPolicy>('projectActionAuthorizationPolicy');
|
||||
const createApiKeyController = container.resolve<ICreateApiKeyController>('createApiKeyController');
|
||||
const listApiKeysController = container.resolve<IListApiKeysController>('listApiKeysController');
|
||||
const deleteApiKeyController = container.resolve<IDeleteApiKeyController>('deleteApiKeyController');
|
||||
const apiKeysRepository = container.resolve<IApiKeysRepository>('apiKeysRepository');
|
||||
const projectMembersRepository = container.resolve<IProjectMembersRepository>('projectMembersRepository');
|
||||
const dataSourcesRepository = container.resolve<IDataSourcesRepository>('dataSourcesRepository');
|
||||
const dataSourceDocsRepository = container.resolve<IDataSourceDocsRepository>('dataSourceDocsRepository');
|
||||
const createProjectController = container.resolve<ICreateProjectController>('createProjectController');
|
||||
const fetchProjectController = container.resolve<IFetchProjectController>('fetchProjectController');
|
||||
const listProjectsController = container.resolve<IListProjectsController>('listProjectsController');
|
||||
const rotateSecretController = container.resolve<IRotateSecretController>('rotateSecretController');
|
||||
const updateWebhookUrlController = container.resolve<IUpdateWebhookUrlController>('updateWebhookUrlController');
|
||||
const updateProjectNameController = container.resolve<IUpdateProjectNameController>('updateProjectNameController');
|
||||
const deleteProjectController = container.resolve<IDeleteProjectController>('deleteProjectController');
|
||||
const updateDraftWorkflowController = container.resolve<IUpdateDraftWorkflowController>('updateDraftWorkflowController');
|
||||
const updateLiveWorkflowController = container.resolve<IUpdateLiveWorkflowController>('updateLiveWorkflowController');
|
||||
const revertToLiveWorkflowController = container.resolve<IRevertToLiveWorkflowController>('revertToLiveWorkflowController');
|
||||
|
||||
export async function listTemplates() {
|
||||
const templatesArray = Object.entries(templates)
|
||||
|
|
@ -55,143 +62,105 @@ export async function projectAuthCheck(projectId: string) {
|
|||
});
|
||||
}
|
||||
|
||||
async function createBaseProject(
|
||||
name: string,
|
||||
user: WithStringId<z.infer<typeof User>>,
|
||||
workflow?: z.infer<typeof Workflow>
|
||||
): Promise<{ id: string } | { billingError: string }> {
|
||||
// fetch project count for this user
|
||||
const projectCount = await projectsCollection.countDocuments({
|
||||
createdByUserId: user._id,
|
||||
});
|
||||
// billing limit check
|
||||
const authResponse = await authorizeUserAction({
|
||||
type: 'create_project',
|
||||
data: {
|
||||
existingProjectCount: projectCount,
|
||||
},
|
||||
});
|
||||
if (!authResponse.success) {
|
||||
return { billingError: authResponse.error || 'Billing error' };
|
||||
}
|
||||
|
||||
// choose a fallback name
|
||||
if (!name) {
|
||||
name = `Assistant ${projectCount + 1}`;
|
||||
}
|
||||
|
||||
const projectId = crypto.randomUUID();
|
||||
const chatClientId = crypto.randomBytes(16).toString('base64url');
|
||||
const secret = crypto.randomBytes(32).toString('hex');
|
||||
|
||||
// Create project
|
||||
await projectsCollection.insertOne({
|
||||
_id: projectId,
|
||||
name,
|
||||
createdAt: (new Date()).toISOString(),
|
||||
lastUpdatedAt: (new Date()).toISOString(),
|
||||
createdByUserId: user._id,
|
||||
draftWorkflow: workflow,
|
||||
liveWorkflow: workflow,
|
||||
chatClientId,
|
||||
secret,
|
||||
testRunCounter: 0,
|
||||
});
|
||||
|
||||
// Add user to project
|
||||
await projectMembersRepository.create({
|
||||
userId: user._id,
|
||||
projectId,
|
||||
});
|
||||
|
||||
// Add first api key
|
||||
await createApiKey(projectId);
|
||||
|
||||
return { id: projectId };
|
||||
}
|
||||
|
||||
export async function createProject(formData: FormData): Promise<{ id: string } | { billingError: string }> {
|
||||
const user = await authCheck();
|
||||
const name = formData.get('name') as string | null;
|
||||
const templateKey = formData.get('template') as string | null;
|
||||
|
||||
const { agents, prompts, tools, startAgent } = templates[templateKey || 'default'];
|
||||
const response = await createBaseProject(name || '', user, {
|
||||
agents,
|
||||
prompts,
|
||||
tools,
|
||||
startAgent,
|
||||
lastUpdatedAt: (new Date()).toISOString(),
|
||||
});
|
||||
if ('billingError' in response) {
|
||||
return response;
|
||||
}
|
||||
try {
|
||||
const project = await createProjectController.execute({
|
||||
userId: user._id,
|
||||
data: {
|
||||
name: name || '',
|
||||
mode: {
|
||||
template: templateKey || 'default',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const projectId = response.id;
|
||||
return { id: projectId };
|
||||
return { id: project.id };
|
||||
} catch (error) {
|
||||
if (error instanceof BillingError) {
|
||||
return { billingError: error.message };
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function createProjectFromWorkflowJson(formData: FormData): Promise<{ id: string } | { billingError: string }> {
|
||||
const user = await authCheck();
|
||||
const name = formData.get('name') as string | null;
|
||||
|
||||
const workflowJson = formData.get('workflowJson') as string;
|
||||
const workflow = Workflow.parse(JSON.parse(workflowJson));
|
||||
const response = await createBaseProject(name || 'Imported project', user, {
|
||||
...workflow,
|
||||
lastUpdatedAt: (new Date()).toISOString(),
|
||||
});
|
||||
if ('billingError' in response) {
|
||||
return response;
|
||||
}
|
||||
|
||||
const projectId = response.id;
|
||||
return { id: projectId };
|
||||
try {
|
||||
const project = await createProjectController.execute({
|
||||
userId: user._id,
|
||||
data: {
|
||||
name: name || '',
|
||||
mode: {
|
||||
workflowJson,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return { id: project.id };
|
||||
} catch (error) {
|
||||
if (error instanceof BillingError) {
|
||||
return { billingError: error.message };
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getProjectConfig(projectId: string): Promise<WithStringId<z.infer<typeof Project>>> {
|
||||
await projectAuthCheck(projectId);
|
||||
const project = await projectsCollection.findOne({
|
||||
_id: projectId,
|
||||
export async function fetchProject(projectId: string): Promise<z.infer<typeof Project>> {
|
||||
const user = await authCheck();
|
||||
const project = await fetchProjectController.execute({
|
||||
caller: 'user',
|
||||
userId: user._id,
|
||||
projectId,
|
||||
});
|
||||
|
||||
if (!project) {
|
||||
throw new Error('Project config not found');
|
||||
throw new Error('Project not found');
|
||||
}
|
||||
|
||||
return project;
|
||||
}
|
||||
|
||||
export async function listProjects(): Promise<z.infer<typeof Project>[]> {
|
||||
const user = await authCheck();
|
||||
const memberships = [];
|
||||
|
||||
const projects = [];
|
||||
let cursor = undefined;
|
||||
do {
|
||||
const result = await projectMembersRepository.findByUserId(user._id, cursor);
|
||||
memberships.push(...result.items);
|
||||
const result = await listProjectsController.execute({
|
||||
userId: user._id,
|
||||
cursor,
|
||||
});
|
||||
projects.push(...result.items);
|
||||
cursor = result.nextCursor;
|
||||
} while (cursor);
|
||||
const projectIds = memberships.map((m) => m.projectId);
|
||||
const projects = await projectsCollection.find({
|
||||
_id: { $in: projectIds },
|
||||
}).toArray();
|
||||
|
||||
return projects;
|
||||
}
|
||||
|
||||
export async function rotateSecret(projectId: string): Promise<string> {
|
||||
await projectAuthCheck(projectId);
|
||||
const secret = crypto.randomBytes(32).toString('hex');
|
||||
await projectsCollection.updateOne(
|
||||
{ _id: projectId },
|
||||
{ $set: { secret } }
|
||||
);
|
||||
return secret;
|
||||
const user = await authCheck();
|
||||
return await rotateSecretController.execute({
|
||||
caller: 'user',
|
||||
userId: user._id,
|
||||
projectId,
|
||||
});
|
||||
}
|
||||
|
||||
export async function updateWebhookUrl(projectId: string, url: string) {
|
||||
await projectAuthCheck(projectId);
|
||||
await projectsCollection.updateOne(
|
||||
{ _id: projectId },
|
||||
{ $set: { webhookUrl: url } }
|
||||
);
|
||||
const user = await authCheck();
|
||||
await updateWebhookUrlController.execute({
|
||||
caller: 'user',
|
||||
userId: user._id,
|
||||
projectId,
|
||||
url,
|
||||
});
|
||||
}
|
||||
|
||||
export async function createApiKey(projectId: string): Promise<z.infer<typeof ApiKey>> {
|
||||
|
|
@ -223,98 +192,51 @@ export async function listApiKeys(projectId: string): Promise<z.infer<typeof Api
|
|||
}
|
||||
|
||||
export async function updateProjectName(projectId: string, name: string) {
|
||||
await projectAuthCheck(projectId);
|
||||
await projectsCollection.updateOne({ _id: projectId }, { $set: { name } });
|
||||
revalidatePath(`/projects/${projectId}`, 'layout');
|
||||
}
|
||||
|
||||
interface McpServerDeletionError {
|
||||
serverName: string;
|
||||
error: string;
|
||||
const user = await authCheck();
|
||||
await updateProjectNameController.execute({
|
||||
caller: 'user',
|
||||
userId: user._id,
|
||||
projectId,
|
||||
name,
|
||||
});
|
||||
}
|
||||
|
||||
export async function deleteProject(projectId: string) {
|
||||
await projectAuthCheck(projectId);
|
||||
|
||||
// delete api keys
|
||||
await apiKeysRepository.deleteAll(projectId);
|
||||
|
||||
// delete data sources data
|
||||
await dataSourceDocsRepository.deleteByProjectId(projectId);
|
||||
await dataSourcesRepository.deleteByProjectId(projectId);
|
||||
await qdrantClient.delete("embeddings", {
|
||||
filter: {
|
||||
must: [
|
||||
{ key: "projectId", match: { value: projectId } },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
// delete project members
|
||||
await projectMembersRepository.deleteByProjectId(projectId);
|
||||
|
||||
// delete workflow versions
|
||||
await db.collection('agent_workflows').deleteMany({
|
||||
const user = await authCheck();
|
||||
await deleteProjectController.execute({
|
||||
caller: 'user',
|
||||
userId: user._id,
|
||||
projectId,
|
||||
});
|
||||
|
||||
// delete scenarios
|
||||
await db.collection('test_scenarios').deleteMany({
|
||||
projectId,
|
||||
});
|
||||
|
||||
// delete project
|
||||
await projectsCollection.deleteOne({
|
||||
_id: projectId,
|
||||
});
|
||||
|
||||
redirect('/projects');
|
||||
}
|
||||
|
||||
export async function saveWorkflow(projectId: string, workflow: z.infer<typeof Workflow>) {
|
||||
await projectAuthCheck(projectId);
|
||||
|
||||
// update the project's draft workflow
|
||||
workflow.lastUpdatedAt = new Date().toISOString();
|
||||
await projectsCollection.updateOne({
|
||||
_id: projectId,
|
||||
}, {
|
||||
$set: {
|
||||
draftWorkflow: workflow,
|
||||
},
|
||||
const user = await authCheck();
|
||||
await updateDraftWorkflowController.execute({
|
||||
caller: 'user',
|
||||
userId: user._id,
|
||||
projectId,
|
||||
workflow,
|
||||
});
|
||||
}
|
||||
|
||||
export async function publishWorkflow(projectId: string, workflow: z.infer<typeof Workflow>) {
|
||||
await projectAuthCheck(projectId);
|
||||
|
||||
// update the project's draft workflow
|
||||
workflow.lastUpdatedAt = new Date().toISOString();
|
||||
await projectsCollection.updateOne({
|
||||
_id: projectId,
|
||||
}, {
|
||||
$set: {
|
||||
liveWorkflow: workflow,
|
||||
},
|
||||
const user = await authCheck();
|
||||
await updateLiveWorkflowController.execute({
|
||||
caller: 'user',
|
||||
userId: user._id,
|
||||
projectId,
|
||||
workflow,
|
||||
});
|
||||
}
|
||||
|
||||
export async function revertToLiveWorkflow(projectId: string) {
|
||||
await projectAuthCheck(projectId);
|
||||
|
||||
const project = await getProjectConfig(projectId);
|
||||
const workflow = project.liveWorkflow;
|
||||
|
||||
if (!workflow) {
|
||||
throw new Error('No live workflow found');
|
||||
}
|
||||
|
||||
workflow.lastUpdatedAt = new Date().toISOString();
|
||||
await projectsCollection.updateOne({
|
||||
_id: projectId,
|
||||
}, {
|
||||
$set: {
|
||||
draftWorkflow: workflow,
|
||||
},
|
||||
const user = await authCheck();
|
||||
await revertToLiveWorkflowController.execute({
|
||||
caller: 'user',
|
||||
userId: user._id,
|
||||
projectId,
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue