391 lines
19 KiB
Markdown
391 lines
19 KiB
Markdown
# Landscape and shared engineering
|
|
|
|
Architecture decisions, specifications and project onboarding are maintained in
|
|
[landscape](http://192.168.2.28:3000/wanjakrah/landscape). Shared agent skills are maintained in landscape/skills/. Reusable tooling,
|
|
standards and the project generator are maintained in
|
|
[engineering](http://192.168.2.28:3000/wanjakrah/engineering).
|
|
|
|
This project commits the engineering version recorded in `engineering.lock.json`
|
|
under `generated/`. Ordinary builds need no other repository checkout. Regenerate
|
|
with `mvn -N -Pregenerate-engineering validate`; CI checks the selected tag and
|
|
commit with `python3 scripts/engineering.py --check`. To upgrade, update both tag
|
|
and commit, follow engineering's UPGRADING.md, regenerate and review the diff.
|
|
Project configuration in `engineering.properties`, workflows and `deploy/` remains
|
|
application-owned. Read CODING_STANDARDS.md and AGENTS.md for project guidance.
|
|
|
|
# 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
|
|
# Set DATABASE_URL, DATABASE_USER and DATABASE_PASSWORD first.
|
|
java -jar agiledancers-be/target/quarkus-app/quarkus-run.jar
|
|
```
|
|
|
|
Build a Docker container against an isolated test database (see [connection setup](docs/persistent-storage.md)):
|
|
|
|
```sh
|
|
docker build --network=host \
|
|
--secret id=database_url,env=TEST_DATABASE_URL \
|
|
--secret id=database_user,env=TEST_DATABASE_USER \
|
|
--secret id=database_password,env=TEST_DATABASE_PASSWORD -t agiledancers .
|
|
docker run --rm --name agiledancers -p 8080:8080 \
|
|
-e DATABASE_URL -e DATABASE_USER -e DATABASE_PASSWORD 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`.
|
|
|
|
PostgreSQL stores all school data, planning state and the latest scheduling run
|
|
through JPA/Hibernate ORM. Persistence entities remain in the repository layer.
|
|
Flyway initializes and versions the schema. Production requires `DATABASE_URL`
|
|
(a JDBC PostgreSQL URL), `DATABASE_USER` and `DATABASE_PASSWORD`; missing credentials,
|
|
connection failures and migration failures prevent startup. See
|
|
[database setup and transition](docs/persistent-storage.md) before replacing an
|
|
in-memory release: export every school before stopping it.
|
|
|
|
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` | JPA/Hibernate entities, repositories and transaction boundaries | 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.
|
|
The latest run and saved assignments survive restart. Startup marks formerly
|
|
running calculations `INTERRUPTED`, preserves the previous assignments, and allows
|
|
a new run. Results and successful status commit in one database transaction.
|
|
|
|
`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
|
|
during the one-time transition from an in-memory release, 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 `generated/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](docs/validation/S8-formatting-evidence.md). Reproduce the isolated failure,
|
|
UTF-8, import, cache and idempotence checks with
|
|
the formatter integration test in engineering (`python3 tests/format_integration.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](docs/validation/S7-validation.md) for current migration status.
|
|
|
|
|
|
Existing-school mutations require `X-School-Revision` with the revision loaded by
|
|
the editor. `GET /api/dance-schools/{id}/data` returns the school and all its editor
|
|
data from one consistent snapshot; `school.revision` identifies that snapshot.
|
|
Missing or stale revisions receive `409` with a reload instruction. The import
|
|
preview returns `existingRevision` for replacement confirmation. Creation of a
|
|
new school does not need a revision. The frontend never automatically retries a
|
|
conflicting edit with a newer revision.
|
|
|
|
|
|
Domain-to-view projection is generated by MapStruct in `rest.mapping.DomainViewMapper`.
|
|
It is a CDI bean with `unmappedTargetPolicy = ERROR`, so unmatched target fields fail
|
|
compilation. REST resources supply calculated validation results explicitly. The
|
|
entity-to-domain graph mappings remain handwritten in the repository layer.
|