Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
Production moved from on-prem VM 249 (10.22.68.249) to OVH VPS (57.128.200.27, inpi-vps-waw01). Updated ALL documentation, slash commands, memory files, architecture docs, and deploy procedures. Added |local_time Jinja filter (UTC→Europe/Warsaw) and converted 155 .strftime() calls across 71 templates so timestamps display in Polish timezone regardless of server timezone. Also includes: created_by_id tracking, abort import fix, ICS calendar fix for missing end times, Pros Poland data cleanup. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6.6 KiB
6.6 KiB
Messaging Redesign — Conversation-Based System
Data: 2026-03-27 Status: Zaakceptowany Zakres: Przebudowa systemu wiadomości z email-like (Odebrane/Wysłane) na konwersacyjny (Messenger/WhatsApp)
Decyzje architektoniczne
- Ujednolicony model — 1:1 i grupy w jednym modelu
Conversation+Message. Rozmowa 1:1 = konwersacja z 2 uczestnikami. - Real-time: SSE — Server-Sent Events + Redis pub/sub. Jedno połączenie SSE per użytkownik.
- Migracja danych — istniejące wiadomości migrowane do nowego modelu. Stare tabele zostają jako backup.
Model danych
conversations
| Kolumna | Typ | Opis |
|---|---|---|
| id | Serial PK | |
| name | String(255), nullable | Null dla 1:1, nadana nazwa dla grup |
| is_group | Boolean | False = 1:1, True = grupa |
| owner_id | FK → users | Twórca |
| created_at | DateTime | |
| updated_at | DateTime | Aktualizowane przy każdej wiadomości |
| last_message_id | FK → messages, nullable | Denormalizacja dla listy |
conversation_members
| Kolumna | Typ | Opis |
|---|---|---|
| conversation_id | FK → conversations, PK | |
| user_id | FK → users, PK | |
| role | String(20) | 'owner', 'member' |
| last_read_at | DateTime | Read receipts |
| is_muted | Boolean | Wyciszenie email + push |
| is_archived | Boolean | Ukrycie z listy |
| joined_at | DateTime | |
| added_by_id | FK → users, nullable |
messages
| Kolumna | Typ | Opis |
|---|---|---|
| id | Serial PK | |
| conversation_id | FK → conversations | |
| sender_id | FK → users | |
| content | Text | HTML (Quill) |
| reply_to_id | FK → messages, nullable | Cytowanie |
| edited_at | DateTime, nullable | |
| is_deleted | Boolean | Soft delete |
| link_preview | JSONB, nullable | {url, title, description, image} |
| created_at | DateTime |
message_reactions
| Kolumna | Typ | Opis |
|---|---|---|
| id | Serial PK | |
| message_id | FK → messages | |
| user_id | FK → users | |
| emoji | String(10) | |
| created_at | DateTime | |
| UNIQUE | (message_id, user_id, emoji) |
message_pins
| Kolumna | Typ | Opis |
|---|---|---|
| id | Serial PK | |
| conversation_id | FK → conversations | |
| message_id | FK → messages | |
| pinned_by_id | FK → users | |
| created_at | DateTime |
message_attachments
Istniejąca tabela. Nowa kolumna new_message_id FK → messages, nullable.
SSE Real-time
Endpoint
GET /api/messages/stream — jedno połączenie per użytkownik.
Zdarzenia
| Event | Dane | Kiedy |
|---|---|---|
| new_message | conversation_id, message JSON | Nowa wiadomość |
| message_read | conversation_id, user_id, read_at | Przeczytano |
| typing | conversation_id, user_id, user_name | Ktoś pisze (TTL 3s) |
| reaction | message_id, user_id, emoji, action | Reakcja |
| message_edited | message_id, new_content, edited_at | Edycja |
| message_deleted | message_id, conversation_id | Usunięcie |
| message_pinned | message_id, conversation_id, pinned_by | Przypięcie |
| presence | user_id, status, last_seen | Online/offline |
Infrastruktura
- Redis pub/sub do rozgłaszania między workerami Gunicorn
- Online status: Redis SETEX z TTL 60s, heartbeat co 30s
- Typing: POST /api/conversations//typing → Redis publish, TTL 3s
API Endpoints
Konwersacje
| Method | URL | Opis |
|---|---|---|
| GET | /wiadomosci | Widok konwersacyjny (HTML) |
| GET | /api/conversations | Lista konwersacji JSON |
| POST | /api/conversations | Nowa (deduplikacja 1:1) |
| GET | /api/conversations/ | Szczegóły + członkowie |
| PATCH | /api/conversations/ | Edytuj nazwę/opis |
| DELETE | /api/conversations/ | Usuń (owner) |
| POST | /api/conversations//members | Dodaj członka |
| DELETE | /api/conversations//members/ | Usuń członka |
| PATCH | /api/conversations//settings | Mute/archive |
Wiadomości
| Method | URL | Opis |
|---|---|---|
| GET | /api/conversations//messages | Paginacja cursor-based |
| POST | /api/conversations//messages | Wyślij |
| PATCH | /api/messages/ | Edytuj (swoje, max 24h) |
| DELETE | /api/messages/ | Soft delete (swoje) |
| POST | /api/messages//forward | Przekaż |
| POST | /api/conversations//read | Oznacz przeczytane |
| POST | /api/conversations//typing | Typing indicator |
Reakcje i przypięcia
| Method | URL | Opis |
|---|---|---|
| POST | /api/messages//reactions | Dodaj |
| DELETE | /api/messages//reactions/ | Usuń |
| POST | /api/messages//pin | Przypnij |
| DELETE | /api/messages//pin | Odepnij |
| GET | /api/conversations//pins | Lista przypiętych |
Inne
| Method | URL | Opis |
|---|---|---|
| GET | /api/messages/stream | SSE |
| GET | /api/users/presence | Online status (batch) |
| POST | /api/messages/upload | Upload pliku |
Frontend
Desktop
- Lewy panel (380px): lista konwersacji posortowana po updated_at
- Prawy panel: nagłówek (avatar, imię, status, typing) + wiadomości (bąbelki) + input (Quill)
Wiadomości
- Bąbelki: moje (niebieskie, prawo) / cudze (szare, lewo)
- Separatory dat
- Reply-to: cytat nad odpowiedzią
- Edytowane: etykieta "(edytowano)"
- Usunięte: "Wiadomość usunięta"
- Załączniki inline (obrazy jako podgląd, pliki jako pill)
- Reakcje: pill badges pod bąbelkiem
- Link preview: karta z tytułem + opisem
- Read receipts 1:1: ptaszki (wysłano/doręczono/przeczytano), hover → timestampy
- Read receipts grupa: awatary (max 4 + "+N")
Menu kontekstowe (hover/long-press)
Odpowiedz, Reaguj (6 emoji), Przekaż, Przypnij, Edytuj, Usuń
Mobile (< 768px)
Lista LUB chat (nie oba). Przycisk "Wróć". Menu kontekstowe jako bottom sheet.
Email notifications
if member.is_muted → nie wysyłaj
elif not user.notify_email_messages → nie wysyłaj
else → wysyłaj
Wyciszona konwersacja: ikona 🔇 na liście.
Link preview
- Backend wykrywa URL, pobiera stronę (timeout 3s), parsuje og:title/og:description/og:image
- Fallback: