Sprint 5 complete · v0.5.0 Full-Stack SaaS

STUDYNEXUS

A platform no HsH student had.
Built by one who needed it.

Deep Dive

Chapter I: The Origin

Real frustration becomes a real product.

Sprint 5 fully complete — 9 sprints, 17 migrations, 122/122 tests passing, 0 TypeScript errors. Case study reflects current state.

Managing your studies is a mess

Every HS Hannover student knows it: QIS for grades, Moodle for materials, Excel for ECTS tracking, a notebook for exam dates — and you still lose track. No tool speaks the language of a student. None knows the exam regulations. None calculates GPA automatically.

This isn't a comfort problem — it's a system failure. System failures deserve systemic solutions.

  • No central hub for study progress
  • Manual ECTS tracking in Excel
  • Exam dates spread across multiple systems
  • Calculating GPA manually with a calculator
  • Schedule conflicts only discovered in the lecture hall
student_tools.sh

# Aktuelle Realität an der HsH

$ open QIS.exe && open moodle.hs-hannover.de
$ open "ECTS_Tracking_v3_FINAL.xlsx"
$ grep -r "Prüfungstermin" ~/Desktop/notizen/

# Die StudyNexus Lösung

yusef@studynexus:~$ docker compose up --build
[✓] frontend started on :3000
[✓] backend started on :8000
[✓] postgres ready — migrations applied
[READY] StudyNexus is running.

Platform Scope

📋
Study Plan & GPA
Exam regulations built-in · ECTS-weighted
📅
Schedule Board
15-min CSS Grid · Collision detection
📌
Kanban Board
Drag & Drop · Persisted in DB
🔐
HsH-Only Access
@stud.hs-hannover.de · Email-verified

An Operating System for your Studies.

StudyNexus is not another to-do app. It's a complete study operating system — built specifically for HS Hannover. The platform knows the real exam regulations, calculates weighted GPA by ECTS, and automatically detects schedule conflicts.

Access is intentionally exclusive: only @stud.hs-hannover.de email addresses are accepted. No noise, no bot accounts — a real student community.

Although the project is still in active development, a complete full-stack MVP is already running: authentication with email verification, study plan with automatic GPA calculation, Kanban board with drag & drop, schedule board with collision detection, and visual study plan.

200+

Source Files
(Frontend + Backend)

17

Alembic
Migrations

22+

Architecture Decision
Records (ADRs)

122

Backend Tests
all green

37

BIN Modules
fully seeded

5

Docker Services
(Compose Stack)

Chapter II: System Architecture

Five containers. One coherent platform.

Frontend & Backend. One Repository.

StudyNexus is built as a monorepo — frontend (Next.js) and backend (FastAPI) live in the same repository, share commit history, and are deployed together. This eliminates context switches and keeps API contracts consistent.

Every change to the API contract is immediately visible in the frontend. No desync between two repos.

📁 studynexus/ ├── 📁 frontend/ // Next.js 14 │ ├── 📁 src/app/[locale]/ │ ├── 📁 src/components/ │ └── 📄 package.json ├── 📁 backend/ // FastAPI │ ├── 📁 app/routers/ │ ├── 📁 app/models/ │ └── 📄 requirements.txt ├── 📄 docker-compose.yml └── 📄 .env

The path of a request

Every mutation in the frontend goes through a secure pipeline: Next.js API Routes proxy all requests to the FastAPI backend. This eliminates browser CORS issues and keeps the backend URL completely hidden. The backend validates with Pydantic, checks the JWT, and writes to PostgreSQL.

Browser
React UI
TanStack Query
Next.js
API Route
Proxy + CSRF
FastAPI
Backend
JWT + Pydantic
PostgreSQL
Database
SQLAlchemy ORM
Redis
Cache
Sessions

Docker Compose: Five Services

The entire infrastructure starts with a single command. In production, the same containers are deployed — no 'works on my machine'.

PostgreSQL and Redis have health checks. The backend only starts when the database is ready.

bash
$ docker compose up --build
✓ db healthy (postgres:16)
✓ redis healthy (redis:7-alpine)
✓ backend started → :8000
✓ frontend started → :3000
✓ adminer started → :8080
frontend :3000 Next.js 14 · App Router Node
backend :8000 FastAPI · Uvicorn · ASGI Python
db :5432 PostgreSQL 16-alpine Health ✓
redis :6379 Redis 7-alpine · Sessions Health ✓
adminer :8080 DB Admin UI · Dev only Tool

Chapter III: Engineering Decisions

14 Architecture Decision Records. Here are the most impactful ones.

Architecture decisions should be documented — not just what was decided, but why and which alternatives were rejected. StudyNexus has 14 ADRs. Here are the ones that shaped the system most.

ADR-01

FastAPI instead of Django

Django is the 'safe' Python framework. FastAPI is the right one for this use case: native async support, Pydantic v2 directly in the schema, automatic OpenAPI docs at /api/docs — and an architecture ready for AI extensions.

async-nativePydantic v2OpenAPI /docsKI-ready
ADR-03

httpOnly Cookies over localStorage

Storing JWT tokens in localStorage is a classic XSS vulnerability: any injected script can read localStorage. httpOnly cookies are fundamentally invisible to JavaScript. The browser sends them automatically — no script can steal them.

XSS-sicherhttpOnlySameSite=LaxSecure
ADR-02

bcrypt directly — no passlib

passlib is de facto unmaintained. Last version 2022, known compatibility issues with Python 3.13+. Using bcrypt 4.1.3 directly is safer, leaner and more future-proof — no abstraction layer that breaks tomorrow.

bcrypt 4.1.3no passlibPython 3.13+ safe
ADR-12

CSRF via Custom Header

Classic CSRF token management requires server state. Instead, every mutating request carries the header x-studynexus-client: true. Browsers don't allow cross-origin requests to set custom headers without a CORS preflight — that's the protection. Stateless, simple, effective.

statelessCORS-PreflightOrigin-Check
ADR-07

HsH-Only Strategy

Restricting access to @stud.hs-hannover.de is not a limitation — it's a design decision. Instead of a generic platform for all universities: a deeply integrated system for one. The exam regulations are built in. The module structure is real.

@stud.hs-hannover.dereal exam regulationsverified communityno fake data
ADR-019

Redis for Admin Session Tokens

JWT cannot be invalidated server-side. For admin actions (Delete, Archive, Reset) we need a token we can revoke instantly. Redis with 15-minute TTL — the sudo concept for the web.

server-side invalidierbar15min TTLRedis.get/setex/delete
ADR-021

is_admin in JWT Payload

Next.js Middleware runs in the Edge Runtime — no database access possible. is_admin: true directly in the JWT enables admin route guards without DB query and without latency.

Edge Runtimezero DB-latencyatob() + Base64url

Chapter IV: Feature Deep-Dive

What the platform can do today.

Mission Control

The Core: Mission Control Dashboard

The first view after login. The dashboard aggregates everything relevant in real-time: GPA status, ECTS progress, upcoming exams and today's events — calculated directly from the database, no caching.

Every widget is functional. The Exam Countdown pulses red when an exam is less than 14 days away. The Daily Focus view automatically switches to 'tomorrow' after 8 PM.

  • GPA tracker with ECTS-weighted real-time calculation
  • Exam countdown with pulse animation (< 14 days = red)
  • Daily Focus: today's events, automatic switch after 8 PM
  • Smart Timeline: task-intelligent sorting by due date
Task Management

Kanban Board with Real Drag & Drop

Not localStorage, not useState tricks — @dnd-kit v6 as the foundation. Four columns: To Do, In Progress, Exam Ready, Done. Tasks have priorities, can be linked to modules and carry a submission flag.

Position-based sorting: every drag-drop action writes immediately to the database. Page reload changes nothing — the board is fully persistent.

To DoIn ProgressExam ReadyDone LOW · MED · HIGHDeadline Flag 📄@dnd-kit v6
Schedule Board

15-Minute CSS Grid Engine

The schedule is not a table — it's a real CSS Grid with 15-minute resolution from 8 AM to 8 PM. Events are placed pixel-perfectly. A red line shows the current time in real-time. Collisions are detected and reported with HTTP 409.

10 event types: LECTURE, EXERCISE, TUTORIAL, SEMINAR, PRACTICUM, CUSTOM_STUDY, FOCUS, EXAM, WORK, LIFE. Semester binding prevents data loss on semester change. Ghosting mode temporarily hides events without deleting them.

LECTUREEXERCISETUTORIALSEMINAR EXAMFOCUSWORKLIFE +2 mehr
Study Plan

Visual Study Plan

