2026-07-01 21:10:06 +02:00
# Pokedexter ⚡
2026-07-01 23:11:55 +02:00
Pokémon TCG-samlerwebapp — bygget med **FastAPI** , **PostgreSQL 16** , **Nginx** og **Podman/Docker** .
2026-07-01 21:10:06 +02:00
2026-07-01 23:11:55 +02:00
Administratorer opretter serier, sæt og kort. Brugere kan logge ind, browse og søge. Ingen åben registrering.
2026-07-01 21:10:06 +02:00
---
## Arkitektur
```
2026-07-01 23:11:55 +02:00
Browser ──► Nginx (:8085) ──► FastAPI (:8000) ──► PostgreSQL (:5432)
2026-07-01 21:10:06 +02:00
▲ │
└─── static/ ───────┘
```
2026-07-01 23:11:55 +02:00
- **Nginx** — reverse proxy, serverer statiske filer på port 8085
2026-07-01 21:10:06 +02:00
- **FastAPI** — async Python backend, Jinja2 templates
2026-07-01 23:11:55 +02:00
- **PostgreSQL 16** — database med SQLAlchemy (async/asyncpg)
2026-07-01 21:10:06 +02:00
---
## Kom i gang
2026-07-01 23:11:55 +02:00
Billeder bygges lokalt først, deploy sker via Portainer.
### Byg lokalt (podman)
2026-07-01 21:10:06 +02:00
```bash
2026-07-01 23:11:55 +02:00
podman build -t pokedexter-backend:latest -f backend/Dockerfile backend/
podman build -t pokedexter-nginx:latest -f nginx/Dockerfile nginx/
2026-07-01 21:10:06 +02:00
```
2026-07-01 23:11:55 +02:00
### Start lokalt
2026-07-01 21:10:06 +02:00
```bash
2026-07-01 23:11:55 +02:00
podman-compose up -d # podman
docker compose up -d # docker
2026-07-01 21:10:06 +02:00
```
### Stop
```bash
2026-07-01 23:11:55 +02:00
podman-compose down
docker compose down
2026-07-01 21:10:06 +02:00
```
2026-07-01 23:11:55 +02:00
Data i volumes (`pokedexter_pgdata` , `pokedexter_images` ) bevares mellem genstarter.
2026-07-01 21:10:06 +02:00
---
## Login
| Brugernavn | Adgangskode | Rolle |
|------------|-------------|-------|
| `admin` | `admin123` | Admin |
Brugeren oprettes automatisk ved første opstart. Skift adgangskoden efter første login.
---
## Sådan bruges det
2026-07-01 23:11:55 +02:00
### Opret en serie
2026-07-01 21:10:06 +02:00
1. Log ind på `/login` med `admin` / `admin123`
2. Gå til **Admin → Serier**
2026-07-01 23:11:55 +02:00
3. Indtast **navn** (f.eks. `Sword & Shield` ) og evt. beskrivelse
2026-07-01 21:10:06 +02:00
4. Tryk **Opret**
2026-07-01 23:11:55 +02:00
### Opret sæt under en serie
2026-07-01 21:10:06 +02:00
1. Gå til **Admin → Serier**
2026-07-01 23:11:55 +02:00
2. Klik på serienavnet (link)
3. Indtast **kode** (f.eks. `EVS` ), **navn** (`Evolving Skies` ) og evt. **release_date**
4. Tryk **Opret sæt**
2026-07-01 21:10:06 +02:00
2026-07-01 23:11:55 +02:00
Eller **bulk import** : indsæt CSV direkte i tekstfeltet:
2026-07-01 21:10:06 +02:00
2026-07-01 23:11:55 +02:00
```
Stellar Crown,SCR,Stellar Crown,2024-09-13,
Surging Sparks,SSP,Surging Sparks,2024-11-08,
```
2026-07-01 21:10:06 +02:00
2026-07-01 23:11:55 +02:00
### Tilføj kort
2026-07-01 21:10:06 +02:00
2026-07-01 23:11:55 +02:00
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**
2026-07-01 21:10:06 +02:00
2026-07-01 23:11:55 +02:00
Eller **bulk import** via CSV:
2026-07-01 21:10:06 +02:00
```
2026-07-01 23:11:55 +02:00
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.
2026-07-01 21:10:06 +02:00
### Browse kort
2026-07-01 23:11:55 +02:00
- `/sets` — liste alle serier med sæt
- `/sets/{code}` — se alle kort i sæt `EVS`
- `/card/{full_code}` — detaljer om et specifikt kort
2026-07-01 21:10:06 +02:00
### Søg
2026-07-01 23:11:55 +02:00
- `/search?q=SCR-001` — søg på fuld kortkode
- `/search?q=pikachu` — søg på kortnavn
2026-07-01 21:10:06 +02:00
---
## API-oversigt
### Offentlige endpoints
2026-07-01 23:11:55 +02:00
| 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) |
2026-07-01 21:10:06 +02:00
| 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)
2026-07-01 23:11:55 +02:00
| 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 |
2026-07-01 21:10:06 +02:00
---
## Datamodel
```
User
id Integer (PK)
username String (unique)
password_hash String (bcrypt)
role String ("admin" | "user")
created_at DateTime
2026-07-01 23:11:55 +02:00
Serie
id Integer (PK)
name String (unique) — "Sword & Shield"
description Text (optional)
created_at DateTime
sets ► Set[]
2026-07-01 21:10:06 +02:00
Set
id Integer (PK)
2026-07-01 23:11:55 +02:00
serie_id Integer (FK → series.id)
code String (unique) — "EVS"
name String — "Evolving Skies"
2026-07-01 21:10:06 +02:00
release_date String (optional)
description Text (optional)
created_at DateTime
2026-07-01 23:11:55 +02:00
serie ► Serie
2026-07-01 21:10:06 +02:00
cards ► Card[]
Card
id Integer (PK)
set_id Integer (FK → sets.id)
2026-07-01 23:11:55 +02:00
number String — "050"
name String — "Raichu"
image_path String (optional) — lokal sti eller URL
source_url String (optional) — original ekstern URL til re-sync
2026-07-01 21:10:06 +02:00
set ► Set
2026-07-01 23:11:55 +02:00
full_code [property] — "EVS-050"
image_url [property] — "/static/..." eller None (ingen eksterne URLs)
2026-07-01 21:10:06 +02:00
```
---
## Miljøvariable
2026-07-01 23:11:55 +02:00
| Variabel | Standard | Beskrivelse |
|----------------|----------------------------------|----------------------|
2026-07-01 21:10:06 +02:00
| `DATABASE_URL` | `postgresql+asyncpg://...` | PostgreSQL forbindelse |
2026-07-01 23:11:55 +02:00
| `SECRET_KEY` | `skift-mig-til-en-sikker-noegle` | JWT signeringsnøgle |
2026-07-01 21:10:06 +02:00
---
## Projektstruktur
```
pokedexter/
├── docker-compose.yml
2026-07-01 23:11:55 +02:00
├── start.sh # podman start
├── stop.sh # podman stop
├── README.md
2026-07-01 21:10:06 +02:00
├── backend/
│ ├── Dockerfile
│ ├── requirements.txt
│ └── app/
2026-07-01 23:11:55 +02:00
│ ├── 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
2026-07-01 21:10:06 +02:00
├── nginx/
│ ├── Dockerfile
│ └── nginx.conf
└── db/
└── init.sql
```
---
## Udvikling
2026-07-01 23:11:55 +02:00
### Genstart backend efter kodeændringer
2026-07-01 21:10:06 +02:00
```bash
2026-07-01 23:11:55 +02:00
# Byg nyt image
podman build -t pokedexter-backend:latest -f backend/Dockerfile backend/
# Genstart via Portainer, eller:
2026-07-01 21:10:06 +02:00
podman stop pokedexter-backend && podman rm pokedexter-backend
2026-07-01 23:11:55 +02:00
podman-compose up -d backend
2026-07-01 21:10:06 +02:00
```
2026-07-01 23:11:55 +02:00
### 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.
2026-07-01 21:10:06 +02:00
---
2026-07-01 23:20:52 +02:00
## Køreplan / To-do
2026-07-01 21:10:06 +02:00
2026-07-01 23:20:52 +02:00
- [ ] **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
2026-07-01 23:22:51 +02:00
- [ ] **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)
2026-07-01 21:10:06 +02:00
- [ ] Personlig mappe — brugere kan markere kort de ejer/samler
2026-07-01 23:11:55 +02:00
- [ ] Eksport af samling til CSV