Groops' Work — Status-audit¶
Dato: 2026-06-10
Revision auditeret: ac1e582 (main, = beta)
Omfang: Læsende gennemgang af hele kodebasen. INGEN kodeændringer foretaget.
Opdateret 2026-06-11 ad to omgange: først efter refresh-faserne A–D
(MR !1–!4), siden efter forløbsmodellen ADR-001 (MR !6–!9: Course-model,
pr.-gruppe-opgaver, samlet oprettelses-/redigeringsflow,
kontrakt-migration) og de tre sidste roadmap-punkter (MR !10–!12:
context-aware aflevering, Afslut emne, help-flag). Alle 11
roadmap-punkter er nu DONE og i produktion. Den aktuelle datamodel og
arkitektur vedligeholdes i docs/architecture.md — dette dokument er
audit-historik med status.
Formål: kortlægge den nuværende arkitektur og datamodel, måle hver roadmap-post mod virkeligheden, og liste teknisk gæld — så roadmap kan opdateres på et faktuelt grundlag, før der ændres noget.
1. Resumé¶
Groops' Work er en fungerende fuld-stack platform: lærere opretter forløb (klasser), inddeler elever i grupper, knytter opgaver til grupperne, og eleverne arbejder sammen i dokumenter (OnlyOffice) i et delt arbejdsrum. Kernen — auth, multi-tenancy, gruppearbejde, dokument-redigering og aflevering — er på plads og kører i produktion.
Roadmap-status efter juni 2026-arbejdet: alle 11 punkter er DONE. Refresh-faserne A–D lukkede quick wins + teknisk gæld; ADR-001 indførte forløbsmodellen (Course, emne = Assignment, unik/delt pr. gruppe, én deadline-kilde) med samlet oprettelses-/redigeringsflow; og MR !10–!12 lukkede de sidste tre punkter (context-aware aflevering, Afslut emne m. OnlyOffice-PDF/ZIP, help-flag m. realtime).
Den tekniske gæld fra den ufærdige Better Auth-migrering er lukket i FASE C: testsuiten er genskrevet (56 tests, 0 skip, blokerende i CI), legacy-JWT-felterne er droppet via migration (testet mod produktionskopi, se ADR 0001), og alle deps er pinnet med committede lockfiles.
2. Arkitektur¶
2.1 Frontend (/frontend)¶
| Område | Detalje |
|---|---|
| Stack | React 18.3.1 + TypeScript 5.6.2, Vite 5.4.10 |
| UI | Material-UI (MUI) 6.1.10; drag-and-drop via @dnd-kit |
| Routing | React Router 7.0.2 — src/routes/index.tsx (+ adminRoutes.tsx) |
| State | Redux Toolkit — 13 slices i src/store/slices/ |
| API-klient | axios-instans src/utils/api.ts, baseURL: /api, withCredentials (cookie-auth, ingen Bearer-tokens) |
| Realtime | socket.io-client 4.8.0, src/context/SocketContext.tsx, sti /api/socket.io |
Redux-slices (hvad de ejer): auth, admin, assignment, class,
group, homeroom, message, user, userSettings, subject, tenant,
workroom. Store'en nulstiller alle slices ved logout/brugerskift
(src/store/index.ts — root-reducer, tilføjet i denne uges fix).
Sidestruktur: pages/{admin,assignments,classes,groups,workspace,messages,profile,settings,dashboards}.
Lærer- og elevvisninger er adskilte (f.eks. assignments/teacher/TeacherAssignment.tsx
vs. assignments/student/StudentAssignment.tsx). Editor og arbejdsrum kører
fuldskærm uden for MainLayout.
2.2 Backend (/backend)¶
| Område | Detalje |
|---|---|
| Stack | Express + TypeScript 5.9.2; Bun som runtime/test-runner |
| Lagdeling | routes/ → controllers/ → services/ → Prisma; middleware/, types/ (Zod), utils/ |
| Realtime | socket.io-server src/config/socket.ts, rum-baseret (user:*, class:*, group:*) |
| Filupload | Multer 1.4.5; gemmes på disk under src/uploads/ |
Middleware-kæde: requestId → requestLogger → authenticate (Better Auth
session fra cookie) → resolveTenant (sætter req.organizationId) →
authorize(roles) → validate(zod) → handler → errorHandler.
Route-filer og ressourcer: auth (Better Auth), health, users,
classes, homerooms, groups, assignments, messages, subjects,
workrooms (dokumenter + OnlyOffice + chat), feedback, activity-logs
(tilføjet i FASE B), admin, tenants.
2.3 Auth¶
- Better Auth (
src/config/auth.ts) med Organization-plugin (multi-tenancy). Session 24t / refresh 1t / cookie-cache 5 min. Password via bcryptjs. - Roller: platform-niveau
User.role∈ {SUPER_ADMIN, ADMIN, TEACHER, STUDENT}; org-niveauMember.role∈ {owner, admin, teacher, student}. Frontend:ProtectedRoutes.tsx; backend:authorize()/superAdminOnly(). - Multi-tenancy:
resolveTenantudleder organisation frasession.activeOrganizationIdeller Host-header (custom domæne/subdomæne); SUPER_ADMIN forbigår org-kravet. De fleste queries filtrerer påorganizationId. - Legacy-rester: fjernet i FASE C — auth-kæden er slettet,
BETTER_AUTH_SECRETer påkrævet, og OnlyOffice kører på sin egenONLYOFFICE_JWT_SECRET. Sedocs/decisions/0001-fjern-legacy-jwt.md.
2.4 Dokumenthåndtering (OnlyOffice)¶
End-to-end i workroom.controller.ts:
GET /workrooms/documents/:id/editor-config— bygger config + signerer JWT.GET /workrooms/documents/:id/file— serverer filen frasrc/uploads/workrooms/{id}.{ext}(eller en blank skabelon frasrc/templates/). Kræver nu DS-JWT (denne uges fix).POST /workrooms/documents/:id/callback— DS gemmer filen tilbage; henter via internt docker-netværk. Kræver nu DS-JWT.- Status:
DRAFT→SUBMITTED(skifter editor til view-only) → felter forgrade/feedback. Unsubmit fører tilbage tilDRAFT.
PDF-eksport: kun OnlyOffice' egen indbyggede knap — ingen server-side eksport (jf. §4, punkt 4).
3. Datamodel¶
Prisma + PostgreSQL, backend/prisma/schema.prisma. NB: Datamodellen
er sidenhen udvidet væsentligt med forløbsmodellen (ADR-001: Course,
isShared/descriptionOverride, pr.-gruppe-filer, LOCKED-status,
help-flag) og kontrakt-migrationen droppede Group.deadline/Group.topic.
Se docs/architecture.md for den aktuelle model — graferne herunder
beskriver tilstanden pr. 2026-06-10.
3.1 De fire kernebegreber¶
| Roadmap-begreb | Model | Nøglefelter |
|---|---|---|
| Forløb | Class (classes) |
name, academicYear, semester, startDate, endDate, status (ACTIVE/ARCHIVED/DRAFT), teacherId, homeroomId?, mange-til-mange subjects via ClassSubject |
| Gruppe | Group (groups) |
name, groupNumber (unik pr. klasse), displayName?, topic?, startDate?, deadline?, requiredDocuments, classId, subjectId? |
| Opgave | Assignment (assignments) |
title, description, deadline, requiredSubmissions, classId — kun på klasse-niveau |
| Dokument | WorkRoomDocument (work_room_documents) |
title, documentType, fileExtension, status (DRAFT default), assignmentId?, submit/grade-felter, lastEditedBy, lastActivity |
Bro-modeller: GroupAssignment (Group ↔ Assignment, med status og
currentSubmissionId), Submission (afleveringshistorik), ClassSubject,
GroupMember, ClassEnrollment, HomeroomEnrollment.
3.2 Relationsgraf¶
Lærer (User role=TEACHER)
└─ Class (FORLØB) ── homeroomId ─> Homeroom (stamklasse, f.eks. "7.A")
├─ ClassEnrollment (elever i forløbet)
├─ ClassSubject ─> Subject (fag, mange-til-mange)
├─ Assignment (OPGAVE, klasse-niveau)
│ └─ GroupAssignment ─> Group (samme opgavetekst til alle grupper)
│ └─ Submission (currentSubmissionId = aktiv aflevering)
└─ Group (GRUPPE)
├─ GroupMember ─> User (elever i gruppen)
├─ subjectId ─> Subject
├─ GroupAttachment (filer på gruppen)
└─ WorkRoom (1:1)
├─ WorkRoomDocument (DOKUMENT, status DRAFT/SUBMITTED)
│ └─ assignmentId? (løs kobling tilbage til opgaven)
└─ WorkRoomMessage (gruppe-/lærer-chat)
3.3 Hvor tingene hænger sammen — og hvor de ikke gør¶
- Opgavetekst er klasse-global.
Assignment.descriptionligger påClass, ikke pr. gruppe.GroupAssignmenthar intetdescriptionOverride/isShared. Det er den direkte årsag til at roadmap-punkt 1 ikke kan laves uden schema-ændring. - Dobbelt deadline. Både
Assignment.deadlineogGroup.deadlinefindes — potentielt modstridende kilder til "frist". - Løs dokument↔opgave-kobling.
WorkRoomDocument.assignmentIder valgfri og sættes manuelt ved aflevering (jf. punkt 5). - Stub-felter:
Group.startDateogGroup.subjectIder ledningsført i FASE B (synlighedsfilter hhv. redigering — den reelle synder var group-controlleren, der ikke videresendte felterne). Stadig defineret men minimalt brugt:ClassEnrollment.completedAt,WorkRoomDocument.lastEditedBy,Organization.metadata.
4. Roadmap-status¶
| # | Punkt | Status | Kort begrundelse |
|---|---|---|---|
| 1 | Unikke opgavebeskrivelser + filer pr. gruppe (toggle unik/delt) | DONE (ADR-001) | Assignment.isShared + GroupAssignment.descriptionOverride + pr.-gruppe-attachments; unik/delt-toggle i wizard og redigering; elever ser kun delte filer + egen gruppes |
| 2 | Fuld redigering af grupper | DONE (ADR-001) | Alt fra oprettelsen kan redigeres inkl. pr.-gruppe-opgavetekst/filer; bulk-redigering m. preview ("Dette ændrer N grupper i …") |
| 3 | Aktive vs. afsluttede opgaver — elevvisning m. slideshow/filter | DONE (FASE B) | Faner/filtrering ✓ + slideshow/carousel-visning med liste-toggle (AssignmentSlideshow.tsx) |
| 4 | "Afslut emne" — lås dokumenter, watch-only + PDF-download | DONE (MR !11) | LOCKED-status, bulk finish/reopen pr. emne (fortrydeligt, gendanner kladde/afleveret), watch-only for elever OG lærer, server-PDF via OnlyOffice conversion API + samlet ZIP |
| 5 | Context-aware aflevering | DONE (MR !10) | Én aktiv opgave => auto-valg uden dropdown; flere => nærmeste deadline forudvalgt m. dropdown-fallback |
| 6 | Planlæg grupper m. fremtidig synlighed | DONE (FASE B) | Elever ser ikke grupper med fremtidig startDate (alle list-/detaljeendpoints + sockets via staff-rum); lærer/admin ser "Planlagt"-badge; dato+tid-vælger i opret/rediger |
| 7 | Samlet overblik over afleveringer m. filtrering (klasse/fag/forløb) | DONE | TeacherAssignment.tsx filtrerer på klasse, fag, forløb og status; API + UI fuldt forbundet |
| 8 | Mappeinddeling af klasser (f.eks. 2.F - Engelsk) | DONE (ADR-001 FASE C) | Lærervisningen har hold → fag-mappe → forløbs-mappe → grupper (lukkede som default), som elevvisningen |
| 9 | Elev-vælger ved gruppeoprettelse (én ad gangen, spring over, gruppenr.) | DONE (ADR-001 FASE C) | Sekventiel "vælg én ad gangen"-picker m. spring-over og auto-skift til næste gruppe, ved siden af drag-drop |
| 10 | Help-flag (elev markerer "brug for hjælp") | DONE (MR !12) | needsHelp + besked på Group; elev-knap i arbejdsrummet; live på lærer-dashboard via group:help-socket (kun staff-rum); "Markér håndteret"; ActivityLog |
| 11 | Aktivitetslog (hvem ændrede hvad, hvornår) | DONE (FASE B) | GET /api/activity-logs (rolle-scopet, filtre, paginering) + tidslinje-UI på /activity; klasse-/gruppe-/opgave-mutationer logger nu (før skrev INTET til tabellen); mock-data i UserProfileView erstattet |
Optælling: 11 DONE · 0 DELVIST · 0 IKKE STARTET. (Oprindeligt audit 2026-06-10: 1 DONE · 6 DELVIST · 4 IKKE STARTET; efter refresh-faserne A–D: 4 DONE · 4 DELVIST · 3 IKKE STARTET.)
Detaljer pr. punkt¶
1 — Unikke opgaver pr. gruppe (DONE — ADR-001). Løst præcis som
auditen foreslog: Assignment.isShared, GroupAssignment.descriptionOverride
og AssignmentAttachment.groupAssignmentId (delt vs. pr.-gruppe-filer),
med effectiveDescription beriget på alle svar og unik/delt-toggle i både
wizard og redigering. Se ADR-001 og docs/architecture.md.
2 — Fuld gruppe-redigering (DONE — FASE B + ADR-001). Gruppefelterne
redigeres i EditGroupDialog (medlemmer/fag/startdato/dokumentkrav), og
emne/opgavetekst/frist/filer — inkl. pr.-gruppe-varianter — redigeres
samlet på emnet (/assignments/:id/edit) med bulk-checkbox og preview.
3 — Aktiv/afsluttet + slideshow (DONE — FASE B). Faner, søgning og
fag-filter fandtes; FASE B tilføjede slideshow/carousel-visningen
(AssignmentSlideshow.tsx) med liste/slideshow-toggle, piletaster og
prikker/tæller — den genbruger de aktive faner/filtre.
4 — Afslut emne (DONE — MR !11). LOCKED-status med
statusBeforeLock (fortrydeligt — genåbning gendanner kladde/afleveret),
bulk-endpoints POST /assignments/:id/finish|reopen (emne-placering jf.
ADR-001), watch-only håndhævet for både elever og lærer (view-mode +
afviste mutationer; elever kan ikke låse op — testet). Server-side PDF via
OnlyOffice Document Servers conversion API + samlet ZIP pr. emne.
5 — Context-aware aflevering (DONE — MR !10). Præcis som foreslået:
én aktiv opgave vælges automatisk (chip, ingen dropdown); flere aktive
forudvælger nærmeste deadline med dropdown som fallback. Backend kræver
fortsat eksplicit assignmentId — frontend udleder.
6 — Planlagt synlighed (DONE — FASE B). Elever ser ikke grupper med
fremtidig startDate: filter i getStudentGroups/getGroup/
getClassGroups, elev-sockets joiner ikke fremtidige gruppe-rum, og
group:created/updated for fremtidige grupper emittes kun til et
staff-rum (class:<id>:staff). Lærer/admin ser "Planlagt"-badge med
tooltip; dato+tid-vælger i både opret- og rediger-dialog. NB: overgangen
fremtidig→synlig sker ved næste opslag/reconnect (ingen scheduler).
7 — Afleveringsoverblik (DONE). TeacherAssignment.tsx + assignment-routes
giver filtrering på klasse, fag, forløb, status og søgning, med
afleverings-statistik pr. opgave.
8 — Klasse-mapper (DONE — ADR-001 FASE C). Lærervisningen har nu hold → fag-mappe → forløbs-mappe → grupper (mapper lukkede som default, "Uden fag"/"Uden forløb" nederst) — samme struktur som elevvisningen.
9 — Elev-vælger (DONE — ADR-001 FASE C). Wizard'ens fordelingstrin har både drag-drop og en sekventiel "vælg én ad gangen"-picker med spring-over (eleven ryger bagest i køen) og automatisk skift til næste gruppe.
10 — Help-flag (DONE — MR !12). needsHelp + timestamp + valgfri
besked på Group (begrundelse i MR: arbejdsrummet er 1:1 med gruppen).
Elev-knap i arbejdsrummet; live på lærerens dashboard via
group:help-socket-event til personale-rummet (elever modtager det ikke —
verificeret); "Markér håndteret" kun for lærer/admin; HELP_REQUESTED/
HELP_RESOLVED i ActivityLog og tidslinjen.
11 — Aktivitetslog (DONE — FASE B). Vigtigt fund undervejs: intet i
den kørende app skrev til ActivityLog-tabellen (eneste kald lå i død
legacy-kode). Nu logger klasse-/gruppe-/opgave-mutationer via
fire-and-forget loggingService.log(). Nyt GET /api/activity-logs med
rolle-scoping (lærer: egne forløb; admin: organisationen), filtre på
klasse/gruppe/kategori/bruger og paginering. Tidslinje-UI på /activity
(menupunktet "Aktivitet"); UserProfileView viser brugerens reelle
aktivitet i stedet for mock-data.
5. Tekniske problemer (prioriteret)¶
Status 2026-06-11: alle 12 punkter er løst (FASE A: 1–4, FASE C: 5–11, FASE D: 12). Detaljer pr. punkt herunder.
Kritisk — løst i FASE A (MR !1)¶
- ✅
RESET_DB=1nukede databasen ved boot. Flaget er fjernet fra entrypoint/compose/CI; reset sker nu kun via det interaktivebackend/scripts/reset-db.sh(kræver TTY + at man tasterRESET). - ✅ Manglende
.env.example. Oprettet i repo-rod (compose),backend/ogfrontend/med dummy-værdier og kommentar pr. variabel. - ✅ Socket.io CORS-fallback til
*. Fjernet —FRONTEND_URLer påkrævet, og serveren nægter at starte uden. - ✅ OnlyOffice-JWT var valgfri.
ONLYOFFICE_JWT_SECRETer nu påkrævet ved boot, og verifikationen iworkroom.controller.tser ubetinget. 1–4 er samlet i fail-fast-valideringen ibackend/src/config/env.ts(én fejlbesked med ALLE manglende variabler).
Høj — løst i FASE C (MR !3)¶
- ✅ Testdækning. De 7 skip'ede filer er genskrevet til Better Auth:
56 tests, 0 skip — dækker authorize()-afvisninger, multi-tenancy-
isolation og FASE B-regressioner.
allow_failureer fjernet, så testfejl blokerer pipelinen. Bonus-fund: gruppe-sletning fejlede med 500 i produktion (prisma.messagefindes ikke) — rettet. - ✅ Legacy JWT-rester. Død auth-kæde slettet; migration
20260610…remove_legacy_jwt_fieldsdropper de fire kolonner (testet mod produktionskopi); dobbelt-secret-fallback fjernet fra koden (compose-fallback beholdt bevidst). Se ADR 0001. - ✅ Deps pinnet. Alle "latest" erstattet af de installerede versioner
(ingen opgraderinger); prisma CLI/klient ensrettet;
bun.lockcommittet og--frozen-lockfilei Dockerfiles + CI.
Middel — løst i FASE C (MR !3)¶
- ✅
fs/pathfjernet fra devDependencies. - ✅
noUnusedLocals/noUnusedParametersslået til i frontend (53 døde React-imports fjernet i samme ombæring). - ✅ TypeScript ensrettet på ~5.9.2 i begge projekter.
- ✅ Én package manager (Bun):
package-lock.json-filerne og rod-bun.lockber fjernet; frontend-CI kører oven/bun.
Trivielt — løst i FASE D (MR !4)¶
- ✅ Alle døde mapper/filer fjernet (verificeret reference-frie);
designmateriale flyttet til
docs/design-inspiration/; CLAUDE.md bragt i sync med den faktiske arkitektur. NB:certbot/confindeholdt committede ACME-konto-nøgler — fjernet, men de ligger i git-historikken (kontiene er forældede).
Migrationer/DB — i orden¶
- Ingen drift; 4 migrationer i korrekt rækkefølge (den nye legacy-felt-
migration er applied i produktion 2026-06-11, 56 brugere intakte).
Upload-volume (
backend_uploads:/app/src/uploads) ogonlyoffice_datapersisteres korrekt idocker-compose.yml.
Kendte rest-observationer (ikke-blokerende)¶
- Backend
tsc --noEmithar ~121 præ-eksisterende fejl (primært express-5-typings-støj i controllers) — ikke en CI-gate; tests + builds gater. Kandidat til en senere oprydningsfase. - Public Better Auth sign-up er reelt ubrugeligt (required
password-kolonne på users) — bevidst uændret, da brugere provisioneres af admin/import. Dokumenteret i ADR 0001 og test-helperen. - Lige efter login mangler sessionens cookie-cache aktiv organisation,
indtil klienten kalder
organization/set-active(frontenden gør det) — værd at vide ved scripting mod API'et.
6. Status: roadmap gennemført¶
Alle 11 punkter fra det oprindelige roadmap er leveret og i produktion
(pr. 2026-06-11, main 87cdff1). Undervejs er desuden leveret det, der
ikke stod på listen men var forudsætninger: forløbsmodellen (ADR-001),
genskrevet testsuite (85 tests, blokerende CI), fail-fast env-validering,
reproducerbare builds og repo-oprydning.
Næste skridt defineres af nye ønsker — oplagte kandidater fra arbejdet: - Frontend-tests (suiten dækker kun backend) - Oprydning i de ~120 præ-eksisterende backend-tsc-fejl (express-typings) og evt. tsc som CI-gate - Better Auth public sign-up er fortsat bevidst ubrugelig (users provisioneres af admin/import) — aktiver hvis selvregistrering ønskes
Auditen pr. 2026-06-10 var rent observerende. Status-opdateringen 2026-06-11 afspejler FASE A–D (MR !1–!4); se CHANGELOG.md og docs/funktioner.md for detaljer.