Modules in semester-based columns. Drag & drop between semesters, automatic ECTS calculation per column, color coding by module type. Data comes from the real HsH exam regulations — no placeholder data.

  • Mandatory modules loaded automatically
  • Elective & supplementary modules addable manually
  • Drag & Drop between semesters via @dnd-kit
  • ECTS sum per column calculated live
Milestone Tracker (Sprint 4)

MilestoneWidget — §6 exam regulation monitored live

The dashboard has a new sidebar widget that evaluates the BIN exam regulation §6 in real time: Which semesters are complete? Is the preliminary exam passed?

  • Sem 1 complete: alle 6 Module BIN-100..116
  • Preliminary exam: all 17 modules from Sem 1–3 passed
  • BA admission: preliminary exam + ≥ 134 ECTS
  • Live progress bar per milestone
Admin Panel (Sprint 5)

Enterprise Admin — 14 pages, 35+ endpoints

A complete admin control center: user management, exam regulation management, analytics dashboard, audit log and JSON import — delivered in full in 2 days.

Two-Layer AuthAudit-LogRecharts AdminDataTable<T>Soft DeleteJSON-Import
PO-Übersicht (Sprint 4)

Study Regulations at a Glance

A dedicated dashboard page displaying all relevant exam regulation rules: admission rules §6, grading scale §10, repeat rules §11 — with live status badges directly from the database.

  • Programm-aware: erkennt BIN via API-Response
  • Exam types PX / EA / R / BAA+Ko color-coded
  • BA admission: live ECTS progress bar
  • No hardcode — Sprint 7 adds more exam regulations

Chapter V: BIN Exam Regulations

3 PDFs. 37 Modules. All §6 rules mapped automatically.

Built from real documents

No placeholder data. All 37 BIN modules were extracted directly from three official HsH documents and transferred into the database.

PO BIN 2019
9 pages
  • §5 — Modulstruktur Abschnitt 1 + 2
  • §6 — Admission & prerequisite rules
  • Annex B1/B2 — complete module lists
ATPO-FIV 2025
20 pages
  • §7 — Prüfungsarten (PX, EA, R, BAA+Ko)
  • §10 — 11 official HsH grades
  • §11 — Repeat rules (max. 3 attempts)
Modulhandbuch BIN 19WS
76 pages
  • SWS per module (semester weekly hours)
  • Exam type per module
  • 37 complete module descriptions

Four types. Directly from ATPO-FIV §7.

Each of the 37 BIN modules carries the official exam type from the module handbook — persisted in the database, color-coded in the UI.

A Pydantic validation ensures that only the 11 official HsH grades can be entered. HTTP 422 for invalid grades.

PX · 26 modules
EA · 3 Module
R · 1 Modul
BAA+Ko · 1 Modul

§6 ADMISSION RULES — FULLY IMPLEMENTED

Module Prerequisite Typ
BIN-200..204 Semester 1 completed SEM_COMPLETE
BIN-206, BIN-208 Preliminary exam passed (Sem 1–3) SEM_COMPLETE
BIN-211..219 (WP) Semesters 1+2 completed SEM_COMPLETE
BIN-210 Preliminary exam + ≥ 134 ECTS ECTS_THRESHOLD

Study progress per §6 — live

GET /me/stats delivers 8 new fields. The dashboard widget evaluates them in real time. No polling — status is calculated on page load.

Semester 1 complete
sem1_complete: true
Semester 2 complete
sem2_complete: true
Preliminary exam passed
vorpruefung_bestanden: true
BA Admission
ba_zulassung_eligible: false

Chapter VI: Security by Design

Security is not an afterthought. Security is architecture.

CSRF Protection

Stateless CSRF via Custom Header

Every mutating request (POST, PUT, DELETE, PATCH) carries the custom header x-studynexus-client: true. Cross-origin requests cannot set custom headers without a CORS preflight. Combined with Origin header validation, this creates complete CSRF protection — without any token storage.

Request
Check Header
Check Origin
✓ Allow
Authentication

JWT in httpOnly Cookies

The JWT token lives exclusively in an httpOnly cookie with Secure and SameSite=Lax flags. JavaScript cannot read this cookie. The browser sends it automatically — no manual token management in the frontend needed.

Set-Cookie: token=eyJ...
HttpOnly; Secure; SameSite=Lax
Path=/; Max-Age=604800 (7d)
Verification

