Files
agentlens/packages/database/prisma/schema.prisma
Vectry 61268f870f 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>
2026-02-10 15:37:49 +00:00

236 lines
5.1 KiB
Plaintext

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
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?
name String
status TraceStatus @default(RUNNING)
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?
startedAt DateTime @default(now())
endedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
decisionPoints DecisionPoint[]
spans Span[]
events Event[]
@@index([sessionId])
@@index([status])
@@index([createdAt])
@@index([name])
@@index([userId])
}
model DecisionPoint {
id String @id @default(cuid())
traceId String
trace Trace @relation(fields: [traceId], references: [id], onDelete: Cascade)
type DecisionType
reasoning String?
chosen Json
alternatives Json[]
contextSnapshot Json?
durationMs Int?
costUsd Float?
parentSpanId String?
span Span? @relation(fields: [parentSpanId], references: [id])
timestamp DateTime @default(now())
@@index([traceId])
@@index([type])
@@index([timestamp])
}
model Span {
id String @id @default(cuid())
traceId String
trace Trace @relation(fields: [traceId], references: [id], onDelete: Cascade)
parentSpanId String?
parentSpan Span? @relation("SpanTree", fields: [parentSpanId], references: [id])
childSpans Span[] @relation("SpanTree")
name String
type SpanType
input Json?
output Json?
tokenCount Int?
costUsd Float?
durationMs Int?
status SpanStatus @default(RUNNING)
statusMessage String?
startedAt DateTime @default(now())
endedAt DateTime?
metadata Json?
decisionPoints DecisionPoint[]
@@index([traceId])
@@index([parentSpanId])
@@index([type])
@@index([startedAt])
}
model Event {
id String @id @default(cuid())
traceId String
trace Trace @relation(fields: [traceId], references: [id], onDelete: Cascade)
spanId String?
type EventType
name String
metadata Json?
timestamp DateTime @default(now())
@@index([traceId])
@@index([type])
@@index([timestamp])
}
enum TraceStatus {
RUNNING
COMPLETED
ERROR
}
enum DecisionType {
TOOL_SELECTION
ROUTING
RETRY
ESCALATION
MEMORY_RETRIEVAL
PLANNING
CUSTOM
}
enum SpanType {
LLM_CALL
TOOL_CALL
MEMORY_OP
CHAIN
AGENT
CUSTOM
}
enum SpanStatus {
RUNNING
COMPLETED
ERROR
}
enum EventType {
ERROR
RETRY
FALLBACK
CONTEXT_OVERFLOW
USER_FEEDBACK
CUSTOM
}