# Contributing This is one of the six sites on the [Bennett platform](https://git.thebennett.net/austin/platform): a Spring Boot 4 / Java 25 backend serving a Vite + React SPA from a single jar. The workflow below is the same for every app — once you know one, you know all of them. **The one rule:** `main` is protected. You cannot push to it. Every change lands through a pull request whose CI passed. There is no exception, including for the repo owner — the server enforces it, so a `git push origin main` will be rejected. --- ## Prerequisites | Tool | Version | Notes | |---|---|---| | JDK | 25 | `java -version` should say 25 | | Maven | 3.9+ | or use the platform image below | | Docker | any recent | **required to run the tests** — they use Testcontainers (a real Postgres) | | Node | — | *not* installed by hand; the Maven build downloads a pinned Node and runs the SPA build | You also need a **Gitea Maven token** to build, because the platform (`net.thebennett.platform:*`) is pulled from the Gitea package registry, not Maven Central. Create a token at `git.thebennett.net → Settings → Applications` with `read:package` scope, then export it: ```sh export MAVEN_USER= export MAVEN_TOKEN= ``` The repo ships `.gitea/ci-settings.xml`, which wires that registry up using those two variables — pass it with `-s` and you never have to touch your global `~/.m2/settings.xml`. --- ## Build and test locally ```sh # Full verify — compiles, builds the SPA, runs every test (needs Docker running). mvn -B -s .gitea/ci-settings.xml verify # Faster inner loop — skip the frontend build when you're only touching Java. mvn -B -s .gitea/ci-settings.xml -DskipFrontend=true verify # Run the app locally (Postgres + any OIDC/storage config supplied via env — see the app's README). mvn -s .gitea/ci-settings.xml spring-boot:run ``` If `verify` fails with a Docker or "could not start container" error, the daemon isn't running — the tests need it. Tests will not be skipped to work around this; a green build means the tests actually ran. ### Frontend The SPA lives in `frontend/`. For a fast UI loop with hot reload: ```sh cd frontend npm install npm run dev # Vite dev server, proxying /api to the running backend ``` For anything that ships, let Maven build it (`mvn verify` runs `npm ci && npm run build` and folds `dist/` into the jar) so what you test is what deploys. The frontend is **vendored per app** — there is no shared frontend package, so a shared component you change here does not change in the other apps. ### Tests - Name test classes `*Test` — Surefire runs those. A class named `*IT` is silently skipped. - Integration tests use Testcontainers (`@ServiceConnection` + a real `postgres` container). No mocks for the database. - Every app inherits a set of **contract tests** from `platform-starter-test` (unknown `/api` paths 404, health is UP, probes exist, SPA routes forward to `index.html`). You get these for free by extending the platform's base test — don't reimplement them. - Protected-page paths return **302 for a browser** (`Accept: text/html`) and **401 for a fetch/XHR** (`Accept: */*`). If you're asserting that `/admin` "redirects to login," send the browser Accept header or the assertion will see a 401 and mislead you. --- ## The change workflow ```sh git switch -c feature/short-description # branch off main # ... make your change, add tests ... mvn -B -s .gitea/ci-settings.xml verify # green locally before you push git push -u origin feature/short-description # push the BRANCH, never main ``` Then open a pull request against `main` (Gitea → *New Pull Request*, or `tea pr create`). CI runs on the PR. When the check is green, merge it. On merge the image builds, publishes, and deploys automatically. ### What CI does (`.gitea/workflows/build.yml`, job `build-and-publish / build`) Runs on every PR **and** on push to `main`: 1. **Test** — runs `mvn verify` in a sibling Maven container (Testcontainers can't run inside `docker build`). Tests gate both the build and the merge. 2. **Build image** — `docker build` (which also runs the SPA build); a broken Dockerfile fails here. 3. **Scan image (Trivy)** — fails on any fixable **HIGH/CRITICAL** library CVE. A new bad CVE blocks the merge even if your code is fine — bump the offending dependency. 4. **Push image** — **only on push to `main`** (`if: github.event_name != 'pull_request'`). PRs build and scan but never publish. `main` requires the check **`build-and-publish / build (pull_request)`** to be green before merge. That context name is what branch protection is pinned to; if you rename the workflow or job, update the required-check setting to match or nothing will ever be mergeable. Commits that only touch `**.md` or `renovate.json` skip the build on `main` (they can't change the image) — but a PR that touches them still runs the PR check. --- ## Dependencies Renovate watches this repo. Platform version bumps (`net.thebennett.platform`) are **auto-merged** once their PR is green — that's how a security fix in the platform reaches every app without hand-editing six `pom.xml` files. Other updates open PRs for a human to merge. Don't hand-bump the platform version to chase a fix; cut a platform release and let Renovate carry it here. Some toolchain upgrades are intentionally pinned back (e.g. ESLint / TS majors blocked by plugin peer caps). If a Renovate PR is red on a peer-dependency conflict, that's why — don't force it. --- ## Deploy Merge to `main` → CI publishes `:latest` and `:` to the Gitea registry → **Watchtower** pulls the new `:latest` and restarts the container → **Caddy** fronts it. There is no manual deploy step and no staging hop; the PR gate *is* the safety net, which is why the tests and scan are non-negotiable. To force an urgent redeploy without a code change (e.g. rolling out a platform fix immediately), trigger the workflow manually (`workflow_dispatch`) rather than pushing an empty commit. --- ## Conventions - **Business logic lives in Java.** This is a Spring app, not a React app with a Java proxy — the server owns the data, the rules, and the validation. The SPA renders and calls `/api`. Prose and layout stay in the markup; only the parts that change without the page changing (prices, lists, links) become data. - Keep commits scoped and their messages in the imperative ("add booking link", not "added"). - Don't commit secrets. Config comes from environment variables at deploy time (see the app's README for the list); there is no `.env` in the repo. - Update the README when you change what the server owns or what env vars it needs — that's the file the next person reads first.