generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model User { id String @id @default(cuid()) email String @unique passwordHash String? name String? did String? @unique // EncryptID DID (passkey identity) credits Int @default(0) lastCreditAt DateTime @default(now()) emailVerified DateTime? votes Vote[] finalVotes FinalVote[] proposals Proposal[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // NextAuth fields accounts Account[] sessions Session[] // Space memberships spaceMemberships SpaceMember[] } model Account { id String @id @default(cuid()) userId String type String provider String providerAccountId String refresh_token String? @db.Text access_token String? @db.Text expires_at Int? token_type String? scope String? id_token String? @db.Text session_state String? user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([provider, providerAccountId]) } model Session { id String @id @default(cuid()) sessionToken String @unique userId String expires DateTime user User @relation(fields: [userId], references: [id], onDelete: Cascade) } model VerificationToken { identifier String token String @unique expires DateTime @@unique([identifier, token]) } // ─── Spaces (Multi-Tenant) ─────────────────────────────────────────── model Space { id String @id @default(cuid()) name String slug String @unique description String? @db.Text visibility String @default("public_read") // public, public_read, authenticated, members_only ownerDid String? // Configurable per-space voting parameters promotionThreshold Int @default(100) votingPeriodDays Int @default(7) creditsPerDay Int @default(10) maxCredits Int @default(500) startingCredits Int @default(50) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt members SpaceMember[] proposals Proposal[] invites SpaceInvite[] } enum SpaceRole { ADMIN MEMBER } model SpaceMember { id String @id @default(cuid()) userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) spaceId String space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade) role SpaceRole @default(MEMBER) // Per-space credits credits Int @default(0) lastCreditAt DateTime @default(now()) joinedAt DateTime @default(now()) @@unique([userId, spaceId]) @@index([spaceId]) } model SpaceInvite { id String @id @default(cuid()) spaceId String space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade) // For email invites email String? // For shareable link invites token String @unique @default(cuid()) // Invite metadata maxUses Int? uses Int @default(0) expiresAt DateTime? createdBy String createdAt DateTime @default(now()) @@index([token]) @@index([email, spaceId]) } // ─── Proposals & Voting ────────────────────────────────────────────── model Proposal { id String @id @default(cuid()) title String description String @db.Text authorId String author User @relation(fields: [authorId], references: [id]) spaceId String? space Space? @relation(fields: [spaceId], references: [id], onDelete: Cascade) status ProposalStatus @default(RANKING) score Int @default(0) votes Vote[] finalVotes FinalVote[] votingEndsAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([spaceId, status]) } enum ProposalStatus { RANKING VOTING PASSED FAILED ARCHIVED } model Vote { id String @id @default(cuid()) userId String user User @relation(fields: [userId], references: [id]) proposalId String proposal Proposal @relation(fields: [proposalId], references: [id], onDelete: Cascade) weight Int creditCost Int createdAt DateTime @default(now()) decaysAt DateTime @@unique([userId, proposalId]) } model FinalVote { id String @id @default(cuid()) userId String user User @relation(fields: [userId], references: [id]) proposalId String proposal Proposal @relation(fields: [proposalId], references: [id], onDelete: Cascade) vote VoteChoice createdAt DateTime @default(now()) @@unique([userId, proposalId]) } enum VoteChoice { YES NO ABSTAIN }