Files
agiledancers/README.md
Wanja Krah c241392132
All checks were successful
Build, test and publish / verify (push) Successful in 7m25s
S12 - Async feedback for scheduling
2026-11-03 23:18:44 +01:00

349 lines
16 KiB
Markdown

# agiledancers
To map dance groups to performance days. The backend runs on Quarkus with Java 25.
Requires JDK 25 and Maven 3.9.0 or newer. Set `JAVA_HOME` to your JDK 25 installation.
Build and run all tests (including `*Sit` tests) from the repository root:
```sh
mvn clean verify
```
Start development mode with live reload from the repository root:
```sh
mvn -pl agiledancers-be quarkus:dev
```
Run the packaged application after building:
```sh
java -jar agiledancers-be/target/quarkus-app/quarkus-run.jar
```
Build and run a Docker container from the repository root (only Docker is required):
```sh
docker build -t agiledancers .
docker run --rm --name agiledancers -p 8080:8080 agiledancers
```
The Docker build compiles the application and runs the tests in a Maven build stage,
then copies the Quarkus application into a Java 25 runtime image running as a non-root user.
Check readiness at <http://localhost:8080/q/health/ready> or overall health at
<http://localhost:8080/q/health>. For example:
```sh
curl --fail http://localhost:8080/q/health/ready
```
Quarkus configuration can be overridden with environment variables, for example
`docker run --rm -e QUARKUS_HTTP_PORT=9090 -p 9090:9090 agiledancers`.
The REST API uses shared in-memory storage; data is lost when the process stops.
Architecture checks run with the normal test suite, or separately with:
```sh
mvn -pl agiledancers-be -Dtest=ArchitectureTest test
```
`ArchitectureTest` guards the following packages below `nl.agilecc.agiledancers`:
| Package | Responsibility | Allowed dependencies within the application |
| --- | --- | --- |
| `model` | Domain entities and specifications | Model |
| `service` | Application use cases and future ports | Model, repositories |
| `repository` | Current in-memory persistence | Model |
| `rest` | Future REST services | Services, model |
| `solver` | Future OR-Tools calculations | Services (ports), model |
REST and solver packages are optional until implemented. OR-Tools dependencies
are allowed only in `solver`; JAX-RS and RESTEasy dependencies only in `rest`.
The model may depend only on itself, Java and Lombok. Package cycles and
production classes outside these packages fail the architecture tests.
API models live in `rest.apimodel`, each in its own Java file as a public top-level
class or record. Their names end in `Request`, `Response`, `View`, `Dto` or `DTO`.
Architecture tests enforce this naming and location convention and require all
records in the REST layer to reside in `rest.apimodel`.
The current services instantiate concrete repositories, so this is not yet strict
Onion architecture. That requires repository interfaces in the core and injected
implementations in an outer adapter. For future calculations, define a port in
`service.port` using domain types and implement it in `solver`; services must not
depend on solver implementations or expose OR-Tools types.
Handwritten production types each have their own file and must be top-level.
Service records live in `service.model`; the school JSON snapshot records and
import preview are service-owned data models. Scheduling records shared with
the solver live in `model.scheduling`. Each group requirement combines its group
ID and appearance count in one record.
`service.port.SchedulingEngine` is the application-owned interface for solving a
scheduling problem. `SchedulingRuns` calls this interface for background runs;
`SchedulingService` retains the synchronous service entry point. Dependency injection
supplies `solver.OrToolsSchedulingEngine`. The port accepts and returns domain
types, so neither the caller nor the contract depends on OR-Tools or the concrete
school-management service. Architecture tests enforce the port's location and
dependencies, the location of service records, and the top-level type rule
(excluding generated Lombok builders and compiler helpers).
## School management REST API
All endpoints consume and produce JSON. Resources are scoped to their dance school.
A dancer can belong to multiple dance groups within the same school. Memberships
are managed separately from group details and survive updates to dancers and groups.
| Resource collection | Item URL |
| --- | --- |
| `/api/dance-schools` | `/api/dance-schools/{schoolId}` |
| `/api/dance-schools/{schoolId}/dancers` | `.../dancers/{dancerId}` |
| `/api/dance-schools/{schoolId}/dance-groups` | `.../dance-groups/{groupId}` |
| `/api/dance-schools/{schoolId}/shows` | `.../shows/{showId}` |
Each collection supports `GET` (list) and `POST` (create). Each item supports `GET`,
`PUT` (replace editable details), and `DELETE`. Creation returns `201` with a
`Location` header; updates return `200`; deletion returns `204`. Invalid input
returns `400`, and missing resources or resources belonging to another school
return `404`. Client-supplied IDs are not used.
Request bodies (dates use ISO `yyyy-MM-dd`):
```json
{"name": "24 Dance Studio"}
```
```json
{"name": "Sam", "birthDate": "2010-05-12"}
```
```json
{
"category": "Teens",
"type": "REGULAR",
"startAge": 12,
"endAge": 18,
"styles": ["HIPHOP", "JAZZ"]
}
```
```json
{"name": "Annual show", "dayParts": [{"date": "2027-06-12", "type": "AFTERNOON"}]}
```
Names and categories must be nonblank. Birth dates cannot be in the future.
Both ages must be nonnegative and `startAge <= endAge`; `styles` is required
and may be empty. Group types are `SHOW_CASE`, `PRE_EDUCATION`, `SELECTION`,
`REGULAR`, `PRE_SCHOOLER`. Styles are `HIPHOP`, `JAZZ`, `MODERN`, `STREETDANCE`,
`CLASSIC`, `BREAKDANCE`.
School responses include `dancerIds`, `danceGroupIds`, and `showIds`; group
responses include `dancerIds`. To add or remove a dancer's group membership:
```sh
curl -X PUT http://localhost:8080/api/dance-schools/0/dance-groups/0/dancers/0
curl -X DELETE http://localhost:8080/api/dance-schools/0/dance-groups/0/dancers/0
```
Both operations return `204` and are idempotent for existing groups and dancers.
Deleting a dancer removes all their memberships; deleting a group retains the
dancers; deleting a school also deletes its dancers, groups and shows.
Shows have a name, zero to six dated dayparts (`MORNING`, `AFTERNOON`, `EVENING`),
with unique date/type combinations. The displayed date range comes from these
dayparts. Each show stores `preferredDayParts`, defaulting to two or one for
one daypart. Ordinary groups, including PRE_EDUCATION, perform in exactly preferredDayParts
dayparts; preschool groups once; showcases every daypart. See
[show-scheduling-rules.md](show-scheduling-rules.md) for the full hard constraints,
coach exceptions, ordering and optimization rules.
`POST /api/dance-schools/{schoolId}/shows/{showId}/schedule` returns `202` with a
run identifier and starts an ordered draft calculation in the background using
OR-Tools with a total thirty-minute time limit. Duplicate starts for the same
show return `409`; different shows may run concurrently. A feasible result is
accepted without claiming optimality. Stale results and failures retain existing
assignments and appear as distinct terminal run states.
`GET .../shows/{showId}/schedule` returns the latest run (`204` if none exists).
`GET /api/scheduling/events` streams SSE JSON objects containing a `runs` snapshot
on connection and changes, or `runs: null` as a fifteen-second heartbeat. Clients
recover current state on reconnection rather than requiring event replay. The
production proxy disables buffering. The UI shows phase, elapsed time, solution
discovery and final objective scores, and reports completion in an application-wide
snackbar. Reloading recovers the latest state without replaying old notifications.
Run state currently shares the application's in-memory lifetime; durable restart
recovery requires the PostgreSQL integration specified in S16.
`PUT .../shows/{showId}/assignments` accepts `{"assignments":[[1,2],[3]]}`, an
ordered list of group IDs per daypart. Incomplete or invalid drafts can be saved;
unknown IDs and malformed lists are rejected. `POST .../shows/{showId}/finalize`
checks every hard rule before marking a schedule final. Show responses include
`preferredDayParts`, `finalized` and `violations`. Relevant edits invalidate final
status. The UI supports ordering and moving performances, and displays violations.
`GET .../coaches` lists coach registrations. `POST .../coaches` with
`{"dancerId":1}` registers a dancer as a coach. `PUT .../coaches/{coachId}` uses
the same body to link an existing coach. `DELETE .../coaches/{coachId}/dancer`
removes the identity link. The dancers screen supports maintaining these links.
School JSON exports use format version **3**, including preferredDayParts, schedule status and
coach-to-dancer links. Version 2 remains importable; its schedules migrate to
drafts with default preferredDayParts. Version 1 is unsupported. Legacy `appearances` fields are
retained for file compatibility; S18 scheduling uses preferredDayParts and fixed type exceptions.
The REST `appearances` input is deprecated; use `preferredDayParts`.
Run the HTTP integration tests with:
```sh
mvn -pl agiledancers-be -Dtest=SchoolManagementResourceTest test
```
## Angular frontend
The Angular 22 frontend is in [`agiledancers-fe`](agiledancers-fe/README.md),
next to `agiledancers-be`. It provides Dutch-language screens to manage schools,
dancers, groups, memberships and shows through the REST API.
Use Node.js 22.22.3+ (22.x), 24.15.0+ (24.x), or 26+.
Start the backend as described above, then run in a second terminal:
```sh
cd agiledancers-fe
npm ci
npm start
```
Open <http://localhost:4200>. The development proxy forwards API requests to the
backend on port 8080. Frontend builds and tests run independently of Maven:
`npm run build` and `npm test`. See the frontend README for browser tests and
production hosting requirements.
## Frontend lint and formatting
Run these commands from `agiledancers-fe/`:
```sh
npm run lint
npm run lint:fix
```
`lint` checks maintained application code, inline and external Angular templates,
unit tests, Playwright tests and JavaScript/TypeScript configuration. It uses the
recommended JavaScript, Angular and type-aware TypeScript rules. It changes no
files; both errors and warnings cause failure. Dependencies, generated output
and `package-lock.json` are excluded. `lint:fix` applies ESLint's available fixes;
review the diff and resolve any remaining findings manually. Prettier retains
responsibility for formatting with the existing configuration.
Every branch pipeline runs `npm run lint` immediately after frontend `npm ci`,
before frontend tests/builds and container E2E. Failure prevents image publication;
findings are in the job log and retained `ci/artifacts/verify.log`. CI only checks
and never fixes or stages source files.
Local `npm ci` automatically installs Husky at the repository root. Its commit
hook runs **Prettier only**, through lint-staged with backup, rollback and hiding
of unstaged changes in partially staged files enabled. It formats applicable
staged frontend TypeScript, JavaScript, HTML, CSS and JSON files, then stages the
formatting. Unrelated unstaged edits and files remain outside the commit. A
formatter error or a conflict restoring unstaged edits aborts the commit and
restores the original staged and unstaged state. Generated files are excluded;
commits without applicable frontend changes skip these checks.
The complete Prettier-plus-ESLint hook exceeds the agreed three-second budget
when all maintained files are staged. The selected Prettier-only hook meets the
budget for single-file, mixed and all-file commits. ESLint is therefore run
explicitly during development and always by CI. See [S2 validation](docs/s2-validation.md)
for the measured environment, timings and preservation checks.
Hook installation is skipped when `CI` is set (including Docker builds) or
`HUSKY=0`. Use `HUSKY=0 npm ci` to skip local installation, `HUSKY=0 git commit`
to skip one hook execution, or `git config --local core.hooksPath /dev/null` to
disable hooks for this clone. A subsequent normal `npm ci` enables them again.
VS Code recommends the ESLint extension in both root and frontend workspaces.
With it installed, findings appear in the editor and Problems panel; the root
workspace selects `agiledancers-fe/` as ESLint's working directory. No automatic
fixing or formatting on save is required. `npm run lint` uses the same rules as CI.
## Gitea CI
Every branch push builds and tests both applications and their Docker images.
Only successful `main` runs publish images to the Synology registry. See
[the CI management guide](docs/gitea-ci.md) for runner setup, registry secrets,
image names, diagnostics and recovery, and [validation evidence](docs/s4-validation.md)
for the current operational status.
Production releases and rollback use separate manual Gitea workflows. See the
[deployment runbook](docs/production-deployment.md) for Synology provisioning,
semantic version tags, release/retry/rollback and the accepted data-loss limitation
until S16, and [S6 evidence](docs/s6-validation.md) for deployment readiness.
### Java formatting (S8)
Maven is the authoritative formatting path. The root build runs the actual
Palantir Java Format **2.101.0**, Palantir style, through a custom OpenRewrite
**8.63.4** text recipe in `tooling/format/Format.java`. The pinned
`exec-maven-plugin` **3.6.3** launches that helper with JDK 25. Native import
ordering and unused-import removal are enabled; extra Javadoc formatting is off.
No OpenRewrite AutoFormat or additional refactoring recipes run.
From the repository root:
```sh
mvn -N exec:exec@format # apply, without application compilation
mvn -N exec:exec@format -Dformat.mode=check # check, without application compilation
mvn compile # applies during validate, before compilation
mvn -Dformat.mode=check clean verify # CI: fails before compilation on differences
```
Root `compile`, `test`, `verify` and `install` may rewrite maintained Java files
in the validate phase. CI's Java orchestration passes `-Dformat.mode=check` and
never repairs source files. On failure, the output lists paths and the apply
command. Apply locally, inspect and commit the result, then repeat the check.
Run root Maven commands; selecting only the backend with `-pl agiledancers-be`
bypasses the root formatting execution.
The scope is every `.java` file recursively under `agiledancers-be/src/main/java`,
`agiledancers-be/src/test/java`, `ci` (including the runner helper), and the
formatter helper itself. Generated `target` files and frontend files are excluded.
The adapter keeps paths and UTF-8 content, computes changes before writing, and
uses atomic per-file replacements. Unsupported syntax fails with its path/cause.
The source launcher compiles the adapter directly in memory from a clean checkout;
no custom recipe jar needs compiling or installing beforehand. Maven downloads
only pinned external tooling dependencies on first use.
Successfully checked/formatted content hashes are cached in
`target/palantir-cache.properties`; new or changed files are always processed.
The root POM, adapter source, dependency classpath and JDK version invalidate the cache. `clean` removes
it. Check and apply share the engine, options, scope and successful-content cache;
a failing check does not write sources or update the cache. For controlled timing
comparisons only, `-Dformat.skip=true` disables the execution.
Editors are optional. Palantir supplies an IntelliJ plugin (see the
[upstream instructions](https://github.com/palantir/palantir-java-format#intellij-plugin)).
Use it only with the same engine version/style and Javadoc setting; if the editor
cannot match them, use Maven to format instead. Editor import optimization or
format-on-save must not override Maven's result. Other editors can invoke the
standalone Maven apply command as an external tool.
Verification and measurements are recorded in
[the S8 evidence](specs/S8-formatting-evidence.md). Reproduce the isolated failure,
UTF-8, import, cache and idempotence checks with
`python3 tooling/format/test.py`, and the sequential end-to-end build measurements
with `python3 tooling/format/benchmark.py` (requires the ordinary backend test
prerequisites). Results are written under `target/`.
## Backend coding standards (S7)
Read [CODING_STANDARDS.md](CODING_STANDARDS.md) before changing backend Java.
It defines the agreed rules, exact metric limits, manual review duties, pinned
analysis tools, coverage policy and standalone commands. `mvn verify` runs standards
checks and requires at least 80% module-wide backend line coverage. CI also collects
and merges coverage from Playwright before publication. Reports are retained with
CI diagnostics. See [S7 validation](specs/S7-validation.md) for current migration status.