0%
balqalam Logo
balqalam
FeaturesCommunityPricingDocumentation
Login
  • Introduction
  • Pitch
  • Hogwarts
  • Live Demo
  • MVP
  • Roadmap
  • Launch Sprint
  • PRD
  • Get Started
  • Localhost
  • Architecture
  • Structure
  • Pattern
  • Page
  • Layout
  • Content
  • Types
  • Config
  • Actions
  • Queries
  • Authorization
  • Validation
  • Form
  • Table
  • Detail
  • Card
  • Util
  • Hooks
  • List Params
  • Views
  • README.md
  • ISSUE.md
  • Technology Stack
  • Database
  • File
  • CDN Assets
  • Entry Points
  • Dashboard
  • Authentication
  • Credentials
  • OAuth
  • Flow Diagrams
  • Multi-Tenancy
  • Offline
  • Onboarding
  • Onboarding Videos
  • Add Values
  • Admission
  • Application
  • Attendance
  • Compliance
  • Profile
  • Exams
  • Exam Wizard
  • Timetable
  • Classrooms
  • Notifications
  • Conference
  • LMS (Lumos)
Finance
  • Finance
  • Fee Management
  • Invoice
  • Wallet
  • Salary
  • Payroll
  • Timesheet
  • Expenses
  • Budget
  • Receipt
  • Accounts
  • Banking
  • Reports
  • Dashboard
  • Permissions
  • Messages
  • Integration Flow
  • Provision
  • AI Document Processing
  • Document Intelligence
  • Internationalization
  • Translation
  • Translation Guide
  • Icons
  • Docs Factory
  • Inspiration
  • Listings
  • Teachers
  • Students
  • Catalog
  • Library
  • Contributing
  • Code of conduct
  • GitHub Workflow
  • Database Seeds
  • Database Safety
  • Test Accounts
  • Playwright
  • Prettier
  • Block Rebound
Sales & GTM
  • Marketing Brief
  • Sales
  • Go-to-market
  • Marketing
  • Admission — Feature Spotlight
  • Pilot Program
  • Leads
  • Proposal
  • Outreach Templates
  • Case Study
  • Competitors
  • Landing-page teardown
  • Competitor FAQ
  • Business model
  • Shared economy
  • Traction
Fundraising & Ecosystem
  • Get Support
  • Investor Deck
  • Data Room
  • Investors
  • Accelerators
  • Incubators
  • Grants
  • Sponsors
  • Partners
  • Competitions & Hackathons
  • Universities & Training Centers

Database

PreviousNext

Multi-tenant PostgreSQL with Prisma — 302 models, 149 enums, 73 schema files. Strict isolation via schoolId.

Hogwarts runs on a multi-tenant PostgreSQL database. All schools share the schema; data is isolated by schoolId on every tenant-scoped row. ORM is Prisma 6.19+ with multi-file schema organisation.

Principles

  • Row-level isolation — every tenant model has schoolId.
  • Shared schema — one schema, many schools.
  • Subdomain-based identity — schools are unique by subdomain (e.g. kingfahad.databayt.org); custom domains via CNAME.
  • End-to-end type safety — Prisma → Zod → TypeScript with no manual handoffs.
  • Multi-file schema — 73 logical .prisma files in prisma/models/ for maintainability.
  • Single-language storage — content stored in one language with a lang field; translation on-demand via Google Translate cached in Translation.

Layout

Database schema diagram

302 models across 73 schema files, grouped by domain. Catalog models intentionally omit schoolId — they're platform-wide reference content shared across schools via the bridge pattern.

DomainModelsFiles
Core (school, year, term, period, subscription)12school.prisma
Auth and users7auth.prisma
People (students, teachers, parents, staff)32students.prisma, staff.prisma, staff-member.prisma, profile.prisma
Academic structure13academic.prisma, subjects.prisma, classrooms.prisma, enrollment.prisma
Catalog (global)22+catalog.prisma, bridge.prisma, chapter.prisma, lesson.prisma, etc.
Attendance33+attendance.prisma, attendance-enhanced.prisma, geo-attendance.prisma
Exams and assessment40+exam.prisma, school-exam.prisma, school-qbank.prisma, quiz.prisma, quiz-game.prisma, quick-assessments.prisma, etc.
Finance36finance-core.prisma, finance-fees.prisma, finance-invoices.prisma, finance-payroll.prisma, finance-banking.prisma, finance-budgets.prisma, finance-reports.prisma, subscription.prisma
Messaging11messages.prisma
Notifications7notifications.prisma
WhatsApp5whatsapp.prisma
Announcements5announcement.prisma
Admission12admission.prisma, visit.prisma, membership.prisma, promotion.prisma
Lumos / LMS8stream.prisma
Timetable9timetable.prisma, schedule.prisma
Transportation7transportation.prisma
Library4book.prisma, school-book.prisma, textbook.prisma
Files / documents15files.prisma, file-record.prisma, document.prisma, document-processing.prisma, image.prisma, video.prisma
Audit / security / misc10audit.prisma, legal.prisma, webhooks.prisma, task.prisma, translation.prisma, purge-tokens.prisma

