- prisma/schema.prisma with Tenant, User, Role, TenantStatus models - tenantId on all tenant-scoped models (RLS-ready convention) - @@unique([email, tenantId]) and @@index([tenantId]) on User - Grace period fields on Tenant (suspendedAt, gracePeriodEndsAt) - RLS comment block documenting tenantId convention for future models - src/lib/prisma.ts singleton PrismaClient pattern (hot-reload safe) - vitest.config.ts with node environment and @/* path alias - src/lib/__tests__/setup.test.ts smoke test (2 tests passing) - package.json scripts: test, test:watch, db:push, db:generate, db:studio - Schema synced to PostgreSQL 16 via prisma db push
95 lines
2.9 KiB
Plaintext
95 lines
2.9 KiB
Plaintext
// =============================================================================
|
|
// NetForge Prisma Schema
|
|
// =============================================================================
|
|
//
|
|
// MULTI-TENANCY & RLS CONVENTION:
|
|
// All tenant-scoped models MUST include a `tenantId` field.
|
|
// This field is the foundation for Row-Level Security (RLS) policies.
|
|
// When adding new models (Subscriber, Invoice, Plan, Payment, etc.),
|
|
// always include: tenantId String + @@index([tenantId])
|
|
//
|
|
// Super-admin models that span tenants (e.g., audit logs, platform config)
|
|
// are the only exception to this rule.
|
|
// =============================================================================
|
|
|
|
generator client {
|
|
provider = "prisma-client-js"
|
|
}
|
|
|
|
datasource db {
|
|
provider = "postgresql"
|
|
url = env("DATABASE_URL")
|
|
}
|
|
|
|
// =============================================================================
|
|
// ENUMS
|
|
// =============================================================================
|
|
|
|
enum TenantStatus {
|
|
ACTIVE
|
|
PENDING_SUSPENSION
|
|
SUSPENDED
|
|
}
|
|
|
|
enum Role {
|
|
ADMIN
|
|
OFFICE_STAFF
|
|
COLLECTOR
|
|
TECHNICIAN
|
|
CLIENT
|
|
}
|
|
|
|
// =============================================================================
|
|
// MODELS
|
|
// =============================================================================
|
|
|
|
/// A Tenant represents a single ISP business using the NetForge platform.
|
|
/// All tenant-scoped data is isolated by tenantId (RLS-ready).
|
|
model Tenant {
|
|
id String @id @default(uuid())
|
|
name String
|
|
/// URL-friendly identifier, auto-generated from name (e.g., "my-isp" from "My ISP")
|
|
slug String @unique
|
|
ownerEmail String
|
|
|
|
status TenantStatus @default(ACTIVE)
|
|
/// Timestamp when suspension was triggered (starts grace period clock)
|
|
suspendedAt DateTime?
|
|
/// When actual service interruption occurs (suspendedAt + 7 days grace period)
|
|
gracePeriodEndsAt DateTime?
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
users User[]
|
|
}
|
|
|
|
/// A User belongs to a Tenant (or is a super-admin with no tenant).
|
|
/// Email uniqueness is enforced per-tenant, not globally.
|
|
/// Super-admins have isSuperAdmin=true and tenantId=null.
|
|
model User {
|
|
id String @id @default(uuid())
|
|
email String
|
|
passwordHash String
|
|
firstName String
|
|
lastName String
|
|
|
|
/// Nullable for super-admins who are not scoped to a specific tenant
|
|
tenantId String?
|
|
tenant Tenant? @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
|
|
|
/// Multi-role support — a user can hold more than one role within a tenant
|
|
roles Role[]
|
|
|
|
isActive Boolean @default(true)
|
|
isSuperAdmin Boolean @default(false)
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
/// Email must be unique within a tenant (super-admins have tenantId=null)
|
|
@@unique([email, tenantId])
|
|
/// RLS-ready index — always present on tenant-scoped models
|
|
@@index([tenantId])
|
|
}
|