feat(01-01): Prisma schema with Tenant/User models and Vitest test setup
- 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
This commit is contained in:
94
prisma/schema.prisma
Normal file
94
prisma/schema.prisma
Normal file
@@ -0,0 +1,94 @@
|
||||
// =============================================================================
|
||||
// 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])
|
||||
}
|
||||
Reference in New Issue
Block a user