Compare commits

..

15 Commits

Author SHA1 Message Date
Julian Tabel
bd267499b8 Add Gitea Actions CI/CD pipeline task to deployment epic
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 10:38:59 +01:00
Julian Tabel
3254103cf6 Add frontend branding and metadata cleanup task
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 10:36:37 +01:00
Julian Tabel
6f4ed3460b Add Unit & Integration Tests epic with subtasks
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 10:34:45 +01:00
Julian Tabel
fae6532b8a Add Game Data Cleanup epic with subtasks
Track the work needed to audit and complete encounter data and route
ordering across all supported games. Covers automated source exploration,
Gen 8+ stub population, ORAS/Let's Go completion, route ordering for
Gen 5+, Gen 1-4 ordering audit, and special encounters review.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 10:00:14 +01:00
Julian Tabel
31355975e1 Update deployment beans to reflect SSH-based approach
Remove Portainer references, mark NPM and env management as
completed, update epic checklist and decided approach.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 09:45:58 +01:00
Julian Tabel
83a17c8f15 Build images for linux/amd64 to run on Unraid
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 09:43:18 +01:00
Julian Tabel
7ea6b30396 Rework deploy script to SSH directly into Unraid
Replace Portainer-based redeployment with direct SSH approach:
- Auto-detect podman/docker for local builds
- SCP compose file to Unraid
- Generate Postgres password in .env if missing
- Pull images and (re)start containers via SSH

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 09:32:41 +01:00
Julian Tabel
3f39b5f0cb Use bind mount for prod database storage instead of named volume
Store PostgreSQL data at ./data/postgres relative to the compose file
so persistent data lives on the Unraid disk at
/mnt/user/appdata/nuzlocke-tracker/data/postgres.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 09:17:14 +01:00
61a7f57f1f Update beans and deployment docs
Update epic checklist, mark completed tasks, fix Gitea username/domain
references, and update DEPLOYMENT.md with correct registry paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 18:28:56 +01:00
03f07ebee5 Add deploy script and update prod compose
Deploy script builds and pushes images to Gitea registry, then triggers
Portainer stack redeployment via API. Includes preflight checks for
branch and uncommitted changes. Also renames prod DB volume to avoid
conflicts with dev and changes frontend port to 9080.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 18:28:17 +01:00
972137acfb Fix TypeScript errors in frontend build
Cast boss type select value to union type and remove unused
AdvanceLegInput import.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 18:10:38 +01:00
fd23d89e71 Add production Dockerfiles and nginx config
Backend: installs non-editable, runs uvicorn without reload.
Frontend: multi-stage build, serves static files via nginx with
API proxy to the backend service and SPA fallback routing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 18:00:28 +01:00
d9d547ef53 Add production docker-compose file
Uses pre-built images from the Gitea container registry, runs Alembic
migrations before API startup, and keeps the database password configurable
via environment variable. No source mounts or debug mode.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 17:58:55 +01:00
349a0cb821 Add DEPLOYMENT.md as living deployment documentation
Covers architecture overview, Gitea container registry setup, branching
strategy, and deployment workflow. Sections not yet implemented are marked
with TODO to be filled in as the deployment epic progresses.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 17:56:06 +01:00
ad4ac6cf8c Update deployment strategy to use Gitea instead of plain Docker registry
Gitea provides source hosting, container registry, and CI/CD in one package.
Images are pushed as user-level packages to the Gitea registry over SSL.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 17:45:43 +01:00
40 changed files with 1038 additions and 54 deletions

View File

@@ -0,0 +1,30 @@
---
# nuzlocke-tracker-0arz
title: Integration tests for Runs & Encounters API
status: draft
type: task
created_at: 2026-02-10T09:33:21Z
updated_at: 2026-02-10T09:33:21Z
parent: nuzlocke-tracker-yzpb
---
Write integration tests for the core run tracking and encounter API endpoints. This is the heart of the application.
## Checklist
- [ ] Test run CRUD operations (create, list, get, update, delete)
- [ ] Test run creation with rules configuration (JSONB field)
- [ ] Test encounter logging on a run (create encounter on a route)
- [ ] Test encounter status changes (alive → dead, alive → retired, etc.)
- [ ] Test duplicate encounter prevention (dupes clause logic)
- [ ] Test shiny encounter handling
- [ ] Test egg encounter handling
- [ ] Test ending a run (completion/failure)
- [ ] Test error cases (encounter on invalid route, duplicate route encounters, etc.)
## Notes
- Run endpoints: `backend/src/app/api/runs.py`
- Encounter endpoints: `backend/src/app/api/encounters.py`
- This is the most critical area — Nuzlocke rules enforcement should be thoroughly tested
- Tests need game + pokemon + route fixtures as prerequisites

View File

@@ -0,0 +1,49 @@
---
# nuzlocke-tracker-1e9k
title: Populate encounter data for Gen 8+ stub games
status: todo
type: task
created_at: 2026-02-10T08:59:02Z
updated_at: 2026-02-10T08:59:02Z
parent: nuzlocke-tracker-rzu4
---
Fill in encounter data for games that currently have null/stub seed files. These games are not covered by PokeAPI and require manual curation or an alternative data source.
## Games with null/stub data:
- [ ] Sword (null)
- [ ] Shield (null)
- [ ] Brilliant Diamond (null)
- [ ] Shining Pearl (null)
- [ ] Scarlet (null)
- [ ] Violet (null)
- [ ] Legends Arceus (null)
## Format requirements:
Each game's JSON file must follow the existing structure:
\`\`\`json
[
{
"name": "Route Name",
"order": 1,
"encounters": [
{
"pokeapi_id": 25,
"pokemon_name": "pikachu",
"method": "walk",
"encounter_rate": 10,
"min_level": 5,
"max_level": 8
}
],
"children": []
}
]
\`\`\`
## Notes:
- This is likely the largest manual task unless the "explore automated sources" task finds a viable alternative
- Depends on findings from the automated data sources task — if automation is viable, this becomes much easier
- Sword/Shield and Scarlet/Violet use open-world/Wild Area mechanics that may need special handling
- Legends Arceus has a fundamentally different encounter system (overworld encounters, alpha Pokémon, space-time distortions)
- BD/SP are remakes of Diamond/Pearl — existing D/P data could serve as a starting point

View File

@@ -0,0 +1,30 @@
---
# nuzlocke-tracker-1guz
title: Component tests for key frontend components
status: draft
type: task
created_at: 2026-02-10T09:33:45Z
updated_at: 2026-02-10T09:33:45Z
parent: nuzlocke-tracker-yzpb
---
Write component tests for the most important frontend React components, focusing on user interactions and rendering correctness.
## Checklist
- [ ] Test `EncounterModal` — form submission, validation, Pokemon selection
- [ ] Test `StatusChangeModal` — status transitions, confirmation flow
- [ ] Test `EndRunModal` — run completion/failure flow
- [ ] Test `GameGrid` — game selection rendering, click handling
- [ ] Test `RulesConfiguration` — rules toggle interactions, state management
- [ ] Test `Layout` — navigation rendering, responsive behavior
- [ ] Test admin form modals (GameFormModal, RouteFormModal, PokemonFormModal) — CRUD form flows
- [ ] Test `AdminTable` — sorting, filtering, action buttons
## Notes
- Focus on user-facing behavior, not implementation details
- Use @testing-library/user-event for simulating clicks, typing, etc.
- Mock API responses for components that fetch data
- Don't aim for 100% coverage — prioritise the most complex/interactive components
- Page components (RunEncounters, RunDashboard, etc.) are large and complex — consider testing their sub-components instead

View File

@@ -0,0 +1,35 @@
---
# nuzlocke-tracker-2b4r
title: Add route ordering for Gen 5+ games
status: todo
type: task
created_at: 2026-02-10T08:58:55Z
updated_at: 2026-02-10T08:58:55Z
parent: nuzlocke-tracker-rzu4
---
Add route progression ordering in `route_order.json` for all games that currently lack it. Routes should be ordered to match the typical in-game progression (main story first, post-game after).
## Games needing route ordering:
- [ ] Diamond/Pearl (could alias to Platinum if progression is similar enough)
- [ ] Black/White
- [ ] Black 2/White 2
- [ ] X/Y
- [ ] Sun/Moon
- [ ] Ultra Sun/Ultra Moon (could alias to Sun/Moon if similar enough)
- [ ] Sword/Shield
- [ ] Brilliant Diamond/Shining Pearl (could alias to Platinum/Diamond-Pearl)
- [ ] Scarlet/Violet
- [ ] Legends Arceus
- [ ] Legends Z-A
- [ ] Let's Go Pikachu/Eevee (currently aliased to firered-leafgreen — verify this is correct)
## Approach:
- Reference Bulbapedia "walkthrough" or "appendix" pages for progression order
- Consider aliasing games that share the same region and route progression
- Mark a clear divider between main story and post-game routes where applicable
- The format is an array of route name strings in `route_order.json`, keyed by version group slug
## Notes:
- This is primarily manual work — play order guides are widely available online
- Verify route names match exactly what's in the encounter data files (case-sensitive)

View File

@@ -1,11 +1,11 @@
--- ---
# nuzlocke-tracker-3lfw # nuzlocke-tracker-3lfw
title: Configure Nginx Proxy Manager for nuzlocke-tracker title: Configure Nginx Proxy Manager for nuzlocke-tracker
status: todo status: completed
type: task type: task
priority: normal priority: normal
created_at: 2026-02-09T15:30:50Z created_at: 2026-02-09T15:30:50Z
updated_at: 2026-02-09T15:31:15Z updated_at: 2026-02-10T08:44:18Z
parent: nuzlocke-tracker-ahza parent: nuzlocke-tracker-ahza
blocking: blocking:
- nuzlocke-tracker-vpn5 - nuzlocke-tracker-vpn5

View File

@@ -0,0 +1,30 @@
---
# nuzlocke-tracker-6lud
title: Audit and fix route ordering for Gen 1-4 games
status: todo
type: task
created_at: 2026-02-10T08:59:16Z
updated_at: 2026-02-10T08:59:16Z
parent: nuzlocke-tracker-rzu4
---
Review the existing route ordering for Gen 1-4 games that already have ordering defined. The current ordering may not accurately reflect typical game progression.
## Games to audit:
- [ ] FireRed/LeafGreen (and aliased: Red/Blue, Yellow, Let's Go)
- [ ] HeartGold/SoulSilver (and aliased: Gold/Silver, Crystal)
- [ ] Emerald (and aliased: Ruby/Sapphire, Omega Ruby/Alpha Sapphire)
- [ ] Platinum
## What to check:
- Routes are in correct progression order (match typical walkthrough)
- No routes are missing from the ordering
- No routes are listed that don't exist in the encounter data
- Aliases make sense (e.g. Red/Blue may have slightly different progression from FR/LG)
- Post-game areas are placed after main story areas
- Sub-areas (children) inherit parent ordering correctly
## Notes:
- Cross-reference with Bulbapedia walkthrough pages
- The Platinum ordering currently starts with cities and mines rather than the starting town/routes — this may be incorrect
- Diamond/Pearl currently aliases to nothing — check if it should alias to Platinum or needs its own ordering

View File

@@ -0,0 +1,33 @@
---
# nuzlocke-tracker-7jba
title: Review and complete special encounters for all games
status: todo
type: task
created_at: 2026-02-10T08:59:24Z
updated_at: 2026-02-10T08:59:24Z
parent: nuzlocke-tracker-rzu4
---
Audit \`special_encounters.json\` to ensure starters, gifts, fossils, and other non-wild encounters are covered for all games.
## What counts as special encounters:
- **Starters** — the three (or more) starter Pokémon choices
- **Gift Pokémon** — NPCs that give you Pokémon (e.g. Eevee in Celadon, Lapras in Silph Co.)
- **Fossils** — Pokémon revived from fossils (e.g. Omanyte/Kabuto, Lileep/Anorith)
- **In-game trades** — Pokémon received via NPC trades (if tracked)
- **Static encounters** — Legendaries, Snorlax blocking the road, Sudowoodo, etc.
- **Event-specific** — One-time encounters tied to story events
## Checklist:
- [ ] Audit Gen 1 (Red/Blue/Yellow/FR/LG) special encounters
- [ ] Audit Gen 2 (Gold/Silver/Crystal/HG/SS) special encounters
- [ ] Audit Gen 3 (Ruby/Sapphire/Emerald/ORAS) special encounters
- [ ] Audit Gen 4 (Diamond/Pearl/Platinum/BD/SP) special encounters
- [ ] Audit Gen 5 (Black/White/B2/W2) special encounters
- [ ] Audit Gen 6 (X/Y) special encounters
- [ ] Audit Gen 7 (Sun/Moon/USUM) special encounters
- [ ] Audit Gen 8+ (Sword/Shield/Scarlet/Violet/Legends) special encounters
## Notes:
- Cross-reference with Bulbapedia "Gift Pokémon" and "In-game trade" pages
- The current file appears focused on Gen 1-2 — later gens likely need additions

View File

@@ -0,0 +1,30 @@
---
# nuzlocke-tracker-9c66
title: Integration tests for Genlockes & Bosses API
status: draft
type: task
created_at: 2026-02-10T09:33:26Z
updated_at: 2026-02-10T09:33:26Z
parent: nuzlocke-tracker-yzpb
---
Write integration tests for the genlocke challenge and boss battle API endpoints.
## Checklist
- [ ] Test genlocke CRUD operations (create, list, get, update, delete)
- [ ] Test leg management (add/remove legs to a genlocke)
- [ ] Test Pokemon transfers between genlocke legs
- [ ] Test boss battle CRUD (create, list, update, delete per game)
- [ ] Test boss battle results per run (record win/loss)
- [ ] Test stats endpoint for run statistics
- [ ] Test export endpoint
- [ ] Test error cases (invalid transfers, boss results for wrong game, etc.)
## Notes
- Genlocke endpoints: `backend/src/app/api/genlockes.py`
- Boss endpoints: `backend/src/app/api/bosses.py`
- Stats endpoints: `backend/src/app/api/stats.py`
- Export endpoints: `backend/src/app/api/export.py`
- Genlocke tests require multiple runs as fixtures

View File

@@ -1,11 +1,11 @@
--- ---
# nuzlocke-tracker-ahza # nuzlocke-tracker-ahza
title: Deployment Strategy title: Deployment Strategy
status: todo status: in-progress
type: epic type: epic
priority: normal priority: normal
created_at: 2026-02-09T14:03:53Z created_at: 2026-02-09T14:03:53Z
updated_at: 2026-02-09T15:08:29Z updated_at: 2026-02-10T08:16:36Z
--- ---
Define and implement a deployment strategy for running the nuzlocke-tracker in production on a local Unraid server while keeping laptop/PC as the development environment. Define and implement a deployment strategy for running the nuzlocke-tracker in production on a local Unraid server while keeping laptop/PC as the development environment.
@@ -16,19 +16,18 @@ Define and implement a deployment strategy for running the nuzlocke-tracker in p
- **Dev environment:** Laptop/PC — continue using the existing `docker-compose.yml` for local development - **Dev environment:** Laptop/PC — continue using the existing `docker-compose.yml` for local development
- **Production host:** Unraid server running Docker containers - **Production host:** Unraid server running Docker containers
- **Networking:** LAN-only access, Nginx Proxy Manager already in place on Unraid - **Networking:** LAN-only access, Nginx Proxy Manager already in place on Unraid
- **Orchestration:** Docker Compose for production (matching dev workflow). Install Portainer for container management and semi-automated deployments. - **Orchestration:** Docker Compose for production (matching dev workflow). Deploy via SSH from the dev machine.
## Decided Approach ## Decided Approach
**Docker Compose + Portainer + Local Docker Registry** **Docker Compose + SSH + Gitea (source hosting, container registry)**
1. **A local Docker registry** runs on Unraid as a container, accessible on the LAN (e.g., `unraid:5000` or behind Nginx Proxy Manager). 1. **Gitea** runs on Unraid behind Nginx Proxy Manager with SSL (e.g., `gitea.nerdboden.de`). It serves as the self-hosted Git remote and container registry.
2. **Images are built on the dev machine** and pushed to the local registry. 2. **Images are built on the dev machine** (podman or docker, cross-compiled for linux/amd64) and pushed to Gitea's container registry as **user-level packages** (e.g., `gitea.nerdboden.de/thefurya/nuzlocke-tracker-api:latest`, `gitea.nerdboden.de/thefurya/nuzlocke-tracker-frontend:latest`).
3. **Production runs docker-compose** on Unraid, pulling images from the local registry instead of mounting source. 3. **Production runs docker compose** on Unraid at `/mnt/user/appdata/nuzlocke-tracker/`, pulling images from the Gitea container registry instead of mounting source.
4. **Portainer** is installed on Unraid to manage stacks, provide a web UI, and enable webhook-triggered redeployments. 4. **A deploy script** on the dev machine automates the full flow: build images → push to Gitea registry → SCP compose file to Unraid → generate `.env` if missing → SSH to pull images and (re)start containers.
5. **A deploy script** on the dev machine automates the full flow: build images → push to local registry → trigger Portainer webhook to redeploy. 5. **Nginx Proxy Manager** handles routing on the LAN (e.g., `nuzlocke.nerdboden.de` → frontend container, `gitea.nerdboden.de` → Gitea).
6. **Nginx Proxy Manager** handles routing on the LAN (e.g., `nuzlocke.local` → frontend container). 6. **Database** uses a bind mount (`./data/postgres`) for persistence on the Unraid disk; migrations run automatically on API container startup.
7. **Database** uses a named Docker volume for persistence; migrations run automatically on API container startup.
## Branching Strategy ## Branching Strategy
@@ -42,19 +41,18 @@ Define and implement a deployment strategy for running the nuzlocke-tracker in p
1. Create `feature/xyz` from `develop` 1. Create `feature/xyz` from `develop`
2. Work on the feature, commit, merge into `develop` 2. Work on the feature, commit, merge into `develop`
3. When ready to deploy: merge `develop``main` 3. When ready to deploy: merge `develop``main`
4. Run `./deploy.sh` (builds from `main`, pushes to local registry, triggers Portainer webhook) 4. Run `./deploy.sh` (builds from `main`, pushes to Gitea registry, deploys to Unraid via SSH)
## Checklist ## Checklist
- [ ] **Set up branching structure** — create `develop` branch from `main`, establish the `main`/`develop`/`feature/*` workflow - [ ] **Set up branching structure** — create `develop` branch from `main`, establish the `main`/`develop`/`feature/*` workflow
- [ ] **Update CLAUDE.md with branching rules** — once the branching structure is in place, add instructions to CLAUDE.md that the branching strategy must be adhered to (always work on feature branches, never commit directly to `main`, merge flow is `feature/*``develop``main`) - [ ] **Update CLAUDE.md with branching rules** — once the branching structure is in place, add instructions to CLAUDE.md that the branching strategy must be adhered to (always work on feature branches, never commit directly to `main`, merge flow is `feature/*``develop``main`)
- [ ] **Set up local Docker registry on Unraid** — run the `registry:2` container, configure storage volume, optionally put it behind Nginx Proxy Manager with a hostname (e.g., `registry.local`) - [ ] **Configure Gitea container registry** — create an access token with `read:package` and `write:package` scopes, verify `docker login gitea.nerdboden.de` works, test pushing and pulling an image as a user-level package
- [ ] **Create production docker-compose file** (`docker-compose.prod.yml`) — uses images from the local registry, production env vars, no source volume mounts, proper restart policies - [x] **Create production docker-compose file** (`docker-compose.prod.yml`) — uses images from the Gitea container registry, production env vars, no source volume mounts, proper restart policies
- [ ] **Create production Dockerfiles (or multi-stage builds)** — ensure frontend is built and served statically (e.g., via the API or a lightweight nginx container), API runs without debug mode - [x] **Create production Dockerfiles (or multi-stage builds)** — ensure frontend is built and served statically (e.g., via the API or a lightweight nginx container), API runs without debug mode
- [ ] **Set up Portainer on Unraid** — install Portainer CE as a Docker container, configure the stack from the production compose file - [x] **Create deploy script**`./deploy.sh` builds images (podman/docker, linux/amd64), pushes to Gitea registry, SCPs compose file, generates `.env` if needed, pulls and starts containers via SSH
- [ ] **Configure Portainer webhook for automated redeployment** — add a webhook trigger in Portainer that pulls latest images and restarts the stack - [x] **Configure Nginx Proxy Manager** — add proxy host entries for Gitea and the nuzlocke-tracker frontend/API on the appropriate ports
- [ ] **Create deploy script**a script (e.g., `./deploy.sh`) that builds images from `main`, tags them for the local registry, pushes them, and triggers the Portainer webhook to redeploy - [x] **Environment & secrets management**deploy script auto-generates `.env` with `POSTGRES_PASSWORD` on Unraid if missing; file lives at `/mnt/user/appdata/nuzlocke-tracker/.env`
- [ ] **Configure Nginx Proxy Manager** — add proxy host entry pointing to the frontend/API containers on the appropriate ports - [ ] **Implement Gitea Actions CI/CD pipeline** — set up Gitea Actions runner on Unraid, create CI workflow (lint/test on `develop`) and deploy workflow (build/push/deploy on `main`); uses GitHub Actions-compatible syntax for portability
- [ ] **Environment & secrets management** — create a `.env.prod` template, document required variables, decide on secret handling (`.env` file on Unraid, Portainer env vars, etc.) - [ ] **Database backup strategy** — set up a simple scheduled backup for the PostgreSQL data (e.g., cron + `pg_dump` script on Unraid)
- [ ] **Database backup strategy** — set up a simple scheduled backup for the PostgreSQL volume/data (e.g., cron + `pg_dump` script on Unraid)
- [ ] **Document the deployment workflow** — README or docs covering how to deploy, redeploy, rollback, and manage the production instance - [ ] **Document the deployment workflow** — README or docs covering how to deploy, redeploy, rollback, and manage the production instance

View File

@@ -1,11 +1,11 @@
--- ---
# nuzlocke-tracker-aiw6 # nuzlocke-tracker-aiw6
title: Create deploy script title: Create deploy script
status: todo status: completed
type: task type: task
priority: normal priority: normal
created_at: 2026-02-09T15:30:48Z created_at: 2026-02-09T15:30:48Z
updated_at: 2026-02-09T15:31:15Z updated_at: 2026-02-09T17:28:22Z
parent: nuzlocke-tracker-ahza parent: nuzlocke-tracker-ahza
blocking: blocking:
- nuzlocke-tracker-izf6 - nuzlocke-tracker-izf6

View File

@@ -0,0 +1,26 @@
---
# nuzlocke-tracker-ch77
title: Integration tests for Games & Routes API
status: draft
type: task
created_at: 2026-02-10T09:33:13Z
updated_at: 2026-02-10T09:33:13Z
parent: nuzlocke-tracker-yzpb
---
Write integration tests for the games and routes API endpoints in `backend/src/app/api/games.py`.
## Checklist
- [ ] Test CRUD operations for games (create, list, get, update, delete)
- [ ] Test route management within a game (create, list, reorder, update, delete)
- [ ] Test route encounter management (add/remove Pokemon to routes)
- [ ] Test bulk import functionality
- [ ] Test region grouping/filtering
- [ ] Test error cases (404 for missing games, validation errors, duplicate handling)
## Notes
- Use the httpx AsyncClient fixture from the test infrastructure task
- Each test should be independent — use fixtures to set up required data
- Test both success and error response codes and bodies

View File

@@ -0,0 +1,33 @@
---
# nuzlocke-tracker-d8cp
title: Set up frontend test infrastructure
status: draft
type: task
priority: normal
created_at: 2026-02-10T09:33:33Z
updated_at: 2026-02-10T09:34:00Z
parent: nuzlocke-tracker-yzpb
blocking:
- nuzlocke-tracker-ee9s
- nuzlocke-tracker-1guz
---
Set up the test infrastructure for the React/TypeScript frontend. No testing tooling currently exists.
## Checklist
- [ ] Install Vitest, @testing-library/react, @testing-library/jest-dom, @testing-library/user-event, jsdom
- [ ] Configure Vitest in `vite.config.ts` or a dedicated `vitest.config.ts`
- [ ] Set up jsdom as the test environment
- [ ] Create a test setup file (e.g. `src/test/setup.ts`) that imports @testing-library/jest-dom matchers
- [ ] Create test utility helpers (e.g. render wrapper with providers — QueryClientProvider, BrowserRouter)
- [ ] Add a \`test\` script to package.json
- [ ] Verify the setup by writing a simple smoke test
- [ ] Set up MSW (Mock Service Worker) or a similar API mocking strategy for hook/component tests
## Notes
- Vitest integrates natively with Vite, which the project already uses
- React Testing Library is the standard for testing React components
- The app uses React Query (TanStack Query) and React Router — the test wrapper needs to provide these contexts
- MSW is recommended for mocking API calls in hook and component tests, but simpler approaches (vi.mock) may suffice initially

View File

@@ -0,0 +1,31 @@
---
# nuzlocke-tracker-ee9s
title: Unit tests for frontend utilities and hooks
status: draft
type: task
created_at: 2026-02-10T09:33:38Z
updated_at: 2026-02-10T09:33:38Z
parent: nuzlocke-tracker-yzpb
---
Write unit tests for the frontend utility functions and custom React hooks.
## Checklist
- [ ] Test `utils/formatEvolution.ts` — evolution chain formatting logic
- [ ] Test `utils/download.ts` — file download utility
- [ ] Test `hooks/useRuns.ts` — run CRUD hook with mocked API
- [ ] Test `hooks/useGames.ts` — game fetching hook
- [ ] Test `hooks/useEncounters.ts` — encounter operations hook
- [ ] Test `hooks/usePokemon.ts` — pokemon data hook
- [ ] Test `hooks/useGenlockes.ts` — genlocke operations hook
- [ ] Test `hooks/useBosses.ts` — boss operations hook
- [ ] Test `hooks/useStats.ts` — stats fetching hook
- [ ] Test `hooks/useAdmin.ts` — admin operations hook
## Notes
- Utility functions are pure functions — straightforward to test
- Hooks wrap React Query — test that they call the right API endpoints, handle loading/error states, and invalidate queries correctly
- Use `@testing-library/react`'s `renderHook` for hook testing
- Mock the API client (from `src/api/`) rather than individual fetch calls

View File

@@ -0,0 +1,28 @@
---
# nuzlocke-tracker-hjkk
title: Unit tests for Pydantic schemas and model validation
status: draft
type: task
created_at: 2026-02-10T09:33:03Z
updated_at: 2026-02-10T09:33:03Z
parent: nuzlocke-tracker-yzpb
---
Write unit tests for the Pydantic schemas in `backend/src/app/schemas/`. These are pure validation logic and can be tested without a database.
## Checklist
- [ ] Test `CamelModel` base class (snake_case → camelCase alias generation)
- [ ] Test run schemas — creation validation, required fields, optional fields, serialization
- [ ] Test game schemas — validation rules, field constraints
- [ ] Test encounter schemas — status enum validation, field dependencies
- [ ] Test boss schemas — nested model validation
- [ ] Test genlocke schemas — complex nested structures
- [ ] Test stats schemas — response model structure
- [ ] Test evolution schemas — validation of evolution chain data
## Notes
- Focus on: valid input acceptance, invalid input rejection, serialization output format
- The `CamelModel` base class does alias generation — verify both input (camelCase) and output (camelCase) work
- Test edge cases like empty strings, negative numbers, missing required fields

View File

@@ -1,10 +1,11 @@
--- ---
# nuzlocke-tracker-hwyk # nuzlocke-tracker-hwyk
title: Set up Portainer on Unraid title: Set up Portainer on Unraid
status: todo status: completed
type: task type: task
priority: normal
created_at: 2026-02-09T15:30:44Z created_at: 2026-02-09T15:30:44Z
updated_at: 2026-02-09T15:30:44Z updated_at: 2026-02-09T16:53:41Z
parent: nuzlocke-tracker-ahza parent: nuzlocke-tracker-ahza
--- ---

View File

@@ -0,0 +1,24 @@
---
# nuzlocke-tracker-iam7
title: Unit tests for services layer
status: draft
type: task
created_at: 2026-02-10T09:33:08Z
updated_at: 2026-02-10T09:33:08Z
parent: nuzlocke-tracker-yzpb
---
Write unit tests for the business logic in `backend/src/app/services/`. Currently this is the `families.py` service which handles Pokemon evolution family resolution.
## Checklist
- [ ] Test family resolution with simple linear evolution chains (e.g. A → B → C)
- [ ] Test family resolution with branching evolutions (e.g. Eevee)
- [ ] Test family resolution with region-specific evolutions
- [ ] Test edge cases: single-stage Pokemon, circular references (if possible), missing data
## Notes
- `services/families.py` contains the core logic for resolving Pokemon evolution families
- These tests may need mock database sessions or in-memory data depending on how the service queries data
- If the service methods take a DB session, mock it; if they operate on data objects, pass test data directly

View File

@@ -1,16 +1,20 @@
--- ---
# nuzlocke-tracker-izf6 # nuzlocke-tracker-izf6
title: Set up local Docker registry on Unraid title: Configure Gitea container registry
status: todo status: completed
type: task type: task
priority: normal
created_at: 2026-02-09T15:30:40Z created_at: 2026-02-09T15:30:40Z
updated_at: 2026-02-09T15:30:40Z updated_at: 2026-02-09T16:53:09Z
parent: nuzlocke-tracker-ahza parent: nuzlocke-tracker-ahza
--- ---
Run a `registry:2` container on the Unraid server for storing production Docker images locally. Set up and verify the Gitea container registry for hosting Docker images as user-level packages.
- Run `registry:2` as a Docker container on Unraid ## Checklist
- Configure a persistent storage volume for the registry data
- Optionally put it behind Nginx Proxy Manager with a hostname (e.g., `registry.local`) - [ ] Create a Gitea access token with `read:package` and `write:package` scopes
- Verify pushing/pulling images from the dev machine works - [ ] Verify `docker login gitea.nerdboden.de` works from the dev machine
- [ ] Test pushing a Docker image as a user-level package (e.g., `gitea.nerdboden.de/thefurya/nuzlocke-tracker-api:latest`)
- [ ] Verify the image appears under the user's Packages tab in Gitea
- [ ] Test pulling the image back (from Unraid or dev machine)

View File

@@ -0,0 +1,28 @@
---
# nuzlocke-tracker-jlzs
title: Implement Gitea Actions CI/CD pipeline
status: draft
type: task
created_at: 2026-02-10T09:38:15Z
updated_at: 2026-02-10T09:38:15Z
parent: nuzlocke-tracker-ahza
---
Set up Gitea Actions as the CI/CD pipeline for the nuzlocke-tracker. Gitea Actions uses the same syntax as GitHub Actions, making it portable if the project goes public on GitHub later.
## Context
- Gitea is already running on Unraid behind Nginx Proxy Manager (`gitea.nerdboden.de`)
- Images are currently built locally and pushed to the Gitea container registry via `deploy.sh`
- Gitea Actions can automate building, pushing images, and triggering deployment on push to `main`
- The workflow syntax is compatible with GitHub Actions, so the same `.github/workflows/` files work on both platforms
## Checklist
- [ ] **Enable Gitea Actions on the Gitea instance** — ensure the Actions feature is enabled in `app.ini` (`[actions] ENABLED = true`) and restart Gitea
- [ ] **Set up a Gitea Actions runner** — deploy an `act_runner` container on Unraid (or the same host as Gitea), register it with the Gitea instance, and verify it picks up jobs
- [ ] **Create CI workflow** (`.github/workflows/ci.yml`) — on push to `develop` and PRs: lint, run tests (backend + frontend), and report status
- [ ] **Create deploy workflow** (`.github/workflows/deploy.yml`) — on push to `main`: build Docker images (linux/amd64), push to the Gitea container registry, and trigger redeployment on Unraid via SSH
- [ ] **Configure secrets in Gitea** — add repository or org-level secrets for registry credentials, SSH key/host for deployment, and any other sensitive values the workflows need
- [ ] **Test the full pipeline** — push a change through `feature/*``develop``main` and verify the CI and deploy workflows run successfully end-to-end
- [ ] **Update deployment docs** — document the Gitea Actions setup, how to manage the runner, and how CI/CD fits into the deployment workflow

View File

@@ -1,18 +1,28 @@
--- ---
# nuzlocke-tracker-jzqz # nuzlocke-tracker-jzqz
title: Configure Portainer webhook for automated redeployment title: Configure Portainer API for automated redeployment
status: todo status: completed
type: task type: task
priority: normal priority: normal
created_at: 2026-02-09T15:30:45Z created_at: 2026-02-09T15:30:45Z
updated_at: 2026-02-09T15:31:15Z updated_at: 2026-02-09T17:28:22Z
parent: nuzlocke-tracker-ahza parent: nuzlocke-tracker-ahza
blocking: blocking:
- nuzlocke-tracker-hwyk - nuzlocke-tracker-hwyk
--- ---
Set up a webhook in Portainer that triggers a stack redeployment when called. Use the Portainer CE REST API to trigger stack redeployments from the deploy script.
- Create a webhook trigger in Portainer for the nuzlocke-tracker stack Portainer webhooks are a Business-only feature, so we use the API directly instead.
- The webhook should pull the latest images from the local registry and restart the stack
- Note the webhook URL for use in the deploy script ## Approach
1. Authenticate with the Portainer API to get a JWT token
2. Call the stack update endpoint with `pullImage: true` to pull latest images and recreate containers
## Checklist
- [ ] Identify the stack ID in Portainer (via API or UI)
- [ ] Test API authentication (`POST /api/auth`)
- [ ] Test triggering a stack redeploy via API
- [ ] Integrate into the deploy script

View File

@@ -0,0 +1,12 @@
---
# nuzlocke-tracker-jzw4
title: Switch prod compose to bind mounts for persistent data
status: completed
type: task
priority: normal
created_at: 2026-02-10T08:15:41Z
updated_at: 2026-02-10T08:16:44Z
parent: nuzlocke-tracker-ahza
---
Replace named Docker volume with bind mount to ./data/postgres subfolder. The docker-compose.prod.yml will live at /mnt/user/appdata/nuzlocke-tracker/ on Unraid, so relative paths resolve to subfolders there.

View File

@@ -0,0 +1,12 @@
---
# nuzlocke-tracker-lhls
title: Add compose file sync to deploy script
status: completed
type: task
priority: normal
created_at: 2026-02-10T08:19:57Z
updated_at: 2026-02-10T08:20:13Z
parent: nuzlocke-tracker-ahza
---
Update deploy.sh to SCP docker-compose.prod.yml to the Unraid server at /mnt/user/appdata/nuzlocke-tracker/ before triggering the Portainer redeployment. SSH target: root@192.168.1.10 (port 22).

View File

@@ -0,0 +1,27 @@
---
# nuzlocke-tracker-q5vd
title: Explore automated data sources for encounter data
status: todo
type: task
created_at: 2026-02-10T08:58:47Z
updated_at: 2026-02-10T08:58:47Z
parent: nuzlocke-tracker-rzu4
---
Research and evaluate automated or semi-automated options for populating encounter data, especially for games where PokeAPI has no data (Gen 8+).
## Potential sources to investigate:
- **PokeAPI CSV/database dumps** — the raw data behind PokeAPI may have more than the REST API exposes
- **veekun/pokedex** — community-maintained Pokémon database with encounter data
- **Bulbapedia / Serebii** — structured wiki data that could be scraped (check terms of use)
- **pkNX / game data extraction** — tools that extract data directly from game files
- **Community GitHub repos** — search for curated encounter datasets (e.g. for romhack tools, fan wikis)
## Goals:
- Determine which games can realistically be populated via automation vs. manual entry
- If a viable source is found, prototype a script/tool to import data into the existing seed JSON format
- Document findings even if no automated approach is viable, so we know what's available
## Notes:
- The existing Go tool (`tools/fetch-pokeapi/`) could serve as a template for new data fetchers
- Output format must match the existing `{game}.json` structure (routes with encounters, children for sub-areas)

View File

@@ -1,11 +1,11 @@
--- ---
# nuzlocke-tracker-re0m # nuzlocke-tracker-re0m
title: Document the deployment workflow title: Document the deployment workflow
status: todo status: in-progress
type: task type: task
priority: normal priority: normal
created_at: 2026-02-09T15:30:57Z created_at: 2026-02-09T15:30:57Z
updated_at: 2026-02-09T15:31:15Z updated_at: 2026-02-10T08:44:29Z
parent: nuzlocke-tracker-ahza parent: nuzlocke-tracker-ahza
blocking: blocking:
- nuzlocke-tracker-aiw6 - nuzlocke-tracker-aiw6
@@ -19,5 +19,7 @@ Write documentation covering the full deployment setup and workflows.
- How to deploy (run `./deploy.sh`) - How to deploy (run `./deploy.sh`)
- How to redeploy after changes - How to redeploy after changes
- How to rollback to a previous version - How to rollback to a previous version
- How to manage the production instance (Portainer UI, logs, etc.) - How to manage the production instance (SSH, docker compose logs, etc.)
- How to set up the production environment from scratch (registry, Portainer, NPM, secrets) - How to set up the production environment from scratch (registry auth, NPM, SSH access)
- Deploy script flow: build images (podman/docker) → push to Gitea registry → SCP compose file → generate .env if missing → pull and start containers via SSH
- Production files live at `/mnt/user/appdata/nuzlocke-tracker/` on Unraid (compose file, .env, data/)

View File

@@ -0,0 +1,36 @@
---
# nuzlocke-tracker-rrcf
title: Set up backend test infrastructure
status: draft
type: task
priority: normal
created_at: 2026-02-10T09:32:57Z
updated_at: 2026-02-10T09:33:59Z
parent: nuzlocke-tracker-yzpb
blocking:
- nuzlocke-tracker-hjkk
- nuzlocke-tracker-iam7
- nuzlocke-tracker-ch77
- nuzlocke-tracker-ugb7
- nuzlocke-tracker-0arz
- nuzlocke-tracker-9c66
---
Set up the foundational test infrastructure for the FastAPI backend so that all subsequent test tasks can build on it.
## Checklist
- [ ] Create `backend/tests/conftest.py` with shared fixtures
- [ ] Set up a test database strategy (use a separate test PostgreSQL database or SQLite for speed — evaluate trade-offs)
- [ ] Create an async test client fixture using `httpx.AsyncClient` with the FastAPI `app`
- [ ] Create a database session fixture that creates/drops tables per test session or uses transactions for isolation
- [ ] Add factory fixtures or helpers for creating common test entities (games, pokemon, runs, etc.)
- [ ] Verify the setup works by writing a simple smoke test (e.g. health endpoint returns 200)
- [ ] Document how to run tests (e.g. `pytest` from backend dir, any env vars needed)
## Notes
- pytest, pytest-asyncio, and httpx are already in pyproject.toml dev dependencies
- AsyncIO mode is set to "auto" in pyproject.toml
- The app uses SQLAlchemy async with asyncpg — test fixtures need to handle async session management
- Consider using `SAVEPOINT`-based transaction rollback for test isolation (faster than recreating tables)

View File

@@ -0,0 +1,31 @@
---
# nuzlocke-tracker-rzu4
title: Game Data Cleanup
status: todo
type: epic
created_at: 2026-02-10T08:58:36Z
updated_at: 2026-02-10T08:58:36Z
---
Audit and fix the game seed data to ensure all supported games have accurate encounter data and correct route ordering. PokeAPI provides a solid basis for Gen 1-7, but later games (Gen 8+) have no encounter data at all (stub/null JSON files), and route ordering is only defined for Gen 1-4. This is mostly manual work, but we should explore automated/semi-automated options where possible (e.g. scraping community wikis, leveraging existing fan-curated datasets).
## Current State
### Encounter data status:
- **Complete (from PokeAPI):** Red, Blue, Yellow, FireRed, LeafGreen, Gold, Silver, Crystal, HeartGold, SoulSilver, Ruby, Sapphire, Emerald, Diamond, Pearl, Platinum, Black, White, Black 2, White 2, X, Y, Sun, Moon, Ultra Sun, Ultra Moon
- **Stub/null (no data):** Sword, Shield, Scarlet, Violet, Brilliant Diamond, Shining Pearl, Legends Arceus
- **Minimal/partial:** Omega Ruby, Alpha Sapphire (96 lines each), Let's Go Pikachu/Eevee (134 lines each), Legends Z-A (1298 lines, partially complete)
### Route ordering status (route_order.json):
- **Defined:** firered-leafgreen (+ aliases: red-blue, yellow, lets-go), heartgold-soulsilver (+ aliases: gold-silver, crystal), emerald (+ aliases: ruby-sapphire, omega-ruby-alpha-sapphire), platinum
- **Missing:** diamond-pearl, black-white, black-2-white-2, x-y, sun-moon, ultra-sun-ultra-moon, sword-shield, brilliant-diamond-shining-pearl, scarlet-violet, legends-arceus, legends-z-a
### Special considerations:
- Existing PokeAPI data may have inaccurate encounter rates or missing special encounters (gifts, fossils, starters)
- Sub-area grouping (children) may need review — some routes are split into too many/few sub-areas
- The `special_encounters.json` file handles manual additions but may be incomplete for later gens
## Approach
- Manual curation is the primary approach for most tasks
- Explore automated options: community datasets (e.g. veekun/pokedex, PokeAPI CSV data), wiki scraping, or other structured data sources
- Use the admin panel and export CLI to verify/edit data after seeding

View File

@@ -0,0 +1,25 @@
---
# nuzlocke-tracker-ugb7
title: Integration tests for Pokemon & Evolutions API
status: draft
type: task
created_at: 2026-02-10T09:33:16Z
updated_at: 2026-02-10T09:33:16Z
parent: nuzlocke-tracker-yzpb
---
Write integration tests for the Pokemon and evolutions API endpoints.
## Checklist
- [ ] Test Pokemon CRUD operations (create, list, search, update, delete)
- [ ] Test Pokemon filtering and search
- [ ] Test evolution chain CRUD (create, list, get, update, delete)
- [ ] Test evolution family resolution endpoint
- [ ] Test error cases (invalid Pokemon references, circular evolutions, etc.)
## Notes
- Pokemon endpoints are in `backend/src/app/api/pokemon.py`
- Evolution endpoints are in `backend/src/app/api/evolutions.py`
- Evolution tests should cover multi-stage and branching chains

View File

@@ -1,10 +1,11 @@
--- ---
# nuzlocke-tracker-up0b # nuzlocke-tracker-up0b
title: Environment and secrets management title: Environment and secrets management
status: todo status: completed
type: task type: task
priority: normal
created_at: 2026-02-09T15:30:52Z created_at: 2026-02-09T15:30:52Z
updated_at: 2026-02-09T15:30:52Z updated_at: 2026-02-10T08:44:18Z
parent: nuzlocke-tracker-ahza parent: nuzlocke-tracker-ahza
--- ---

View File

@@ -0,0 +1,27 @@
---
# nuzlocke-tracker-v5zc
title: Complete ORAS and Let's Go encounter data
status: todo
type: task
created_at: 2026-02-10T08:59:09Z
updated_at: 2026-02-10T08:59:09Z
parent: nuzlocke-tracker-rzu4
---
Omega Ruby, Alpha Sapphire, Let's Go Pikachu, and Let's Go Eevee have minimal stub data that needs to be completed.
## Current state:
- **Omega Ruby:** 96 lines (stub — likely just a skeleton)
- **Alpha Sapphire:** 96 lines (stub — likely just a skeleton)
- **Let's Go Pikachu:** 134 lines (minimal)
- **Let's Go Eevee:** 134 lines (minimal)
## Approach:
- **ORAS:** These are Gen 3 remakes. Emerald data could be used as a base, but ORAS added new areas (Mirage spots, soaring encounters), DexNav encounters, and different encounter tables. Supplement from community sources.
- **Let's Go:** These are Gen 1 remakes with simplified mechanics. FireRed/LeafGreen data could serve as a base, but encounters are different (no wild battles in traditional sense, overworld spawns). Version exclusives differ between Pikachu and Eevee versions.
## Checklist:
- [ ] Review and complete Omega Ruby encounter data
- [ ] Review and complete Alpha Sapphire encounter data
- [ ] Review and complete Let's Go Pikachu encounter data
- [ ] Review and complete Let's Go Eevee encounter data

View File

@@ -1,10 +1,11 @@
--- ---
# nuzlocke-tracker-vpn5 # nuzlocke-tracker-vpn5
title: Create production docker-compose file title: Create production docker-compose file
status: todo status: completed
type: task type: task
priority: normal
created_at: 2026-02-09T15:30:41Z created_at: 2026-02-09T15:30:41Z
updated_at: 2026-02-09T15:30:41Z updated_at: 2026-02-09T16:59:00Z
parent: nuzlocke-tracker-ahza parent: nuzlocke-tracker-ahza
--- ---

View File

@@ -1,10 +1,11 @@
--- ---
# nuzlocke-tracker-xmyh # nuzlocke-tracker-xmyh
title: Create production Dockerfiles title: Create production Dockerfiles
status: todo status: completed
type: task type: task
priority: normal
created_at: 2026-02-09T15:30:42Z created_at: 2026-02-09T15:30:42Z
updated_at: 2026-02-09T15:30:42Z updated_at: 2026-02-09T17:00:32Z
parent: nuzlocke-tracker-ahza parent: nuzlocke-tracker-ahza
--- ---

View File

@@ -0,0 +1,34 @@
---
# nuzlocke-tracker-xvaw
title: Clean up frontend branding and metadata
status: todo
type: task
created_at: 2026-02-10T09:36:24Z
updated_at: 2026-02-10T09:36:24Z
---
The frontend currently uses all Vite defaults — generic title, Vite favicon, no manifest, no meta tags. Clean it up so it looks polished and professional as "Nuzlocke Tracker".
## Current State
- Page title: "frontend" (Vite default)
- Favicon: `/vite.svg` (Vite logo)
- No `manifest.json` / `site.webmanifest`
- No meta description or Open Graph tags
- No theme-color meta tag
- `package.json` name is "frontend"
- Default `react.svg` sitting unused in `src/assets/`
## Checklist
- [ ] Design or source a proper favicon (Pokeball-themed or similar, in SVG + PNG formats)
- [ ] Add favicon files to `public/` (favicon.ico, favicon.svg, apple-touch-icon.png, favicon-16x16.png, favicon-32x32.png)
- [ ] Update `index.html` title from "frontend" to "Nuzlocke Tracker"
- [ ] Add meta description tag (e.g. "Track your Nuzlocke challenge runs across all Pokemon games")
- [ ] Add theme-color meta tag matching the app's primary color
- [ ] Add Open Graph meta tags (og:title, og:description, og:type) for link previews
- [ ] Create a `site.webmanifest` with app name, short_name, icons, theme_color, background_color
- [ ] Link the manifest in `index.html`
- [ ] Remove unused default assets (`public/vite.svg`, `src/assets/react.svg`)
- [ ] Update `package.json` name from "frontend" to "nuzlocke-tracker" (or "nuzlocke-tracker-frontend")
- [ ] Consider adding dynamic page titles per route (e.g. "Runs | Nuzlocke Tracker") — optional stretch goal

View File

@@ -0,0 +1,27 @@
---
# nuzlocke-tracker-yzpb
title: Implement Unit & Integration Tests
status: draft
type: epic
created_at: 2026-02-10T09:32:47Z
updated_at: 2026-02-10T09:32:47Z
---
Add comprehensive unit and integration test coverage to both the backend (FastAPI/Python) and frontend (React/TypeScript). The project currently has zero tests — pytest is configured in pyproject.toml with pytest-asyncio and httpx, but no actual test files exist. The frontend has no test tooling at all.
**Scope:**
- Unit tests for isolated logic (schemas, services, utilities)
- Integration tests for API endpoints (using httpx AsyncClient against a test database)
- Frontend unit/component tests (using Vitest + React Testing Library)
**Explicitly out of scope:**
- End-to-end / browser tests (e.g. Selenium, Playwright) — requires specialised infrastructure
## Success Criteria
- [ ] Backend test infrastructure is set up (conftest, fixtures, test DB)
- [ ] Backend schemas and services have unit test coverage
- [ ] Backend API endpoints have integration test coverage
- [ ] Frontend test infrastructure is set up (Vitest, RTL)
- [ ] Frontend utilities and hooks have unit test coverage
- [ ] Frontend components have basic render/interaction tests

152
DEPLOYMENT.md Normal file
View File

@@ -0,0 +1,152 @@
# Deployment
This document describes the deployment architecture and workflows for the nuzlocke-tracker. It is a living document — sections marked with **TODO** are planned but not yet implemented.
## Architecture Overview
| Component | Dev (Laptop/PC) | Production (Unraid) |
|---|---|---|
| API | `docker-compose.yml` (hot reload) | `docker-compose.prod.yml` (built image) |
| Frontend | `docker-compose.yml` (Vite dev server) | `docker-compose.prod.yml` (built image) |
| Database | PostgreSQL 16 (Docker volume) | PostgreSQL 16 (Docker volume) |
| Container Registry | — | Gitea (user-level packages) |
| Container Management | — | Portainer CE |
| Reverse Proxy | — | Nginx Proxy Manager |
### Services
- **Gitea** — self-hosted Git server, container registry, and (future) CI/CD. Accessible at `gitea.nerdboden.de` via SSL.
- **Portainer** — Docker management UI. Accessible at `portainer.nerdboden.de` via SSL. Manages the production stack and provides webhook-triggered redeployments.
- **Nginx Proxy Manager** — reverse proxy with SSL termination for all services on the Unraid server.
## Container Registry
Docker images are hosted on Gitea's built-in container registry as **user-level packages**.
### Image naming
Images use the format `gitea.nerdboden.de/<user>/<image>:<tag>`:
```
gitea.nerdboden.de/thefurya/nuzlocke-tracker-api:latest
gitea.nerdboden.de/thefurya/nuzlocke-tracker-frontend:latest
```
### Authentication
1. Create a Gitea access token at **Settings > Applications** with `read:package` and `write:package` scopes.
2. Log in from the dev machine:
```bash
docker login gitea.nerdboden.de
```
Use your Gitea username and the access token as password.
### Pushing images
```bash
# Build and tag
docker build -t gitea.nerdboden.de/thefurya/nuzlocke-tracker-api:latest ./backend
docker build -t gitea.nerdboden.de/thefurya/nuzlocke-tracker-frontend:latest ./frontend
# Push
docker push gitea.nerdboden.de/thefurya/nuzlocke-tracker-api:latest
docker push gitea.nerdboden.de/thefurya/nuzlocke-tracker-frontend:latest
```
Pushed images are visible under the **Packages** tab on your Gitea user profile.
## Branching Strategy
The project uses a `main` / `develop` / `feature/*` branching model.
| Branch | Purpose |
|---|---|
| `main` | Always production-ready. Deploy script builds from here. |
| `develop` | Integration branch for day-to-day work. |
| `feature/*` | Short-lived branches off `develop` for individual features/fixes. |
### Workflow
1. Create `feature/xyz` from `develop`
2. Work on the feature, commit, merge into `develop`
3. When ready to deploy: merge `develop` into `main`
4. Run the deploy script (see below)
## Deploying
> **TODO** — deploy script (`./deploy.sh`) not yet created.
The deploy script will automate:
1. Build Docker images from `main`
2. Tag and push to the Gitea container registry
3. Trigger the Portainer webhook to pull new images and restart the stack
### Manual deployment
Until the deploy script is in place, deploy manually:
```bash
# 1. Ensure you're on main with latest changes
git checkout main
# 2. Build and push images
docker build -t gitea.nerdboden.de/thefurya/nuzlocke-tracker-api:latest ./backend
docker build -t gitea.nerdboden.de/thefurya/nuzlocke-tracker-frontend:latest ./frontend
docker push gitea.nerdboden.de/thefurya/nuzlocke-tracker-api:latest
docker push gitea.nerdboden.de/thefurya/nuzlocke-tracker-frontend:latest
# 3. On Unraid (or via Portainer): pull and restart
docker compose -f docker-compose.prod.yml pull
docker compose -f docker-compose.prod.yml up -d
```
## Production Compose
> **TODO** — `docker-compose.prod.yml` not yet created.
The production compose file will differ from the dev compose in:
- Uses pre-built images from the Gitea registry (no source volume mounts)
- No hot reload / debug mode
- Production environment variables
- Proper restart policies
- Frontend served as a static build (not Vite dev server)
## Portainer
Portainer CE is running on Unraid at `portainer.nerdboden.de`.
- Manages the production Docker stack
- **TODO**: Configure a webhook for automated redeployment (pull latest images + restart on trigger)
## Nginx Proxy Manager
NPM runs on Unraid and handles SSL termination and routing for:
- `gitea.nerdboden.de` → Gitea
- `portainer.nerdboden.de` → Portainer
- **TODO**: `nuzlocke.nerdboden.de` (or similar) → nuzlocke-tracker frontend/API
## Environment & Secrets
> **TODO** — `.env.prod` template not yet created.
Production environment variables to configure:
- `DATABASE_URL` — PostgreSQL connection string
- `DEBUG` — must be `false` in production
- Additional secrets TBD
## Database
PostgreSQL 16 with data stored in a named Docker volume.
- Migrations run automatically on API container startup (Alembic)
- **TODO**: Set up scheduled backups (`pg_dump` cron job on Unraid)
## Rollback
> **TODO** — rollback procedure to be documented once image tagging strategy is finalized.
General approach: tag images with version/commit hash in addition to `latest`, so rolling back means redeploying a previous tag.

19
backend/Dockerfile.prod Normal file
View File

@@ -0,0 +1,19 @@
# Production Dockerfile for the backend API
FROM python:3.14-slim
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
&& rm -rf /var/lib/apt/lists/*
# Install Python dependencies
COPY pyproject.toml README.md alembic.ini ./
COPY src/ ./src/
RUN pip install --no-cache-dir .
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--app-dir", "src"]

80
deploy.sh Executable file
View File

@@ -0,0 +1,80 @@
#!/usr/bin/env bash
set -euo pipefail
# ── Configuration ──────────────────────────────────────────────
REGISTRY="gitea.nerdboden.de"
OWNER="thefurya"
IMAGES=("nuzlocke-tracker-api" "nuzlocke-tracker-frontend")
DOCKERFILES=("backend/Dockerfile.prod" "frontend/Dockerfile.prod")
CONTEXTS=("./backend" "./frontend")
if command -v podman &>/dev/null; then
CONTAINER_CMD="podman"
elif command -v docker &>/dev/null; then
CONTAINER_CMD="docker"
else
echo "Neither podman nor docker found." >&2; exit 1
fi
UNRAID_SSH="root@192.168.1.10"
UNRAID_DEPLOY_DIR="/mnt/user/appdata/nuzlocke-tracker"
# ── Helpers ────────────────────────────────────────────────────
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
info() { echo -e "${GREEN}[✓]${NC} $1"; }
warn() { echo -e "${YELLOW}[!]${NC} $1"; }
error() { echo -e "${RED}[✗]${NC} $1"; exit 1; }
# ── Preflight checks ──────────────────────────────────────────
BRANCH=$(git rev-parse --abbrev-ref HEAD)
if [[ "$BRANCH" != "main" ]]; then
warn "You are on branch '$BRANCH', not 'main'."
read -rp "Continue anyway? [y/N] " confirm
[[ "$confirm" =~ ^[Yy]$ ]] || exit 0
fi
if ! git diff --quiet || ! git diff --cached --quiet; then
warn "You have uncommitted changes."
read -rp "Continue anyway? [y/N] " confirm
[[ "$confirm" =~ ^[Yy]$ ]] || exit 0
fi
# ── Build and push images ─────────────────────────────────────
for i in "${!IMAGES[@]}"; do
IMAGE="${REGISTRY}/${OWNER}/${IMAGES[$i]}:latest"
info "Building ${IMAGES[$i]}..."
$CONTAINER_CMD build --platform linux/amd64 -t "$IMAGE" -f "${DOCKERFILES[$i]}" "${CONTEXTS[$i]}"
info "Pushing ${IMAGES[$i]}..."
$CONTAINER_CMD push "$IMAGE"
done
info "All images built and pushed."
# ── Sync compose file to Unraid ──────────────────────────────────
info "Copying docker-compose.prod.yml to Unraid..."
scp docker-compose.prod.yml "${UNRAID_SSH}:${UNRAID_DEPLOY_DIR}/docker-compose.yml" \
|| error "Failed to copy compose file to Unraid."
info "Compose file synced."
# ── Ensure .env with Postgres password exists ────────────────────
info "Checking for .env on Unraid..."
ssh "${UNRAID_SSH}" "
if [ ! -f '${UNRAID_DEPLOY_DIR}/.env' ]; then
PASS=\$(head -c 32 /dev/urandom | base64 | tr -dc 'A-Za-z0-9' | head -c 32)
echo \"POSTGRES_PASSWORD=\${PASS}\" > '${UNRAID_DEPLOY_DIR}/.env'
echo 'Generated new .env with POSTGRES_PASSWORD'
else
echo '.env already exists, skipping'
fi
" || error "Failed to check/create .env on Unraid."
# ── Pull images and (re)start on Unraid ──────────────────────────
info "Pulling images and starting containers on Unraid..."
ssh "${UNRAID_SSH}" "cd '${UNRAID_DEPLOY_DIR}' && docker compose pull && docker compose up -d" \
|| error "Failed to start containers on Unraid."
info "Deploy complete!"

36
docker-compose.prod.yml Normal file
View File

@@ -0,0 +1,36 @@
services:
api:
image: gitea.nerdboden.de/thefurya/nuzlocke-tracker-api:latest
command: >
sh -c "alembic upgrade head && uvicorn app.main:app --host 0.0.0.0 --port 8000 --app-dir src"
environment:
- DEBUG=false
- DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@db:5432/nuzlocke
depends_on:
db:
condition: service_healthy
restart: unless-stopped
frontend:
image: gitea.nerdboden.de/thefurya/nuzlocke-tracker-frontend:latest
ports:
- "9080:80"
depends_on:
- api
restart: unless-stopped
db:
image: postgres:16-alpine
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=nuzlocke
volumes:
- ./data/postgres:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
restart: unless-stopped

21
frontend/Dockerfile.prod Normal file
View File

@@ -0,0 +1,21 @@
# Production Dockerfile for the frontend
# Stage 1: Build
FROM node:24-slim AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: Serve
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

20
frontend/nginx.conf Normal file
View File

@@ -0,0 +1,20 @@
server {
listen 80;
root /usr/share/nginx/html;
index index.html;
# Proxy API requests to the backend service
location /api/ {
proxy_pass http://api:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Serve static files, fall back to index.html for SPA routing
location / {
try_files $uri $uri/ /index.html;
}
}

View File

@@ -107,7 +107,7 @@ export function BossBattleFormModal({
<label className="block text-sm font-medium mb-1">Type</label> <label className="block text-sm font-medium mb-1">Type</label>
<select <select
value={bossType} value={bossType}
onChange={(e) => setBossType(e.target.value)} onChange={(e) => setBossType(e.target.value as typeof bossType)}
className="w-full px-3 py-2 border rounded-md dark:bg-gray-700 dark:border-gray-600" className="w-full px-3 py-2 border rounded-md dark:bg-gray-700 dark:border-gray-600"
> >
{BOSS_TYPES.map((t) => ( {BOSS_TYPES.map((t) => (

View File

@@ -1,6 +1,6 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { advanceLeg, createGenlocke, getGamesByRegion, getGenlockes, getGenlocke, getGenlockeGraveyard, getGenlockeLineages, getLegSurvivors } from '../api/genlockes' import { advanceLeg, createGenlocke, getGamesByRegion, getGenlockes, getGenlocke, getGenlockeGraveyard, getGenlockeLineages, getLegSurvivors } from '../api/genlockes'
import type { AdvanceLegInput, CreateGenlockeInput } from '../types/game' import type { CreateGenlockeInput } from '../types/game'
export function useGenlockes() { export function useGenlockes() {
return useQuery({ return useQuery({