feat: user auth, API keys, Stripe billing, and dashboard scoping

- NextAuth v5 credentials auth with registration/login pages
- API key CRUD (create, list, revoke) with secure hashing
- Stripe checkout, webhooks, and customer portal integration
- Rate limiting per subscription tier
- All dashboard API endpoints scoped to authenticated user
- Prisma schema: User, Account, Session, ApiKey, plus Stripe fields
- Auth middleware protecting dashboard and API routes

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-Claude)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Vectry
2026-02-10 15:37:49 +00:00
parent 07cf717c15
commit 61268f870f
33 changed files with 2247 additions and 57 deletions

View File

@@ -7,6 +7,82 @@ generator client {
provider = "prisma-client-js"
}
// ─── Auth & Billing ────────────────────────────────────────────
model User {
id String @id @default(cuid())
email String @unique
passwordHash String
name String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
subscription Subscription?
apiKeys ApiKey[]
traces Trace[]
@@index([email])
}
model ApiKey {
id String @id @default(cuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
name String @default("Default")
keyHash String @unique // SHA-256 hash of the actual key
keyPrefix String // First 8 chars for display: "al_xxxx..."
lastUsedAt DateTime?
revoked Boolean @default(false)
createdAt DateTime @default(now())
@@index([keyHash])
@@index([userId])
}
model Subscription {
id String @id @default(cuid())
userId String @unique
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
tier SubscriptionTier @default(FREE)
stripeCustomerId String? @unique
stripeSubscriptionId String? @unique
stripePriceId String?
currentPeriodStart DateTime?
currentPeriodEnd DateTime?
// Usage tracking for the current billing period
sessionsUsed Int @default(0)
sessionsLimit Int @default(20) // Free tier: 20/day, paid: per month
status SubscriptionStatus @default(ACTIVE)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([stripeCustomerId])
@@index([stripeSubscriptionId])
}
enum SubscriptionTier {
FREE // 20 sessions/day
STARTER // $5/mo — 1,000 sessions/mo
PRO // $20/mo — 100,000 sessions/mo
}
enum SubscriptionStatus {
ACTIVE
PAST_DUE
CANCELED
UNPAID
}
// ─── Observability ─────────────────────────────────────────────
model Trace {
id String @id @default(cuid())
sessionId String?
@@ -15,6 +91,10 @@ model Trace {
tags String[] @default([])
metadata Json?
// Owner — nullable for backward compat with existing unowned traces
userId String?
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
totalCost Float?
totalTokens Int?
totalDuration Int?
@@ -32,6 +112,7 @@ model Trace {
@@index([status])
@@index([createdAt])
@@index([name])
@@index([userId])
}
model DecisionPoint {