Skip to content

Commit fbac642

Browse files
committed
Første versjon
frem til første versjon fix: make header and footer always visible with scrollable main area style: set fish opacity to 0.8 for softer appearance feat: persist bubble pop score in localStorage fix: prevent anchor color from overriding btn text color on hover feat: add front page content editing in admin panel feat: replace age number input with styled range slider fix: align course info card with hero section and make it sticky fix: improve CourseCard layout with full-width link and better spacing feat: add toast notifications for all admin actions fix: access raw SQL result as array instead of .rows in registration API feat: add event edit form in admin panel fix: ensure footer is always visible at bottom of viewport feat: add course detail page with capacity info - New route /arrangementer/[arrangementId]/kurs/[courseId] - Shows course description, age range, capacity bar, spots left - Registration button or status message based on registration window - CourseCard now links to detail page instead of directly to registration feat: add SvelteKit dev container to Docker Compose with hot reload - Add Dockerfile.dev for development with volume-mounted source - Add app service to docker-compose.yml depending on healthy db - Expose frontend on port 5175 - Enable Vite file polling for Docker volume compatibility - Update BASE_URL to port 5175 feat: add admin panel with dashboard, event management, and registrations Implements Tasks 9 and 10: admin sidebar layout, dashboard stats page, event/course CRUD API routes with active-registration guards, registrations table with status filtering and CSV export, and admin-initiated cancellation with waitlist promotion logic. feat: add registration system with waitlist and admin authentication Implements Tasks 7 and 8: registration page with waitlist logic and row-level locking, cancellation with waitlist promotion, admin login/logout API routes with session cookies, and admin layout auth guard. Also fixes Resend client lazy-init to unblock production build. fix: use $derived for reactive values in Svelte 5 components feat: add events list and event detail pages Implements Task 6: /arrangementer index with upcoming/past sections, and dynamic /arrangementer/[arrangementId] detail page with course grid and registration-window awareness. feat: add landing page, about, and contact pages Implements Task 5: landing page with hero section and next-event preview, EventCard and CourseCard reusable components, and static Om/Kontakt pages. feat: add underwater theme, nav, footer, and bubbles components feat: add validation, auth, email, and rate limiting utilities feat: add database schema, migrations, and seed script chore: scaffold SvelteKit project with Docker Compose - SvelteKit 2 + Svelte 5 minimal TypeScript template - Configured @sveltejs/adapter-node for Node.js deployment - Added drizzle-orm, postgres, zod, bcrypt, resend dependencies - Added drizzle.config.ts for PostgreSQL schema management - Added Docker Compose for local Postgres development (postgres:17) - Added multi-stage Dockerfile for production builds - Added .env.example with required environment variables
0 parents  commit fbac642

File tree

106 files changed

+15597
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

106 files changed

+15597
-0
lines changed

.env.example

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
DATABASE_URL=postgres://javabin:javabin@localhost:5432/javabinkids
2+
RESEND_API_KEY=re_your_api_key
3+
ADMIN_USERNAME=admin
4+
ADMIN_PASSWORD=changeme
5+
BASE_URL=http://localhost:5175

.gitignore

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
node_modules
2+
3+
# Output
4+
.output
5+
.vercel
6+
.netlify
7+
.wrangler
8+
/.svelte-kit
9+
/build
10+
11+
# OS
12+
.DS_Store
13+
Thumbs.db
14+
15+
# Env
16+
.env
17+
.env.*
18+
!.env.example
19+
!.env.test
20+
21+
# Vite
22+
vite.config.js.timestamp-*
23+
vite.config.ts.timestamp-*
24+
25+
.superpowers/
26+
27+
28+
# IDEs
29+
.idea

.npmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
engine-strict=true

.vscode/extensions.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"recommendations": ["svelte.svelte-vscode"]
3+
}

CLAUDE.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# CLAUDE.md
2+
3+
## Git
4+
5+
Aldri commit til git. Utvikleren håndterer alle commits selv.
6+
7+
## Database
8+
9+
Drizzle-migreringsfiler skal ha oppsummerende navn som beskriver hva migreringen gjør, f.eks. `0003_rename_tables_to_camelcase.sql`, ikke auto-genererte navn som `0001_windy_mattie_franklin.sql`.

