ikset og deployet. Kort oppsummering av endringen:
backend/weather_forecast.py: weather_sync_loop tar nå en valgfri on_updated-callback som kalles etter hver synkroniseringsrunde, men kun når noe faktisk ble oppdatert. backend/main.py: sender inn invalidate_public_api_caches(include_place_pages=True) som denne callbacken når værsynken startes ved oppstart. api-containeren er restartet og kjører rent. Effekten: neste gang værsynken faktisk endrer noe (skjer omtrent hver time), tømmes backendens interne cache og frontendens unstable_cache for anleggslisten/steds-sidene automatisk — så "Ikke regn meldt"-filteret vil vise fersk værdata i stedet for en fastfrosset snapshot.
This commit is contained in:
parent
aabc1afc20
commit
d0e60fc271
4 changed files with 198 additions and 2 deletions
|
|
@ -3812,7 +3812,11 @@ async def lifespan(app: FastAPI):
|
|||
await ensure_public_query_indexes(conn)
|
||||
app.state.weather_sync_stop_event = asyncio.Event()
|
||||
app.state.weather_sync_task = asyncio.create_task(
|
||||
weather_sync_loop(app.state.pool, app.state.weather_sync_stop_event)
|
||||
weather_sync_loop(
|
||||
app.state.pool,
|
||||
app.state.weather_sync_stop_event,
|
||||
on_updated=lambda: invalidate_public_api_caches(include_place_pages=True),
|
||||
)
|
||||
)
|
||||
app.state.contact_submission_tracker = {}
|
||||
initialize_public_api_caches()
|
||||
|
|
|
|||
|
|
@ -428,7 +428,12 @@ async def sync_all_weather_forecasts(pool, *, force: bool = False) -> dict[str,
|
|||
return stats
|
||||
|
||||
|
||||
async def weather_sync_loop(pool, stop_event: asyncio.Event) -> None:
|
||||
async def weather_sync_loop(
|
||||
pool,
|
||||
stop_event: asyncio.Event,
|
||||
*,
|
||||
on_updated=None,
|
||||
) -> None:
|
||||
await asyncio.sleep(WEATHER_SYNC_INITIAL_DELAY_SECONDS + random.uniform(0, 30))
|
||||
|
||||
while not stop_event.is_set():
|
||||
|
|
@ -441,6 +446,13 @@ async def weather_sync_loop(pool, stop_event: asyncio.Event) -> None:
|
|||
f"{stats['cached']} cachet, "
|
||||
f"{stats['failed']} feilet"
|
||||
)
|
||||
if stats["updated"] > 0 and on_updated is not None:
|
||||
try:
|
||||
result = on_updated()
|
||||
if asyncio.iscoroutine(result):
|
||||
await result
|
||||
except Exception as exc:
|
||||
print(f"Vær-sync: cache-invalidering feilet: {exc}")
|
||||
except Exception as exc:
|
||||
print(f"Vær-sync feilet på batch-nivå: {exc}")
|
||||
|
||||
|
|
|
|||
106
docs/kodeanalyse-2026-07-25.md
Normal file
106
docs/kodeanalyse-2026-07-25.md
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
# Kodeanalyse — teeoff.no
|
||||
|
||||
**Dato:** 2026-07-25
|
||||
**Omfang:** Hele repoet (`backend/` – Python/FastAPI, `frontend/` – Next.js 16/React 19, `migrations/`, `schema.sql`/`init.sql`, `deploy/Caddyfile`, `docker-compose*.yml`).
|
||||
**Metode:** Statisk gjennomgang av kildekoden (ingen kjøring av app eller tester, siden det ikke finnes noen testsuite å kjøre).
|
||||
|
||||
---
|
||||
|
||||
## 1. Oppsummering
|
||||
|
||||
Teeoff.no er en golf-nettside med et offentlig nettsted (baneinfo, medlemskap, greenfee, VTG/simulatorer, redaksjonelt innhold) og et admin-panel for å kuratere data som hentes inn via automatiserte scraping-jobber (Playwright + Gemini LLM). Stack: FastAPI + asyncpg + PostgreSQL/PostGIS i backend, Next.js App Router + Tailwind i frontend, Caddy som reverse proxy i produksjon.
|
||||
|
||||
Prosjektet fungerer og har et par genuint gjennomtenkte deler (SEO-laget, magic-link-autentisering, worker-kø for scraping). Hovedbildet er likevel et **enmanns-/AI-assistert prosjekt som har vokst raskt uten refaktorering underveis**: én 6500-linjers backend-fil, én 2634-linjers admin-side, ingen tester, ingen strukturert logging, og betydelig duplisert kode mellom de fem "godkjenn scrape-forslag"-admin-sidene. Ingen kritiske sikkerhetshull ble funnet (SQL-injeksjon ser ut til å være unngått konsekvent), men det er noen sikkerhetsrelaterte forbedringspunkter (rate limiting, secret-gjenbruk, avhengigheter uten versjonspinning).
|
||||
|
||||
---
|
||||
|
||||
## 2. Arkitektur
|
||||
|
||||
### Backend (`backend/`, ~12 200 linjer Python)
|
||||
|
||||
- **`main.py` er 6501 linjer** og inneholder *alt*: konfig, cache, e-post/SMTP, IndexNow-integrasjon, Google OAuth, skjemamigrering (`ensure_*`-funksjoner), Pydantic-modeller og alle 72 REST-endepunktene — i én modul, uten `APIRouter`. Det finnes ingen inndeling i auth/admin/public/media-routere.
|
||||
- Øverst i filen står en kommentar rettet mot en AI-kodeassistent: *"LOV: Aldri trunker eller slett logikk for 'effektivitet'"* — dette er sannsynligvis en medvirkende årsak til at filen har fått vokse ukontrollert i stedet for å bli delt opp.
|
||||
- Ingen ORM — alt er rå, parameteriserte `asyncpg`-spørringer. Der SQL bygges dynamisk med f-strings, er det kun kolonnenavn (fra en eksplisitt allow-list) som settes sammen, aldri brukerdata direkte.
|
||||
- **Databaseskjemaet finnes ikke ett sted.** `schema.sql` og `init.sql` er innbyrdes inkonsistente (ulik representasjon av geodata) og gjenspeiler ikke faktisk skjema. Det reelle skjemaet skapes av 21 `ensure_*`/`ALTER TABLE IF NOT EXISTS`-kall i `main.py`, som kjøres ved oppstart. `migrations/`-mappen brukes bare ad hoc (6 filer, april–mai 2026). Dette er reell skjema-drift.
|
||||
- **Scraping**: fem aktive jobbtyper (banestatus, medlemskap, greenfee, VTG, golfpakker), hver i sin egen fil, lagt i kø via `enqueue_scrape_job` og kjørt av `worker.py` (solid kø-implementasjon med heartbeat, retry og feilklassifisering). Sterk duplisering av Playwright-oppsett og Gemini-kallmønster mellom scraperne. To ulike Gemini-SDK-er brukes samtidig (`google-genai` i `scrape_status.py`, det eldre `google-generativeai` i de fire andre).
|
||||
- `scrape_nsg_3.py` og `scrape_golfamore1.3.py` importeres ingen steder — trolig utrangerte/manuelt kjørte scripts.
|
||||
- Tre overlappende scripts for å administrere admin-brukere (`create_admin.py`, `bootstrap_admin_access.py`, `update_admin.py`) uten at de eldre er fjernet.
|
||||
- `facility_contacts_export.csv` (87 KB) er committet til git og eid av `root` i filsystemet — tyder på at eksport-scriptet er kjørt automatisk/manuelt utenfor normal arbeidsflyt og sjekket inn ved en feil.
|
||||
|
||||
### Frontend (`frontend/src/`, 85 filer)
|
||||
|
||||
- App Router-strukturen er stort sett konsistent (`page.tsx` + `[slug]/page.tsx` + klientkomponent), men noen delte moduler (`FacilitySearch.tsx`, `facilityData.ts`, `seo.ts`) ligger løst i `app/`-roten i stedet for i `components/`/en egen domain-mappe.
|
||||
- **Ingen sentral, typet API-klient.** Offentlige sider gjør rå `fetch()`-kall spredt i `page.tsx`-filer (23 forekomster i 15 filer). Admin har kun `adminFetch()` (10 linjer) som eneste fellesnevner, og den gjør bare 401→login-redirect, ingen typede responser eller feilhåndtering.
|
||||
- `src/middleware.ts` sjekker kun *om* cookien `admin_session` finnes, ikke om den er gyldig — reell verifisering skjer først i backend/route-handlers.
|
||||
- To identiske revalidate-endepunkter (`app/api/admin/revalidate-public/route.ts` og `app/internal/revalidate-public/route.ts`, 92 linjer, byte-for-byte like) — rester fra en migrering som ikke ble ryddet opp.
|
||||
- **Betydelig duplisering** mellom de tre "gjennomgå scrape-forslag"-admin-sidene (`golfpakker`, `greenfee`, `medlemskap` under `admin/`) — strukturelt identisk state og logikk (`drafts`, `toggleSelectAll`, `fetchDrafts`, JSON-parse-fallback), bare med ulike feltnavn. Samme mønster er implementert en *tredje* gang inne i `admin/page.tsx`.
|
||||
- **Store "god components"**: `admin/page.tsx` (2634 linjer), `EditFacilityClient.tsx` (1615), `FacilityDetailView.tsx` (1468), `FacilitySearch.tsx` (1300), `admin/artikler/page.tsx` (1144), `SimulatorAdminClient.tsx` (1024), `admin/sider/page.tsx` (960).
|
||||
- `eslint.config.mjs` slår eksplisitt av `@typescript-eslint/no-explicit-any` m.fl. — over 100 forekomster av `any` i kildekoden, konsentrert i admin-sidene, til tross for at `tsconfig.json` har `strict: true`.
|
||||
- **Ingen tester**: ingen jest/vitest/playwright-oppsett, ingen test-script i `package.json`.
|
||||
- SEO-laget (`seo.ts`, `pageSeo.ts`, `sitemap.ts`, `robots.ts`) er det mest gjennomarbeidede i frontend — konsistent, godt strukturert, i tydelig kontrast til resten.
|
||||
- `docs/design-system.md` er et reelt, godt skrevet designdokument med fargetokens i `globals.css`, men etterlevelsen er svak: 1294 forekomster av vilkårlige `text-[#...]`/`bg-[#...]`-Tailwind-klasser, mange med hex-verdier som ikke finnes i det dokumenterte palettet — et de facto parallelt fargesett har vokst frem, i strid med dokumentets egen anbefaling.
|
||||
|
||||
### Deploy/infra
|
||||
|
||||
- `docker-compose.yml` kjører 4 tjenester: `db` (postgis/postgis:15-3.4), `api`, `worker` (samme image som `api`, men unødvendig siden begge installerer Playwright+Chromium), `frontend`.
|
||||
- Backend-`Dockerfile` er single-stage på `python:3.11-slim`, installerer full Playwright+Chromium-stack i samme image som brukes til API-serveren (som ikke trenger nettleseren) — unødvendig stort image, ingen non-root-bruker.
|
||||
- `deploy/Caddyfile` ruter `teeoff.no` og `www.teeoff.no`, men inneholder også en `teecup.teeoff.no`-blokk som proxyer til `teecup-minio`, `teecup_api` og `teecup_frontend` — tjenester som **ikke finnes i dette repoets `docker-compose.yml`**, og en kommentar som viser til "CLAUDE.md-status" i et annet prosjekt. Dette virker å være en separat, relatert applikasjon ("teecup") som deler samme server/Caddy-instans, men konfigurasjonen for den ligger ikke i dette repoet — verdt å avklare om det er tilsiktet.
|
||||
|
||||
---
|
||||
|
||||
## 3. Sikkerhet
|
||||
|
||||
| Funn | Alvorlighet | Kommentar |
|
||||
|---|---|---|
|
||||
| Ingen rate limiting/brute-force-beskyttelse på `/api/auth/login` | Middels | 2FA (TOTP) reduserer risikoen, men passordfeltet kan brute-forces ubegrenset |
|
||||
| `PUBLIC_SESSION_SECRET` faller tilbake til `JWT_SECRET`; `FRONTEND_REVALIDATE_SECRET` faller tilbake til samme igjen | Lav–middels | Sekret-gjenbruk på tvers av sikkerhetsdomener — kompromittering ett sted lekker til et annet |
|
||||
| `python-jose` (kjente CVE-er, algoritmeforvirring) og `passlib` (vedlikeholdsmodus siden ~2020) | Lav–middels | Vurder `pyjwt` + aktivt vedlikeholdt hash-bibliotek |
|
||||
| `requirements.txt` uten versjonspinning i det hele tatt | Middels | Ingen reproduserbare builds; sikkerhetsoppdateringer/brytende endringer innføres usporet |
|
||||
| Login-endepunkter tar `data: dict` i stedet for Pydantic-modeller | Lav | Ingen automatisk input-validering/typesjekk på disse rutene |
|
||||
| Dockerfile: ingen non-root-bruker, `COPY . .` inkluderer data-/CSV-filer | Lav | Standard hardening-forbedringer |
|
||||
| SQL-injeksjon | Ingen funn | Konsekvent bruk av parameteriserte queries; dynamisk SQL bygges kun fra allow-listede kolonnenavn |
|
||||
| Magic-link-innlogging | Ingen funn | Token hashes før lagring, cooldown og enumereringsbeskyttelse er korrekt implementert |
|
||||
| Path traversal i `uploads/[...path]/route.ts` | Ingen funn | Eksplisitt `startsWith(uploadsRoot)`-sjekk |
|
||||
|
||||
---
|
||||
|
||||
## 4. Teknisk gjeld / duplisering (prioritert etter antatt gevinst)
|
||||
|
||||
1. **`main.py` (6501 linjer)** bør deles i moduler/`APIRouter`-er (auth, admin, public, media, scraping-triggere). Størst risiko for feil ved videre utvikling, vanskeligst å navigere.
|
||||
2. **Tre "Washer"-admin-sider** (`golfpakker`, `greenfee`, `medlemskap`) + samme logikk *igjen* i `admin/page.tsx` → kandidat for en delt `useDraftReview`-hook/generisk komponent.
|
||||
3. **Skjema-drift**: `schema.sql`/`init.sql`/`migrations/` gjenspeiler ikke faktisk databasestruktur, som i praksis styres av `ensure_*`-funksjoner i `main.py`. Risiko for at en frisk `docker compose up` med `init.sql` gir en annen database enn produksjon.
|
||||
4. **Duplisert scraper-boilerplate** (Playwright-oppsett, Gemini-klient, feilmeldinger) — kunne vært samlet i `scrape_utils.py`.
|
||||
5. **To identiske revalidate-endepunkter** i frontend — trygt å fjerne det ene.
|
||||
6. **`admin/page.tsx` og de andre "god components"** — bør deles opp i mindre komponenter etter hvert som de vedlikeholdes.
|
||||
7. **Ubrukte/utrangerte scripts** (`scrape_nsg_3.py`, `scrape_golfamore1.3.py`, to av tre admin-scripts, `sync_greenfee.py`/`sync_weather_forecast.py` uten synlig cron) — bør enten dokumenteres som "kjør manuelt ved behov" eller fjernes.
|
||||
8. **To parallelle Gemini-SDK-er** (`google-genai` og `google-generativeai`) installert samtidig — bør konsolideres til én.
|
||||
|
||||
---
|
||||
|
||||
## 5. Kodekvalitet generelt
|
||||
|
||||
- **Ingen testdekning** i noen del av kodebasen (verken backend eller frontend). `test_login.py`/`test_gemini.py` er manuelle debug-scripts, ikke en pytest-suite.
|
||||
- **Ingen strukturert logging** i backend — kun spredte `print()`-kall med emoji-prefiks; ingen loggnivåer, avhengig av at Docker fanger stdout.
|
||||
- Flere stumme `except:`-klausuler som svelger alle feil (`sync_greenfee.py`, `scrape_nsg_3.py`, `import_wp.py`).
|
||||
- Frontend: `strict: true` i TypeScript undergraves av at ESLint sin `no-explicit-any` er slått av — over 100 `any`-treff.
|
||||
- Kommentarer i stil "REGEL 1: ALDRI trunker eller fjern data", "RETTING: Flyttet NextRequest for å fikse build-error" tyder på at store deler av koden er skrevet iterativt med en AI-assistent uten etterfølgende opprydning — nyttig å vite når man vurderer hvor mye av koden som er bevisst arkitektur vs. ad hoc-lapping.
|
||||
|
||||
---
|
||||
|
||||
## 6. Det som fungerer bra
|
||||
|
||||
- **Magic-link- og OAuth-autentisering** er korrekt implementert med enumereringsbeskyttelse og token-hashing.
|
||||
- **`worker.py`** er en solid, godt strukturert jobbkø med heartbeat, backoff og feilklassifisering.
|
||||
- **SEO-laget** i frontend (JSON-LD, sitemap, robots, OG-bilder) er gjennomtenkt og konsistent.
|
||||
- **SQL-sikkerhet**: bevisst og konsekvent bruk av parameteriserte queries selv uten ORM.
|
||||
- **Design-system-dokumentet** (`docs/design-system.md`) er et godt utgangspunkt — problemet er etterlevelse, ikke dokumentet selv.
|
||||
|
||||
---
|
||||
|
||||
## 7. Anbefalte neste steg (prioritert)
|
||||
|
||||
Se `docs/oppgaver.md` for en konkret, sporbar oppgaveliste basert på funnene over. De tre høyest prioriterte tiltakene er:
|
||||
|
||||
1. Etabler én autoritativ kilde for databaseskjemaet (enten gå all-in på migrations-mappen, eller generer `schema.sql` fra faktisk produksjonsskjema) — reduserer risiko for miljøforskjeller.
|
||||
2. Del opp `main.py` i routere per domene — gjør videre arbeid tryggere og raskere.
|
||||
3. Slå sammen de tre "Washer"-admin-sidene til én gjenbrukbar komponent/hook — reduserer trippel vedlikeholdsbyrde ved neste endring i godkjenningsflyten.
|
||||
74
docs/oppgaver.md
Normal file
74
docs/oppgaver.md
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
# Oppgaver — teeoff.no
|
||||
|
||||
Løpende referanse for arbeid på prosjektet. Bakgrunn og detaljerte funn står i [`kodeanalyse-2026-07-25.md`](./kodeanalyse-2026-07-25.md).
|
||||
|
||||
**Slik brukes filen:**
|
||||
- Nye oppgaver legges under riktig seksjon med dato i parentes.
|
||||
- Når noe er avklart og klart til å jobbes med, flytt det fra "Trenger avklaring" til "Klar til å gjøres".
|
||||
- Når noe er fullført, flytt det til "Gjort" nederst med dato og ev. commit-referanse.
|
||||
|
||||
---
|
||||
|
||||
## 🔴 Sikkerhet (klar til å gjøres)
|
||||
|
||||
- [ ] Legg til rate limiting/lockout på `/api/auth/login` (backend/main.py) — ingen brute-force-beskyttelse på passordfeltet i dag utover 2FA. (2026-07-25)
|
||||
- [ ] Fjern secret-fallback-kjeden: `PUBLIC_SESSION_SECRET` → `JWT_SECRET` → `FRONTEND_REVALIDATE_SECRET`. Gi hver sitt eget, obligatoriske secret. (2026-07-25)
|
||||
- [ ] Lås versjoner i `backend/requirements.txt` (i dag helt uten pinning). (2026-07-25)
|
||||
- [ ] Erstatt `python-jose` (kjente CVE-er) og vurder om `passlib` (vedlikeholdsmodus) bør byttes ut. (2026-07-25)
|
||||
- [ ] Fjern `facility_contacts_export.csv` fra git (committet ved en feil, eid av root i filsystemet) og legg til i `.gitignore`. (2026-07-25)
|
||||
|
||||
## 🧹 Teknisk gjeld / refaktorering (klar til å gjøres)
|
||||
|
||||
- [ ] Del `backend/main.py` (6501 linjer) opp i `APIRouter`-moduler: auth, admin, public/facilities, media, scraping-triggere. (2026-07-25)
|
||||
- [ ] Slå sammen `admin/golfpakker`, `admin/greenfee`, `admin/medlemskap` (frontend) til én gjenbrukbar `useDraftReview`-hook/komponent — i dag nesten identisk kode tre (fire, inkl. `admin/page.tsx`) steder. (2026-07-25)
|
||||
- [ ] Fjern ett av de to identiske revalidate-endepunktene: `frontend/src/app/api/admin/revalidate-public/route.ts` vs. `frontend/src/app/internal/revalidate-public/route.ts`. (2026-07-25)
|
||||
- [ ] Samle duplisert Playwright-/Gemini-boilerplate fra scraperne (`scrape_golfpakker.py`, `scrape_greenfee.py`, `scrape_membership.py`, `scrape_vtg.py`, `scrape_status.py`) i `scrape_utils.py`. (2026-07-25)
|
||||
- [ ] Konsolider til én Gemini-SDK — i dag installeres både `google-genai` og `google-generativeai`, brukt inkonsekvent på tvers av scraperne. (2026-07-25)
|
||||
- [ ] Splitt opp de største "god components" i frontend etter hvert som de vedlikeholdes: `admin/page.tsx` (2634 linjer), `EditFacilityClient.tsx` (1615), `FacilityDetailView.tsx` (1468), `FacilitySearch.tsx` (1300), `admin/artikler/page.tsx` (1144), `SimulatorAdminClient.tsx` (1024). (2026-07-25)
|
||||
- [ ] Flytt delte moduler ut av `app/`-roten til `components/`/en domain-mappe: `FacilitySearch.tsx`, `facilityData.ts`, `seo.ts`. (2026-07-25)
|
||||
- [ ] Bygg egen, mindre Docker-image for `api`-tjenesten uten Playwright/Chromium (kun `worker` trenger nettleseren). (2026-07-25)
|
||||
|
||||
## ✅ Kvalitet / prosess (klar til å gjøres, lavere prioritet)
|
||||
|
||||
- [ ] Innfør strukturert logging i backend i stedet for spredte `print()`-kall. (2026-07-25)
|
||||
- [ ] Erstatt stumme `except:`-klausuler (`sync_greenfee.py`, `scrape_nsg_3.py`, `import_wp.py`) med eksplisitt feilhåndtering/logging. (2026-07-25)
|
||||
- [ ] Skriv et minimum av automatiserte tester (backend: pytest for auth-flyt og kritiske endepunkter; frontend: i det minste ett rammeverk satt opp) — i dag null testdekning. (2026-07-25)
|
||||
- [ ] Rydd opp i vilkårlige `text-[#...]`/`bg-[#...]`-hex-klasser (1294 forekomster) til fordel for tokens fra `docs/design-system.md`/`globals.css`. (2026-07-25)
|
||||
- [ ] Vurder å slå på `@typescript-eslint/no-explicit-any` igjen og rydde de 100+ eksisterende `any`-bruken gradvis. (2026-07-25)
|
||||
|
||||
---
|
||||
|
||||
## 🌍 Internasjonal ekspansjon (planlagt, faset) (2026-07-25)
|
||||
|
||||
Bakgrunn og vurderte alternativer i [`kodeanalyse-2026-07-25.md`](./kodeanalyse-2026-07-25.md)-samtalen. Retningsvalg tatt 2026-07-25: full flerspråklig side (ikke bare DB-oversettelse av innhold), og normalisert `hole_points`-modell for hullkoordinater.
|
||||
|
||||
**Fase 1 — Databaselag**
|
||||
- [ ] Migrasjon: legg til `country_code` (ISO 3166-1 alpha-2) på anleggstabellen, backfill `'NO'` på alle eksisterende rader, gjør feltet obligatorisk uten default fremover.
|
||||
- [ ] Avklar region/fylke-modell for ikke-norske anlegg (dagens `REGIONS`/fylkesfilter er Norge-spesifikt).
|
||||
- [ ] Migrasjon: `*_translations`-tabeller for redaksjonelt innhold (f.eks. `facility_translations(facility_id, locale, description, ...)`, tilsvarende for artikler), én rad per locale, med fallback til `'no'`.
|
||||
- [ ] Migrasjon: `course_holes(id, facility_id, hole_number, par, ...)` + `hole_points(id, hole_id, point_type, geom geography(Point,4326))` med `point_type` som enum (`green_front`, `green_center`, `green_back`, utvidbart til teested/hasard senere).
|
||||
- [ ] Bygg admin-verktøy for å registrere hull-punkter (trolig kart-klikk via Leaflet, siden `FacilityDetailLeafletMap.tsx` allerede finnes).
|
||||
|
||||
**Fase 2 — UI-i18n-infrastruktur**
|
||||
- [ ] Velg og sett opp i18n-rammeverk for frontend (f.eks. `next-intl`) med locale-prefikset routing.
|
||||
- [ ] **Avklar**: skal URL-slugs oversettes per språk (`/en/golf-courses`) eller beholdes norske på alle språk (`/en/golfbaner`)? Må avgjøres før routing-arbeidet starter.
|
||||
- [ ] Oppdater SEO-laget (`seo.ts`, `sitemap.ts`, `robots.ts`) med hreflang og per-locale sitemap/robots.
|
||||
- [ ] Bygg språkvelger i UI.
|
||||
|
||||
**Fase 3 — Oversettelse av faktisk innhold/UI**
|
||||
- [ ] Trekk ut hardkodet norsk UI-tekst til meldingskataloger.
|
||||
- [ ] Oversett/legg inn innhold for de første ikke-norske anleggene.
|
||||
|
||||
## ❓ Trenger avklaring
|
||||
|
||||
- [ ] **`teecup`-blokken i `deploy/Caddyfile`** ruter til `teecup-minio`, `teecup_api`, `teecup_frontend` — tjenester som ikke finnes i dette repoets `docker-compose.yml`, og viser til "CLAUDE.md-status" i tilsynelatende et annet prosjekt. Er "teecup" en separat applikasjon som deler server med teeoff? Bør det dokumenteres her, eller hører Caddyfile-blokken ikke hjemme i dette repoet? (2026-07-25)
|
||||
- [ ] **Skjema-kilde**: Skal `schema.sql`/`init.sql` oppdateres til å reflektere faktisk skjema (slik det skapes av `ensure_*`-funksjonene i `main.py`), eller skal man gå motsatt vei og fjerne `ensure_*`-migreringen til fordel for filene i `migrations/`? Trenger en beslutning om hvilken tilnærming som er "sannheten" videre. (2026-07-25)
|
||||
- [ ] **Ubrukte scripts**: `scrape_nsg_3.py`, `scrape_golfamore1.3.py`, `sync_greenfee.py`, `sync_weather_forecast.py` importeres ikke av verken `main.py` eller `worker.py`. Kjøres disse fortsatt manuelt/via ekstern cron på serveren? Skal de beholdes, dokumenteres som manuelle verktøy, eller slettes? (2026-07-25)
|
||||
- [ ] **Admin-brukerscripts**: `create_admin.py`, `bootstrap_admin_access.py`, `update_admin.py` overlapper — sistnevntes docstring hevder å være en "trygg erstatning" for de to andre. Kan de to eldre slettes, eller er det en grunn til at alle tre fortsatt finnes? (2026-07-25)
|
||||
- [ ] **Dashboard vs. dedikerte admin-sider**: `admin/page.tsx` inneholder faner som gjør samme jobb som de dedikerte `admin/golfpakker`, `admin/greenfee`, `admin/medlemskap`, `admin/vtg`-sidene. Er begge i aktiv bruk, eller kan én av de to variantene fases ut? Påvirker hvilken retning refaktoreringen i "Washer"-oppgaven over bør ta. (2026-07-25)
|
||||
|
||||
---
|
||||
|
||||
## Gjort
|
||||
|
||||
- [x] **"Ikke regn meldt"-filteret viste stale værdata** — `weather_sync_loop` (backend/weather_forecast.py) oppdaterte `facility_weather_forecast` hver time, men kalte aldri `invalidate_public_api_caches()`, så frontendens `unstable_cache` (uten tidsbasert utløp) og backendens interne cache ble aldri fornyet av værsynken — kun av urelaterte admin-redigeringer. Løst ved å legge til en `on_updated`-callback i `weather_sync_loop` som trigger `invalidate_public_api_caches(include_place_pages=True)` når noe faktisk er oppdatert. `api`-containeren restartet. (2026-07-26)
|
||||
Loading…
Reference in a new issue