// Prisma schema for the organizer app. // Learn more: https://pris.ly/d/prisma-schema generator client { provider = "prisma-client" output = "../lib/generated/prisma" } datasource db { provider = "postgresql" } enum Role { ADMIN USER PENDING } // How the site handles new sign-ups. See SiteSettings.signupMode. enum SignupMode { // Anyone can sign up and is immediately a USER. Today's behavior. OPEN // Anyone can sign up, but starts PENDING until an admin approves them // (moves them to USER or ADMIN) on the Admin page. APPROVED // Anyone can sign up, but starts PENDING until they confirm their email. // Not implemented yet -- the Admin page exposes this option greyed out. CONFIRMED // Sign-ups are turned off entirely: no form, no working sign-up action. CLOSED } model User { id String @id @default(cuid()) email String @unique passwordHash String name String? // The very first person to sign up becomes ADMIN regardless of // signupMode (the site needs at least one admin to bootstrap). Everyone // after that gets USER (signupMode OPEN) or PENDING (APPROVED/CONFIRMED), // per SiteSettings.signupMode. role Role @default(USER) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt categories Category[] projects Project[] @relation("ProjectOwner") } /** * An isolated board: its own Categories/Groups/To-Dos, invisible from Home * and from every other Project. Sole-owner for now -- `ownerId` is both * "who created it" and (for now) the only person with access. When * Projects become shared spaces, add a `ProjectMember` join table * (projectId, userId, role) and extend the access checks in * lib/access.ts to consult it too; `ownerId` stays as the implicit * full-access member and doesn't need to change. */ model Project { id String @id @default(cuid()) title String ownerId String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt owner User @relation("ProjectOwner", fields: [ownerId], references: [id], onDelete: Cascade) categories Category[] @@index([ownerId, createdAt]) } // Single-row table of site-wide settings. Always has exactly one row, at // the fixed id below -- read with `findFirst` (falling back to defaults // when the row doesn't exist yet) and written with `upsert`. model SiteSettings { id String @id @default("singleton") signupMode SignupMode @default(OPEN) updatedAt DateTime @updatedAt } model Category { id String @id @default(cuid()) name String order Int // Exactly one of these is set: userId for a Home category (personal, // outside any project), projectId for a category inside a Project. // Enforced by a DB check constraint (see migration) as well as by every // access path going through lib/access.ts rather than querying userId // or projectId directly. userId String? projectId String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User? @relation(fields: [userId], references: [id], onDelete: Cascade) project Project? @relation(fields: [projectId], references: [id], onDelete: Cascade) groups Group[] @@index([userId, order]) @@index([projectId, order]) } model Group { id String @id @default(cuid()) // Short display title, enforced at <=20 chars in app-level validation too. title String @db.VarChar(20) // Palette key (see lib/colors.ts) rather than a raw hex value, so the // curated palette can be retinted centrally without a data migration. color String // Position within its Category lane. order Int // Single evolving markdown document for the group. Not a separate model: // it's a 1:1, always-exists field with no independent lifecycle. noteContent String @default("") @db.Text // Set once a group is archived; archived groups are kept (not deleted) // but excluded from the Home board query. Null = active. archivedAt DateTime? categoryId String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt category Category @relation(fields: [categoryId], references: [id], onDelete: Cascade) todos Todo[] @@index([categoryId, order]) } model Todo { id String @id @default(cuid()) title String @db.VarChar(20) details String? @db.Text completed Boolean @default(false) // Position within its Group's to-do list. order Int groupId String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt group Group @relation(fields: [groupId], references: [id], onDelete: Cascade) @@index([groupId, order]) }