From 29e4a8fb9ece35c6c3c211f98621291ee166d904 Mon Sep 17 00:00:00 2001 From: Ramnique Singh <30795890+ramnique@users.noreply.github.com> Date: Mon, 18 Aug 2025 16:45:10 +0530 Subject: [PATCH] better project deletion cleanup --- ...rigger-deployments.repository.interface.ts | 8 +++ .../conversations.repository.interface.ts | 8 +++ .../repositories/jobs.repository.interface.ts | 8 +++ ...ecurring-job-rules.repository.interface.ts | 8 +++ ...cheduled-job-rules.repository.interface.ts | 8 +++ .../projects/delete-project.use-case.ts | 54 +++++++++++++++++-- ...composio-trigger-deployments.repository.ts | 7 +++ .../mongodb.conversations.repository.ts | 4 ++ .../repositories/mongodb.jobs.repository.ts | 4 ++ .../mongodb.recurring-job-rules.repository.ts | 4 ++ .../mongodb.scheduled-job-rules.repository.ts | 4 ++ 11 files changed, 113 insertions(+), 4 deletions(-) diff --git a/apps/rowboat/src/application/repositories/composio-trigger-deployments.repository.interface.ts b/apps/rowboat/src/application/repositories/composio-trigger-deployments.repository.interface.ts index d013ea22..1bc60e72 100644 --- a/apps/rowboat/src/application/repositories/composio-trigger-deployments.repository.interface.ts +++ b/apps/rowboat/src/application/repositories/composio-trigger-deployments.repository.interface.ts @@ -88,4 +88,12 @@ export interface IComposioTriggerDeploymentsRepository { * @returns Promise resolving to the number of records deleted */ deleteByConnectedAccountId(connectedAccountId: string): Promise; + + /** + * Deletes all trigger deployments associated with a specific project. + * + * @param projectId - The unique identifier of the project + * @returns Promise resolving to void + */ + deleteByProjectId(projectId: string): Promise; } \ No newline at end of file diff --git a/apps/rowboat/src/application/repositories/conversations.repository.interface.ts b/apps/rowboat/src/application/repositories/conversations.repository.interface.ts index bb73a2b9..5373cc6f 100644 --- a/apps/rowboat/src/application/repositories/conversations.repository.interface.ts +++ b/apps/rowboat/src/application/repositories/conversations.repository.interface.ts @@ -37,4 +37,12 @@ export interface IConversationsRepository { // add turn data to conversation // returns the created turn addTurn(conversationId: string, data: z.infer): Promise>; + + /** + * Deletes all conversations associated with a specific project. + * + * @param projectId - The unique identifier of the project + * @returns Promise resolving to void + */ + deleteByProjectId(projectId: string): Promise; } \ No newline at end of file diff --git a/apps/rowboat/src/application/repositories/jobs.repository.interface.ts b/apps/rowboat/src/application/repositories/jobs.repository.interface.ts index 90db0d4d..03c5e820 100644 --- a/apps/rowboat/src/application/repositories/jobs.repository.interface.ts +++ b/apps/rowboat/src/application/repositories/jobs.repository.interface.ts @@ -139,4 +139,12 @@ export interface IJobsRepository { cursor?: string, limit?: number ): Promise>>>; + + /** + * Deletes all jobs associated with a specific project. + * + * @param projectId - The unique identifier of the project + * @returns Promise resolving to void + */ + deleteByProjectId(projectId: string): Promise; } \ No newline at end of file diff --git a/apps/rowboat/src/application/repositories/recurring-job-rules.repository.interface.ts b/apps/rowboat/src/application/repositories/recurring-job-rules.repository.interface.ts index eedebc68..9b491d11 100644 --- a/apps/rowboat/src/application/repositories/recurring-job-rules.repository.interface.ts +++ b/apps/rowboat/src/application/repositories/recurring-job-rules.repository.interface.ts @@ -89,4 +89,12 @@ export interface IRecurringJobRulesRepository { * @returns Promise resolving to true if the rule was deleted, false if not found */ delete(id: string): Promise; + + /** + * Deletes all recurring job rules associated with a specific project. + * + * @param projectId - The unique identifier of the project + * @returns Promise resolving to void + */ + deleteByProjectId(projectId: string): Promise; } diff --git a/apps/rowboat/src/application/repositories/scheduled-job-rules.repository.interface.ts b/apps/rowboat/src/application/repositories/scheduled-job-rules.repository.interface.ts index 2fc1e7df..81391dc3 100644 --- a/apps/rowboat/src/application/repositories/scheduled-job-rules.repository.interface.ts +++ b/apps/rowboat/src/application/repositories/scheduled-job-rules.repository.interface.ts @@ -95,4 +95,12 @@ export interface IScheduledJobRulesRepository { * @returns Promise resolving to true if the rule was deleted, false if not found */ delete(id: string): Promise; + + /** + * Deletes all scheduled job rules associated with a specific project. + * + * @param projectId - The unique identifier of the project + * @returns Promise resolving to void + */ + deleteByProjectId(projectId: string): Promise; } \ No newline at end of file diff --git a/apps/rowboat/src/application/use-cases/projects/delete-project.use-case.ts b/apps/rowboat/src/application/use-cases/projects/delete-project.use-case.ts index 4cac8c47..e4ae3876 100644 --- a/apps/rowboat/src/application/use-cases/projects/delete-project.use-case.ts +++ b/apps/rowboat/src/application/use-cases/projects/delete-project.use-case.ts @@ -6,6 +6,13 @@ import { IApiKeysRepository } from "../../repositories/api-keys.repository.inter import { IDataSourceDocsRepository } from "../../repositories/data-source-docs.repository.interface"; import { IDataSourcesRepository } from "../../repositories/data-sources.repository.interface"; import { qdrantClient } from "@/app/lib/qdrant"; +import { IComposioTriggerDeploymentsRepository } from "../../repositories/composio-trigger-deployments.repository.interface"; +import { IConversationsRepository } from "../../repositories/conversations.repository.interface"; +import { IJobsRepository } from "../../repositories/jobs.repository.interface"; +import { IRecurringJobRulesRepository } from "../../repositories/recurring-job-rules.repository.interface"; +import { IScheduledJobRulesRepository } from "../../repositories/scheduled-job-rules.repository.interface"; +import { NotFoundError } from "@/src/entities/errors/common"; +import { deleteConnectedAccount } from "../../lib/composio/composio"; export const InputSchema = z.object({ projectId: z.string(), @@ -25,14 +32,24 @@ export class DeleteProjectUseCase implements IDeleteProjectUseCase { private readonly apiKeysRepository: IApiKeysRepository; private readonly dataSourceDocsRepository: IDataSourceDocsRepository; private readonly dataSourcesRepository: IDataSourcesRepository; + private readonly composioTriggerDeploymentsRepository: IComposioTriggerDeploymentsRepository; + private readonly conversationsRepository: IConversationsRepository; + private readonly jobsRepository: IJobsRepository; + private readonly recurringJobRulesRepository: IRecurringJobRulesRepository; + private readonly scheduledJobRulesRepository: IScheduledJobRulesRepository; - constructor({ projectsRepository, projectMembersRepository, projectActionAuthorizationPolicy, apiKeysRepository, dataSourceDocsRepository, dataSourcesRepository}: { + constructor({ projectsRepository, projectMembersRepository, projectActionAuthorizationPolicy, apiKeysRepository, dataSourceDocsRepository, dataSourcesRepository, composioTriggerDeploymentsRepository, conversationsRepository, jobsRepository, recurringJobRulesRepository, scheduledJobRulesRepository }: { projectsRepository: IProjectsRepository, projectMembersRepository: IProjectMembersRepository, projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy, apiKeysRepository: IApiKeysRepository, dataSourceDocsRepository: IDataSourceDocsRepository, dataSourcesRepository: IDataSourcesRepository, + composioTriggerDeploymentsRepository: IComposioTriggerDeploymentsRepository, + conversationsRepository: IConversationsRepository, + jobsRepository: IJobsRepository, + recurringJobRulesRepository: IRecurringJobRulesRepository, + scheduledJobRulesRepository: IScheduledJobRulesRepository, }) { this.projectsRepository = projectsRepository; this.projectMembersRepository = projectMembersRepository; @@ -40,6 +57,11 @@ export class DeleteProjectUseCase implements IDeleteProjectUseCase { this.apiKeysRepository = apiKeysRepository; this.dataSourceDocsRepository = dataSourceDocsRepository; this.dataSourcesRepository = dataSourcesRepository; + this.composioTriggerDeploymentsRepository = composioTriggerDeploymentsRepository; + this.conversationsRepository = conversationsRepository; + this.jobsRepository = jobsRepository; + this.recurringJobRulesRepository = recurringJobRulesRepository; + this.scheduledJobRulesRepository = scheduledJobRulesRepository; } async execute(request: z.infer): Promise { @@ -51,12 +73,39 @@ export class DeleteProjectUseCase implements IDeleteProjectUseCase { projectId, }); + const project = await this.projectsRepository.fetch(projectId); + if (!project) { + throw new NotFoundError('Project not found'); + } + + // delete connected accounts + await Promise.all( + Object.values(project.composioConnectedAccounts || {}).map(account => + deleteConnectedAccount(account.id) + ) + ); + // delete memberships await this.projectMembersRepository.deleteByProjectId(projectId); // delete api keys await this.apiKeysRepository.deleteAll(projectId); + // delete composio trigger deployments + await this.composioTriggerDeploymentsRepository.deleteByProjectId(projectId); + + // delete conversations + await this.conversationsRepository.deleteByProjectId(projectId); + + // delete jobs + await this.jobsRepository.deleteByProjectId(projectId); + + // delete recurring job rules + await this.recurringJobRulesRepository.deleteByProjectId(projectId); + + // delete scheduled job rules + await this.scheduledJobRulesRepository.deleteByProjectId(projectId); + // delete data sources data await this.dataSourceDocsRepository.deleteByProjectId(projectId); await this.dataSourcesRepository.deleteByProjectId(projectId); @@ -68,9 +117,6 @@ export class DeleteProjectUseCase implements IDeleteProjectUseCase { }, }); - // delete project members - await this.projectMembersRepository.deleteByProjectId(projectId); - // delete project await this.projectsRepository.delete(projectId); } diff --git a/apps/rowboat/src/infrastructure/repositories/mongodb.composio-trigger-deployments.repository.ts b/apps/rowboat/src/infrastructure/repositories/mongodb.composio-trigger-deployments.repository.ts index d7bb3342..296c44e7 100644 --- a/apps/rowboat/src/infrastructure/repositories/mongodb.composio-trigger-deployments.repository.ts +++ b/apps/rowboat/src/infrastructure/repositories/mongodb.composio-trigger-deployments.repository.ts @@ -172,4 +172,11 @@ export class MongodbComposioTriggerDeploymentsRepository implements IComposioTri return result.deletedCount; } + + /** + * Deletes all trigger deployments associated with a specific project. + */ + async deleteByProjectId(projectId: string): Promise { + await this.collection.deleteMany({ projectId }); + } } \ No newline at end of file diff --git a/apps/rowboat/src/infrastructure/repositories/mongodb.conversations.repository.ts b/apps/rowboat/src/infrastructure/repositories/mongodb.conversations.repository.ts index 07e4d0cf..ee836df7 100644 --- a/apps/rowboat/src/infrastructure/repositories/mongodb.conversations.repository.ts +++ b/apps/rowboat/src/infrastructure/repositories/mongodb.conversations.repository.ts @@ -109,4 +109,8 @@ export class MongoDBConversationsRepository implements IConversationsRepository nextCursor: hasNextPage ? results[limit - 1]._id.toString() : null, }; } + + async deleteByProjectId(projectId: string): Promise { + await this.collection.deleteMany({ projectId }); + } } \ No newline at end of file diff --git a/apps/rowboat/src/infrastructure/repositories/mongodb.jobs.repository.ts b/apps/rowboat/src/infrastructure/repositories/mongodb.jobs.repository.ts index c987ccbb..6129e660 100644 --- a/apps/rowboat/src/infrastructure/repositories/mongodb.jobs.repository.ts +++ b/apps/rowboat/src/infrastructure/repositories/mongodb.jobs.repository.ts @@ -267,4 +267,8 @@ export class MongoDBJobsRepository implements IJobsRepository { nextCursor: hasNextPage ? results[_limit - 1]._id.toString() : null, }; } + + async deleteByProjectId(projectId: string): Promise { + await this.collection.deleteMany({ projectId }); + } } diff --git a/apps/rowboat/src/infrastructure/repositories/mongodb.recurring-job-rules.repository.ts b/apps/rowboat/src/infrastructure/repositories/mongodb.recurring-job-rules.repository.ts index aac7db47..a0a43d7c 100644 --- a/apps/rowboat/src/infrastructure/repositories/mongodb.recurring-job-rules.repository.ts +++ b/apps/rowboat/src/infrastructure/repositories/mongodb.recurring-job-rules.repository.ts @@ -238,4 +238,8 @@ export class MongoDBRecurringJobRulesRepository implements IRecurringJobRulesRep return this.convertDocToModel(result); } + + async deleteByProjectId(projectId: string): Promise { + await this.collection.deleteMany({ projectId }); + } } diff --git a/apps/rowboat/src/infrastructure/repositories/mongodb.scheduled-job-rules.repository.ts b/apps/rowboat/src/infrastructure/repositories/mongodb.scheduled-job-rules.repository.ts index 50b7f2e5..0bbb206a 100644 --- a/apps/rowboat/src/infrastructure/repositories/mongodb.scheduled-job-rules.repository.ts +++ b/apps/rowboat/src/infrastructure/repositories/mongodb.scheduled-job-rules.repository.ts @@ -218,4 +218,8 @@ export class MongoDBScheduledJobRulesRepository implements IScheduledJobRulesRep return result.deletedCount > 0; } + + async deleteByProjectId(projectId: string): Promise { + await this.collection.deleteMany({ projectId }); + } }