Gå til indholdet

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: requestIdrequestLoggerauthenticate (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-niveau Member.role ∈ {owner, admin, teacher, student}. Frontend: ProtectedRoutes.tsx; backend: authorize() / superAdminOnly().
  • Multi-tenancy: resolveTenant udleder organisation fra session.activeOrganizationId eller 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_SECRET er påkrævet, og OnlyOffice kører på sin egen ONLYOFFICE_JWT_SECRET. Se docs/decisions/0001-fjern-legacy-jwt.md.

2.4 Dokumenthåndtering (OnlyOffice)

End-to-end i workroom.controller.ts:

  1. GET /workrooms/documents/:id/editor-config — bygger config + signerer JWT.
  2. GET /workrooms/documents/:id/file — serverer filen fra src/uploads/workrooms/{id}.{ext} (eller en blank skabelon fra src/templates/). Kræver nu DS-JWT (denne uges fix).
  3. POST /workrooms/documents/:id/callback — DS gemmer filen tilbage; henter via internt docker-netværk. Kræver nu DS-JWT.
  4. Status: DRAFTSUBMITTED (skifter editor til view-only) → felter for grade/feedback. Unsubmit fører tilbage til DRAFT.

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, classIdkun 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.description ligger på Class, ikke pr. gruppe. GroupAssignment har intet descriptionOverride/isShared. Det er den direkte årsag til at roadmap-punkt 1 ikke kan laves uden schema-ændring.
  • Dobbelt deadline. Både Assignment.deadline og Group.deadline findes — potentielt modstridende kilder til "frist".
  • Løs dokument↔opgave-kobling. WorkRoomDocument.assignmentId er valgfri og sættes manuelt ved aflevering (jf. punkt 5).
  • Stub-felter: Group.startDate og Group.subjectId er 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)

  1. RESET_DB=1 nukede databasen ved boot. Flaget er fjernet fra entrypoint/compose/CI; reset sker nu kun via det interaktive backend/scripts/reset-db.sh (kræver TTY + at man taster RESET).
  2. Manglende .env.example. Oprettet i repo-rod (compose), backend/ og frontend/ med dummy-værdier og kommentar pr. variabel.
  3. Socket.io CORS-fallback til *. Fjernet — FRONTEND_URL er påkrævet, og serveren nægter at starte uden.
  4. OnlyOffice-JWT var valgfri. ONLYOFFICE_JWT_SECRET er nu påkrævet ved boot, og verifikationen i workroom.controller.ts er ubetinget. 1–4 er samlet i fail-fast-valideringen i backend/src/config/env.ts (én fejlbesked med ALLE manglende variabler).

Høj — løst i FASE C (MR !3)

  1. 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_failure er fjernet, så testfejl blokerer pipelinen. Bonus-fund: gruppe-sletning fejlede med 500 i produktion (prisma.message findes ikke) — rettet.
  2. Legacy JWT-rester. Død auth-kæde slettet; migration 20260610…remove_legacy_jwt_fields dropper de fire kolonner (testet mod produktionskopi); dobbelt-secret-fallback fjernet fra koden (compose-fallback beholdt bevidst). Se ADR 0001.
  3. Deps pinnet. Alle "latest" erstattet af de installerede versioner (ingen opgraderinger); prisma CLI/klient ensrettet; bun.lock committet og --frozen-lockfile i Dockerfiles + CI.

Middel — løst i FASE C (MR !3)

  1. fs/path fjernet fra devDependencies.
  2. noUnusedLocals/noUnusedParameters slået til i frontend (53 døde React-imports fjernet i samme ombæring).
  3. ✅ TypeScript ensrettet på ~5.9.2 i begge projekter.
  4. ✅ Én package manager (Bun): package-lock.json-filerne og rod-bun.lockb er fjernet; frontend-CI kører oven/bun.

Trivielt — løst i FASE D (MR !4)

  1. ✅ 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/conf indeholdt 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) og onlyoffice_data persisteres korrekt i docker-compose.yml.

Kendte rest-observationer (ikke-blokerende)

  • Backend tsc --noEmit har ~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.