Min leverance · groopworks-new

Launch-hærdning — alt jeg har rettet, bygget og sat i drift

Det startede med ni fejl, jeg selv gik og blev irriteret over. Det endte med en komplet leverance: UX-fixes, karakterer, tom-instans-flow, sikkerheds­hærdning, en ny drifts­model og en QA-gennemgang jeg ikke gik på kompromis med. Alt kører nu på app.groopswork.dk.

LIVE I PRODUKTION main · 6cfdcd0 MR !34 + !35 merged 11. aug 2026
62
commits
104
kode-filer rørt
37k+
linjer tilføjet
0
data tabt

846 filer i alt (742 af dem er mine QA-artefakter), 56 frontend · 41 backend · 1 DB-migration · 2 docs. Jeg tog backup først: alle ~84 brugere, 36 hold, 158 opgaver og 78 grupper er urørte.

Del 1

01Fejlene, jeg meldte ind — og hvad jeg gjorde ved dem

Jeg fik en gennemgang til at bekræfte hver enkelt, og så rettede jeg dem én for én. Alt herunder er verificeret på den kørende build — ikke bare “burde virke”.

PunktHvad der var galtSådan løste jeg detStatus
Knyt forløbInvarianten group.courseId === assignment.courseId blev brudt; næste emne-gem nulstillede knytningen; fejltekster nåede aldrig frem; picker viste arkiverede forløb.Nu flytter emnet med gruppen i én transaktion, jeg validerer faget, filtrerer picker'en til aktive forløb, og lader backend-fejlen boble helt op.Rettet
Slet-knap (lærer)Lærere havde slet ingen slet-mulighed; tomme forløb kunne aldrig slettes; sletning fejlede stille.Jeg gav læreren en synlig sletning med bekræftelses­dialog og konkret konsekvens, kaskade­sletning, og fik fejlene frem i lyset.Rettet
Lærerchat vækEt gammelt commit havde hardcodet chatType='GROUP' og fjernet fane-skifteren; backend blokerede eleven med 403 oveni.Jeg redefinerede lærerchatten som en elev↔lærer-kanal, gendannede fanerne, og sikrede privacy pr. kanal over websocket — lærere kan ikke kigge med i elevernes private rum.Rettet
OpgavematerialeArbejdsrummet viste intet materiale og hentede ikke engang dataen; kun et lillebitte ikon-klik, skjult når tomt.Nu vises opgavens materiale i arbejdsrummet med samme download-flow, hele rækken er klikbar, og fejl vises i stedet for at forsvinde i et tomt catch.Rettet
Elever så alle klasserKlasse-ruterne havde ingen rolle-gate — enhver elev kunne læse enhver klasse i org'en, inkl. navne og emails.Det her tog jeg alvorligt: rolle-gates + ejer/enrollment-scoping på klasse-, skema- og homeroom-ruterne, og en negativ sikkerhedstest der beviser det.Sikkerhed
Afleveringer → dropdownEn lodret stak af afleverings­kort uden stabil rækkefølge.Erstattet med en dropdown hvor man vælger gruppe/aflevering, og jeg gav den en deterministisk sortering på gruppenummer.Rettet
Gå til arbejdsrumFuldskærms-rute uden nav, stale-flash af det forrige arbejdsrum, og knappen forsvandt hvis arbejdsrummet manglede.Jeg rydder state ved mount, gav en tydelig vej tilbage til opgaven, og lazy-opretter arbejdsrummet så knappen altid virker.Rettet
KaraktererGruppe-karakter lå i koden men var utilgængelig; individuel elev-karakter fandtes slet ikke.Jeg byggede en rigtig karaktermodel — gruppe og individuel pr. elev, synlig for eleven. Mere i Del 2.Bygget
Lyst temaHvide kort på næsten-hvid baggrund (1.05:1), usynlig zebra/hover, og forgrunds­farver der dumpede AA-kontrast.Jeg hævede flade-adskillelsen, lavede synlig zebra/hover og AA-sikre tekst­farver — det hele styret fra design-tokens, ikke tilfældige hex.Rettet
Del 2

02Karakterer — gruppe & individuel

