Skip to content

Student sieht eigene Deployments nicht — keine CourseMember/GroupMember-Rows werden angelegt #169

Description

@Gree44

Bug

Wenn ein Lecturer im Wizard ein Deployment erstellt, sehen die zugeordneten Studenten das Deployment nicht unter GET /api/v1/student/deployments — Response: 0 Deployments.

Root Cause

Der Student-List-Query in src/api/student.py:102-120 inner-joined über die ganze Kette:

Deployment → DeploymentInstance → DeploymentInstanceAccess
            → CourseGroup       (via access.group_id)
            → GroupMember       (via group.id)
            → CourseMember      (via group_member.course_member_id, left_at IS NULL)
            → user_id == student

Es gibt im gesamten Backend keinen einzigen Code-Pfad, der CourseMember- oder GroupMember-Rows aus dem Wizard-Payload anlegt. grep -rn "CourseMember(" über src/ liefert ausschließlich die Modell-Definition und Tests — keine db.add(CourseMember(...)) in irgendeinem Service.

  • UserSyncService (src/services/user_sync_service.py) schreibt nur in users.
  • DeploymentService.create_deployment() (src/services/deployment_service.py:147-154) legt den Course an, aber keine Mitgliedschaften.
  • deploy_tasks.persist_credentials() (src/tasks/deploy_tasks.py:268) persistiert nur DeploymentInstanceAccess.group_id.
  • Der einzige Endpoint, der GroupMember erzeugt, ist POST /api/v1/courses/{id}/groups/{group_id}/members (src/api/courses.py:495) — und der setzt voraus, dass die CourseMember-Rows schon existieren (Filter Zeile 467).

CourseMember.user_id == student_user_id matcht nie → Join leer → 0 Deployments.

Bestätigung: tests/api/test_student_routes.py:130-134 legt CourseMember+GroupMember manuell in der Session an — der Test zeigt also, wovon der Read-Pfad ausgeht, aber im echten Code passiert das nirgends.

Wann ist das eingeschlichen worden

Commit der course_group_id//student/*-Read-Seite hat das Schema-Requirement eingeführt, ohne die Write-Seite mitzunehmen. Der Read-Pfad erwartet Mitgliedschafts-Rows, die niemand anlegt.

Fix (Vorschlag — Backend owns it)

In DeploymentService.create_deployment() direkt nach dem Auto-Create des Course (Zeilen 147-154) über stack_assignments[].groups[].students[] iterieren und idempotent upserten:

  1. CourseMember(course_id, user_id=student.id) — falls fehlt
  2. CourseGroup(course_id, name=group_name) — falls fehlt (returned id als course_group_id verwenden)
  3. GroupMember(group_id, course_member_id) — falls fehlt

Vorteil: Wizard kann course_group_id: null schicken und es funktioniert trotzdem, Frontend-Chicken-and-Egg (siehe Frontend-Issue) löst sich automatisch. Idempotenz wichtig wegen Retry-Flow.

Alternative (worse): einen neuen POST /courses/{id}/members Endpoint anbieten, den das Frontend separat aufruft — drei Extra-Round-Trips pro Deploy plus Race-Conditions.

Verwandt

Frontend-Issue (Chicken-and-Egg bei internalCourseId): siehe DoziLab/appstore-frontend.

Wie reproduzieren

  1. Lecturer-Login, neuen Course (Keycloak-Group) wählen, der noch nie deployed wurde.
  2. Wizard durchklicken, mind. 1 Student zuordnen, Deployment abschicken → succeeded.
  3. Als zugeordneter Student einloggen → /api/v1/student/deployments liefert [].

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions