4ab0b0b8da
- Dockerfiles nu i repo roden så Portainer kan bygge direkte fra Gitea URL - COPY paths opdateret til at inkludere subdirectory (backend/, nginx/) - docker-compose.yml build paths opdateret - README build-kommandoer + Portainer guide + projektstruktur opdateret
313 lines
11 KiB
Markdown
313 lines
11 KiB
Markdown
# Pokedexter ⚡
|
|
|
|
Pokémon TCG-samlerwebapp — bygget med **FastAPI**, **PostgreSQL 16**, **Nginx** og **Podman/Docker**.
|
|
|
|
Administratorer opretter serier, sæt og kort. Brugere kan logge ind, browse og søge. Ingen åben registrering.
|
|
|
|
---
|
|
|
|
## Arkitektur
|
|
|
|
```
|
|
Browser ──► Nginx (:8085) ──► FastAPI (:8000) ──► PostgreSQL (:5432)
|
|
▲ │
|
|
└─── static/ ───────┘
|
|
```
|
|
|
|
- **Nginx** — reverse proxy, serverer statiske filer på port 8085
|
|
- **FastAPI** — async Python backend, Jinja2 templates
|
|
- **PostgreSQL 16** — database med SQLAlchemy (async/asyncpg)
|
|
|
|
---
|
|
|
|
## Kom i gang
|
|
|
|
Billeder bygges lokalt først, deploy sker via Portainer.
|
|
|
|
### Byg lokalt (podman)
|
|
|
|
```bash
|
|
podman build -t pokedexter-backend:latest -f Dockerfile_backend .
|
|
podman build -t pokedexter-nginx:latest -f Dockerfile_nginx .
|
|
```
|
|
|
|
### Byg fra Gitea (Portainer)
|
|
|
|
I Portainer under **Image → Build from repository**:
|
|
|
|
| Felt | Værdi |
|
|
|--------------------|------------------------------------------|
|
|
| Repository URL | `https://git.itedb.dk/glennigen/pokedexter.git` |
|
|
| Dockerfile path | `Dockerfile_backend` (eller `Dockerfile_nginx`) |
|
|
|
|
### Start lokalt
|
|
|
|
```bash
|
|
podman-compose up -d # podman
|
|
docker compose up -d # docker
|
|
```
|
|
|
|
### Stop
|
|
|
|
```bash
|
|
podman-compose down
|
|
docker compose down
|
|
```
|
|
|
|
Data i volumes (`pokedexter_pgdata`, `pokedexter_images`) bevares mellem genstarter.
|
|
|
|
---
|
|
|
|
## Login
|
|
|
|
| Brugernavn | Adgangskode | Rolle |
|
|
|------------|-------------|-------|
|
|
| `admin` | `admin123` | Admin |
|
|
|
|
Brugeren oprettes automatisk ved første opstart. Skift adgangskoden efter første login.
|
|
|
|
---
|
|
|
|
## Sådan bruges det
|
|
|
|
### Opret en serie
|
|
|
|
1. Log ind på `/login` med `admin` / `admin123`
|
|
2. Gå til **Admin → Serier**
|
|
3. Indtast **navn** (f.eks. `Sword & Shield`) og evt. beskrivelse
|
|
4. Tryk **Opret**
|
|
|
|
### Opret sæt under en serie
|
|
|
|
1. Gå til **Admin → Serier**
|
|
2. Klik på serienavnet (link)
|
|
3. Indtast **kode** (f.eks. `EVS`), **navn** (`Evolving Skies`) og evt. **release_date**
|
|
4. Tryk **Opret sæt**
|
|
|
|
Eller **bulk import**: indsæt CSV direkte i tekstfeltet:
|
|
|
|
```
|
|
Stellar Crown,SCR,Stellar Crown,2024-09-13,
|
|
Surging Sparks,SSP,Surging Sparks,2024-11-08,
|
|
```
|
|
|
|
### Tilføj kort
|
|
|
|
1. Gå til **Admin → Sæt**
|
|
2. Klik **Vis kort** på det ønskede sæt
|
|
3. Udfyld **nummer**, **navn** og evt. **image URL**
|
|
4. Tryk **Opret kort**
|
|
|
|
Eller **bulk import** via CSV:
|
|
|
|
```
|
|
SCR,001,Caterpie,https://images.pokemontcg.io/scarlet_ivy/1.png
|
|
SCR,002,Metapod,
|
|
```
|
|
|
|
Kortet får automatisk koden `SCR-001`.
|
|
|
|
### Billedhåndtering
|
|
|
|
Når en URL angives ved kortoprettelse:
|
|
- Billedet **downloades automatisk** og gemmes lokalt i volume'et
|
|
- Hvis download fejler, bevares URL'en som `image_path` og et ⚠-badge vises
|
|
- `Card.image_url` returnerer **aldrig eksterne URLs** — ingen streaming
|
|
|
|
#### Synkronisér billeder
|
|
|
|
På **Admin → Sæt → Vis kort** findes en **Synkronisér billeder**-knap, der downloader alle eksterne billeder for sættet til lokal lagring.
|
|
|
|
#### Verificér & genopret
|
|
|
|
På **Admin**-dashboardet findes:
|
|
|
|
- **Verificér & genopret** — tjekker om alle lokale billedfiler findes på disken. Manglende filer re-downloades fra `source_url`
|
|
- **Force-genhent alle** — genhenter ALLE billeder fra deres originale kilder (uanset om de findes)
|
|
|
|
Dette er nyttigt hvis volume'et afmonteres ved en fejl, eller billeder tabes ved genstart.
|
|
|
|
### Opret brugere (admin)
|
|
|
|
På **Admin**-dashboardet under "Brugere" kan du oprette og slette brugere. Du kan ikke slette dig selv.
|
|
|
|
### Browse kort
|
|
|
|
- `/sets` — liste alle serier med sæt
|
|
- `/sets/{code}` — se alle kort i sæt `EVS`
|
|
- `/card/{full_code}` — detaljer om et specifikt kort
|
|
|
|
### Søg
|
|
|
|
- `/search?q=SCR-001` — søg på fuld kortkode
|
|
- `/search?q=pikachu` — søg på kortnavn
|
|
|
|
---
|
|
|
|
## API-oversigt
|
|
|
|
### Offentlige endpoints
|
|
|
|
| Metode | Sti | Beskrivelse |
|
|
|--------|----------------------|------------------------|
|
|
| GET | `/` | Forside |
|
|
| GET | `/sets` | Liste alle serier |
|
|
| GET | `/sets/{code}` | Kort i et sæt |
|
|
| GET | `/search` | Søgeside |
|
|
| GET | `/api/search` | Søg (JSON) |
|
|
| GET | `/api/cards/{full_code}` | Enkelt kort (JSON) |
|
|
|
|
### Auth endpoints
|
|
|
|
| Metode | Sti | Beskrivelse |
|
|
|--------|----------------|------------------------------|
|
|
| GET | `/login` | Loginside |
|
|
| POST | `/api/login` | Login (sætter cookie) |
|
|
| GET | `/logout` | Logout |
|
|
|
|
### Admin endpoints (kræver admin)
|
|
|
|
| Metode | Sti | Beskrivelse |
|
|
|--------|--------------------------------------------|-------------------------------------|
|
|
| GET | `/admin` | Admin dashboard |
|
|
| GET | `/admin/series` | Administrér serier |
|
|
| GET | `/admin/series/{name}` | Serie-detail med sæt |
|
|
| GET | `/admin/sets` | Administrér sæt |
|
|
| GET | `/admin/sets/{code}` | Administrér kort i sæt |
|
|
| POST | `/api/admin/users` | Opret bruger |
|
|
| DELETE | `/api/admin/users/{id}` | Slet bruger (ikke dig selv) |
|
|
| POST | `/api/admin/series` | Opret serie |
|
|
| POST | `/api/admin/series/csv` | Bulk import af serier (CSV) |
|
|
| DELETE | `/api/admin/series/{id}` | Slet serie |
|
|
| POST | `/api/admin/series/{name}/sets` | Opret sæt under serie |
|
|
| POST | `/api/admin/series/{name}/sets/csv` | Bulk import af sæt (CSV) |
|
|
| POST | `/api/admin/sets/{code}/cards` | Opret kort |
|
|
| POST | `/api/admin/sets/{code}/cards/csv` | Bulk import af kort (CSV) |
|
|
| DELETE | `/api/admin/cards/{id}` | Slet kort |
|
|
| POST | `/api/admin/cards/{id}/upload` | Upload billede (fil) |
|
|
| POST | `/api/admin/sets/{code}/sync-images` | Synkronisér billeder for sæt |
|
|
| POST | `/api/admin/sync-all-images` | Synkronisér alle billeder |
|
|
| POST | `/api/admin/verify-images` | Verificér og genopret billeder |
|
|
| POST | `/api/admin/verify-images?force=true` | Force-genhent alle billeder |
|
|
|
|
---
|
|
|
|
## Datamodel
|
|
|
|
```
|
|
User
|
|
id Integer (PK)
|
|
username String (unique)
|
|
password_hash String (bcrypt)
|
|
role String ("admin" | "user")
|
|
created_at DateTime
|
|
|
|
Serie
|
|
id Integer (PK)
|
|
name String (unique) — "Sword & Shield"
|
|
description Text (optional)
|
|
created_at DateTime
|
|
sets ► Set[]
|
|
|
|
Set
|
|
id Integer (PK)
|
|
serie_id Integer (FK → series.id)
|
|
code String (unique) — "EVS"
|
|
name String — "Evolving Skies"
|
|
release_date String (optional)
|
|
description Text (optional)
|
|
created_at DateTime
|
|
serie ► Serie
|
|
cards ► Card[]
|
|
|
|
Card
|
|
id Integer (PK)
|
|
set_id Integer (FK → sets.id)
|
|
number String — "050"
|
|
name String — "Raichu"
|
|
image_path String (optional) — lokal sti eller URL
|
|
source_url String (optional) — original ekstern URL til re-sync
|
|
set ► Set
|
|
full_code [property] — "EVS-050"
|
|
image_url [property] — "/static/..." eller None (ingen eksterne URLs)
|
|
```
|
|
|
|
---
|
|
|
|
## Miljøvariable
|
|
|
|
| Variabel | Standard | Beskrivelse |
|
|
|----------------|----------------------------------|----------------------|
|
|
| `DATABASE_URL` | `postgresql+asyncpg://...` | PostgreSQL forbindelse |
|
|
| `SECRET_KEY` | `skift-mig-til-en-sikker-noegle` | JWT signeringsnøgle |
|
|
|
|
---
|
|
|
|
## Projektstruktur
|
|
|
|
```
|
|
pokedexter/
|
|
├── docker-compose.yml
|
|
├── Dockerfile_backend
|
|
├── Dockerfile_nginx
|
|
├── README.md
|
|
├── backend/
|
|
│ ├── requirements.txt
|
|
│ └── app/
|
|
│ ├── main.py # FastAPI app, middleware
|
|
│ ├── database.py # async engine + session
|
|
│ ├── models.py # SQLAlchemy modeller
|
|
│ ├── auth.py # bcrypt + JWT
|
|
│ ├── static/
|
|
│ │ └── images/ # uploadet/downloadede billeder (volume)
|
|
│ ├── templates/ # Jinja2 HTML templates
|
|
│ │ ├── base.html # layout m. dark mode toggle
|
|
│ │ └── admin/ # admin UI templates
|
|
│ └── routers/
|
|
│ ├── auth.py # login/logout + admin user mgmt
|
|
│ ├── admin.py # admin CRUD for series/sets/cards
|
|
│ └── cards.py # browse + search
|
|
├── nginx/
|
|
│ ├── Dockerfile
|
|
│ └── nginx.conf
|
|
└── db/
|
|
└── init.sql
|
|
```
|
|
|
|
---
|
|
|
|
## Udvikling
|
|
|
|
### Genstart backend efter kodeændringer
|
|
|
|
```bash
|
|
# Byg nyt image
|
|
podman build -t pokedexter-backend:latest -f Dockerfile_backend .
|
|
|
|
# Genstart via Portainer, eller:
|
|
podman stop pokedexter-backend && podman rm pokedexter-backend
|
|
podman-compose up -d backend
|
|
```
|
|
|
|
### Dark mode
|
|
|
|
Dark mode gemmes i `localStorage` og sættes via render-blokerende script i `<head>` — ingen flash ved side-skift.
|
|
|
|
### Billeder og volumes
|
|
|
|
Billeder gemmes i volume `pokedexter_images` mountet på `/app/static/images`.
|
|
Volume'et skal være mountet før første hentning — ellers går billeder tabt ved container-genstart.
|
|
|
|
Hvis billeder mangler: brug **Verificér & genopret** på admin-dashboardet.
|
|
|
|
---
|
|
|
|
## Køreplan / To-do
|
|
|
|
- [ ] **Søgning optimeres** — PostgreSQL full-text search (tsvector/tsquery) eller trigram similarity (pg_trgm). Nuværende LIKE-søgning rammer ikke forkortelser, delvise navne eller stavefejl (`ar` → `Arceus`, `pika` → `Pikachu`)
|
|
- [ ] **Opdater billede på kort** — admin skal kunne ændre source-billed-URL på et eksisterende kort, hvorefter billedet re-downloades og lokale sti opdateres
|
|
- [ ] **Galleri/bladring i sæt** — vis kortbilleder i et sæt som et galleri/carousel, så man kan bladre frem/tilbage uden at gå til listen
|
|
- [ ] **Internal Docker-netværk** — backend og DB skal isoleres på et internt netværk, så de ikke er tilgængelige udefra
|
|
- [ ] **Nginx-proxy mod NPM** — tilføj proxy-konfiguration så containeren kan snakke med Nginx Proxy Manager (eller anden reverse proxy-container)
|
|
- [ ] Personlig mappe — brugere kan markere kort de ejer/samler
|
|
- [ ] Eksport af samling til CSV |