Dockerfile

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
FROM node:22-alpine AS build
2+
WORKDIR /app
3+
COPY package*.json ./
4+
RUN npm ci
5+
COPY . .
6+
RUN npm run build
7+
8+
FROM node:22-alpine
9+
WORKDIR /app
10+
COPY --from=build /app/build ./build
11+
COPY --from=build /app/package*.json ./
12+
COPY --from=build /app/node_modules ./node_modules
13+
EXPOSE 3000
14+
ENV NODE_ENV=production
15+
CMD ["node", "build"]

Dockerfile.dev

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
FROM node:22-alpine
2+
WORKDIR /app
3+
COPY package*.json ./
4+
RUN npm ci
5+
EXPOSE 5175
6+
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0", "--port", "5175"]

README.md

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
# javaBin Kids
2+
3+
Nettside for javaBin Kids — kode-arrangementer for barn i regi av [javaBin](https://java.no), i forbindelse med [JavaZone](https://javazone.no)-konferansen.
4+
5+
Nettsiden presenterer arrangementer, håndterer påmelding med venteliste, og har et admin-panel for arrangørene.
6+
7+
## Tech stack
8+
9+
| Lag | Teknologi |
10+
|-----|-----------|
11+
| Frontend | [SvelteKit 2](https://svelte.dev/docs/kit) + [Svelte 5](https://svelte.dev) (runes) |
12+
| Backend | SvelteKit server routes (`+page.server.ts`, `+server.ts`) |
13+
| Database | PostgreSQL 17 via [Drizzle ORM](https://orm.drizzle.team) |
14+
| Validering | [Zod](https://zod.dev) |
15+
| E-post | [Resend](https://resend.com) |
16+
| Markdown | [marked](https://marked.js.org) (kursbeskrivelser) |
17+
| Auth | Cookie-basert sesjoner med bcrypt |
18+
| Infra | Docker Compose (dev + backup) |
19+
20+
## Kom i gang
21+
22+
### Forutsetninger
23+
24+
- [Docker](https://docker.com) og Docker Compose
25+
- Node.js 22+ (for lokal utvikling uten Docker)
26+
27+
### Med Docker (anbefalt)
28+
29+
```bash
30+
# Start alt
31+
docker compose up -d
32+
33+
# Kjør migrering og seed
34+
docker compose exec app npx drizzle-kit migrate
35+
docker compose exec app npx tsx seed.ts
36+
37+
# Appen kjører på http://localhost:5175
38+
```
39+
40+
### Uten Docker
41+
42+
```bash
43+
# Forutsetter en kjørende PostgreSQL-instans
44+
cp .env.example .env
45+
# Rediger .env med din DATABASE_URL
46+
47+
npm install
48+
npm run db:migrate
49+
npm run db:seed
50+
npm run dev
51+
```
52+
53+
### Miljøvariabler
54+
55+
Se `.env.example`:
56+
57+
| Variabel | Beskrivelse |
58+
|----------|-------------|
59+
| `DATABASE_URL` | PostgreSQL connection string |
60+
| `RESEND_API_KEY` | API-nøkkel fra [resend.com](https://resend.com) |
61+
| `ADMIN_USERNAME` | Brukernavn for admin (brukes av seed) |
62+
| `ADMIN_PASSWORD` | Passord for admin (brukes av seed) |
63+
| `BASE_URL` | Offentlig URL for e-postlenker |
64+
65+
## Prosjektstruktur
66+
67+
```
68+
src/
69+
├── lib/
70+
│ ├── server/
71+
│ │ ├── db/
72+
│ │ │ ├── schema.ts # Drizzle-skjema (alle tabeller)
73+
│ │ │ └── index.ts # DB-tilkobling
74+
│ │ ├── ... # Komponenter og utils for server-side (validering, auth, e-post)
75+
│ ├── components/
76+
│ │ ├── ... # Frontend komponenter
77+
│ └── toast.svelte.ts # Toast state management
78+
├── routes/
79+
│ ├── ... # Offentlige sider
80+
│ ├── admin/ # Admin-panel (auth-beskyttet)
81+
│ └── api/ # API-ruter levert av SvelteKit
82+
└── app.css # Globale stiler (undervanns-tema)
83+
```
84+
85+
## Database
86+
87+
### Tabeller
88+
89+
| Tabell | Beskrivelse |
90+
|-----------------|-------------|
91+
| `events` | Arrangementer med dato, sted, registreringsperiode |
92+
| `courses` | Kurs innenfor et arrangement (aldersgruppe, kapasitet) |
93+
| `registrations` | Påmeldinger med status (confirmed/waitlisted/cancelled) |
94+
| `adminUsers` | Admin-brukere (opprettet via seed) |
95+
| `sessions` | Admin-sesjoner (cookie-basert) |
96+
| `siteContent` | Nøkkel/verdi-par for redigerbart sideinnhold |
97+
| `contactCards` | Kontaktkort for kontaktsiden |
98+
99+
### Migreringer
100+
101+
Drizzle Kit håndterer migreringer. SQL-filer ligger i `drizzle/migrations/`.
102+
103+
```bash
104+
# Generer ny migrering etter skjemaendring
105+
npm run db:generate
106+
107+
# Kjør migreringer
108+
npm run db:migrate
109+
```
110+
111+
### Seed
112+
113+
`seed.ts` oppretter en admin-bruker og et eksempel-arrangement med kurs:
114+
115+
```bash
116+
npm run db:seed
117+
```
118+
119+
## Viktige konsepter
120+
121+
### Påmeldingsflyt
122+
123+
1. Bruker velger kurs og fyller ut skjema
124+
2. Server validerer (Zod), sjekker duplikat, sjekker kapasitet
125+
3. Plassering skjer i en DB-transaksjon med `SELECT ... FOR UPDATE` (row-level locking)
126+
4. Hvis plass: `confirmed`, ellers: `waitlisted` med posisjon
127+
5. E-postbekreftelse sendes (med kanselleringslenke)
128+
129+
### Venteliste
130+
131+
- Ved kansellering rykker neste person på ventelisten opp automatisk
132+
- Alle gjenværende ventelisteposisjoner dekrementeres
133+
- Den opprykkte personen får e-post
134+
135+
### Admin-autentisering
136+
137+
- Cookie-basert sesjoner lagret i `sessions`-tabellen
138+
- Sesjoner utløper etter 24 timer
139+
- `+layout.server.ts` under `/admin` sjekker sesjon og redirecter til login
140+
- Alle admin API-ruter validerer sesjon
141+
142+
### Typesikkerhet
143+
144+
- Drizzle ORM gir type-safe database-tilgang
145+
- SvelteKit genererer `PageData`-typer fra `+page.server.ts` load-funksjoner
146+
- Alle sider importerer `PageData` fra `./$types`
147+
- Zod validerer all bruker-input på serveren
148+
149+
### Visuelt design
150+
151+
Inspirert av JavaZone 2026 sitt undervanns-tema:
152+
- Mørk blågrønn gradient bakgrunn
153+
- Turkise overskrifter, gylne aksenter
154+
- Animerte bobler med interaktiv fiskefigur (poengteller i localStorage)
155+
- Responsivt, mobil-først design
156+
157+
## Tester
158+
159+
```bash
160+
npx vitest run
161+
```
162+
163+
Tester dekker:
164+
- Zod-validering (`validation.test.ts`)
165+
- Rate limiter (`rateLimit.test.ts`)
166+
- Påmeldingslogikk (`registration.test.ts`)
167+
168+
## Backup
169+
170+
Backup-containeren kjører automatisk som en del av Docker Compose.
171+
172+
### Automatisk backup
173+
174+
- Kjører `pg_dump | gzip` daglig kl. 03:00
175+
- Lagres til Docker-volumet `backups`
176+
- Backups eldre enn 14 dager slettes automatisk (konfigurerbart via `BACKUP_KEEP_DAYS`)
177+
178+
### Manuell backup
179+
180+
```bash
181+
docker compose exec backup bash -c \
182+
'pg_dump | gzip > /backups/javabinkids-manual-$(date +%Y%m%d-%H%M%S).sql.gz'
183+
```
184+
185+
### Se eksisterende backups
186+
187+
```bash
188+
docker compose exec backup ls -lh /backups/
189+
```
190+
191+
### Restore
192+
193+
```bash
194+
# Finn ønsket backup
195+
docker compose exec backup ls -lh /backups/
196+
197+
# Restore (erstatter all data i databasen)
198+
docker compose exec backup bash -c \
199+
'gunzip -c /backups/javabinkids-XXXXXXXX-XXXXXX.sql.gz | psql'
200+
```
201+
202+
### Se backup-logger
203+
204+
```bash
205+
docker compose logs backup
206+
```
207+
208+
## Produksjon
209+
210+
Prosjektet inkluderer en `Dockerfile` for produksjonsbygg med multi-stage build:
211+
212+
```bash
213+
docker build -t javabinkids .
214+
docker run -p 3000:3000 \
215+
-e DATABASE_URL=postgres://... \
216+
-e RESEND_API_KEY=re_... \
217+
-e BASE_URL=https://kids.javabin.no \
218+
javabinkids
219+
```
220+
221+
## npm scripts
222+
223+
| Script | Beskrivelse |
224+
|--------|-------------|
225+
| `npm run dev` | Start dev-server |
226+
| `npm run build` | Produksjonsbygg |
227+
| `npm run preview` | Forhåndsvis produksjonsbygg |
228+
| `npm run check` | TypeScript type-checking |
229+
| `npm run db:generate` | Generer Drizzle-migrering |
230+
| `npm run db:migrate` | Kjør migreringer |
231+
| `npm run db:seed` | Seed database |

docker-compose.prod.yml

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
services:
2+
db:
3+
image: postgres:17
4+
restart: unless-stopped
5+
environment:
6+
POSTGRES_USER: ${DB_USER:-javabin}
7+
POSTGRES_PASSWORD: ${DB_PASSWORD}
8+
POSTGRES_DB: ${DB_NAME:-javabinkids}
9+
volumes:
10+
- pgdata:/var/lib/postgresql/data
11+
healthcheck:
12+
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-javabin} -d ${DB_NAME:-javabinkids}"]
13+
interval: 10s
14+
timeout: 5s
15+
retries: 5
16+
17+
app:
18+
build:
19+
context: .
20+
dockerfile: Dockerfile
21+
restart: unless-stopped
22+
ports:
23+
- "${APP_PORT:-3000}:3000"
24+
environment:
25+
DATABASE_URL: postgres://${DB_USER:-javabin}:${DB_PASSWORD}@db:5432/${DB_NAME:-javabinkids}
26+
RESEND_API_KEY: ${RESEND_API_KEY}
27+
BASE_URL: ${BASE_URL}
28+
NODE_ENV: production
29+
depends_on:
30+
db:
31+
condition: service_healthy
32+
33+
backup:
34+
image: postgres:17
35+
restart: unless-stopped
36+
environment:
37+
PGHOST: db
38+
PGUSER: ${DB_USER:-javabin}
39+
PGPASSWORD: ${DB_PASSWORD}
40+
PGDATABASE: ${DB_NAME:-javabinkids}
41+
BACKUP_KEEP_DAYS: ${BACKUP_KEEP_DAYS:-30}
42+
volumes:
43+
- backups:/backups
44+
entrypoint: ["/bin/bash", "-c"]
45+
command:
46+
- |
47+
echo "Backup service started. Running daily at 03:00."
48+
while true; do
49+
NOW=$$(date +%H%M)
50+
if [ "$$NOW" = "0300" ]; then
51+
TIMESTAMP=$$(date +%Y%m%d-%H%M%S)
52+
FILENAME="javabinkids-$$TIMESTAMP.sql.gz"
53+
echo "[$$TIMESTAMP] Starting backup..."
54+
pg_dump | gzip > /backups/$$FILENAME
55+
if [ $$? -eq 0 ]; then
56+
echo "[$$TIMESTAMP] Backup saved: $$FILENAME ($$(du -h /backups/$$FILENAME | cut -f1))"
57+
else
58+
echo "[$$TIMESTAMP] ERROR: Backup failed!"
59+
fi
60+
find /backups -name "javabinkids-*.sql.gz" -mtime +$$BACKUP_KEEP_DAYS -delete
61+
echo "[$$TIMESTAMP] Cleanup done. Current backups:"
62+
ls -lh /backups/javabinkids-*.sql.gz 2>/dev/null || echo " (none)"
63+
sleep 60
64+
fi
65+
sleep 50
66+
done
67+
depends_on:
68+
db:
69+
condition: service_healthy
70+
71+
volumes:
72+
pgdata:
73+
backups:

0 commit comments

Comments
 (0)