The modernized replacement for the legacy FTA application. It administers three forest-tenure business lines for the BC Ministry of Forests:
- Harvest Authorizations & Tenures — forest files, cutting permits, blocks, roads, hauling
- Range — grazing and hay-cutting agreements, range usage and zones
- Private Timber Marks — mark registration, amendment, transfer and cancellation
(The legacy system's Parks & Recreation area is intentionally not carried over.)
| Component | Technology |
|---|---|
| Frontend | React 19, TypeScript, Carbon Design System |
| Backend | Spring Boot 3.5, Java 21 (modular monolith) |
| Database | Oracle (shared, BC Gov-managed FDS) |
| Auth | BC Gov SSO (Keycloak) via CSS |
One React frontend + one Spring Boot backend, deployed as two OpenShift
services against the shared BC Gov Oracle (FDS) database — the same shape as the
sibling FDS apps nr-rept and nr-fsp-new.
The backend is a modular monolith: a single deployable whose Java packages are hard module boundaries, one per business line plus a shared core:
ca.bc.gov.nrs.fta
├── shared/ — tenure spine, client/party, code lists, app metadata (the shared core)
├── tenure/ — Harvest Authorizations & Tenures
├── range/ — Range
├── mark/ — Private Timber Marks
├── configuration/ security/ exception/ util/ — cross-cutting framework
The three business modules depend on shared only, never on each other. This
keeps each line independently evolvable and lets any one of them be extracted
into its own service later as a packaging change rather than a rewrite — without
paying the distributed-systems cost up front. See
docs/adr/0001-modular-monolith.md for the
rationale.
Two supported ways to run FTA locally. Pick whichever fits your workflow.
| Option A — direct on host | Option B — Docker Compose | |
|---|---|---|
| Backend hot reload | Manual restart (no spring-boot-devtools) | Manual restart |
| Frontend hot reload (Vite HMR) | Yes | Yes |
| First-time setup cost | Install Java 21 + Node 22 on host | Just Docker Desktop |
| Best for | Day-to-day backend dev where you re-mvn often |
Quick smoke tests, frontend-only work |
Both options share the same prerequisites and property files below — only the launch step differs.
- BC Gov VPN connected. The backend needs to reach the BC Gov Oracle host configured in
application-local.yml; Compose can't route that for you. - Maven 3.9+ and Java 21 (Option A only). The repo has no Maven wrapper.
- Node 22+ (Option A only).
- Docker Desktop (Option B only).
These are all gitignored — you set them up once and they stay on your machine.
Activated by the Spring local profile. Holds DB credentials, KEYCLOAK_ISSUER_URI / KEYCLOAK_CLIENT_ID, and TRUSTSTORE_PATH. The header comment in that file documents every field. Copy from a teammate or from the oc cp template in the file's comment block.
Note for Option B: the absolute TRUSTSTORE_PATH you set here is overridden inside Docker to /app/src/main/resources/cert/jssecacerts via compose env — no edit needed.
Java keystore containing the trusted CA chain for the Oracle TLS connection. Copy it from a running pod (the same one-liner is in the application-local.yml comment block).
FTA runs on the gold cluster, so oc login there first. Pods are labelled app=nr-fta-backend-<zone>, where the zone is the PR number for a preview deploy, or test / prod — and each zone lives in its own namespace:
mkdir -p backend/src/main/resources/cert
NS=ccec72-dev # ccec72-test / ccec72-prod for those environments
ZONE=<pr-number> # 'test' or 'prod' in those namespaces
oc -n $NS cp \
$(oc -n $NS get pod -l app=nr-fta-backend-$ZONE -o jsonpath='{.items[0].metadata.name}'):/cert/jssecacerts \
backend/src/main/resources/cert/jssecacertsCopy frontend/.env.example and fill in VITE_KEYCLOAK_URL (the realm issuer URI) and VITE_KEYCLOAK_CLIENT_ID (the CSS integration's client id — it must equal the backend's KEYCLOAK_CLIENT_ID, which is checked as the token's azp). Those, plus VITE_API_BASE_URL and VITE_APP_NAME, are inlined into the app bundle by Vite (via import.meta.env); changing .env requires restarting npm run dev. In a container they are re-supplied at runtime instead — docker-entrypoint.sh renders them into /srv/config.js and src/env.ts merges those over import.meta.env, so one built image serves every environment.
There is nothing else to configure: oidc-client-ts discovers the authorize, token and end-session endpoints from the issuer URI. For local dev, http://localhost:3000/authCallback must be registered as a redirect URI on the CSS integration, and http://localhost:3000 as a post-logout redirect URI.
Two terminal tabs. Backend in one, frontend in the other.
Backend:
cd backend
mvn -DskipTests spring-boot:run -Dspring-boot.run.profiles=local,oracleListens on http://localhost:8080. Health: http://localhost:8080/actuator/health.
For a Java code change, hit Ctrl-C and re-run. If you want true hot reload, add spring-boot-devtools to backend/pom.xml — not in there by default.
Frontend:
cd frontend
npm ci
npm run devVite serves at http://localhost:3000. HMR is on; save a .tsx file and the browser auto-refreshes. /api/* requests proxy to http://localhost:8080 via Vite's dev proxy (configured in vite.config.ts).
docker compose up # foreground; Ctrl-C to stop
docker compose up -d # detached
docker compose down # stop containers, keep cache
docker compose down -v # stop + drop the Maven cache volume
docker compose logs -f backendServices:
backend→localhost:8080(Spring Boot viamvn spring-boot:runinsidemaven:3.9.9-amazoncorretto-21-alpine).frontend→localhost:3000(Vite vianpm run devinsidenode:22-alpine).
First up downloads the Maven dependency graph (~3–5 min) into the maven-cache named volume. Subsequent ups are fast.
An optional production-like frontend variant is on the caddy profile:
docker compose --profile caddy up caddy backendThat builds the real frontend/Dockerfile (Caddy + Coraza WAF + runtime config.js seeding) and serves it at localhost:3005. Useful for reproducing prod CSP/header behaviour before pushing.
- If you Ctrl-C mid–dependency-download, you can end up with zero-byte POMs in the
maven-cachevolume and Maven will refuse to start withNon-readable POM ... input contained no data. Fix:docker compose down -v && docker compose up. - The backend is not hot-reloading. Java changes need
docker compose restart backend. - HMR uses WebSocket from your browser back to
localhost:3000. If you remap the published port, also overrideVITE_HMR_PORTincompose.yml.
Regardless of option:
curl http://localhost:8080/actuator/health→{"status":"UP"}- Open
http://localhost:3000→ app loads, IDIR login round-trips through/authCallback.
If /actuator/health returns DOWN, the most likely cause is the Oracle connection — check VPN, application-local.yml credentials, and the truststore path.
- backend/README.md — Spring profile reference, env-var table, API endpoints, test commands.
- docs/architecture.md — runtime topology, the auth model end to end, and the delivery pipeline.
- docs/analysis-legacy-datafixes.md — why the recurring
nr-fta-automatedb-datafixfixes exist, and a ranked list of changes that would retire the 427 data fixes run since 2024. - frontend/README.md — Vite scripts, project structure, routing/access model, auth flow.