Multi-tenant isolation

School model

model School {
  id           String  @id @default(cuid())
  name         String
  domain       String  @unique          // e.g. "kingfahad"
  preferredLanguage String @default("ar")
  logoUrl      String?
  address      String?
  phoneNumber  String?
  email        String?
  timezone     String  @default("Africa/Khartoum")
  schoolType   String?                  // private, public, international, technical, special
  schoolLevel  String?                  // primary, secondary, both
  curriculum   String?                  // @map column: timetable-structure slug ("us-standard"); curriculum CODE (US/SD/GB/IB-DP) is inferred at runtime, not stored
  planType     String  @default("basic")
  maxStudents  Int     @default(100)
  maxTeachers  Int     @default(10)
  isActive     Boolean @default(true)
}

Query rules

// correct — always include schoolId
await db.student.findMany({ where: { schoolId } })
 
// wrong — leaks data across tenants
await db.student.findMany()

Every tenant-scoped Prisma model has:

  • schoolId String field.
  • school School @relation(...) with onDelete: Cascade.
  • @@unique([schoolId, ...]) so cross-tenant collisions are impossible.
  • @@index([schoolId]) for query performance.

Models without schoolId

About 44 models intentionally omit schoolId:

  • Auth tokens (4): Account, VerificationToken, PasswordResetToken, TwoFactorToken — email-scoped, ephemeral.
  • Catalog globals (22+): Subject, Chapter, Lesson, Book, Textbook, Curriculum, etc. — platform-wide reference content; bridge models scope per-school selections. (CurriculumStandard is school-scoped, not a catalog global.)
  • Subscription tiers (1): platform-wide pricing.

Dual LMS architecture

Two parallel content systems:

SystemScopeModelsSource
LumosSchool-scoped (schoolId)StreamCourse, StreamCategory, StreamEnrollment, StreamLesson, StreamCertificateSchool-created courses
CatalogPlatform-wide (no schoolId)Subject, Chapter, Lesson, Material, Questioncurated

The bridge.prisma models (SubjectSelection, BookSelection, ContentOverride, InstructorPreference) connect catalog content to school-specific academic structure. Schools select which subjects they use and override content without touching shared originals.

User roles

8 roles in prisma/models/auth.prisma:UserRole. See Multi-tenancy for the RBAC matrix.

RoleSchool scopeUse
DEVELOPERAll schoolsPlatform admin, cross-tenant ops
ADMINSingleSchool management, user oversight
TEACHERSingleClass management, grading, attendance
STUDENTSingleOwn grades, attendance, assignments
GUARDIANSingleLinked-student data, payments, notifications
ACCOUNTANTSingleBilling, financial reporting (no academic)
STAFFSingleOperational support
USERNoneDefault post-signup, pre-onboarding

Subscription and billing

model SubscriptionTier {
  id           String   @id @default(cuid())
  name         String   // basic, premium, enterprise
  monthlyPrice Int      // cents
  annualPrice  Int      // cents
  maxStudents  Int
  features     String[]
  isActive     Boolean  @default(true)
 
  subscriptions Subscription[]
  discounts     Discount[]
}
 
model Discount {
  id          String   @id @default(cuid())
  schoolId    String
  tierId      String
  code        String   @unique
  type        String   // percentage, fixed
  value       Int
  validFrom   DateTime
  validUntil  DateTime
  maxUses     Int?
  currentUses Int      @default(0)
 
  school           School           @relation(fields: [schoolId], references: [id])
  subscriptionTier SubscriptionTier @relation(fields: [tierId], references: [id])
}

Plus BillingPaymentMethod, BillingHistory, UsageMetrics, CreditNote, BillingPreferences, AppliedDiscount for the full billing lifecycle.

Legal and compliance

model LegalConsent {
  id              String    @id @default(cuid())
  schoolId        String
  userId          String
  documentType    String    // terms, privacy, data-processing
  documentVersion String
  consentType     String    // explicit, implicit, parental
  ipAddress       String?
  userAgent       String?
  consentedAt     DateTime  @default(now())
  revokedAt       DateTime?
 
  @@unique([schoolId, userId, documentType, documentVersion])
}
 
model LegalDocument {
  id               String    @id @default(cuid())
  schoolId         String
  type             String    // terms, privacy, data-processing
  version          String
  content          String    @db.Text
  effectiveFrom    DateTime
  effectiveUntil   DateTime?
  isActive         Boolean   @default(true)
  requiresExplicit Boolean   @default(true)
}
 
model ComplianceLog {
  id        String   @id @default(cuid())
  schoolId  String
  eventType String
  eventData Json
  userId    String?
  timestamp DateTime @default(now())
}

Single-language storage

Content stored in one language with a lang field. Translation on-demand via Google Translate API, cached in Translation.

model Announcement {
  title String?
  body  String? @db.Text
  lang  String  @default("ar")
}
 
