ADR-018 er nå helt ferdig — backend, begge frontend-skjermer, og nå Open Graph-metadata, alt live samme dag. Oppsummert:
Én reell driftsfeil funnet før den nådde produksjon: generateMetadata() kjører server-side ved forespørselstid — går derfor ikke gjennom next.config.mjs sin rewrites() (som bare gjelder nettleser-trafikk). Den trengte TEECUP_API_ORIGIN direkte, men den variabelen fantes kun i Dockerfile sitt build-steg — ENV fra ett steg arves ikke til det neste i en multi-stage build. Rettet ved å sette den på nytt i runner-steget. Verifisert presist, ikke bare at det kompilerte: bygget det ekte produksjonsimaget (ikke dev-server), pekte det mot en scratch-backend med ekte data, og hentet den faktiske server-rendrede HTML-en — bekreftet ekte og:title/og:description, og en trygg fallback-tittel for en ukjent turnering. teeoff.no upåvirket gjennom hele runden.
This commit is contained in:
parent
8445b137ec
commit
6d84aafef8
7 changed files with 118 additions and 9 deletions
|
|
@ -203,7 +203,9 @@
|
||||||
"Bash(curl -s -o /dev/null -w '%{http_code}\\\\n' https://teecup.teeoff.no/t/00000000-0000-0000-0000-000000000000)",
|
"Bash(curl -s -o /dev/null -w '%{http_code}\\\\n' https://teecup.teeoff.no/t/00000000-0000-0000-0000-000000000000)",
|
||||||
"Bash(python3 -m zipfile -l \"tee-cup-login-screen \\(4\\).zip\")",
|
"Bash(python3 -m zipfile -l \"tee-cup-login-screen \\(4\\).zip\")",
|
||||||
"Bash(rm -f /opt/teecup/.env.scratch /tmp/cowner.txt)",
|
"Bash(rm -f /opt/teecup/.env.scratch /tmp/cowner.txt)",
|
||||||
"Bash(curl -s -o /dev/null -w '/clubs/ukjent: %{http_code}\\\\n' https://teecup.teeoff.no/clubs/ukjent-slug-garantert)"
|
"Bash(curl -s -o /dev/null -w '/clubs/ukjent: %{http_code}\\\\n' https://teecup.teeoff.no/clubs/ukjent-slug-garantert)",
|
||||||
|
"Bash(rm -f /opt/teecup/.env.scratch /tmp/ogowner.txt)",
|
||||||
|
"Bash(curl -s https://teecup.teeoff.no/t/00000000-0000-0000-0000-000000000000)"
|
||||||
],
|
],
|
||||||
"additionalDirectories": [
|
"additionalDirectories": [
|
||||||
"/opt/teeoff/deploy",
|
"/opt/teeoff/deploy",
|
||||||
|
|
|
||||||
30
CLAUDE.md
30
CLAUDE.md
|
|
@ -580,12 +580,34 @@ Ferdig og verifisert:
|
||||||
**ADR-018s planlagte skjermer er dermed komplette.** Gjenstår: Open
|
**ADR-018s planlagte skjermer er dermed komplette.** Gjenstår: Open
|
||||||
Graph-metadata for deling, MinIO/bilder — begge bevisst egne, senere
|
Graph-metadata for deling, MinIO/bilder — begge bevisst egne, senere
|
||||||
runder.
|
runder.
|
||||||
|
- **Open Graph-metadata LIVE (2026-07-18), samme dag — ADR-018 dermed
|
||||||
|
helt ferdig:** `generateMetadata()` lagt til på `/t/[id]` og
|
||||||
|
`/clubs/[slug]` (ekte tittel/beskrivelse fra API-et, `og:site_name`,
|
||||||
|
trygg fallback-tittel ved ukjent id/slug — feil her skal ALDRI hindre
|
||||||
|
selve siden i å laste).
|
||||||
|
**Reell driftsfeil funnet FØR den nådde produksjon, ikke etter:**
|
||||||
|
`generateMetadata()` kjører server-side ved REQUEST-tid, ikke i
|
||||||
|
nettleseren — går derfor IKKE gjennom `next.config.mjs` sin
|
||||||
|
`rewrites()` (som kun gjelder nettleser-trafikk inn til Next.js-
|
||||||
|
serveren). Måtte derfor lese `TEECUP_API_ORIGIN` direkte, men den
|
||||||
|
variabelen fantes KUN i `Dockerfile` sitt builder-steg — `ENV` satt i
|
||||||
|
ett `FROM`-steg arves ikke til et senere. Rettet ved å sette samme
|
||||||
|
`ARG`/`ENV` på nytt i runner-steget også.
|
||||||
|
**Verifisert presist at fiksen faktisk virker, ikke bare at bygget gikk
|
||||||
|
gjennom:** bygget det EKTE produksjonsimaget (ikke dev-server) pekt mot
|
||||||
|
en scratch-backend med en ekte offentlig turnering+org, hentet den
|
||||||
|
faktiske server-rendrede HTML-en og bekreftet ekte `<title>`/`og:title`/
|
||||||
|
`og:description` — ikke bare at TypeScript kompilerte. Ukjent
|
||||||
|
turnering-id ga korrekt trygg fallback-tittel.
|
||||||
|
**Bevisst utenfor omfang:** `og:image` — ingen ekte bilde finnes ennå
|
||||||
|
(MinIO-runden). La til `metadataBase` i `app/layout.tsx` nå likevel, som
|
||||||
|
forarbeid slik at et fremtidig relativt bilde-URL løses riktig uten en
|
||||||
|
egen fiks da.
|
||||||
|
**Rullet ut live**, kun `teecup_frontend`, `teeoff.no` upåvirket.
|
||||||
|
|
||||||
Neste steg:
|
Neste steg:
|
||||||
1. Open Graph-metadata (og:title/og:image/og:description) på
|
1. MinIO/bildeopplasting (hero-bilder, sponsorlogoer, og:image) — siste
|
||||||
`/t/[id]`/`/clubs/[slug]` for delingsforhåndsvisning, deretter
|
gjenstående punkt fra ADR-018, egen runde.
|
||||||
MinIO/bildeopplasting som egen runde — de to gjenværende punktene fra
|
|
||||||
ADR-018.
|
|
||||||
2. Flere V0-skjermer (økt/program, blind draw, scorekort, leaderboard) —
|
2. Flere V0-skjermer (økt/program, blind draw, scorekort, leaderboard) —
|
||||||
samme mønster: design i V0 (fortsett i samme prosjekt), FORVENT en full
|
samme mønster: design i V0 (fortsett i samme prosjekt), FORVENT en full
|
||||||
re-eksport hver gang — diff mot live-treet i et scratch-område før noe
|
re-eksport hver gang — diff mot live-treet i et scratch-område før noe
|
||||||
|
|
|
||||||
|
|
@ -405,11 +405,11 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Landingssider (turnering + organisasjon) — ADR-018 ✅ LIVE 2026-07-18
|
## Landingssider (turnering + organisasjon) — ADR-018 ✅ HELT FERDIG 2026-07-18
|
||||||
|
|
||||||
Reist av brukeren 2026-07-18, rett etter registrerings-ADR-en (ADR-017).
|
Reist av brukeren 2026-07-18, rett etter registrerings-ADR-en (ADR-017).
|
||||||
Backend OG begge planlagte frontend-skjermer bygget og live samme dag.
|
Backend, begge frontend-skjermer OG Open Graph-metadata bygget og live
|
||||||
Open Graph-metadata og MinIO/bilder gjenstår, bevisst egne runder.
|
samme dag. Kun MinIO/bilder (bevisst egen runde) gjenstår.
|
||||||
|
|
||||||
| Del | Status | Notat |
|
| Del | Status | Notat |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -421,7 +421,7 @@ Open Graph-metadata og MinIO/bilder gjenstår, bevisst egne runder.
|
||||||
| Lesbar URL (slug) for organisasjon | ✅ | Fantes faktisk allerede i skjemaet siden migrasjon 001 (oversett, funnet da migrasjon 009 feilet mot scratch — se CLAUDE.md-status). Kun `CHECK`-constraints lagt til i 009. |
|
| Lesbar URL (slug) for organisasjon | ✅ | Fantes faktisk allerede i skjemaet siden migrasjon 001 (oversett, funnet da migrasjon 009 feilet mot scratch — se CLAUDE.md-status). Kun `CHECK`-constraints lagt til i 009. |
|
||||||
| Organisator kan faktisk SETTE disse feltene | ✅ | Implisitt hull fylt under bygging: `PATCH /orgs/{id}/tournaments/{id}` (visibility/description/registrering), `PATCH /orgs/{id}` (slug/public_profile), full sponsor-CRUD. |
|
| Organisator kan faktisk SETTE disse feltene | ✅ | Implisitt hull fylt under bygging: `PATCH /orgs/{id}/tournaments/{id}` (visibility/description/registrering), `PATCH /orgs/{id}` (slug/public_profile), full sponsor-CRUD. |
|
||||||
| Bilder (hero, sponsorlogoer) | 📋 | Bevisst utsatt (brukerens valg). `hero_image_key`/`logo_key`-kolonner finnes allerede (inerte), men krever MinIO — ikke satt opp. Egen, senere runde. |
|
| Bilder (hero, sponsorlogoer) | 📋 | Bevisst utsatt (brukerens valg). `hero_image_key`/`logo_key`-kolonner finnes allerede (inerte), men krever MinIO — ikke satt opp. Egen, senere runde. |
|
||||||
| Del-metadata (Open Graph: og:title/og:image/og:description) | 📋 | Frontend-arbeid, ikke gjort ennå. |
|
| Del-metadata (Open Graph: og:title/og:description) | ✅ | `generateMetadata()` på `/t/[id]`+`/clubs/[slug]`, ekte data fra API-et, verifisert mot produksjonsimaget. `og:image` gjenstår (MinIO). |
|
||||||
| Blind draw-skjuling på offentlig side | ✅ | Arves automatisk via delt `_fetch_sessions()`-hjelpefunksjon (ADR-018 Beslutning E) — ikke reimplementert. |
|
| Blind draw-skjuling på offentlig side | ✅ | Arves automatisk via delt `_fetch_sessions()`-hjelpefunksjon (ADR-018 Beslutning E) — ikke reimplementert. |
|
||||||
| Antall påmeldte / ledige plasser vist åpent | ✅ | `confirmed_count` i `GET /public/tournaments/{id}`. |
|
| Antall påmeldte / ledige plasser vist åpent | ✅ | `confirmed_count` i `GET /public/tournaments/{id}`. |
|
||||||
| Frontend: turnering-landingsside + påmeldingsskjema, LIVE | ✅ | `/t/[id]`. Tre bekreftelsestilstander (bekreftet/venteliste/godkjenning venter). Verifisert med ekte `POST`-registrering mot scratch. |
|
| Frontend: turnering-landingsside + påmeldingsskjema, LIVE | ✅ | `/t/[id]`. Tre bekreftelsestilstander (bekreftet/venteliste/godkjenning venter). Verifisert med ekte `POST`-registrering mot scratch. |
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,13 @@ RUN pnpm build
|
||||||
FROM node:22-slim AS runner
|
FROM node:22-slim AS runner
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
|
# ENV fra builder-steget arves IKKE til et nytt FROM -- må settes på nytt her.
|
||||||
|
# Trengs av server-side kode som kjører ved REQUEST-tid, ikke build-tid, f.eks.
|
||||||
|
# generateMetadata() sine egne fetch-kall (Open Graph-deling) -- disse går
|
||||||
|
# IKKE gjennom next.config.mjs sin rewrites() (som kun gjelder nettleser-
|
||||||
|
# trafikk inn til Next.js-serveren), så de trenger API-origin direkte.
|
||||||
|
ARG TEECUP_API_ORIGIN=http://teecup_api:8000
|
||||||
|
ENV TEECUP_API_ORIGIN=$TEECUP_API_ORIGIN
|
||||||
|
|
||||||
COPY --from=builder /app/public ./public
|
COPY --from=builder /app/public ./public
|
||||||
COPY --from=builder /app/.next/standalone ./
|
COPY --from=builder /app/.next/standalone ./
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,39 @@
|
||||||
|
import type { Metadata } from "next"
|
||||||
import { PublicClub } from "@/components/public-club"
|
import { PublicClub } from "@/components/public-club"
|
||||||
|
|
||||||
|
// Server-side -- går IKKE gjennom next.config.mjs sin rewrites() (kun for
|
||||||
|
// nettleser-trafikk inn til Next.js-serveren). Peker direkte på API-et.
|
||||||
|
const API_ORIGIN = process.env.TEECUP_API_ORIGIN || "http://localhost:8000"
|
||||||
|
|
||||||
|
type PublicOrgInfo = {
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateMetadata({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: Promise<{ slug: string }>
|
||||||
|
}): Promise<Metadata> {
|
||||||
|
const { slug } = await params
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${API_ORIGIN}/public/orgs/${slug}`, { cache: "no-store" })
|
||||||
|
if (!res.ok) {
|
||||||
|
return { title: "Klubb | TeeCup" }
|
||||||
|
}
|
||||||
|
const data: PublicOrgInfo = await res.json()
|
||||||
|
const title = `${data.name} | TeeCup`
|
||||||
|
const description = `Se ${data.name} sine turneringer på TeeCup.`
|
||||||
|
return {
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
openGraph: { title, description, siteName: "TeeCup", type: "website" },
|
||||||
|
twitter: { card: "summary", title, description },
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return { title: "TeeCup" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default async function ClubPage({
|
export default async function ClubPage({
|
||||||
params,
|
params,
|
||||||
}: {
|
}: {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,10 @@ const nunito = Nunito({
|
||||||
})
|
})
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
|
// Trengs for at relative og:image-URL-er (MinIO-runden, ikke bygget ennå)
|
||||||
|
// skal løses til en fullstendig URL -- uten denne advarer Next.js ved
|
||||||
|
// build og faller tilbake til en gjettet verdi.
|
||||||
|
metadataBase: new URL('https://teecup.teeoff.no'),
|
||||||
title: 'TeeCup — Logg inn',
|
title: 'TeeCup — Logg inn',
|
||||||
description: 'Logg inn på TeeCup, din golfturnerings-app. Passordløs innlogging med magisk lenke.',
|
description: 'Logg inn på TeeCup, din golfturnerings-app. Passordløs innlogging med magisk lenke.',
|
||||||
generator: 'v0.app',
|
generator: 'v0.app',
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,45 @@
|
||||||
|
import type { Metadata } from "next"
|
||||||
import { PublicTournament } from "@/components/public-tournament"
|
import { PublicTournament } from "@/components/public-tournament"
|
||||||
|
|
||||||
|
// Server-side -- IKKE nettleser-fetch, går derfor IKKE gjennom
|
||||||
|
// next.config.mjs sin rewrites() (kun for trafikk inn til Next.js-serveren
|
||||||
|
// fra en klient). Peker direkte på API-et.
|
||||||
|
const API_ORIGIN = process.env.TEECUP_API_ORIGIN || "http://localhost:8000"
|
||||||
|
|
||||||
|
type PublicTournamentInfo = {
|
||||||
|
name: string
|
||||||
|
organization_name: string
|
||||||
|
description: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateMetadata({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: Promise<{ id: string }>
|
||||||
|
}): Promise<Metadata> {
|
||||||
|
const { id } = await params
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${API_ORIGIN}/public/tournaments/${id}`, { cache: "no-store" })
|
||||||
|
if (!res.ok) {
|
||||||
|
return { title: "Turnering | TeeCup" }
|
||||||
|
}
|
||||||
|
const data: PublicTournamentInfo = await res.json()
|
||||||
|
const title = `${data.name} | ${data.organization_name}`
|
||||||
|
const description =
|
||||||
|
data.description?.trim() || `${data.name} arrangeres av ${data.organization_name} på TeeCup.`
|
||||||
|
return {
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
openGraph: { title, description, siteName: "TeeCup", type: "website" },
|
||||||
|
twitter: { card: "summary", title, description },
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Feil her skal ALDRI hindre selve siden i å laste -- kun forringe
|
||||||
|
// delingsforhåndsvisningen, som fortsatt får en trygg standardtittel.
|
||||||
|
return { title: "TeeCup" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default async function PublicTournamentPage({
|
export default async function PublicTournamentPage({
|
||||||
params,
|
params,
|
||||||
}: {
|
}: {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue