nordabiz/docs/superpowers/specs/2026-03-20-group-messages-and-search-design.md
Maciej Pienczyn 2b0907c2ad docs: design spec for group messages and message search
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 10:54:57 +01:00

9.2 KiB

Wiadomości grupowe i wyszukiwanie — Design Spec

Data: 2026-03-20 Status: Approved Moduł: Messages (/wiadomosci)

Kontekst

Moduł wiadomości obsługuje wyłącznie komunikację 1:1 (tabela private_messages z sender_id/recipient_id, wątki przez parent_id). Brak wyszukiwania i brak możliwości pisania do wielu osób jednocześnie.

Zakres

Dwie niezależne funkcjonalności:

  1. Wyszukiwanie wiadomości — pasek search w skrzynce
  2. Wiadomości grupowe — czaty grupowe (ad-hoc i nazwane grupy)

1. Wyszukiwanie wiadomości

Opis

Pasek search na górze listy wiadomości w /wiadomosci (inbox) i /wiadomosci/wyslane (sent). Jedno pole tekstowe z ikoną lupy, placeholder "Szukaj w wiadomościach...".

Zakres wyszukiwania

Wyszukuje jednocześnie po:

  • Temacie (subject)
  • Treści (content — stripped z HTML tagów)
  • Nadawcy/Odbiorcy (imię, nazwisko użytkownika, nazwa firmy powiązanej)

Implementacja

  • PostgreSQL ILIKE na polach subject, regexp_replace(content, '<[^>]+>', '', 'g') (strip HTML) z JOIN na users (imię, nazwisko) i companies (nazwa firmy). Przy ~150 firmach wydajność wystarczająca bez indeksów FTS
  • Parametr ?q= w URL — zachowanie wyszukiwania po odświeżeniu strony
  • Wyniki wyświetlane w tej samej liście co inbox, z zachowaniem paginacji (20/strona)
  • Podświetlenie frazy w wynikach

UI

  • Pole tekstowe nad listą wiadomości, pod nagłówkiem strony
  • Ikona lupy po lewej, przycisk "x" do czyszczenia po prawej (gdy query niepuste)
  • Wyszukiwanie uruchamiane po wciśnięciu Enter lub po 500ms debounce
  • Pusta lista wyników → komunikat "Nie znaleziono wiadomości"

2. Wiadomości grupowe

Model danych

Tabela message_group

Kolumna Typ Opis
id Integer, PK
name String(255), nullable Nazwa grupy (pusta = ad-hoc czat)
description Text, nullable Opis grupy
owner_id FK → users.id Twórca grupy
is_named Boolean, default=False True = trwała nazwana grupa
created_at DateTime
updated_at DateTime Aktualizowane przy nowej wiadomości

Tabela message_group_member

Kolumna Typ Opis
group_id FK → message_group.id (CASCADE), PK
user_id FK → users.id (CASCADE), PK
role Enum('owner', 'moderator', 'member') Rola w grupie
last_read_at DateTime, nullable Timestamp ostatniego odczytu — wiadomości z created_at > last_read_at są nieprzeczytane
joined_at DateTime
added_by_id FK → users.id, nullable Kto dodał

Tabela group_message

Kolumna Typ Opis
id Integer, PK
group_id FK → message_group.id (CASCADE)
sender_id FK → users.id (SET NULL), nullable Zachowaj wiadomość po usunięciu usera
content Text, NOT NULL Treść (HTML z Quill)
created_at DateTime

Załączniki

Rozszerzenie istniejącej tabeli message_attachments:

  • Dodanie nullable group_message_id (FK → group_message.id, CASCADE)
  • ALTER istniejącego message_id na nullable (obecnie NOT NULL)
  • CHECK constraint: (message_id IS NOT NULL) != (group_message_id IS NOT NULL) — dokładnie jedno z dwóch musi być ustawione

Śledzenie przeczytanych wiadomości

Podejście: kolumna last_read_at na message_group_member. Aktualizowana przy otwarciu widoku grupy (GET /wiadomosci/grupa/<id>). Liczba nieprzeczytanych = COUNT z group_message WHERE created_at > last_read_at AND sender_id != user_id. Endpoint /api/messages/unread-count rozszerzony o sumę nieprzeczytanych z grup.

Role w grupie

Rola Może pisać Może dodawać/usuwać osoby Może nadawać moderatora Może usunąć grupę
owner tak tak tak tak
moderator tak tak nie nie
member tak nie nie nie
  • Właściciel może nadać rolę moderator dowolnemu uczestnikowi
  • Tylko właściciel może nadawać/odbierać rolę moderatora
  • Moderator NIE może usunąć właściciela z grupy
  • Zapraszać można wyłącznie osoby z aktywnym członkostwem Nordy
  • Blokady (UserBlock): zablokowany użytkownik nie może być dodany do grupy, w której jest blokujący
  • Jeśli konto właściciela zostanie dezaktywowane → najstarszy moderator (lub najstarszy członek) zostaje automatycznie właścicielem

Tworzenie grupy

Nowy przycisk "Nowa grupa" obok istniejącego "Nowa wiadomość" w /wiadomosci.

