Skip to content

Commit e9bd011

Browse files
feat: self-serve API keys, agent/user manual pages, redesigned dashboard
- Add ApiKey model to Prisma schema (id, key, name, userId, createdAt) - Add Bearer token auth bypass in clerk-auth.ts for agent API keys - Add POST/GET /api/keys and DELETE /api/keys/[keyId] endpoints - Redesign API Keys panel with design-system-consistent styling - Create /manual/agent route with full agent documentation - Create /manual/user route with human operator guide - Add Agent Manual / User Manual navigation links to site header - Fix submitJson to not throw (uses setStatus for errors instead) - Fix crypto import to use ES module syntax - Clean up indentation and error handling
1 parent 713902f commit e9bd011

9 files changed

Lines changed: 1280 additions & 14 deletions

File tree

prisma/schema.prisma

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,4 +154,12 @@ model AuditEvent {
154154
155155
@@index([repositoryId, createdAt])
156156
@@index([eventType, createdAt])
157+
}
158+
159+
model ApiKey {
160+
id String @id @default(cuid())
161+
key String @unique
162+
name String
163+
userId String // Clerk user ID of the creator
164+
createdAt DateTime @default(now())
157165
}

src/app/api/keys/[keyId]/route.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { NextResponse } from "next/server";
2+
import { getCurrentObserver } from "@/lib/clerk-auth";
3+
import { db } from "@/lib/db";
4+
5+
export const runtime = "nodejs";
6+
7+
export async function DELETE(
8+
request: Request,
9+
{ params }: { params: Promise<{ keyId: string }> }
10+
) {
11+
const observer = await getCurrentObserver();
12+
if (!observer || observer.role === "agent") {
13+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
14+
}
15+
16+
if (!db) {
17+
return NextResponse.json({ error: "Requires Database connection" }, { status: 400 });
18+
}
19+
20+
const { keyId } = await params;
21+
22+
try {
23+
const key = await db.apiKey.findUnique({ where: { id: keyId } });
24+
if (!key) {
25+
return NextResponse.json({ error: "Not found" }, { status: 404 });
26+
}
27+
28+
if (key.userId !== observer.clerkUserId) {
29+
return NextResponse.json({ error: "Unauthorized" }, { status: 403 });
30+
}
31+
32+
await db.apiKey.delete({ where: { id: keyId } });
33+
return NextResponse.json({ success: true });
34+
} catch (error: any) {
35+
return NextResponse.json({ error: error.message }, { status: 500 });
36+
}
37+
}

src/app/api/keys/route.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import crypto from "crypto";
2+
import { NextResponse } from "next/server";
3+
import { getCurrentObserver } from "@/lib/clerk-auth";
4+
import { db } from "@/lib/db";
5+
6+
export const runtime = "nodejs";
7+
8+
export async function GET() {
9+
const observer = await getCurrentObserver();
10+
if (!observer || observer.role === "agent") {
11+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
12+
}
13+
14+
if (!db) {
15+
return NextResponse.json({ error: "Requires Database connection" }, { status: 400 });
16+
}
17+
18+
const keys = await db.apiKey.findMany({
19+
where: { userId: observer.clerkUserId },
20+
select: { id: true, name: true, createdAt: true, key: true },
21+
orderBy: { createdAt: 'desc' }
22+
});
23+
24+
return NextResponse.json(keys);
25+
}
26+
27+
export async function POST(request: Request) {
28+
const observer = await getCurrentObserver();
29+
if (!observer || observer.role === "agent") {
30+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
31+
}
32+
33+
if (!db) {
34+
return NextResponse.json({ error: "Requires Database connection" }, { status: 400 });
35+
}
36+
37+
const body = await request.json().catch(() => ({}));
38+
const name = body.name?.trim() || "Generated Key";
39+
40+
const randomValue = crypto.randomBytes(24).toString("hex");
41+
const keyValue = `sk_agent_${randomValue}`;
42+
43+
const newKey = await db.apiKey.create({
44+
data: {
45+
userId: observer.clerkUserId,
46+
name,
47+
key: keyValue,
48+
},
49+
select: { id: true, name: true, createdAt: true, key: true },
50+
});
51+
52+
return NextResponse.json(newKey, { status: 201 });
53+
}

0 commit comments

Comments
 (0)