From d1806bfd7eeb3fabe4801af61348ec9336103222 Mon Sep 17 00:00:00 2001 From: Oleg Proskurin Date: Sat, 25 Oct 2025 18:57:19 +0700 Subject: [PATCH] feat: enhance api key api --- apps/api-service/src/routes/admin/keys.ts | 15 ++-- .../api-service/src/services/ApiKeyService.ts | 84 ++++++++++++++++++- 2 files changed, 91 insertions(+), 8 deletions(-) diff --git a/apps/api-service/src/routes/admin/keys.ts b/apps/api-service/src/routes/admin/keys.ts index a512a96..1a43bd5 100644 --- a/apps/api-service/src/routes/admin/keys.ts +++ b/apps/api-service/src/routes/admin/keys.ts @@ -112,20 +112,25 @@ router.get('/', async (req, res) => { try { const keys = await apiKeyService.listKeys(); - // Don't expose key hashes + // Format response with nested objects, ISO dates, and no sensitive data const safeKeys = keys.map((key) => ({ id: key.id, type: key.keyType, - projectId: key.projectId, name: key.name, scopes: key.scopes, isActive: key.isActive, - createdAt: key.createdAt, - expiresAt: key.expiresAt, - lastUsedAt: key.lastUsedAt, + createdAt: key.createdAt.toISOString(), + expiresAt: key.expiresAt ? key.expiresAt.toISOString() : null, + lastUsedAt: key.lastUsedAt ? key.lastUsedAt.toISOString() : null, createdBy: key.createdBy, + organization: key.organization, + project: key.project, })); + console.log( + `[${new Date().toISOString()}] API keys listed by admin: ${req.apiKey!.id} - returned ${safeKeys.length} keys`, + ); + res.json({ keys: safeKeys, total: safeKeys.length, diff --git a/apps/api-service/src/services/ApiKeyService.ts b/apps/api-service/src/services/ApiKeyService.ts index 395632c..c97eb8e 100644 --- a/apps/api-service/src/services/ApiKeyService.ts +++ b/apps/api-service/src/services/ApiKeyService.ts @@ -9,6 +9,21 @@ export interface ApiKeyWithSlugs extends ApiKey { projectSlug?: string; } +// Extended API key type with full organization and project details for admin listing +export interface ApiKeyWithDetails extends ApiKey { + organization: { + id: string; + slug: string; + name: string; + email: string; + } | null; + project: { + id: string; + slug: string; + name: string; + } | null; +} + export class ApiKeyService { /** * Generate a new API key @@ -182,10 +197,73 @@ export class ApiKeyService { } /** - * List all keys (for admin) + * List all keys (for admin) with organization and project details */ - async listKeys(): Promise { - return db.select().from(apiKeys).orderBy(desc(apiKeys.createdAt)); + async listKeys(): Promise { + const results = await db + .select({ + // API key fields + id: apiKeys.id, + keyHash: apiKeys.keyHash, + keyPrefix: apiKeys.keyPrefix, + keyType: apiKeys.keyType, + organizationId: apiKeys.organizationId, + projectId: apiKeys.projectId, + scopes: apiKeys.scopes, + createdAt: apiKeys.createdAt, + expiresAt: apiKeys.expiresAt, + lastUsedAt: apiKeys.lastUsedAt, + isActive: apiKeys.isActive, + name: apiKeys.name, + createdBy: apiKeys.createdBy, + // Organization fields + orgId: organizations.id, + orgSlug: organizations.slug, + orgName: organizations.name, + orgEmail: organizations.email, + // Project fields + projId: projects.id, + projSlug: projects.slug, + projName: projects.name, + }) + .from(apiKeys) + .leftJoin(organizations, eq(apiKeys.organizationId, organizations.id)) + .leftJoin(projects, eq(apiKeys.projectId, projects.id)) + .orderBy(desc(apiKeys.createdAt)); + + // Transform flat results to nested structure + return results.map((row) => ({ + id: row.id, + keyHash: row.keyHash, + keyPrefix: row.keyPrefix, + keyType: row.keyType, + organizationId: row.organizationId, + projectId: row.projectId, + scopes: row.scopes, + createdAt: row.createdAt, + expiresAt: row.expiresAt, + lastUsedAt: row.lastUsedAt, + isActive: row.isActive, + name: row.name, + createdBy: row.createdBy, + organization: + row.orgId && row.orgSlug && row.orgName && row.orgEmail + ? { + id: row.orgId, + slug: row.orgSlug, + name: row.orgName, + email: row.orgEmail, + } + : null, + project: + row.projId && row.projSlug && row.projName + ? { + id: row.projId, + slug: row.projSlug, + name: row.projName, + } + : null, + })); } /**