Det her var den største enkeltting. Jeg ville have det ordentligt: to niveauer, valideret i backenden, og synligt for eleven. Migrationen add_grades er den eneste DB-ændring i hele leverancen — og jeg holdt den bevidst rent additiv, så den ikke kunne gøre skade.

Del 3

03Tom instans på eget subdomæne — nu med cert

Jeg ville have, at en super-admin kan trykke på en knap og få en helt tom organisation med sit eget subdomæne <slug>.groopswork.dk og gyldig TLS. App-laget var klar — det var certet, der drillede.

Multi-host var på plads

nginx er catch-all (server_name _), og jeg havde allerede åbnet auth trustedOrigins og socket-CORS for enhver *.groopswork.dk, plus host → org-resolution og bootstrap af første admin i ét trin.

Din melding: “no cert gets added”

Du fangede en rigtig fejl. Rod­årsagen: Traefiks Let's Encrypt kører HTTP-01, som kræver et konkret domæne — en regex-router kan aldrig få et cert. Og min gamle domain.service.ts var ærligt talt et no-op (forkerte service-navne, ikke-monteret sti, blev aldrig kaldt).

Del 4

04Sikkerhed & data-tab — det der ikke måtte gå live

Undervejs faldt jeg over en håndfuld huller, der var værre end de rapporterede punkter. Dem lukkede jeg alle — med en test til hver.

Kritisk Uploads overlevede ikke redeploy

Afleveringsfiler og opgave-vedhæftninger blev skrevet uden for volumen og forsvandt ved hver deploy. Jeg samlede alle upload-stier ét sted (config/uploads.ts) under volumen.

Kritisk Vedhæftninger gav 404

Skrevet ét sted, læst et andet. Nu samme sti begge steder — og jeg tilføjede en autoriseret download-rute til afleveringsfiler, som slet ikke fandtes før.

Kritisk OnlyOffice-token = alle dokumenter

Token tjekkede kun signaturen, ikke hvilket dokument, og udløb aldrig. Nu bundet til dokument-id + exp.

Kritisk Cross-tenant-læk (IDOR)

Aktivitet, elev-opgaver/grupper og admin-bruger­styring manglede org/ejer-scoping. Lukket med org-scope, ejer-tjek og slankede socket-payloads.

“Skift adgangskode” var et no-op

Den skrev til et felt Better Auth slet ikke verificerer imod. Nu opdaterer jeg Account-rækken, så nye koder faktisk virker.

Auth-hærdning