Formularz:

  • Opcjonalna nazwa grupy (pusta → ad-hoc czat wyświetlany jako lista imion)
  • Autocomplete wyboru osób (reuse istniejącego autocomplete z compose.html, rozszerzony o multi-select)
  • Pierwsza wiadomość w formularzu (Quill editor)
  • Załączniki (drag & drop, reuse MessageUploadService)

Widok skrzynki (inbox)

Czaty grupowe pojawiają się w tym samym inbox co wiadomości 1:1:

  • Badge z liczbą uczestników (np. "👥 4")
  • Nazwane grupy wyświetlane pod nazwą (np. "Zarząd Nordy")
  • Ad-hoc czaty wyświetlane jako lista imion (np. "Jan, Anna, Piotr")
  • Sortowane po dacie ostatniej wiadomości (razem z 1:1)
  • Nieprzeczytane wiadomości oznaczone jak dotychczas (bold + badge)

Widok czatu grupowego

Styl konwersacji (flat chat):

  • Lista wiadomości chronologicznie (najstarsze na górze)
  • Każda wiadomość: avatar + imię nadawcy + timestamp + treść
  • Na dole: pole do pisania (Quill) + załączniki
  • Sidebar (lub sekcja na górze na mobile): lista uczestników z rolami
  • Przycisk "Zarządzaj grupą" widoczny dla owner/moderator

Zarządzanie grupą

Panel dostępny dla owner i moderator:

  • Dodawanie nowych osób (autocomplete, tylko aktywni członkowie)
  • Usuwanie osób z grupy
  • Właściciel dodatkowo: nadawanie/odbieranie roli moderator
  • Zmiana nazwy i opisu grupy (tylko owner)

Powiadomienia

  • Nowa wiadomość w grupie → UserNotification dla wszystkich uczestników (oprócz nadawcy)
  • Email notification (reuse build_message_notification_email)
  • Treść: "[Nazwa grupy / lista imion] — Nowa wiadomość od {nadawca}"
  • Przy obecnej skali (~150 firm) volume powiadomień jest akceptowalny. Jeśli grupy będą duże i aktywne — dodamy throttling/digest w przyszłości

Ad-hoc vs nazwana grupa

Cecha Ad-hoc Nazwana
Nazwa Brak — wyświetlana jako lista imion Wyświetlana pod swoją nazwą
Trwałość Żyje w historii Żyje w historii
Zarządzanie Identyczne Identyczne
Różnica Tylko prezentacja Tylko prezentacja

Wyszukiwanie w grupach

Pasek search z sekcji 1 obejmuje również wiadomości grupowe — szuka po nazwie grupy (zamiennik subject dla grup), treści wiadomości i uczestnikach. Wyniki z 1:1 i grup łączone w Pythonie (dwa osobne query, merge po dacie) — nie SQL UNION, bo modele są różne.

Inbox — łączenie 1:1 i grup

Dwa osobne query:

  1. PrivateMessage jak dotychczas (inbox: WHERE recipient_id = current_user)
  2. MessageGroup WHERE current_user jest członkiem, z subquery na ostatnią wiadomość i liczbę nieprzeczytanych

Wyniki łączone w Pythonie, sortowane po dacie ostatniej aktywności, paginowane. Podgląd ostatniej wiadomości pobierany z group_message (ORDER BY created_at DESC LIMIT 1 per group).


Avatary — zdjęcia profilowe zamiast inicjałów

Obecne szablony wiadomości wyświetlają inicjały (pierwsza litera imienia) jako avatar. Model User ma pole avatar_path ze zdjęciem profilowym. Przy okazji implementacji wiadomości grupowych:

  • Jeśli użytkownik ma avatar_path → wyświetl <img> ze zdjęciem
  • Jeśli nie → fallback na inicjał (jak dotychczas)
  • Dotyczy: inbox, sent, view (wątki 1:1), group_view, compose (autocomplete, preview)

Czego NIE robimy

  • Reakcje/emoji na wiadomościach
  • Przypinanie wiadomości
  • Wątki wewnątrz grupy (flat chat only)
  • Wyciszanie grup
  • Opuszczanie grupy przez uczestnika (tylko owner/moderator usuwa)
  • Limity liczby grup lub uczestników (na razie)

Nowe routes

Method URL Opis
GET /wiadomosci/nowa-grupa Formularz tworzenia grupy
POST /wiadomosci/grupa/utworz Utworzenie grupy + pierwsza wiadomość
GET /wiadomosci/grupa/<id> Widok czatu grupowego
POST /wiadomosci/grupa/<id>/wyslij Wysłanie wiadomości do grupy
GET/POST /wiadomosci/grupa/<id>/zarzadzaj Panel zarządzania członkami
POST /wiadomosci/grupa/<id>/dodaj-czlonka Dodanie osoby
POST /wiadomosci/grupa/<id>/usun-czlonka Usunięcie osoby
POST /wiadomosci/grupa/<id>/zmien-role Zmiana roli (owner only)

Nowe szablony

Plik Opis
templates/messages/group_compose.html Formularz tworzenia grupy
templates/messages/group_view.html Widok czatu grupowego
templates/messages/group_manage.html Panel zarządzania członkami

Migracje SQL

Plik Opis
088_message_groups.sql Tabele message_group, message_group_member, group_message
089_message_attachments_group.sql Dodanie group_message_id do message_attachments