nordabiz/docs/superpowers/specs/2026-03-27-messaging-redesign-design.md
Maciej Pienczyn 110d971dca
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
feat: migrate prod docs to OVH VPS + UTC→Warsaw timezone in all templates
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>
2026-04-06 13:41:53 +02:00

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

  1. Ujednolicony model — 1:1 i grupy w jednym modelu Conversation + Message. Rozmowa 1:1 = konwersacja z 2 uczestnikami.
  2. Real-time: SSE — Server-Sent Events + Redis pub/sub. Jedno połączenie SSE per użytkownik.
  3. 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.

  • Backend wykrywa URL, pobiera stronę (timeout 3s), parsuje og:title/og:description/og:image
  • Fallback: