# 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 or overall health at . 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. `SchedulingService` calls this interface; 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 `appearances` for every group type. Omitting counts initializes defaults on creation or the initial daypart configuration; later edits preserve saved counts and reject counts outside 1..N. With no dayparts, shows remain unscheduled and saved counts are retained. `POST /api/dance-schools/{schoolId}/shows/{showId}/schedule` uses OR-Tools to assign every current school group to its required number of distinct dayparts, minimizing the busiest/quietest appearance gap. It replaces assignments only on a valid, current result. The response distinguishes optimal balance from a valid result whose optimality is unproven. Concurrent runs return `409`; changed inputs also reject the result with `409`. Failed runs preserve existing assignments. Scheduling runs synchronously with a 30-minute solver limit; development and production proxies allow 31 minutes. The UI warns before edits that may invalidate existing schedules and offers explicit scheduling again afterward. School JSON exports use format version **2**, including dated dayparts, assignments, and all appearance counts. Version 1 files are intentionally unsupported. 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 . 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.