Skip to content

jeoooo/fictional-computing-machine

Repository files navigation

Student Portal API

A REST API for a college student portal: authentication and student profiles, course registration, grades and transcripts, and tuition billing. Built with ASP.NET Core and EF Core to demonstrate production-grade .NET backend patterns — not just CRUD, but a real academic domain with rules that matter (registration windows, seat capacity, GPA computation, invoice generation).

Once running, the full API is browsable at /api/v1/docs.

The four modules

  1. Auth and student profiles — registration, login, JWT-based auth, Student/Admin roles, profile CRUD.
  2. Enrollment and course registration — courses, per-term course offerings, enroll/drop with capacity and registration-window checks.
  3. Grades and transcripts — post grades per enrollment, compute GPA, generate a transcript view.
  4. Billing and tuition — generate an invoice from a student's enrolled units, record payments, track balance.

Architecture

Lean, single-project layout: Controllers → Services → AppDbContext, with DTOs at the boundary. No CQRS/MediatR — a clean layered structure is enough to demonstrate the pattern without over-engineering for an MVP; it could scale to vertical slices later.

StudentPortal.Api/
  Auth/          Identity + JWT issuance, register/login/me, roles
  Students/      student profile CRUD
  Courses/       courses, terms, offerings (+ remaining-seat counts)
  Enrollments/   enroll/drop — window + capacity + duplicate rules live here
  Grades/        grade posting, GPA computation (GpaCalculator), transcript
  Billing/       invoice generation (InvoiceCalculator), payments, status derivation
  Data/          AppDbContext, EF entities, IEntityTypeConfiguration classes, migrations, DbSeeder
  Common/        error envelope, exception-handling middleware, paging
  Validators/    FluentValidation validators

StudentPortal.Tests/
  Unit/          pure logic — GPA math, invoice totals, payment-status transitions (no DB)
  Integration/   WebApplicationFactory + Testcontainers Postgres — full HTTP flows

Auth flow: POST /auth/register creates a Student-role account → POST /auth/login returns a JWT bearer token (HMAC-SHA256, 60 min expiry) → every other endpoint requires Authorization: Bearer <token>. Admin-only endpoints additionally require the Admin role (seeded automatically on first run — see below).

Concurrency-safe enrollment: POST /enrollments opens a transaction, takes a SELECT ... FOR UPDATE row lock on the CourseOfferings row, then checks the registration window, duplicate enrollment, and seat capacity before inserting — so two students racing for the last seat can't both succeed.

Grading scale and GPA formula

Uses the Philippine college convention: grades run 1.00 (highest) to 5.00 (failing).

GPA is a weighted average of posted grades, weighted by course units — not inverted, so a lower number is a better GPA (consistent with the scale itself):

GPA = sum(grade.Value × course.Units) / sum(course.Units)

computed both per-term and cumulative, over Completed enrollments with a Posted grade. Only posting a grade marks the enrollment Completed; that's what makes it eligible for the transcript. See Grades/GpaCalculator.cs.

Business rules enforced

  • Enrollment window — enroll/drop rejected with 409 when Term.IsRegistrationOpen is false.
  • Capacity409 when active enrollments already equal the offering's Capacity; concurrency-safe via row lock (see above).
  • No duplicate enrollment — unique DB constraint on (StudentProfileId, CourseOfferingId), checked gracefully in the service first.
  • Invoice generationtotal = sum(enrolled units in term) × per-unit rate + misc fees; idempotent — regenerating for the same student+term updates the existing invoice rather than duplicating it.
  • Payment status — derived from sum(payments) vs total (Unpaid / Partial / Paid); a payment that would push the total over the invoice amount is rejected with 400.

Tech stack

.NET 10 · ASP.NET Core Web API · EF Core 10 (Npgsql) · PostgreSQL · ASP.NET Core Identity + JWT · FluentValidation · Swashbuckle · xUnit + FluentAssertions + Testcontainers.

Quickstart

docker compose up -d               # starts Postgres on localhost:5433 (5432 is often already taken on dev machines)
cd StudentPortal.Api
dotnet ef database update          # applies migrations
dotnet run                         # migrates again (idempotent) + seeds + starts the API

Then open http://localhost:5080/api/v1/docs (or whatever port dotnet run prints).

The connection string, JWT signing key, and billing rates live in appsettings.json. The JWT key is a placeholder — replace it with a real secret (env var / user-secrets) before deploying anywhere that matters.

Seed data

On first startup (skipped once any Program row exists), Data/DbSeeder.cs creates a realistic, traceable demo roster — every id is a fixed GUID rather than random, so the same records exist on every fresh database and can be referenced reliably (e.g. in the .http walkthrough or a demo script):

  • An Admin account — admin@studentportal.local / Admin!12345 (override via Seed:AdminEmail / Seed:AdminPassword)
  • 4 programs (BSCS, BSIT, BSBA, BSA) across Computing and Business
  • 10 courses spanning gen-ed, CS/IT, and business/accountancy
  • 2 terms: 2nd Sem 2025-2026 (closed — a completed past term) and 1st Sem 2026-2027 (open — the current term), with 8 course offerings across them
  • 6 students with realistic names, spread across all 4 programs and different year levels — every student account logs in with Student!123 (e.g. juan.delacruz@studentportal.local / Student!123)
  • Past-term history: every student completed the same 3 gen-ed/major courses with posted grades (varied 1.00–3.00 range) and a fully Paid invoice — so GET /transcript/me and a closed billing record are demoable immediately
  • Current-term activity: students are actively Enrolled in 2-3 offerings each (no grades yet), with invoices cycling through Unpaid / Partial / Paid across the roster so all three billing states are visible without any manual setup. The BA101 offering is seeded at exactly 2/2 capacity to demo a fully-booked offering out of the box.

So the API is demoable immediately after dotnet run — no manual setup required.

Running the tests

dotnet test

Unit tests run with no external dependencies. Integration tests spin up a real Postgres container via Testcontainers (Docker must be running) and exercise the full HTTP stack through WebApplicationFactory — deliberately not an in-memory provider, since that hides real SQL/constraint behavior.

Example walkthrough

See StudentPortal.Api/requests.http for a runnable register → login → enroll → grade → transcript → invoice → payment walkthrough (works with the VS Code REST Client extension or Rider's HTTP client).

Known limitations (by design, for the MVP)

  • Prerequisite enforcement, faculty role, refresh tokens, rate limiting, and a frontend are explicitly out of scope — see the stretch goals in the original spec.
  • The enrollment unique constraint is on (StudentProfileId, CourseOfferingId) with no status exception, so a dropped enrollment cannot currently be re-created for the same offering.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages