handleDeleteKey(key._id)}
+ onDelete={() => handleDeleteKey(key.id)}
/>
diff --git a/apps/rowboat/di/container.ts b/apps/rowboat/di/container.ts
index d3a44ac2..ab40d5db 100644
--- a/apps/rowboat/di/container.ts
+++ b/apps/rowboat/di/container.ts
@@ -64,6 +64,14 @@ import { ListRecurringJobRulesController } from "@/src/interface-adapters/contro
import { ToggleRecurringJobRuleController } from "@/src/interface-adapters/controllers/recurring-job-rules/toggle-recurring-job-rule.controller";
import { DeleteRecurringJobRuleController } from "@/src/interface-adapters/controllers/recurring-job-rules/delete-recurring-job-rule.controller";
+// API Keys
+import { CreateApiKeyUseCase } from "@/src/application/use-cases/api-keys/create-api-key.use-case";
+import { ListApiKeysUseCase } from "@/src/application/use-cases/api-keys/list-api-keys.use-case";
+import { DeleteApiKeyUseCase } from "@/src/application/use-cases/api-keys/delete-api-key.use-case";
+import { CreateApiKeyController } from "@/src/interface-adapters/controllers/api-keys/create-api-key.controller";
+import { ListApiKeysController } from "@/src/interface-adapters/controllers/api-keys/list-api-keys.controller";
+import { DeleteApiKeyController } from "@/src/interface-adapters/controllers/api-keys/delete-api-key.controller";
+
export const container = createContainer({
injectionMode: InjectionMode.PROXY,
strict: true,
@@ -96,6 +104,12 @@ container.register({
// api keys
// ---
apiKeysRepository: asClass(MongoDBApiKeysRepository).singleton(),
+ createApiKeyUseCase: asClass(CreateApiKeyUseCase).singleton(),
+ listApiKeysUseCase: asClass(ListApiKeysUseCase).singleton(),
+ deleteApiKeyUseCase: asClass(DeleteApiKeyUseCase).singleton(),
+ createApiKeyController: asClass(CreateApiKeyController).singleton(),
+ listApiKeysController: asClass(ListApiKeysController).singleton(),
+ deleteApiKeyController: asClass(DeleteApiKeyController).singleton(),
// jobs
// ---
diff --git a/apps/rowboat/src/application/repositories/api-keys.repository.interface.ts b/apps/rowboat/src/application/repositories/api-keys.repository.interface.ts
index 383443d0..cde6d3c3 100644
--- a/apps/rowboat/src/application/repositories/api-keys.repository.interface.ts
+++ b/apps/rowboat/src/application/repositories/api-keys.repository.interface.ts
@@ -1,3 +1,41 @@
+import { PaginatedList } from "@/src/entities/common/paginated-list";
+import { ApiKey } from "@/src/entities/models/api-key";
+import { z } from "zod";
+
+export const CreateSchema = ApiKey.pick({
+ projectId: true,
+ key: true,
+});
+
+// Interface for repository operations related to API keys.
export interface IApiKeysRepository {
+ /**
+ * Creates a new API key for a given project.
+ * @param data - The data required to create an API key (projectId and key).
+ * @returns The created ApiKey object.
+ */
+ create(data: z.infer): Promise>;
+
+ /**
+ * Lists all API keys for a given project.
+ * @param projectId - The ID of the project whose API keys are to be listed.
+ * @returns A list of ApiKey objects.
+ */
+ listAll(projectId: string): Promise[]>;
+
+ /**
+ * Deletes an API key by its ID for a given project.
+ * @param projectId - The ID of the project.
+ * @param id - The ID of the API key to delete.
+ * @returns True if the key was deleted, false if not found.
+ */
+ delete(projectId: string, id: string): Promise;
+
+ /**
+ * Checks if an API key is valid for a project and consumes it (e.g., for rate limiting or one-time use).
+ * @param projectId - The ID of the project.
+ * @param apiKey - The API key to check and consume.
+ * @returns True if the key is valid and was consumed, false otherwise.
+ */
checkAndConsumeKey(projectId: string, apiKey: string): Promise;
}
\ No newline at end of file
diff --git a/apps/rowboat/src/application/use-cases/api-keys/create-api-key.use-case.ts b/apps/rowboat/src/application/use-cases/api-keys/create-api-key.use-case.ts
new file mode 100644
index 00000000..ec2e92d4
--- /dev/null
+++ b/apps/rowboat/src/application/use-cases/api-keys/create-api-key.use-case.ts
@@ -0,0 +1,59 @@
+import { IApiKeysRepository } from "@/src/application/repositories/api-keys.repository.interface";
+import { z } from "zod";
+import { ApiKey } from "@/src/entities/models/api-key";
+import { IProjectActionAuthorizationPolicy } from "@/src/application/policies/project-action-authorization.policy";
+import crypto from "crypto";
+import { BadRequestError } from "@/src/entities/errors/common";
+
+const inputSchema = z.object({
+ caller: z.enum(["user", "api"]),
+ userId: z.string().optional(),
+ apiKey: z.string().optional(),
+ projectId: z.string(),
+});
+
+export class MaxKeysReachedError extends BadRequestError {
+ constructor(message?: string, options?: ErrorOptions) {
+ super(message, options);
+ }
+}
+
+export interface ICreateApiKeyUseCase {
+ execute(data: z.infer): Promise>;
+}
+
+export class CreateApiKeyUseCase implements ICreateApiKeyUseCase {
+ private readonly apiKeysRepository: IApiKeysRepository;
+ private readonly projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy;
+
+ constructor({
+ apiKeysRepository,
+ projectActionAuthorizationPolicy,
+ }: {
+ apiKeysRepository: IApiKeysRepository,
+ projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy,
+ }) {
+ this.apiKeysRepository = apiKeysRepository;
+ this.projectActionAuthorizationPolicy = projectActionAuthorizationPolicy;
+ }
+
+ async execute(data: z.infer): Promise> {
+ const { caller, userId, apiKey, projectId } = data;
+ await this.projectActionAuthorizationPolicy.authorize({
+ caller,
+ userId,
+ apiKey,
+ projectId,
+ });
+
+ // count existing keys
+ const keys = await this.apiKeysRepository.listAll(projectId);
+ if (keys.length >= 3) {
+ throw new MaxKeysReachedError("You can only have up to 3 API keys per project.");
+ }
+
+ // Generate a random key using crypto
+ const key = crypto.randomBytes(32).toString('hex');
+ return await this.apiKeysRepository.create({ projectId, key });
+ }
+}
diff --git a/apps/rowboat/src/application/use-cases/api-keys/delete-api-key.use-case.ts b/apps/rowboat/src/application/use-cases/api-keys/delete-api-key.use-case.ts
new file mode 100644
index 00000000..3a9db601
--- /dev/null
+++ b/apps/rowboat/src/application/use-cases/api-keys/delete-api-key.use-case.ts
@@ -0,0 +1,42 @@
+import { IApiKeysRepository } from "@/src/application/repositories/api-keys.repository.interface";
+import { z } from "zod";
+import { IProjectActionAuthorizationPolicy } from "@/src/application/policies/project-action-authorization.policy";
+
+const inputSchema = z.object({
+ caller: z.enum(["user", "api"]),
+ userId: z.string().optional(),
+ apiKey: z.string().optional(),
+ projectId: z.string(),
+ id: z.string(),
+});
+
+export interface IDeleteApiKeyUseCase {
+ execute(data: z.infer): Promise;
+}
+
+export class DeleteApiKeyUseCase implements IDeleteApiKeyUseCase {
+ private readonly apiKeysRepository: IApiKeysRepository;
+ private readonly projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy;
+
+ constructor({
+ apiKeysRepository,
+ projectActionAuthorizationPolicy,
+ }: {
+ apiKeysRepository: IApiKeysRepository,
+ projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy,
+ }) {
+ this.apiKeysRepository = apiKeysRepository;
+ this.projectActionAuthorizationPolicy = projectActionAuthorizationPolicy;
+ }
+
+ async execute(data: z.infer): Promise {
+ const { caller, userId, apiKey, projectId, id } = data;
+ await this.projectActionAuthorizationPolicy.authorize({
+ caller,
+ userId,
+ apiKey,
+ projectId,
+ });
+ return await this.apiKeysRepository.delete(projectId, id);
+ }
+}
diff --git a/apps/rowboat/src/application/use-cases/api-keys/list-api-keys.use-case.ts b/apps/rowboat/src/application/use-cases/api-keys/list-api-keys.use-case.ts
new file mode 100644
index 00000000..3e01ca42
--- /dev/null
+++ b/apps/rowboat/src/application/use-cases/api-keys/list-api-keys.use-case.ts
@@ -0,0 +1,42 @@
+import { IApiKeysRepository } from "@/src/application/repositories/api-keys.repository.interface";
+import { z } from "zod";
+import { ApiKey } from "@/src/entities/models/api-key";
+import { IProjectActionAuthorizationPolicy } from "@/src/application/policies/project-action-authorization.policy";
+
+const inputSchema = z.object({
+ caller: z.enum(["user", "api"]),
+ userId: z.string().optional(),
+ apiKey: z.string().optional(),
+ projectId: z.string(),
+});
+
+export interface IListApiKeysUseCase {
+ execute(data: z.infer): Promise[]>;
+}
+
+export class ListApiKeysUseCase implements IListApiKeysUseCase {
+ private readonly apiKeysRepository: IApiKeysRepository;
+ private readonly projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy;
+
+ constructor({
+ apiKeysRepository,
+ projectActionAuthorizationPolicy,
+ }: {
+ apiKeysRepository: IApiKeysRepository,
+ projectActionAuthorizationPolicy: IProjectActionAuthorizationPolicy,
+ }) {
+ this.apiKeysRepository = apiKeysRepository;
+ this.projectActionAuthorizationPolicy = projectActionAuthorizationPolicy;
+ }
+
+ async execute(data: z.infer): Promise[]> {
+ const { caller, userId, apiKey, projectId } = data;
+ await this.projectActionAuthorizationPolicy.authorize({
+ caller,
+ userId,
+ apiKey,
+ projectId,
+ });
+ return await this.apiKeysRepository.listAll(projectId);
+ }
+}
diff --git a/apps/rowboat/src/entities/models/api-key.ts b/apps/rowboat/src/entities/models/api-key.ts
new file mode 100644
index 00000000..af07b86a
--- /dev/null
+++ b/apps/rowboat/src/entities/models/api-key.ts
@@ -0,0 +1,9 @@
+import { z } from "zod";
+
+export const ApiKey = z.object({
+ id: z.string(),
+ projectId: z.string(),
+ key: z.string(),
+ createdAt: z.string().datetime(),
+ lastUsedAt: z.string().datetime().optional(),
+});
\ No newline at end of file
diff --git a/apps/rowboat/src/infrastructure/repositories/mongodb.api-keys.repository.ts b/apps/rowboat/src/infrastructure/repositories/mongodb.api-keys.repository.ts
index 657a2461..1a8fe17d 100644
--- a/apps/rowboat/src/infrastructure/repositories/mongodb.api-keys.repository.ts
+++ b/apps/rowboat/src/infrastructure/repositories/mongodb.api-keys.repository.ts
@@ -1,12 +1,59 @@
import { IApiKeysRepository } from "@/src/application/repositories/api-keys.repository.interface";
-import { apiKeysCollection } from "@/app/lib/mongodb";
+import { db } from "@/app/lib/mongodb";
+import { ApiKey } from "@/src/entities/models/api-key";
+import { z } from "zod";
+import { ObjectId } from "mongodb";
+import { CreateSchema } from "@/src/application/repositories/api-keys.repository.interface";
+
+const DocSchema = ApiKey
+ .omit({
+ id: true,
+ });
export class MongoDBApiKeysRepository implements IApiKeysRepository {
+ private readonly collection = db.collection>("api_keys");
+
async checkAndConsumeKey(projectId: string, apiKey: string): Promise {
- const result = await apiKeysCollection.findOneAndUpdate(
+ const result = await this.collection.findOneAndUpdate(
{ projectId, key: apiKey },
{ $set: { lastUsedAt: new Date().toISOString() } }
);
return !!result;
}
+
+ async create(data: z.infer): Promise> {
+ const now = new Date().toISOString();
+ const _id = new ObjectId();
+
+ const doc = {
+ ...data,
+ createdAt: now,
+ };
+
+ const result = await this.collection.insertOne({
+ _id,
+ ...doc,
+ });
+
+ return {
+ ...doc,
+ id: _id.toString(),
+ };
+ }
+
+ async listAll(projectId: string): Promise[]> {
+ const results = await this.collection.find({ projectId }).sort({ createdAt: -1 }).toArray();
+ return results.map(doc => {
+ const { _id, ...rest } = doc;
+ return {
+ ...rest,
+ id: _id.toString(),
+ };
+ });
+ }
+
+ async delete(projectId: string, id: string): Promise {
+ const result = await this.collection.deleteOne({ projectId, _id: new ObjectId(id) });
+ return result.deletedCount > 0;
+ }
}
\ No newline at end of file
diff --git a/apps/rowboat/src/interface-adapters/controllers/api-keys/create-api-key.controller.ts b/apps/rowboat/src/interface-adapters/controllers/api-keys/create-api-key.controller.ts
new file mode 100644
index 00000000..b03453cd
--- /dev/null
+++ b/apps/rowboat/src/interface-adapters/controllers/api-keys/create-api-key.controller.ts
@@ -0,0 +1,30 @@
+import { BadRequestError } from "@/src/entities/errors/common";
+import z from "zod";
+import { ApiKey } from "@/src/entities/models/api-key";
+import { ICreateApiKeyUseCase } from "@/src/application/use-cases/api-keys/create-api-key.use-case";
+
+const inputSchema = z.object({
+ caller: z.enum(["user", "api"]),
+ userId: z.string().optional(),
+ apiKey: z.string().optional(),
+ projectId: z.string(),
+});
+
+export interface ICreateApiKeyController {
+ execute(request: z.infer): Promise>;
+}
+
+export class CreateApiKeyController implements ICreateApiKeyController {
+ private readonly createApiKeyUseCase: ICreateApiKeyUseCase;
+ constructor({ createApiKeyUseCase }: { createApiKeyUseCase: ICreateApiKeyUseCase }) {
+ this.createApiKeyUseCase = createApiKeyUseCase;
+ }
+ async execute(request: z.infer): Promise> {
+ const result = inputSchema.safeParse(request);
+ if (!result.success) {
+ throw new BadRequestError(`Invalid request: ${JSON.stringify(result.error)}`);
+ }
+ return await this.createApiKeyUseCase.execute(result.data);
+ }
+}
+export { inputSchema as createApiKeyInputSchema };
diff --git a/apps/rowboat/src/interface-adapters/controllers/api-keys/delete-api-key.controller.ts b/apps/rowboat/src/interface-adapters/controllers/api-keys/delete-api-key.controller.ts
new file mode 100644
index 00000000..1381e5c3
--- /dev/null
+++ b/apps/rowboat/src/interface-adapters/controllers/api-keys/delete-api-key.controller.ts
@@ -0,0 +1,30 @@
+import { BadRequestError } from "@/src/entities/errors/common";
+import z from "zod";
+import { IDeleteApiKeyUseCase } from "@/src/application/use-cases/api-keys/delete-api-key.use-case";
+
+const inputSchema = z.object({
+ caller: z.enum(["user", "api"]),
+ userId: z.string().optional(),
+ apiKey: z.string().optional(),
+ projectId: z.string(),
+ id: z.string(),
+});
+
+export interface IDeleteApiKeyController {
+ execute(request: z.infer): Promise;
+}
+
+export class DeleteApiKeyController implements IDeleteApiKeyController {
+ private readonly deleteApiKeyUseCase: IDeleteApiKeyUseCase;
+ constructor({ deleteApiKeyUseCase }: { deleteApiKeyUseCase: IDeleteApiKeyUseCase }) {
+ this.deleteApiKeyUseCase = deleteApiKeyUseCase;
+ }
+ async execute(request: z.infer): Promise {
+ const result = inputSchema.safeParse(request);
+ if (!result.success) {
+ throw new BadRequestError(`Invalid request: ${JSON.stringify(result.error)}`);
+ }
+ return await this.deleteApiKeyUseCase.execute(result.data);
+ }
+}
+export { inputSchema as deleteApiKeyInputSchema };
diff --git a/apps/rowboat/src/interface-adapters/controllers/api-keys/list-api-keys.controller.ts b/apps/rowboat/src/interface-adapters/controllers/api-keys/list-api-keys.controller.ts
new file mode 100644
index 00000000..6b02aab5
--- /dev/null
+++ b/apps/rowboat/src/interface-adapters/controllers/api-keys/list-api-keys.controller.ts
@@ -0,0 +1,30 @@
+import { BadRequestError } from "@/src/entities/errors/common";
+import z from "zod";
+import { ApiKey } from "@/src/entities/models/api-key";
+import { IListApiKeysUseCase } from "@/src/application/use-cases/api-keys/list-api-keys.use-case";
+
+const inputSchema = z.object({
+ caller: z.enum(["user", "api"]),
+ userId: z.string().optional(),
+ apiKey: z.string().optional(),
+ projectId: z.string(),
+});
+
+export interface IListApiKeysController {
+ execute(request: z.infer): Promise[]>;
+}
+
+export class ListApiKeysController implements IListApiKeysController {
+ private readonly listApiKeysUseCase: IListApiKeysUseCase;
+ constructor({ listApiKeysUseCase }: { listApiKeysUseCase: IListApiKeysUseCase }) {
+ this.listApiKeysUseCase = listApiKeysUseCase;
+ }
+ async execute(request: z.infer): Promise[]> {
+ const result = inputSchema.safeParse(request);
+ if (!result.success) {
+ throw new BadRequestError(`Invalid request: ${JSON.stringify(result.error)}`);
+ }
+ return await this.listApiKeysUseCase.execute(result.data);
+ }
+}
+export { inputSchema as listApiKeysInputSchema };
From ab81fa3c4604ebf64b29020cb67a24ac489020a4 Mon Sep 17 00:00:00 2001
From: Ramnique Singh <30795890+ramnique@users.noreply.github.com>
Date: Fri, 15 Aug 2025 12:53:27 +0530
Subject: [PATCH 6/7] housekeeping
---
apps/rowboat/app/actions/project_actions.ts | 5 +----
apps/rowboat/app/lib/mongodb.ts | 2 --
2 files changed, 1 insertion(+), 6 deletions(-)
diff --git a/apps/rowboat/app/actions/project_actions.ts b/apps/rowboat/app/actions/project_actions.ts
index 4abcb730..062d3a68 100644
--- a/apps/rowboat/app/actions/project_actions.ts
+++ b/apps/rowboat/app/actions/project_actions.ts
@@ -1,7 +1,7 @@
'use server';
import { redirect } from "next/navigation";
import { ObjectId } from "mongodb";
-import { db, dataSourcesCollection, embeddingsCollection, projectsCollection, projectMembersCollection, apiKeysCollection, dataSourceDocsCollection } from "../lib/mongodb";
+import { db, dataSourcesCollection, projectsCollection, projectMembersCollection, apiKeysCollection, dataSourceDocsCollection } from "../lib/mongodb";
import { z } from 'zod';
import crypto from 'crypto';
import { revalidatePath } from "next/cache";
@@ -244,9 +244,6 @@ export async function deleteProject(projectId: string) {
const ids = sources.map(s => s._id);
// delete data sources
- await embeddingsCollection.deleteMany({
- sourceId: { $in: ids.map(i => i.toString()) },
- });
await dataSourcesCollection.deleteMany({
_id: {
$in: ids,
diff --git a/apps/rowboat/app/lib/mongodb.ts b/apps/rowboat/app/lib/mongodb.ts
index 60c01afd..bb9fc838 100644
--- a/apps/rowboat/app/lib/mongodb.ts
+++ b/apps/rowboat/app/lib/mongodb.ts
@@ -16,10 +16,8 @@ const client = new MongoClient(process.env["MONGODB_CONNECTION_STRING"] || "mong
export const db = client.db("rowboat");
export const dataSourcesCollection = db.collection>("sources");
export const dataSourceDocsCollection = db.collection>("source_docs");
-export const embeddingsCollection = db.collection>("embeddings");
export const projectsCollection = db.collection>("projects");
export const projectMembersCollection = db.collection>("project_members");
-export const webpagesCollection = db.collection>('webpages');
export const agentWorkflowsCollection = db.collection>("agent_workflows");
export const apiKeysCollection = db.collection>("api_keys");
export const chatsCollection = db.collection>("chats");
From 0d50777b9324ee4bb924965d5ab0ee82bb502e92 Mon Sep 17 00:00:00 2001
From: Ramnique Singh <30795890+ramnique@users.noreply.github.com>
Date: Fri, 15 Aug 2025 12:57:19 +0530
Subject: [PATCH 7/7] ddd refactor: api-keys repo
---
apps/rowboat/app/actions/project_actions.ts | 8 ++++----
apps/rowboat/app/lib/mongodb.ts | 1 -
.../repositories/api-keys.repository.interface.ts | 6 ++++++
.../repositories/mongodb.api-keys.repository.ts | 4 ++++
4 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/apps/rowboat/app/actions/project_actions.ts b/apps/rowboat/app/actions/project_actions.ts
index 062d3a68..b1aa9b8b 100644
--- a/apps/rowboat/app/actions/project_actions.ts
+++ b/apps/rowboat/app/actions/project_actions.ts
@@ -1,7 +1,7 @@
'use server';
import { redirect } from "next/navigation";
import { ObjectId } from "mongodb";
-import { db, dataSourcesCollection, projectsCollection, projectMembersCollection, apiKeysCollection, dataSourceDocsCollection } from "../lib/mongodb";
+import { db, dataSourcesCollection, projectsCollection, projectMembersCollection, dataSourceDocsCollection } from "../lib/mongodb";
import { z } from 'zod';
import crypto from 'crypto';
import { revalidatePath } from "next/cache";
@@ -18,12 +18,14 @@ import { IProjectActionAuthorizationPolicy } from "@/src/application/policies/pr
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";
const KLAVIS_API_KEY = process.env.KLAVIS_API_KEY || '';
const projectActionAuthorizationPolicy = container.resolve('projectActionAuthorizationPolicy');
const createApiKeyController = container.resolve('createApiKeyController');
const listApiKeysController = container.resolve('listApiKeysController');
const deleteApiKeyController = container.resolve('deleteApiKeyController');
+const apiKeysRepository = container.resolve('apiKeysRepository');
export async function listTemplates() {
const templatesArray = Object.entries(templates)
@@ -228,9 +230,7 @@ export async function deleteProject(projectId: string) {
await projectAuthCheck(projectId);
// delete api keys
- await apiKeysCollection.deleteMany({
- projectId,
- });
+ await apiKeysRepository.deleteAll(projectId);
// delete embeddings
const sources = await dataSourcesCollection.find({
diff --git a/apps/rowboat/app/lib/mongodb.ts b/apps/rowboat/app/lib/mongodb.ts
index bb9fc838..7461d3c4 100644
--- a/apps/rowboat/app/lib/mongodb.ts
+++ b/apps/rowboat/app/lib/mongodb.ts
@@ -19,7 +19,6 @@ export const dataSourceDocsCollection = db.collection>("projects");
export const projectMembersCollection = db.collection>("project_members");
export const agentWorkflowsCollection = db.collection>("agent_workflows");
-export const apiKeysCollection = db.collection>("api_keys");
export const chatsCollection = db.collection>("chats");
export const chatMessagesCollection = db.collection>("chat_messages");
export const twilioConfigsCollection = db.collection>("twilio_configs");
diff --git a/apps/rowboat/src/application/repositories/api-keys.repository.interface.ts b/apps/rowboat/src/application/repositories/api-keys.repository.interface.ts
index cde6d3c3..b33b637f 100644
--- a/apps/rowboat/src/application/repositories/api-keys.repository.interface.ts
+++ b/apps/rowboat/src/application/repositories/api-keys.repository.interface.ts
@@ -31,6 +31,12 @@ export interface IApiKeysRepository {
*/
delete(projectId: string, id: string): Promise;
+ /**
+ * Deletes all API keys for a given project.
+ * @param projectId - The ID of the project.
+ */
+ deleteAll(projectId: string): Promise;
+
/**
* Checks if an API key is valid for a project and consumes it (e.g., for rate limiting or one-time use).
* @param projectId - The ID of the project.
diff --git a/apps/rowboat/src/infrastructure/repositories/mongodb.api-keys.repository.ts b/apps/rowboat/src/infrastructure/repositories/mongodb.api-keys.repository.ts
index 1a8fe17d..bd953437 100644
--- a/apps/rowboat/src/infrastructure/repositories/mongodb.api-keys.repository.ts
+++ b/apps/rowboat/src/infrastructure/repositories/mongodb.api-keys.repository.ts
@@ -56,4 +56,8 @@ export class MongoDBApiKeysRepository implements IApiKeysRepository {
const result = await this.collection.deleteOne({ projectId, _id: new ObjectId(id) });
return result.deletedCount > 0;
}
+
+ async deleteAll(projectId: string): Promise {
+ await this.collection.deleteMany({ projectId });
+ }
}
\ No newline at end of file