Skip to content

obcode/plexams.go

Repository files navigation

GoDoc Go Report Card

plexams.go

plexams.go is a GraphQL/REST server for planning university exams (Prüfungsplanung) at HM (Hochschule München, FK07). It imports exam and teacher data from the ZPA system and student registration/conflict data from Primuss, connects them, and helps schedule exams, assign rooms and plan invigilations (Aufsichten).

It is the backend for plexams.gui and is driven entirely through that GUI. Most domain terminology and log messages are in German.

The former Cobra CLI has been removed: all functionality is now exposed as GraphQL queries/mutations/subscriptions or REST endpoints and driven from the GUI. It remains a single-user, local tool (no auth, localhost-only CORS).

Architecture

Layers, each its own package, wired together in bootstrap/ + main.go:

  • bootstrap/ — server entrypoint. main.go calls bootstrap.Serve(), which parses the three flags, loads config and constructs the central *plexams.Plexams instance, then starts the server. (Replaced the former Cobra cmd/ package.)
  • plexams/ — business logic. Almost all domain operations are methods on *Plexams, grouped by concern across many files.
  • db/ — MongoDB persistence (mongo-driver). Each semester is its own MongoDB database (named e.g. 2026-SS); rooms and NTAs live in a shared plexams database.
  • zpa/ — HTTP client for the external ZPA REST API.
  • graph/ — GraphQL API generated by gqlgen.

See CLAUDE.md for more detail on the internals.

Prerequisites

  • Go (see go.mod for the required version)
  • A running MongoDB instance
  • Access to the ZPA and Primuss systems (credentials in the config)

Build, test, lint

go build -o plexams.go .         # build the binary
go test ./...                    # run all tests
go vet ./...
golangci-lint-v2 run             # the linter used in CI/pre-commit
go generate ./...                # regenerate gqlgen code after editing *.graphqls

Install the pre-commit hooks once (gofmt, go vet, golangci-lint-v2, gitleaks):

pre-commit install

Configuration

Config is loaded via viper from a single file .plexams.yaml (in . or $HOME), which may optionally pin the semester (e.g. 2026-SS). There is no per-semester <semester>.yaml anymore — the per-semester config lives in the database and is edited through the GUI.

Key sections consumed by the code:

Section Purpose
semester optional pin of the active semester, e.g. 2026-SS (else auto-selected from the DB)
db.uri, db.database MongoDB connection
zpa.* baseurl, username, password/token, fk07programs, oldprograms
smtp.* mail server + testmail (dry-run recipient)
jira.* on-prem Jira (jira.cc.hm.edu) integration: baseurl, token (PAT), project (default key FK07PP); url feeds the jiraURL email helper
planer.* name/email of the planner (bootstrap/fallback; lives in DB)
operator.* name/email of the local operator running this instance; stamped onto the mutation_log (audit "who did what"). Local per planner, never in DB
server.* port, allowedorigins

The per-semester planning config (from/until, slots, forbiddenDays, emails, MUC.DAI slots) is not in the YAML — it lives in the DB (semester_config_input) and is edited through the GUI.

The only command-line surface is three flags: -v/--verbose, --db-uri, --semester. --semester optionally pins a workspace; without it the active workspace is auto-selected from the DB (and can be switched at runtime from the GUI via the setSemester mutation).

Secrets (ZPA token/password, SMTP password, Jira PAT) live in the config file, not in the DB. Keep the file out of version control.

Running

plexams.go is a server only — it has no subcommands anymore. Running it starts the GraphQL/REST server that plexams.gui talks to (GraphQL playground at /, queries/mutations at POST /query, subscriptions over websocket, and REST up/download routes on the same router; default CORS/origin allows localhost:5173/8080/3000):

plexams.go

Everything the old CLI did is now a GraphQL query/mutation/subscription or a REST endpoint, driven from the GUI.

Planning workflow

A typical semester runs roughly in this order, all from the GUI:

  1. Create the semester (createSemester mutation) and fill in the per-semester config.
  2. Import from ZPA — the importExamsFromZPA / importTeachersFromZPA / importInvigilatorRequirementsFromZPA subscriptions.
  3. Select exams to plan and add constraints.
  4. Import from Primuss (upload the Sammellisten ZIP to /upload/primuss-zip) and connect the exams, then generate the assembled exams and student regs (generatePreparation / generateAssembledExams / generateStudentRegs).
  5. Schedule — place exams (setExamTime), check with the validateConflicts / validateConstraints subscriptions.
  6. Rooms — assign rooms (assignRoomsForExams subscription); request building-management rooms (see below).
  7. Validate & upload — run the validate* subscriptions, then upload with uploadExamsToZPA (…WithRooms/WithInvigilators); publish the plan and send the announcement emails.
  8. Invigilations — collect requirements, generate the invigilation plan (assignInvigilations subscription), validate, and send the published-invigilations emails.

Building-management room requests

Some rooms must be requested externally: T-building rooms via Anny, the others via the Gebäudemanagement (building management). The management requests are managed in the DB (collection room_requests, per semester):

  1. Dry run: the roomRequestsPreview query shows which rooms would be requested for which exams — read-only, changes nothing.
  2. Apply once (applyRoomRequestsPreview mutation): writes the requests; it refuses to overwrite existing ones unless forced, so an already-approved set is not lost. Add extras with addRoomRequest, extend a request (e.g. for an NTA) with updateRoomRequestTime.
  3. Approve / deactivate individual requests as the management responds.
  4. Send the request email: the sendEmailRoomRequests subscription (with run: false for a dry run to smtp.testmail). Requires semesterConfig.emails.roomManagement.

Stored time ranges include a 15-minute buffer before and after the exam for setup and teardown.

API surface

The full surface is the GraphQL schema in graph/*.graphqls (queries, mutations, and LogLine-streaming subscriptions for imports, generation, validation, emails and ZPA upload) plus the REST routes registered in graph/server.go:

Route Purpose
POST /upload/primuss-zip, /upload/email-attachment(s-zip) Primuss Sammellisten ZIP, email-attachment uploads
POST /upload/jira-attachment attach an uploaded file (PDF/CSV) to a Jira issue (multipart: key, file)
GET /download/planned-rooms.json planned rooms export (for external cover-page generation)
GET /download/pdf/{kind} draft/plan PDFs (exams-to-plan, constraints, draft-fk08/fk10/exahm/muc.dai/fs/lba-rep, same-module-name; draft-si returns a ZIP)
GET /download/csv/{kind} draft CSVs (draft?program=…, exahm, lba-repeater)
GET /download/ics/{program} per-program exam calendar (ICS)
`GET/POST /download upload/semester-dump.zip, /dataset, /dataset-csv, /my-inputs-csv.zip`

Emails and ZPA upload run as subscriptions with a run/dryRun argument; with run: false/dryRun: true they only mail the smtp.testmail recipient / do not upload.

Optimization algorithms

The invigilation planning (simulated annealing) and the SEB/EXaHM pre-planning (DSATUR + SA repair) are documented in docs/algorithmen.md (German).

GraphQL / code generation

The GraphQL layer is generated by gqlgen (gqlgen.yml). Edit the schema in graph/*.graphqls, then run go generate ./.... Do not hand-edit the generated files (graph/generated/generated.go, graph/model/models_gen.go, the resolver signatures in graph/*.resolvers.go). Hand-written model types live alongside the generated ones in graph/model/ and are autobound.

License

BSD 3-Clause License — see LICENSE.

About

Rewrite of obcode/plexams in Go

Resources

License

Stars

0 stars

Watchers

1 watching

Forks

Packages

 
 
 

Contributors

Languages