feat: add subscription service — user auth, Stripe billing, API keys, dashboard

- NextAuth v5 with email+password credentials, JWT sessions
- Registration, login, email verification, password reset flows
- Stripe integration: Free (15/day), Starter ($5/1k/mo), Pro ($20/100k/mo)
- API key management (cb_ prefix) with hash-based validation
- Dashboard with generations history, settings, billing management
- Rate limiting: Redis daily counter (free), DB monthly (paid)
- Generate route auth: Bearer API key + session, anonymous allowed
- Worker userId propagation for generation history
- Pricing section on landing page, auth-aware navbar
- Middleware with route protection, CORS for codeboard.vectry.tech
- Docker env vars for auth, Stripe, email (smtp.migadu.com)
This commit is contained in:
Vectry
2026-02-10 20:08:13 +00:00
parent 7ff493a89a
commit 64ce70daa4
45 changed files with 3073 additions and 34 deletions

View File

@@ -7,6 +7,112 @@ generator client {
provider = "prisma-client-js"
}
model User {
id String @id @default(cuid())
email String @unique
passwordHash String
name String?
emailVerified Boolean @default(false)
image String?
stripeCustomerId String? @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
subscription Subscription?
apiKeys ApiKey[]
generations Generation[]
passwordResetTokens PasswordResetToken[]
emailVerificationTokens EmailVerificationToken[]
@@index([email])
}
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?
generationsUsed Int @default(0)
generationsLimit Int @default(15)
status SubscriptionStatus @default(ACTIVE)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([stripeCustomerId])
@@index([stripeSubscriptionId])
}
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
keyPrefix String
lastUsedAt DateTime?
revoked Boolean @default(false)
createdAt DateTime @default(now())
@@index([keyHash])
@@index([userId])
}
model PasswordResetToken {
id String @id @default(cuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
token String @unique
expiresAt DateTime
used Boolean @default(false)
createdAt DateTime @default(now())
@@index([token])
@@index([userId])
}
model EmailVerificationToken {
id String @id @default(cuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
token String @unique
expiresAt DateTime
used Boolean @default(false)
createdAt DateTime @default(now())
@@index([token])
@@index([userId])
}
enum SubscriptionTier {
FREE
STARTER
PRO
}
enum SubscriptionStatus {
ACTIVE
PAST_DUE
CANCELED
UNPAID
}
model Generation {
id String @id @default(cuid())
repoUrl String
@@ -27,16 +133,7 @@ model Generation {
@@unique([repoUrl, commitHash])
@@index([repoUrl])
@@index([status])
}
model User {
id String @id @default(cuid())
githubId String @unique
login String
email String?
avatarUrl String?
createdAt DateTime @default(now())
generations Generation[]
@@index([userId])
}
enum Status {