model Translation {
  id             String   @id @default(cuid())
  schoolId       String
  sourceText     String   @db.Text
  sourceLanguage String                       // "ar" or "en"
  targetLanguage String                       // "ar" or "en"
  translatedText String   @db.Text
  provider       String   @default("google")
  hitCount       Int      @default(0)
  lastAccessedAt DateTime @default(now())
 
  @@unique([schoolId, sourceText, sourceLanguage, targetLanguage])
  @@index([schoolId, sourceLanguage, targetLanguage])
  @@index([lastAccessedAt])
}
import { getText } from "@/components/translation/display"
 
// Stored in Arabic, viewing in English → translates and caches
const title = await getText("مرحبا", "ar", "en", schoolId)
 
// Same language → returns directly, no API call
const title = await getText("مرحبا", "ar", "ar", schoolId)

Models with lang

Announcement, AnnouncementTemplate, NotificationTemplate, Subject, Class, YearLevel, Department, AcademicLevel, AcademicGrade, AcademicStream, AttendanceBadge, AttendanceCompetition, Chapter, Lesson, Book, Textbook, Material, CurriculumStandard, GradingScheme, QuickAssessment, Video, StreamCourse, StreamCategory. Default lang = "ar" for school-content; lang = "en" for Lumos/LMS.

See Internationalization and Translation.

Performance

Recommended indexes:

CREATE INDEX idx_students_school_id   ON students(school_id);
CREATE INDEX idx_teachers_school_id   ON teachers(school_id);
CREATE INDEX idx_classes_school_id    ON classes(school_id);
CREATE INDEX idx_attendance_school_id ON attendance(school_id);

Guidelines:

  • Always filter by schoolId first in WHERE clauses.
  • Use composite indexes for frequently queried field combinations.
  • Connection pooling via Neon serverless.
  • Read replicas for reporting and analytics.

Deployment

Environment

DATABASE_URL="postgresql://username:password@host:port/database"
DEFAULT_SCHOOL_DOMAIN="demo"
ALLOW_SCHOOL_SIGNUP="true"
BASIC_MAX_STUDENTS=100
PREMIUM_MAX_STUDENTS=500
ENTERPRISE_MAX_STUDENTS=2000

Migrations

pnpm prisma generate                       # Regenerate client
pnpm prisma migrate dev --name <name>      # LOCAL only — creates + applies a migration
pnpm db:seed:single <name>                 # Seed one module — never `pnpm db:seed`

prisma migrate deploy is the wrong tool against production, and this page used to recommend it. Verified 2026-08-18: the production database has no _prisma_migrations table — it is db push-managed — so migrate deploy would try to apply all 38 repo migrations from scratch onto a schema that already exists. Do not run it against prod.

To change the production schema, take the DDL from a diff and apply only what you meant:

# The FOLDER, not prisma/schema.prisma. prisma.config.ts loads models from prisma/, so
# pointing at the lone datasource file yields an EMPTY datamodel — and the diff then claims
# 719 dropped foreign keys and 328 dropped tables. That is an artifact of the argument, not
# drift. Real drift is a couple of dozen lines.
pnpm prisma migrate diff --from-url "$PROD_DATABASE_URL" --to-schema-datamodel prisma --script

Read what it prints, apply just the statements you intend, then re-run the diff to confirm it comes back empty. Adding a nullable column with no default is a catalog-only change in Postgres — instant, no table rewrite, existing rows untouched.

Order matters: the database changes before the code that expects it. Vercel's build runs only prisma generate (see postinstall), never migrate deploy, so a schema field that ships ahead of its column makes every query on that model fail in production.

Destructive ops (db execute, db push --accept-data-loss, migrate reset, DROP TABLE, TRUNCATE) are blocked by hooks. Use Neon's Branch-Before-Touch protocol for risky DB ops.

Demo school

Single demo tenant (demo.databayt.org) with full K-12 Sudanese simulation. See Safe seeds.

EntityCountNotes
Users3,105All roles, @databayt.org emails
Teachers100Arabic names, 6 departments
Students970KG1–Grade 12
Guardians~1,9402 per student
Departments6Arabic primary + lang field
Subjects19Arabic primary + lang field
Year levels14KG1–12
Classrooms28Labs, rooms, special facilities
Classes187Subject-level combinations

See also

  • Multi-tenancy — request flow, RBAC, isolation guarantees
  • Internationalization — i18n contract
  • Translation — dictionary coverage
  • Catalog — global content + bridge pattern
  • Seeds — full seed catalogue
  • Safe seeds — protected dev accounts
Technology StackFile

On This Page

PrinciplesLayoutMulti-tenant isolationSchool modelQuery rulesModels without schoolIdDual LMS architectureUser rolesSubscription and billingLegal and complianceSingle-language storageModels with langPerformanceDeploymentEnvironmentMigrationsDemo schoolSee also

Built by Databayt ·

Welcome to balqalam.

A great journey is about to begin.