Incident: W4 File Upload Broken — 2026-07-01

PoleWartość
IDINCIDENT-2026-07-01-002
PlatformaW4 (w4.pinbox24.com)
SevernośćP1 — upload plików niedostępny
StatusRESOLVED
Wykryto2026-07-01T12:37Z
Root cause ustalony2026-07-01T12:50Z
Hotfix wdrożony2026-07-01T13:05Z (s3-v2-v42-prod PM2 reload)
Zweryfikowano2026-07-01T16:00Z (patch na linii 162, v42-prod stable 0 restartów)
GH Issueradieu/p24-infra#2403

Symptom

Upload pliku w Pinbox24 W4 (w4.pinbox24.com) zwraca HTTP 500. Przeglądarka następnie robi GET na ten sam URL i dostaje 404.

{"success":false,"message":"Method not found.","stack":"HTTP404Error: Method not found.\n    at Object.exports.notFoundError (/app/src/util/ErrorHandler.ts:6:11)..."}

Oś czasu

Czas (UTC)Zdarzenie
12:37Użytkownik testuje upload W4 po naprawie W3
12:38POST /api/offices/files/uploadHTTP 500 (nginx)
12:38Browser fallback: GET /api/offices/files/upload404
12:50Root cause ustalony z logów v42-prod i kodu s3-v2-v42-prod
13:05Hotfix wdrożony: res.json({ result: filesAdded }) + PM2 reload 4 workers
15:14Rotation agent zrestartował s3-v2-v42-prod (Wasabi key rotation) — patch zgubiony
15:32Patch re-aplikowany ponownie (Python script via SSH)
15:45v42-prod crash-loop (506 restartów) — root cause: 8 brakujących pinbox24Public* zmiennych
15:58Druga sesja naprawiła crash-loop v42-prod (dodała brakujące env vars)
16:00Weryfikacja: patch na miejscu (linia 162), v42-prod 2 workers online, 0 restartów

Root cause

Contract mismatch między s3-v2-v42-prod a v42-prod.

Przepływ W4

Browser → api.w4.pinbox24.com → nginx → v42-prod (TypeScript)
v42-prod uploadFile controller → POST http://s3-v2-v42-prod:3000/api/v3/storage/{officeId}/upload
s3-v2-v42-prod → Wasabi + MongoDB → response

Błąd

s3-v2-v42-prod storage.controller.js zwraca:

res.json(filesAdded);   // bare array [fileDoc, ...]

v42-prod s3Request.helper.js sprawdza:

if (httpResponse.statusCode === 200 && body && body.result && body.result[0]) {
    resolve(body.result[0]);    // ← body.result jest undefined na bare array
} else {
    reject(new Error(`...S3_statusCode: ${httpResponse.statusCode}...`));
}

body.result jest undefined na bare array → warunek false → reject → v42-prod zwraca 500.

Potwierdzenie z logów

v42-prod err log 14:38 (CEST = 12:38 UTC):
Server: Error: Error occurred while uploading the file.
  Reason: S3_statusCode: 200 and S3_statusMessage: OK

S3_statusCode: 200 dowodzi, że Wasabi upload i MongoDB save w s3-v2-v42-prod działają poprawnie.


Fix

Jedna linia w s3-v2-v42-prod /app/dist/apps/storage/storage.controller.js:

// PRZED (linia ~65):
res.json(filesAdded);
 
// PO:
res.json({ result: filesAdded });

Następnie: docker exec s3-v2-v42-prod pm2 restart s3-v2-v42-prod_backend

Hotfix jest idempotentny — files w Wasabi i MongoDB są poprawne, tylko response wraca do przeglądarki z właściwym formatem.


Zmienione pliki / kontenery (po wdrożeniu fix)

KontenerPlikZmiana
s3-v2-v42-prod na bms-1/app/dist/apps/storage/storage.controller.jsres.json(filesAdded)res.json({ result: filesAdded })

Rollback

# Oryginalny plik: /tmp/s3-v2-v42-storage-controller.js (backup z bms-1)
docker cp /tmp/s3-v2-v42-storage-controller.js s3-v2-v42-prod:/app/dist/apps/storage/storage.controller.js
docker exec s3-v2-v42-prod pm2 restart s3-v2-v42-prod_backend

Długoterminowy fix

Poprawić kontrakt między s3-v2-v42-prod a v42-prod w kodzie TypeScript źródłowym. Opcja A: fix w s3-v2-v42-prod (zwracaj {result: []}) Opcja B: fix w v42-prod (akceptuj bare array)

Opcja A jest preferowana — v42-prod ma spójne oczekiwanie na body.result[0] w całej bazie kodu.


Dodatkowy incydent — v42-prod crash-loop (2026-07-01T15:45Z)

Po rotacji REDIS_PASSWORD + Wasabi key agent zrestartował v42-prod (nowe środowisko). v42-prod wpadł w crash-loop (506 restartów, uptime 0-1s).

Root cause: publicFiles.helper.ts:16 inicjalizuje multer-s3 na starcie modułu. multer-s3 wymaga env s3Bucket_api_bucket (i 7 innych pinbox24Public* zmiennych), które nie były w backend-environment.env — były tylko w s3-v2-environment.env dla oddzielnego kontenera s3-v2-v42-prod.

Fix: dodanie 8 brakujących zmiennych do backend-environment.env + restart v42-prod.

Backup hotfix script: docs/pinbox24/hotfix-w4-s3-response-format.sh (re-aplikuj po każdym recreate s3-v2-v42-prod)


Lekcje

  1. Contract test między usługami — brak testu API między v42-prod a s3-v2-v42-prod. Integration test sprawdzający response format wykryłby to natychmiast.
  2. Inne s3 odpowiedzi — s3-v32-prod zwraca {result: []}, s3-v2-v42-prod zwraca []. Niezgodność typów między wersjami.
  3. HTTP 500 nie loguje URL — v42-prod logger nie loguje 500 z URL (tylko error message). Trudno było zlokalizować 500 bez nginx access logu.
  4. Env vars split across containerspublicFiles.helper.ts ładuje się przy starcie v42-prod i wymaga zmiennych, które logicznie należą do s3-v2-v42-prod. Każda zmiana env powinna obejmować obie grupy zmiennych.
  5. Hotfix nie przeżywa container recreate — patch na skompilowanym JS w kontenerze ginie przy docker restart. Backup skryptu w repozytorium jest obowiązkowy.