Documentation Index
Fetch the complete documentation index at: https://docs.nullpass.xyz/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Null Pass uses PostgreSQL with Prisma ORM for database management. This document describes the database schema and relationships.
Prisma Schema
The complete schema is defined in prisma/schema.prisma:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
enum ServiceIdentifier {
DROP
MAILS
VAULT
DB
}
enum AuditAction {
USER_LOGIN
USER_LOGOUT
USER_REGISTER
PASSWORD_CHANGE
TWO_FACTOR_ENABLE
TWO_FACTOR_DISABLE
USER_UPDATE
USER_DELETE
SESSION_CREATE
SESSION_DELETE
SERVICE_ACCESS_GRANT
SERVICE_ACCESS_REVOKE
SERVICE_TIER_CHANGE
SUBSCRIPTION_CREATE
SUBSCRIPTION_UPDATE
SUBSCRIPTION_CANCEL
SUBSCRIPTION_REVOKE
USER_BAN
USER_DISABLE
SERVICE_ENTITLEMENT_DISCONNECT
SERVICE_ENTITLEMENT_CONNECT
UNKNOWN
}
model User {
id String @id @default(cuid())
email String @unique
passwordHash String?
avatar String? @db.Text
displayName String?
twoFactorEnabled Boolean @default(false)
banned Boolean @default(false)
disabled Boolean @default(false)
twoFactorSecret String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
migraited Boolean @default(false)
sessions Session[]
serviceAccess UserServiceEntitlement[]
auditLogs AuditLog[]
@@map("users")
}
model Session {
id String @id @default(cuid())
userId String
ip String
token String @unique
expiresAt DateTime
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@map("sessions")
@@index([userId])
@@index([expiresAt])
}
model AuditLog {
id String @id @default(cuid())
action AuditAction @default(UNKNOWN)
data Json @default("{}")
userId String
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@map("audit_logs")
@@index([userId])
@@index([action])
@@index([createdAt])
}
model UserServiceEntitlement {
id String @id @default(cuid())
userId String
service ServiceIdentifier
tier String @default("free")
isPremium Boolean @default(false)
accessFlags Json?
metadata Json?
connected Boolean @default(true)
customStorageLimit Int?
customApiKeyLimit Int?
polarCustomerId String?
polarSubscriptionId String?
polarSubscriptionStatus String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@map("user_service_entitlements")
@@unique([userId, service])
@@index([service])
}
Models
User
Core user account model.
Fields:
id: CUID primary key
email: Unique email address
passwordHash: bcrypt hash (nullable for OAuth users)
avatar: Avatar URL (text field)
displayName: User display name
twoFactorEnabled: 2FA status
twoFactorSecret: TOTP secret (base32)
banned: Account ban flag
disabled: Account disable flag
migraited: Migration flag (LEGACY)
createdAt: Creation timestamp
updatedAt: Last update timestamp
Relations:
sessions: One-to-many with Session
serviceAccess: One-to-many with UserServiceEntitlement
auditLogs: One-to-many with AuditLog
Session
User session management.
Fields:
id: CUID primary key
userId: Foreign key to User
ip: Encrypted IP address
token: JWT token (unique)
expiresAt: Expiration timestamp
createdAt: Creation timestamp
Indexes:
userId: For user session queries
expiresAt: For expiration cleanup
Cascade: Deleted when user is deleted
AuditLog
Audit trail for all actions.
Fields:
id: CUID primary key
action: AuditAction enum
data: JSON metadata
userId: Foreign key to User
createdAt: Timestamp
Indexes:
userId: For user audit queries
action: For action type queries
createdAt: For time-based queries
Cascade: Deleted when user is deleted
UserServiceEntitlement
Service access entitlements.
Fields:
id: CUID primary key
userId: Foreign key to User
service: ServiceIdentifier enum
tier: Access tier string
isPremium: Premium flag
accessFlags: JSON object
metadata: JSON object
connected: Connection status
customStorageLimit: Custom storage limit (bytes)
customApiKeyLimit: Custom API key limit
polarCustomerId: Polar customer ID
polarSubscriptionId: Polar subscription ID
polarSubscriptionStatus: Polar subscription status
createdAt: Creation timestamp
updatedAt: Last update timestamp
Unique Constraint: userId + service
Indexes:
service: For service-based queries
Cascade: Deleted when user is deleted
Migrations
Migrations are managed with Prisma:
# Create migration
npm run prisma:migrate
# Generate Prisma Client
npm run prisma:generate
# Open Prisma Studio
npm run prisma:studio
Database Operations
Prisma Client Usage
import { prisma } from '@/lib/prisma'
// Find user
const user = await prisma.user.findUnique({
where: { email: 'user@example.com' }
})
// Create session
const session = await prisma.session.create({
data: {
userId: user.id,
token: jwtToken,
expiresAt: expiresAt,
ip: encryptedIp
}
})
// Create audit log
await prisma.auditLog.create({
data: {
userId: user.id,
action: 'USER_LOGIN',
data: { ip: encryptedIp }
}
})
Indexes
Indexes are defined for performance:
sessions.userId: Fast user session lookup
sessions.expiresAt: Expiration cleanup queries
audit_logs.userId: User audit queries
audit_logs.action: Action type filtering
audit_logs.createdAt: Time-based queries
user_service_entitlements.service: Service filtering
Constraints
users.email: Unique constraint
sessions.token: Unique constraint
user_service_entitlements.userId_service: Unique constraint
Cascade Deletes
When a user is deleted:
- All sessions are deleted
- All audit logs are deleted
- All service entitlements are deleted
This ensures data consistency and prevents orphaned records.