Backend: Status auf confirmed ohne formFinal blockieren #159

Closed
opened 2026-06-30 21:51:04 +00:00 by niboer · 1 comment
Owner

Problem

PUT /api/v1/events/{event_id}/guests/{person_id}/status erlaubt aktuell jedes status-Value ohne Prüfung. Ein Aufruf mit { "status": "confirmed" } setzt den Gast auf "zugesagt", auch wenn kein formFinal existiert.

Das führt zu inkonsistenten Zuständen: Der Gast ist "zugesagt", hat aber nie ein Formular ausgefüllt.

Änderungen

Datei: backend/src/routes/guests.tsupdateStatus-Handler (ca. Zeile 249)

Aktuell:

const updateData: Record<string, unknown> = { status, respondedAt: new Date() };

if (status === 'invited') {
  updateData.formFinal = null;
  updateData.formDraft = null;
  updateData.formOverride = null;
  updateData.respondedAt = null;
}

Neu — vor dem update-Aufruf eine Prüfung einfügen:

const updateData: Record<string, unknown> = { status, respondedAt: new Date() };

if (status === 'confirmed') {
  const raw = existing.formFinal as Record<string, unknown> | null;
  if (!raw || Object.keys(raw).length === 0) {
    return reply.status(400).send({
      statusCode: 400,
      error: 'Bad Request',
      message: 'Zusage erfordert eine Formular-Antwort',
    });
  }
}

if (status === 'invited') {
  updateData.formFinal = null;
  updateData.formDraft = null;
  updateData.formOverride = null;
  updateData.respondedAt = null;
}

Betroffene Flows die weiterhin funktionieren:

Flow formFinal vor status=confirmed Status
GuestFormModal → confirmWithForm (form-final + status) form-final zuerst OK
GuestFormModal → overrideForm (form-override) — (kein status-change) OK
Meine-Antwort → submitFinalSelf (form-final + status) form-final zuerst OK
Dropdown → "Eingeladen" (invited) — (kein confirmed) OK
Dropdown → "Abgesagt" (declined) — (kein confirmed) OK
Seed / POST /events (direct DB write) — (kein API-Call) OK
Direkter API-Call PUT status=confirmed ohne Form BLOCKIERT

Betroffene Dateien

Datei Änderung
backend/src/routes/guests.ts Prüfung auf formFinal bei confirmed
backend/src/__tests__/guests.test.ts Neuer Test: 400 bei confirmed ohne formFinal

Tests

Neuer Test in guests.test.ts:

it('gibt 400 bei confirmed ohne formFinal', async () => {
  mockFindUnique.mockResolvedValue({
    eventId: 'event-1',
    personId: 'p1',
    status: 'invited',
    formFinal: {},
  });

  const res = await server.inject({
    method: 'PUT',
    url: '/api/v1/events/event-1/guests/p1/status',
    headers: { authorization: 'Bearer token' },
    body: { status: 'confirmed' },
  });

  expect(res.statusCode).toBe(400);
  expect(res.json().message).toContain('Zusage erfordert');
});

Commit-Konvention

fix(backend): Status confirmed ohne formFinal blockiert

- PUT .../status prueft bei confirmed ob formFinal existiert
- 400 Bad Request falls kein formFinal vorhanden
### Problem `PUT /api/v1/events/{event_id}/guests/{person_id}/status` erlaubt aktuell jedes `status`-Value ohne Prüfung. Ein Aufruf mit `{ "status": "confirmed" }` setzt den Gast auf "zugesagt", auch wenn kein `formFinal` existiert. Das führt zu inkonsistenten Zuständen: Der Gast ist "zugesagt", hat aber nie ein Formular ausgefüllt. ### Änderungen **Datei:** `backend/src/routes/guests.ts` — `updateStatus`-Handler (ca. Zeile 249) Aktuell: ```typescript const updateData: Record<string, unknown> = { status, respondedAt: new Date() }; if (status === 'invited') { updateData.formFinal = null; updateData.formDraft = null; updateData.formOverride = null; updateData.respondedAt = null; } ``` Neu — vor dem `update`-Aufruf eine Prüfung einfügen: ```typescript const updateData: Record<string, unknown> = { status, respondedAt: new Date() }; if (status === 'confirmed') { const raw = existing.formFinal as Record<string, unknown> | null; if (!raw || Object.keys(raw).length === 0) { return reply.status(400).send({ statusCode: 400, error: 'Bad Request', message: 'Zusage erfordert eine Formular-Antwort', }); } } if (status === 'invited') { updateData.formFinal = null; updateData.formDraft = null; updateData.formOverride = null; updateData.respondedAt = null; } ``` **Betroffene Flows die weiterhin funktionieren:** | Flow | formFinal vor status=confirmed | Status | |------|-------------------------------|--------| | GuestFormModal → `confirmWithForm` (form-final + status) | ✅ form-final zuerst | OK | | GuestFormModal → `overrideForm` (form-override) | — (kein status-change) | OK | | Meine-Antwort → `submitFinalSelf` (form-final + status) | ✅ form-final zuerst | OK | | Dropdown → "Eingeladen" (invited) | — (kein confirmed) | OK | | Dropdown → "Abgesagt" (declined) | — (kein confirmed) | OK | | Seed / POST /events (direct DB write) | — (kein API-Call) | OK | | **Direkter API-Call `PUT status=confirmed` ohne Form** | ❌ | **BLOCKIERT** ✅ | ### Betroffene Dateien | Datei | Änderung | |-------|----------| | `backend/src/routes/guests.ts` | Prüfung auf `formFinal` bei `confirmed` | | `backend/src/__tests__/guests.test.ts` | Neuer Test: 400 bei `confirmed` ohne `formFinal` | ### Tests Neuer Test in `guests.test.ts`: ```typescript it('gibt 400 bei confirmed ohne formFinal', async () => { mockFindUnique.mockResolvedValue({ eventId: 'event-1', personId: 'p1', status: 'invited', formFinal: {}, }); const res = await server.inject({ method: 'PUT', url: '/api/v1/events/event-1/guests/p1/status', headers: { authorization: 'Bearer token' }, body: { status: 'confirmed' }, }); expect(res.statusCode).toBe(400); expect(res.json().message).toContain('Zusage erfordert'); }); ``` ### Commit-Konvention ``` fix(backend): Status confirmed ohne formFinal blockiert - PUT .../status prueft bei confirmed ob formFinal existiert - 400 Bad Request falls kein formFinal vorhanden ```
Author
Owner

Implemented in 925479a

  • PUT .../status prueft bei confirmed ob formFinal existiert
  • 400 Bad Request falls kein formFinal vorhanden
Implemented in 925479a - PUT .../status prueft bei confirmed ob formFinal existiert - 400 Bad Request falls kein formFinal vorhanden
Sign in to join this conversation.
No description provided.