Skip to main content

Architecture

info

This page covers the technical decisions, key patterns, and project structure of the school platform.

School Platform series

  1. School Platform
  2. Architecture - You are here
  3. AI features
  4. Calendar integration
  5. Deployment

Technology choices

Next.js 16 with App Router

App Router provides server components for initial page loads, API routes in the same codebase, built-in routing with dynamic segments, and streaming with Suspense support.

Key patterns used:

  • 'use client' directive for interactive components
  • export const dynamic = 'force-dynamic' on all API routes using Prisma (prevents static rendering at build time when DATABASE_URL is not available)
  • Server-side data fetching where possible

Prisma 7 with PostgreSQL

Type-safe database access with a lazy proxy pattern to avoid instantiation at build time:

export const prisma = new Proxy({} as PrismaClient, {
get(_target, prop) {
const client = getPrismaClient()
const value = client[prop as keyof PrismaClient]
if (typeof value === 'function') {
return value.bind(client)
}
return value
},
})

This prevents DATABASE_URL not set errors during next build. The Prisma client is only created when first accessed at runtime.

Other decisions:

  • Default @prisma/client output location (not custom path) for simpler Docker builds
  • @prisma/adapter-pg for connection pooling
  • prisma.config.ts with conditional dotenv (only loads in dev, production has env vars from Kubernetes)

Auth.js v5 with Zitadel

Follows a "local network trust" model. Most operations are public because the app runs on a private network for the family. Admin functions (delete, backup, restore, import) require Zitadel OIDC authentication.

Key files:

FilePurpose
src/auth.config.tsEdge-compatible config (no Prisma imports)
src/auth.tsFull config extending auth.config
src/lib/auth.tsRe-exports for backward compatibility

shadcn/ui with Tailwind 4

All UI components from shadcn/ui in src/components/ui/. Dark mode support via CSS variables with consistent styling across the app.

Light and dark theme side by side.

Mobile responsiveness

The platform works on iPhone and Android with a mobile-first approach using Tailwind responsive breakpoints.

Key patterns:

Auto-collapsing sidebar - Desktop sidebar hidden on mobile (hidden md:flex), replaced with a hamburger menu and Sheet drawer component.

Stacking layouts - Headers with titles and buttons stack vertically on mobile, side-by-side on desktop.

iOS scroll containment - overscroll-contain prevents scroll leaking to parent containers.

Responsive grids - Form fields stack on mobile, grid on desktop using grid-cols-1 sm:grid-cols-2.

Scrollable tabs - Horizontal scroll for tab groups that overflow on mobile.

Breakpoints used:

BreakpointWidthPurpose
sm:640pxSmall tablets, landscape phones
md:768pxTablets, sidebar visibility
lg:1024pxDesktops, larger grids

Mobile navigation with hamburger menu and slide-out drawer.

Dockerfile pattern

Three-stage build for a minimal production image:

StagePurpose
BuilderAll dependencies, build Next.js
DepsProduction dependencies only, generate Prisma client
RunnerMinimal image with standalone output

Key decisions:

  • openssl and libc6-compat required for Prisma
  • postgresql-client included for backup and restore scripts
  • Pinned npm version for reproducible builds
  • HEALTHCHECK for Kubernetes probe support
  • Standalone output means no node_modules in the final image

Project structure

platform/
├── prisma/
│ ├── schema.prisma # 22 database models
│ ├── prisma.config.ts # Prisma 7 config
│ └── seed.ts # 8 learning areas, key dates, student
├── src/
│ ├── app/ # Next.js App Router pages
│ │ ├── api/ # API routes
│ │ │ ├── activities/ # Activity CRUD
│ │ │ ├── study-units/ # Study units and AI generation
│ │ │ ├── conversations/ # Ask Bob chat threads
│ │ │ ├── calendar/ # Public calendar APIs
│ │ │ └── admin/ # Admin-only APIs
│ │ │ ├── db/ # Backup, restore, verify
│ │ │ ├── import/ # Obsidian import
│ │ │ └── calendar/ # Calendar sync, admin events
│ │ ├── dashboard/ # Dashboard page
│ │ ├── activities/ # Activity CRUD pages
│ │ ├── study-units/ # AI study unit pages
│ │ ├── questions/ # Ask Bob Q&A pages
│ │ ├── calendar/ # Calendar page
│ │ ├── curriculum/ # Learning area pages
│ │ ├── evidence/ # Evidence gallery
│ │ ├── moderator/ # Moderator prep page
│ │ ├── admin/ # Admin page (auth required)
│ │ └── settings/ # Theme and AI settings
│ ├── components/ # React components
│ │ ├── ui/ # shadcn/ui components
│ │ ├── layout/ # Header, sidebar, mobile-nav
│ │ ├── calendar/ # FullCalendar, event dialogs
│ │ ├── questions/ # Ask Bob chat, floating button
│ │ ├── rating/ # Star and emoji ratings
│ │ ├── admin/ # Database management UI
│ │ └── curriculum/ # Progress ring, outcome checklist
│ └── lib/ # Utilities
│ ├── db.ts # Prisma client (lazy proxy)
│ ├── google-calendar.ts # Google Calendar API client
│ ├── backup-utils.ts # Backup and restore helpers
│ ├── slack.ts # Slack notification client
│ └── ai/ # AI integration
│ ├── openai.ts # OpenAI client
│ ├── prompts.ts # Prompt templates
│ ├── study-unit-generator.ts
│ └── question-answerer.ts
├── k8s/
│ └── prod/ # Kubernetes manifests (15 files)
└── Dockerfile # Three-stage production build

Database overview

The database has 22 models organised into four domains:

Core entities - Student, Activity, LearningArea, Evidence, CurriculumOutcome, and their junction tables for many-to-many relationships.

AI and study units - StudyUnit, Question (legacy), Conversation, Message, AppSettings, PreferenceMemory.

Calendar and integrations - GoogleCalendarConnection, CalendarEvent with activity and study unit linking.

Supporting tables - ModeratorMeeting, KeyDate, UserPreference, ImportLog.

See Deployment for database backup and restore details.