Nettside for javaBin Kids — kode-arrangementer for barn i regi av javaBin, i forbindelse med JavaZone-konferansen.
Nettsiden presenterer arrangementer, håndterer påmelding med venteliste, og har et admin-panel for arrangørene.
| Lag | Teknologi |
|---|---|
| Frontend | SvelteKit 2 + Svelte 5 (runes) |
| Backend | SvelteKit server routes (+page.server.ts, +server.ts) |
| Database | PostgreSQL 17 via Drizzle ORM |
| Validering | Zod |
| E-post | SendGrid |
| Markdown | marked (kursbeskrivelser) |
| Auth | Cookie-basert sesjoner med bcrypt |
| Infra | Docker Compose (dev + backup) |
- Docker og Docker Compose
- Node.js 22+ (for lokal utvikling uten Docker)
# Start alt
docker compose up -d
# Kjør migrering og seed
docker compose exec app npx drizzle-kit migrate
docker compose exec app npx tsx seed.ts
# Appen kjører på http://localhost:5175# Forutsetter en kjørende PostgreSQL-instans
cp .env.example .env
# Rediger .env med din DATABASE_URL
npm install
npm run db:migrate
npm run db:seed
npm run devSe .env.example:
| Variabel | Beskrivelse |
|---|---|
DATABASE_URL |
PostgreSQL connection string |
SENDGRID_API_KEY |
API-nøkkel fra sendgrid.com |
ADMIN_USERNAME |
Brukernavn for admin (brukes av seed) |
ADMIN_PASSWORD |
Passord for admin (brukes av seed) |
BASE_URL |
Offentlig URL for e-postlenker |
EMAIL_SUBJECT_PREFIX |
(Valgfritt) Prefix som legges til alle e-post-emnelinjer. Nyttig for å skille test-e-poster fra produksjon, f.eks. [DEV] . |
src/
├── lib/
│ ├── server/
│ │ ├── db/
│ │ │ ├── schema.ts # Drizzle-skjema (alle tabeller)
│ │ │ └── index.ts # DB-tilkobling
│ │ ├── ... # Komponenter og utils for server-side (validering, auth, e-post)
│ ├── components/
│ │ ├── ... # Frontend komponenter
│ └── toast.svelte.ts # Toast state management
├── routes/
│ ├── ... # Offentlige sider
│ ├── admin/ # Admin-panel (auth-beskyttet)
│ └── api/ # API-ruter levert av SvelteKit
└── app.css # Globale stiler (undervanns-tema)
Drizzle-skjemaet ligger i src/lib/server/db/schema.ts. Alle timestamp-kolonner lagres uten tidssone, alle UUID-kolonner er gen_random_uuid()-defaulted der ikke annet er spesifisert.
Arrangementer — én rad per javaBin Kids-dag (f.eks. "javaBin Kids Vår 2026"). Kurs og forslag henger under et arrangement.
| Kolonne | Type | Beskrivelse |
|---|---|---|
arrangementId |
uuid, PK | Unik ID |
title |
text | Visningstittel |
description |
text | Markdown-beskrivelse |
date |
timestamp | Dato for arrangementet |
location |
text | Sted/adresse |
cancelled |
boolean | Om arrangementet er avlyst |
registrationOpens |
timestamp | Start for påmelding |
registrationCloses |
timestamp | Slutt for påmelding |
imageUrl |
text, nullable | URL til forsidebilde (fra images-tabellen eller ekstern) |
openForSubmissions |
boolean | Om åpent for innsending av kursforslag |
submissionDeadline |
timestamp, nullable | Frist for innsending av forslag |
createdAt, updatedAt |
timestamp | Auto |
Kurs som arrangeres innenfor et gitt arrangement. Hvert kurs har egen alders- og kapasitetsgrense.
| Kolonne | Type | Beskrivelse |
|---|---|---|
courseId |
uuid, PK | Unik ID |
arrangementId |
uuid, FK → events |
Hvilket arrangement kurset tilhører (ON DELETE RESTRICT) |
title |
text | Kurstittel |
introduction |
text | Kort introduksjonstekst |
description |
text | Fullstendig Markdown-beskrivelse |
thumbnailUrl |
text, nullable | URL til thumbnail |
ageMin, ageMax |
integer | Aldersgruppe (inkluderende) |
maxParticipants |
integer | Maks antall påmeldte før venteliste |
createdAt, updatedAt |
timestamp | Auto |
Kursforslag sendt inn av eksterne foredragsholdere via forslagsskjemaet. Går gjennom admin-vurdering (submitted → approved/rejected).
| Kolonne | Type | Beskrivelse |
|---|---|---|
submissionId |
uuid, PK | Unik ID |
arrangementId |
uuid, FK → events |
Hvilket arrangement forslaget er sendt til |
status |
text | submitted, approved eller rejected |
title |
text | Kurstittel |
description |
text | Markdown-beskrivelse |
equipmentRequirements |
text, nullable | Utstyrsbehov |
ageMin, ageMax |
integer | Foreslått aldersgruppe |
maxParticipants |
integer | Foreslått maks antall |
speakerName |
text | Foredragsholders navn |
speakerEmail |
text | E-post (brukes til mottatt/godkjent/avvist-mail) |
speakerBio |
text | Kort bio |
editToken |
uuid | Token i redigeringslenke, lar foredragsholder endre forslaget uten innlogging |
createdAt, updatedAt |
timestamp | Auto |
Påmeldinger fra foreldre til et spesifikt kurs. Unik på (courseId, parentEmail, childName) for å hindre duplikater.
| Kolonne | Type | Beskrivelse |
|---|---|---|
registrationId |
uuid, PK | Unik ID |
courseId |
uuid, FK → courses |
Kurset barnet meldes på |
parentName |
text | Forelders navn |
parentEmail |
text | E-post (brukes til bekreftelse/påminnelse) |
parentPhone |
text | Telefonnummer |
childName |
text | Barnets navn |
childAge |
integer | Barnets alder |
status |
text | confirmed, waitlisted eller cancelled |
waitlistPosition |
integer, nullable | Posisjon på venteliste (1 = først i køen); null for confirmed/cancelled |
consentGiven |
boolean | Om samtykke til databehandling er gitt |
cancellationToken |
uuid | Token i bekreftelse/avmeldings-lenke — gjør at forelder kan avmelde uten innlogging |
createdAt, updatedAt |
timestamp | Auto |
Admin-brukere som kan logge inn i admin-panelet. Opprettes via npm run db:seed.
| Kolonne | Type | Beskrivelse |
|---|---|---|
adminUserId |
uuid, PK | Unik ID |
username |
text, unique | Brukernavn |
passwordHash |
text | bcrypt-hash av passord |
createdAt |
timestamp | Auto |
Aktive admin-sesjoner. Sesjons-ID-en lagres som HTTP-cookie og valideres på hver admin-request.
| Kolonne | Type | Beskrivelse |
|---|---|---|
sessionId |
uuid, PK | ID som lagres i cookien |
adminUserId |
uuid, FK → adminUsers |
Hvilken bruker sesjonen tilhører |
expiresAt |
timestamp | Utløp (typisk 24 timer etter innlogging) |
createdAt |
timestamp | Auto |
Nøkkel/verdi-tekst som admin kan redigere fra /admin/innhold. Brukes for forside-hero, om-siden osv.
| Kolonne | Type | Beskrivelse |
|---|---|---|
key |
text, PK | Identifikator, f.eks. hero_title, om_content |
content |
text | Fritekst/Markdown |
updatedAt |
timestamp | Auto |
Binær-lagring av opplastede bilder (thumbnails, forsidebilder). Serveres via /api/images/{imageId} som bruker mimeType som Content-Type.
| Kolonne | Type | Beskrivelse |
|---|---|---|
imageId |
uuid, PK | Unik ID (del av serve-URL) |
filename |
text | Opprinnelig filnavn |
mimeType |
text | image/jpeg, image/png, osv. |
data |
bytea | Rå bilde-bytes |
createdAt |
timestamp | Auto |
Redigerbare e-postmaler for de åtte transaksjonelle e-postene (bekreftelse, venteliste, forslag-godkjent, osv.). Hvis en rad mangler eller et felt er tomt, faller email.ts tilbake på hardkodet default. Tekstfelter kan inneholde {{variabel}}-plassholdere og **fet tekst**.
| Kolonne | Type | Beskrivelse |
|---|---|---|
templateKey |
text, PK | Én av confirmation, waitlist, promotion, cancellation, reminder, submissionReceived, submissionApproved, submissionRejected |
subject |
text | Emnelinje |
heading |
text | Overskrift i e-post-kroppen |
introText |
text | Paragrafer før info-bokser (tom linje = ny paragraf) |
outroText |
text | Paragrafer etter knapp/info-bokser |
buttonText |
text, nullable | CTA-knappens tekst (kun for maler med knapp) |
updatedAt |
timestamp | Auto |
Kontaktkort som vises på kontaktsiden, administreres fra /admin/innhold.
| Kolonne | Type | Beskrivelse |
|---|---|---|
contactCardId |
uuid, PK | Unik ID |
title |
text | Kort-tittel |
actionType |
text | email, link eller phone |
actionValue |
text | E-post, URL eller telefonnummer |
sortOrder |
integer | Visningsrekkefølge (stigende) |
createdAt, updatedAt |
timestamp | Auto |
Drizzle Kit håndterer migreringer. SQL-filer ligger i drizzle/migrations/.
# Generer ny migrering etter skjemaendring
npm run db:generate
# Kjør migreringer
npm run db:migrateseed.ts oppretter en admin-bruker og et eksempel-arrangement med kurs:
npm run db:seed- Bruker velger kurs og fyller ut skjema
- Server validerer (Zod), sjekker duplikat, sjekker kapasitet
- Plassering skjer i en DB-transaksjon med
SELECT ... FOR UPDATE(row-level locking) - Hvis plass:
confirmed, ellers:waitlistedmed posisjon - E-postbekreftelse sendes (med kanselleringslenke)
- Ved kansellering rykker neste person på ventelisten opp automatisk
- Alle gjenværende ventelisteposisjoner dekrementeres
- Den opprykkte personen får e-post
- Cookie-basert sesjoner lagret i
sessions-tabellen - Sesjoner utløper etter 24 timer
+layout.server.tsunder/adminsjekker sesjon og redirecter til login- Alle admin API-ruter validerer sesjon
- Drizzle ORM gir type-safe database-tilgang
- SvelteKit genererer
PageData-typer fra+page.server.tsload-funksjoner - Alle sider importerer
PageDatafra./$types - Zod validerer all bruker-input på serveren
Inspirert av JavaZone 2026 sitt undervanns-tema:
- Mørk blågrønn gradient bakgrunn
- Turkise overskrifter, gylne aksenter
- Animerte bobler med interaktiv fiskefigur (poengteller i localStorage)
- Responsivt, mobil-først design
npx vitest runTester dekker:
- Zod-validering (
validation.test.ts) - Rate limiter (
rateLimit.test.ts) - Påmeldingslogikk (
registration.test.ts)
Backup-containeren kjører automatisk som en del av Docker Compose.
- Kjører
pg_dump | gzipdaglig kl. 03:00 - Lagres til Docker-volumet
backups - Backups eldre enn 14 dager slettes automatisk (konfigurerbart via
BACKUP_KEEP_DAYS)
docker compose exec backup bash -c \
'pg_dump | gzip > /backups/javabinkids-manual-$(date +%Y%m%d-%H%M%S).sql.gz'docker compose exec backup ls -lh /backups/# Finn ønsket backup
docker compose exec backup ls -lh /backups/
# Restore (erstatter all data i databasen)
docker compose exec backup bash -c \
'gunzip -c /backups/javabinkids-XXXXXXXX-XXXXXX.sql.gz | psql'docker compose logs backupProsjektet inkluderer en Dockerfile for produksjonsbygg med multi-stage build:
docker build -t javabinkids .
docker run -p 3000:3000 \
-e DATABASE_URL=postgres://... \
-e SENDGRID_API_KEY=SG.... \
-e BASE_URL=https://kids.javabin.no \
javabinkids| Script | Beskrivelse |
|---|---|
npm run dev |
Start dev-server |
npm run build |
Produksjonsbygg |
npm run preview |
Forhåndsvis produksjonsbygg |
npm run check |
TypeScript type-checking |
npm run db:generate |
Generer Drizzle-migrering |
npm run db:migrate |
Kjør migreringer |
npm run db:seed |
Seed database |