6-Digit Email Verification

After registration, the student receives an email with a 6-digit code via Resend API. The code expires after 15 minutes. The account is only active after verification — and only @stud.hs-hannover.de addresses are accepted.

Register
6-digit Email
Verify (15min)
Active
Data Isolation

Row-Level User Isolation

Every database query filters by user_id. No student can see another student's data, even if they know the UUID. No separate permissions system — the ORM model enforces isolation structurally.

tasks.py
db.query(Task)
  .filter(Task.user_id == current_user.id)
  .all()
Admin Two-Layer Auth (ADR-019)

Sudo Concept: Read vs. Destroy

A stolen admin JWT alone cannot cause damage. Destructive operations require a second factor — a short-lived Redis token issued through password re-verification.

Layer 1 — get_admin_user

JWT-based admin check

is_admin: true in the JWT payload (ADR-021). Edge-Runtime compatible — no DB lookup, zero latency. Sufficient for all read-only admin operations.

GET /admin/users GET /admin/analytics GET /admin/audit-log
Layer 2 — get_verified_admin

Redis Session Token (Sudo)

Admin re-enters password → Redis token with 15-min TTL. Only this token unlocks destructive ops. Immediately revocable — no waiting for JWT expiry.

DELETE user ARCHIVE module RESET password

Chapter VII: Admin Panel

Enterprise Control Center. Planned for one week. Delivered in two days.

Sprint 5 — Delivery Metrics (2026-05-09 bis 2026-05-10)
2 Days
14 Admin pages
35+ Endpoints
122 Tests green
0 TS errors

What the admin panel can do

01 — Analytics

Dashboard & KPI

13-field KPI response, growth chart (Recharts LineChart, 7d/30d/90d/1y), user segmentation, DB size via pg_database_size().

RechartsResponsiveContainerPostgreSQL
02 — User Management

Full user access

Paginated list (25/page), 5 filter tabs, search by email+name, PATCH all fields, password reset, hard delete with mandatory reason.

AdminDataTable<T>debounced Search350ms
03 — PO-Verwaltung

University → Module → Prerequisite

6 router files, ~30 endpoints. Soft delete on exam regulations/modules (protection of existing data), hard delete on universities/faculties.

Soft DeleteJSON-Import (500 Module)Duplikat-Skip
04 — Audit-Log

Complete since phase 2

Every admin mutation logged since day one. Timeline layout, ActionBadge with 8 color variants, DiffBlock (old→new diff, strikethrough for removed values).

DiffBlockActionBadgefilter: entity/action/date
05 — Import-System

JSON bulk import up to 500 modules

Validate → Preview (first 10) → POST → Result. Idempotent via abbreviation lookup within the same exam regulation. PDF placeholder for Sprint 7 (ML/NLP).

idempotentPreview-StepAdmin-Session-Guard
06 — System & Health

Real-time system status

Overall badge (ok/degraded/down), ServiceBadge per service (DB ping + Redis.ping), DB version + size, auto-refresh every 60 seconds.

health checkRedis.pingauto-refresh 60s

AdminDataTable — one component for all lists

Instead of 6 separate table implementations: one generic TypeScript component. Column sort, debounced search (350ms), server-side pagination, hideOnMobile per column — all configurable via props.

22 new TypeScript interfaces, 11 new TanStack Query hooks, adminFetch.ts as a thin wrapper: no direct fetch(), centralized header logic, 204-handling.

strict: true0 TS-Fehler22 Interfaces11 Hooks~400 i18n-Keys
pytest tests/ -q
test_admin_auth.py·······7 ✓ test_admin_users.py··········10 ✓ test_admin_po.py················16 ✓ test_admin_analytics.py············12 ✓ test_admin_audit_log.py···········11 ✓ test_auth.py········8 ✓ test_gpa.py··············14 ✓ test_grades.py············12 ✓ test_health.py··2 ✓ test_stats.py·····5 ✓ test_study_plan.py···············15 ✓ test_universities.py··········10 ✓
122 passed in 2.34s

Chapter VIII: Database Architecture

12+ tables. 17 Alembic migrations. Complete university hierarchy.

From University to Single Module

The data model reflects the real university hierarchy. A student selects an exam regulation — the system automatically loads all mandatory modules into their personal study plan.

9 SQLAlchemy models with complete foreign key constraints and UUID primary keys. PostgreSQL-native ENUMs for status, priority and event_type.

University name, kuerzel, stadt
└── FK ──
Faculty name, kuerzel
└── FK ──
Program abschluss, gesamt_ects
└── FK ──
ExamRegulation version, gueltig_ab
└── FK ──
Module ects, modul_typ, gewichtung
└── FK (user) ──
StudentModule status, note, semester

Weighted Grade — like at real universities

GPA is not simply averaged. Each module has an ECTS-based weighting. Only passed, graded modules are included. The result: a GPA that actually reflects the curriculum.

GPA_ALGORITHM · gpa_service.py
GPA  = 
Σ ( Note × ECTS × Gewichtung )
Σ ( ECTS × Gewichtung )
Nur bestandene (status=PASSED), benotete (ist_benotet=true) Module fließen ein.

17 Alembic Migrations — Every step versioned

No manual SQL changes — every schema change is versioned, reversible, and reproducible. Sprint 4 (0012–0014) added BIN exam regulation data. Sprint 5 (0015–0017) delivered admin infrastructure.

Sprints 1–3 laid the foundation (0001–0011). Sprint 4 added the BIN exam regulation data (0012–0014). Sprint 5 delivered the admin infrastructure (0015–0017).

001 · Create users table
UUID PK, email, hashed_password, is_verified
002 · Study plan tables
University → Faculty → Program → ExamReg → Module
003–005 · Module data, email verification, Tasks+Events
Kanban & Schedule Board foundation
006–009 · Profil-Felder, Semester-Binding
matrikelnummer, FOCUS-Typ, birth_date, hochschule
010–011 · Studienplan + BIN-Full-Seed
parent_student_module, 37 BIN-Module komplett
012 · pruefungsart + sws
Sprint 4 — alle BIN-Module mit Prüfungsart aus ATPO-FIV §7
013 · module_prerequisites
Sprint 4 — §6-Voraussetzungen: SEM_COMPLETE, ECTS_THRESHOLD
014 · BIN-209 gewichtung fix
Sprint 4 — Datenfehler: 1.0 → 1.5 (Anlage B2 PO BIN)
015 · Admin-Felder auf users
Sprint 5 — is_admin, last_login_at, admin_notes
016 · admin_audit_logs Tabelle
Sprint 5 — entity_type, action, admin_id, diff_snapshot (3 Indizes)
017 · Soft-Delete-Felder
Sprint 5 — is_archived, archive_reason auf Module/Programs/ExamRegs

Chapter IX: Sprint Roadmap

Where the project stands. Where it's going.

Sprint 1-2 ✓

Foundation: Auth, DB & Docker

JWT Authentication, PostgreSQL-Schema mit Alembic, Docker Compose Stack mit 5 Services, Email-Verifikation via Resend API, vollständige Studienplan-CRUD mit GPA-Berechnung.

Sprint 3.x ✓

Mission Control & Mobile

Kanban Board (@dnd-kit), Schedule Board (15-Min CSS Grid), Dashboard Widgets, Mobile FAB, Agenda View für kleine Screens, TanStack Query Migration, vollständige i18n (DE/EN).

Sprint 4 ✓

BIN Prüfungsordnung Integration

37 BIN-Module aus 3 PDFs, Prüfungsart-System (PX/EA/R/BAA+Ko), §6-Voraussetzungen als DB-Constraints, MilestoneWidget, PO-Übersicht-Seite, GPA-Fix BIN-209.

Sprint 5 ✓

Admin Panel — Enterprise Control Center

14 Admin-Seiten, 35+ Endpunkte, Two-Layer Auth (JWT + Redis Sudo), Audit-Log, Analytics (Recharts), JSON-Bulk-Import, 122/122 Tests grün.

Sprint 6

Security Audit & Email-Templates

Admin-API-Rate-Limiting, Toast-Notifications bei API-Errors, E-Mail-Templates für Passwort-Reset, UI-Polishing und Dropdown-Auswahl für UUID-Felder.

v1.0 — Ziel

Production Launch für die HsH

Öffentlicher Launch für alle HS Hannover Studierenden. Onboarding-Flow mit vorausgefüllten Prüfungsordnungen. Gamification: XP, Badges, Streaks.

A Project
in Motion.

StudyNexus is not finished — and that's exactly the point. Real software lives. It grows with its requirements. Every week a sprint. Every sprint a new feature.