Rate-limit monteret på /api/auth/* (både IP og email), selv-registrering lukket, CORS/localhost strammet, og magic-link uden en hardcoded kode.

Del 5

05Design-system, tema & drifts­model

UI-refresh (ADR-002)

Jeg lagde et token-baseret design-system i theme.ts: login-redesign, dashboards med rigtige stat-kort, fag-farver, lift-kort og en pænere admin. CI afviser nu rå hex uden for tokens.

Lyst tema med AA-kontrast

Dybere flader, en reel surface.raised, synlig zebra/hover, AA-sikker sekundær tekst, og TEAL_DEEP som forgrund i stedet for de farver der dumpede kontrast.

Lokal-autoritativ drift

Det her var en bevidst beslutning: prod kører nu lokalt fra docker compose -p groopworks-main og er autoritativt. CI er bare test+build-gate, og deploy_production er manuel — så et push aldrig overskriver den kørende stack.

CI som kvalitetsgate

Blokerende tsc --noEmit, token-gate på frontend, mkdocs --strict og backend-tests. Jeg merger ikke før alt er grønt.

Del 6

06QA — her gik jeg ikke på kompromis

Jeg kørte hele appen igennem flere lag automatiseret og ekspektations-drevet review, og loopede til rene runder — per rolle, tema, viewport og data-tilstand.

rene coverage-runder
41
sider dyb-inspiceret
1040
elementer gennemgået
553
klik bevist
Del 7

07Polish & find-barhed — de små ting

Det er de her detaljer, der afgør om det føles færdigt. Jeg tog dem seriøst.

Dansk ental/flertal

En fælles pluralDa-hjælper, jeg brugte 33 steder i 15 komponenter — “1 gruppe”, ikke “1 grupper”. Sådan noget stikker i øjnene på mig.

Lærerchat-farven

Fra en klashende pink (#f093fb) til brand-teal, ændret ét sted i theme.ts så hele kanalen fulgte med. Gruppechatten beholdt sin violette identitet.

Ensret terminologi

Opgave-headeren siger nu konsekvent “emne”, og admin er ensrettet til “Hold” overalt — stat-kort, knapper, tomme tilstande, nav. Ingen “Forløb” hvor det er et hold.

Dansk dato-casing

capitalize gjorde det til “Onsdag D. 11. Juni”. Nu er kun første bogstav stort, og måneder skrives med småt — som dansk skal være.

Find-barhed på lister

Resultat-tælling (“Viser 9 af 10”), “Ryd filtre” og kolonne-sortering på Brugere, Hold, Fag og Grupper.

Dag-separatorer

Én delt formatDaySeparator — “I dag / I går / lørdag 8. august 2026” i både aktivitet og chat.

Del 8

08Go-live — sådan flyttede jeg produktionen

Jeg flyttede prod over på den verificerede kode i sikre trin — uden datatab, med sessionerne i behold.

TRIN 1 · sikring
Secrets reddet + backup

Jeg reddede prod-hemmelighederne til en root-only prod.env (uden at printe værdierne), og tog fuld pg_dump (31 tabeller, verificeret) + tar af uploads-volumen.

TRIN 2 · build
Byg lokalt først

Jeg byggede images lokalt med prod-secrets uden at røre de kørende containere. Hvis build fejlede, stod prod urørt.

TRIN 3 · overtagelse
docker compose up -d

Samme projektnavn og volumener; add_grades kørte ved boot; samme BETTER_AUTH_SECRET → aktive sessioner overlevede.

TRIN 4 · verifikation
Sund + data intakt

/api/health/deep = DB connected, ingen console-fejl eller 5xx, data urørt. Jeg oprettede prod-super-admin med koden gemt i en root-only fil.

TRIN 5 · main
MR !34 + !35 → main

Hele leverancen merget til main, prod redeployet fra main og verificeret byte-identisk. Tenant-TLS-fixet (!35) fulgte lige efter.

Undervejs snublede jeg i et lokalt git-uheld: et checkout main blev blokeret af root-ejede QA-filer og rullede arbejds­træet tilbage til gammel kode. Jeg fangede det før nogen rebuild, bekræftede at prod var urørt hele tiden, ryddede op og fast-forwardede rent. Ingen risiko for produktionen, intet arbejde tabt.
Resultat

app.groopswork.dk er live og sund på main, prod == main, og arbejds­træet er rent. Rollback (DB-dump + de forrige cda3601a-images) er dokumenteret i qa/go-live.md.

Del 9

09Det jeg selv skal huske at gøre

Handling

1 · Rotér de hardcodede adgangskoder. Nogle rigtige koder har ligget i update*Password.ts i git-historikken på main. Jeg scrubbede dem fremadrettet, men historikken har dem stadig — så jeg bør rotere de konti og overveje at scrubbe historikken.

2 · Super-admin-koden. Læses én gang med sudo cat /srv/groopworks/superadmin-credentials.txt og skiftes ved første login.

3 · Nice-to-have: vise det nye subdomæne-URL til admin i UI'et efter jeg opretter en tom instans — i dag skal man selv vide, at det er <slug>.groopswork.dk.

Del 10

10Det hele i tal

OmrådeFilerHvad der ligger i det
Backend41services (assignment, class, course, group, tenant, domain, workspace, user), controllers, middleware (auth, class, tenant, upload, security), routes, config (auth, socket, uploads), schema + 1 migration, super-admin-script
Frontend56dashboards, opgaver (lærer/elev), grupper, hold, arbejdsrum, admin (brugere/fag/hold/tenants/overblik), layout (TopBar/SideNav), utils (theme, plural, relativeTime, api), routes
Docs & rod7CHANGELOG, CLAUDE.md, .gitlab-ci.yml, compose.ci.yml, .gitignore, docs/deployment.md, docs/design-system.md
QA-artefakter742coverage-/deep-/forms-/keyboard-harnesses, per-side-verdicts, screenshots, go-live-runbook, decisions- og polish-logs