From d4f0db1f098ae908137f2c36ee9f8893601eb323 Mon Sep 17 00:00:00 2001 From: Ramnique Singh <30795890+ramnique@users.noreply.github.com> Date: Fri, 15 Aug 2025 12:48:10 +0530 Subject: [PATCH] ddd refactor: api-keys --- apps/rowboat/app/actions/project_actions.ts | 53 +++--- apps/rowboat/app/lib/mongodb.ts | 2 +- apps/rowboat/app/lib/types/project_types.ts | 7 - .../app/projects/[projectId]/config/app.tsx | 156 +----------------- .../[projectId]/config/components/project.tsx | 10 +- apps/rowboat/di/container.ts | 14 ++ .../api-keys.repository.interface.ts | 38 +++++ .../api-keys/create-api-key.use-case.ts | 59 +++++++ .../api-keys/delete-api-key.use-case.ts | 42 +++++ .../api-keys/list-api-keys.use-case.ts | 42 +++++ apps/rowboat/src/entities/models/api-key.ts | 9 + .../mongodb.api-keys.repository.ts | 51 +++++- .../api-keys/create-api-key.controller.ts | 30 ++++ .../api-keys/delete-api-key.controller.ts | 30 ++++ .../api-keys/list-api-keys.controller.ts | 30 ++++ 15 files changed, 377 insertions(+), 196 deletions(-) create mode 100644 apps/rowboat/src/application/use-cases/api-keys/create-api-key.use-case.ts create mode 100644 apps/rowboat/src/application/use-cases/api-keys/delete-api-key.use-case.ts create mode 100644 apps/rowboat/src/application/use-cases/api-keys/list-api-keys.use-case.ts create mode 100644 apps/rowboat/src/entities/models/api-key.ts create mode 100644 apps/rowboat/src/interface-adapters/controllers/api-keys/create-api-key.controller.ts create mode 100644 apps/rowboat/src/interface-adapters/controllers/api-keys/delete-api-key.controller.ts create mode 100644 apps/rowboat/src/interface-adapters/controllers/api-keys/list-api-keys.controller.ts diff --git a/apps/rowboat/app/actions/project_actions.ts b/apps/rowboat/app/actions/project_actions.ts index 22e41161..4abcb730 100644 --- a/apps/rowboat/app/actions/project_actions.ts +++ b/apps/rowboat/app/actions/project_actions.ts @@ -8,17 +8,22 @@ import { revalidatePath } from "next/cache"; import { templates } from "../lib/project_templates"; import { authCheck } from "./auth_actions"; import { User, WithStringId } from "../lib/types/types"; -import { ApiKey } from "../lib/types/project_types"; +import { ApiKey } from "@/src/entities/models/api-key"; import { Project } from "../lib/types/project_types"; import { USE_AUTH } from "../lib/feature_flags"; import { authorizeUserAction } from "./billing_actions"; import { Workflow } from "../lib/types/workflow_types"; import { container } from "@/di/container"; 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"; 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'); export async function listTemplates() { const templatesArray = Object.entries(templates) @@ -180,36 +185,32 @@ export async function updateWebhookUrl(projectId: string, url: string) { ); } -export async function createApiKey(projectId: string): Promise>> { - await projectAuthCheck(projectId); - - // count existing keys - const count = await apiKeysCollection.countDocuments({ projectId }); - if (count >= 3) { - throw new Error('Maximum number of API keys reached'); - } - - // create key - const key = crypto.randomBytes(32).toString('hex'); - const doc: z.infer = { +export async function createApiKey(projectId: string): Promise> { + const user = await authCheck(); + return await createApiKeyController.execute({ + caller: 'user', + userId: user._id, projectId, - key, - createdAt: new Date().toISOString(), - }; - await apiKeysCollection.insertOne(doc); - const { _id, ...rest } = doc as WithStringId>; - return { ...rest, _id: _id.toString() }; + }); } export async function deleteApiKey(projectId: string, id: string) { - await projectAuthCheck(projectId); - await apiKeysCollection.deleteOne({ projectId, _id: new ObjectId(id) }); + const user = await authCheck(); + return await deleteApiKeyController.execute({ + caller: 'user', + userId: user._id, + projectId, + id, + }); } -export async function listApiKeys(projectId: string): Promise>[]> { - await projectAuthCheck(projectId); - const keys = await apiKeysCollection.find({ projectId }).toArray(); - return keys.map(k => ({ ...k, _id: k._id.toString() })); +export async function listApiKeys(projectId: string): Promise[]> { + const user = await authCheck(); + return await listApiKeysController.execute({ + caller: 'user', + userId: user._id, + projectId, + }); } export async function updateProjectName(projectId: string, name: string) { diff --git a/apps/rowboat/app/lib/mongodb.ts b/apps/rowboat/app/lib/mongodb.ts index e47555f5..60c01afd 100644 --- a/apps/rowboat/app/lib/mongodb.ts +++ b/apps/rowboat/app/lib/mongodb.ts @@ -1,7 +1,7 @@ import { MongoClient } from "mongodb"; import { User, Webpage } from "./types/types"; import { Workflow } from "./types/workflow_types"; -import { ApiKey } from "./types/project_types"; +import { ApiKey } from "@/src/entities/models/api-key"; import { ProjectMember } from "./types/project_types"; import { Project } from "./types/project_types"; import { EmbeddingDoc } from "./types/datasource_types"; diff --git a/apps/rowboat/app/lib/types/project_types.ts b/apps/rowboat/app/lib/types/project_types.ts index 9dd1957d..18e37af6 100644 --- a/apps/rowboat/app/lib/types/project_types.ts +++ b/apps/rowboat/app/lib/types/project_types.ts @@ -41,11 +41,4 @@ export const ProjectMember = z.object({ projectId: z.string(), createdAt: z.string().datetime(), lastUpdatedAt: z.string().datetime(), -}); - -export const ApiKey = z.object({ - 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/app/projects/[projectId]/config/app.tsx b/apps/rowboat/app/projects/[projectId]/config/app.tsx index e7b0d26b..30698143 100644 --- a/apps/rowboat/app/projects/[projectId]/config/app.tsx +++ b/apps/rowboat/app/projects/[projectId]/config/app.tsx @@ -4,14 +4,10 @@ import { Metadata } from "next"; import { Spinner, Dropdown, DropdownMenu, DropdownItem, DropdownTrigger, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Input, useDisclosure, Divider, Textarea } from "@heroui/react"; import { Button } from "@/components/ui/button"; import { ReactNode, useEffect, useState } from "react"; -import { getProjectConfig, updateProjectName, updateWebhookUrl, createApiKey, deleteApiKey, listApiKeys, deleteProject, rotateSecret } from "../../../actions/project_actions"; +import { getProjectConfig, updateProjectName, updateWebhookUrl, deleteProject, rotateSecret } from "../../../actions/project_actions"; import { CopyButton } from "../../../../components/common/copy-button"; import { InputField } from "../../../lib/components/input-field"; import { EyeIcon, EyeOffIcon, Settings, Plus, MoreVertical } from "lucide-react"; -import { WithStringId } from "../../../lib/types/types"; -import { ApiKey } from "../../../lib/types/project_types"; -import { z } from "zod"; -import { RelativeTime } from "@primer/react"; import { Label } from "../../../lib/components/label"; import { FormSection } from "../../../lib/components/form-section"; import { Panel } from "@/components/common/panel-common"; @@ -108,156 +104,6 @@ export function BasicSettingsSection({ ; } -export function ApiKeysSection({ - projectId, -}: { - projectId: string; -}) { - const [keys, setKeys] = useState>[]>([]); - const [loading, setLoading] = useState(true); - const [message, setMessage] = useState<{ - type: 'success' | 'error' | 'info'; - text: string; - } | null>(null); - - useEffect(() => { - const loadKeys = async () => { - const keys = await listApiKeys(projectId); - setKeys(keys); - setLoading(false); - }; - loadKeys(); - }, [projectId]); - - const handleCreateKey = async () => { - setLoading(true); - setMessage(null); - try { - const key = await createApiKey(projectId); - setLoading(false); - setMessage({ - type: 'success', - text: 'API key created successfully', - }); - setKeys([...keys, key]); - - setTimeout(() => { - setMessage(null); - }, 2000); - } catch (error) { - setLoading(false); - setMessage({ - type: 'error', - text: error instanceof Error ? error.message : "Failed to create API key", - }); - } - }; - - const handleDeleteKey = async (id: string) => { - if (!window.confirm("Are you sure you want to delete this API key? This action cannot be undone.")) { - return; - } - - try { - setLoading(true); - setMessage(null); - await deleteApiKey(projectId, id); - setKeys(keys.filter((k) => k._id !== id)); - setLoading(false); - setMessage({ - type: 'info', - text: 'API key deleted successfully', - }); - setTimeout(() => { - setMessage(null); - }, 2000); - } catch (error) { - setLoading(false); - setMessage({ - type: 'error', - text: error instanceof Error ? error.message : "Failed to delete API key", - }); - } - }; - - return
-
-
-

- API keys are used to authenticate requests to the Rowboat API. -

- -
- - - {loading && } - {!loading &&
-
-
API Key
-
Created
-
Last Used
-
-
- {message?.type === 'success' &&
-
{message.text}
-
} - {message?.type === 'error' &&
-
{message.text}
-
} - {message?.type === 'info' &&
-
{message.text}
-
} -
- {keys.map((key) => ( -
-
- -
-
- -
-
- {key.lastUsedAt ? : 'Never'} -
-
- - - - - - handleDeleteKey(key._id)} - > - Delete - - - -
-
- ))} - {keys.length === 0 && ( -
- No API keys created yet -
- )} -
-
} -
-
; -} - export function SecretSection({ projectId, }: { diff --git a/apps/rowboat/app/projects/[projectId]/config/components/project.tsx b/apps/rowboat/app/projects/[projectId]/config/components/project.tsx index 4c2c9661..8b375b62 100644 --- a/apps/rowboat/app/projects/[projectId]/config/components/project.tsx +++ b/apps/rowboat/app/projects/[projectId]/config/components/project.tsx @@ -7,7 +7,7 @@ import { getProjectConfig, createApiKey, deleteApiKey, listApiKeys, deleteProjec import { CopyButton } from "../../../../../components/common/copy-button"; import { EyeIcon, EyeOffIcon, PlusIcon, Trash2Icon } from "lucide-react"; import { WithStringId } from "../../../../lib/types/types"; -import { ApiKey } from "../../../../lib/types/project_types"; +import { ApiKey } from "@/src/entities/models/api-key"; import { z } from "zod"; import { RelativeTime } from "@primer/react"; import { Label } from "../../../../lib/components/label"; @@ -224,7 +224,7 @@ function ApiKeyDisplay({ apiKey, onDelete }: { apiKey: string; onDelete: () => v } function ApiKeysSection({ projectId }: { projectId: string }) { - const [keys, setKeys] = useState>[]>([]); + const [keys, setKeys] = useState[]>([]); const [loading, setLoading] = useState(true); const [message, setMessage] = useState<{ type: 'success' | 'error' | 'info'; @@ -270,7 +270,7 @@ function ApiKeysSection({ projectId }: { projectId: string }) { try { setLoading(true); await deleteApiKey(projectId, id); - setKeys(keys.filter((k) => k._id !== id)); + setKeys(keys.filter((k) => k.id !== id)); setMessage({ type: 'info', text: 'API key deleted successfully', @@ -325,11 +325,11 @@ function ApiKeysSection({ projectId }: { projectId: string }) { )} {keys.map((key) => ( -
+
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 };