notifications
Notificações in-app por usuário (socket + persistência) — não inclui push.
Tabela de notificações persistidas pra cada usuário. Quando um evento dispara (amigo aceitou, oferta recebida, post comentado), é INSERT aqui + emit socket pro recipient. Push (Web Push) é separado — alimentado pelos mesmos eventos via PushService.
A tabela é flat (sem subtipos) — type (enum) + entity_id (UUID polimórfico) carregam o contexto. Frontend interpreta type pra decidir copy e link de destino.
Colunas
| Coluna | Tipo | Nullable | Default |
|---|---|---|---|
| id ● | uuid | NOT NULL | gen_random_uuid() |
| recipient_id | uuid | NOT NULL | — |
| actor_id | uuid | NOT NULL | — |
| type | enumcolumn | NOT NULL | — |
| entity_id | uuid | NULL ok | — |
| read | boolean | NOT NULL | false |
| created_at | timestamp | NOT NULL | now() |
● primary key ◆ unique
Funcionalidade dos campos
id— UUID, PKrecipient_id— quem recebe (cascade)actor_id— quem causou (cascade). Permite renderizar "X comentou seu post"type— enum extensa, ver lista abaixoentity_id— UUID polimórfico: aponta pra um post, oferta, friendship, message, etc., dependendo dotype. Frontend resolve por type.read— boolean. Defaultfalse; UPDATE quando user clica/marca como lida.created_at— momento da notificação
Foreign keys
| Coluna(s) | Referência | ON DELETE | ON UPDATE |
|---|---|---|---|
| recipient_id | users.id | cascade | no action |
| actor_id | users.id | cascade | no action |
Índices
| Nome | Único | Colunas | WHERE (parcial) |
|---|---|---|---|
| notifications_recipient_created_at_idx | não | recipient_id, created_at | — |
notifications_recipient_created_at_idx — query principal: "minhas notificações ordem cronológica decrescente".
Constraints
PRIMARY KEY (id)
Tipos de notificação
Enum notification_type:
| Type | Disparado por | entity_id aponta pra | UI sugere |
|---|---|---|---|
friend_request | POST /friends/request | friendship pending | Aceitar/rejeitar |
friend_accepted | aceite de friendship | friendship | Abrir perfil |
post_comment | comment em post do user | post | Abrir post |
post_reaction | reaction em post do user | post | Abrir post |
dm_request_received | non-friend manda DM | dm_request | Abrir solicitações |
offer_received | nova oferta no listing | item_offer | Abrir Vitrine → ofertas |
offer_accepted | dono aceitou oferta | item_offer | Abrir Vitrine |
offer_rejected | dono rejeitou (ou auto-reject por close) | item_offer | Mostrar histórico |
offer_completed | transação executou | item_offer | Pedir avaliação |
offer_cancelled | ofertante cancelou | item_offer | — |
offer_expired | TTL 7 dias após accepted sem confirm | item_offer | — |
counter_proposal_received | counter-proposta nova | offer_proposal | Abrir negociação |
proposal_rejected | rodada rejeitada (offer continua) | offer_proposal | Counter-propor? |
rating_received | alguém te avaliou | listing_rating | Abrir reputação |
steam_import_done | batch Steam terminou ok | collection | Abrir coleção |
steam_import_partial | batch terminou com falhas | collection | Abrir coleção |
NotifySafe
Service NotificationsService.notify() é chamado de muitos pontos. Pra evitar que falhas de notificação derrubem fluxos principais, hooks usam notifySafe(...).catch(() => {}).
Push integration
Quando uma notificação é criada, NotificationsService emite via socket pro user (se conectado) E via Web Push (se houver push_subscriptions ativas). Implementação atual em chat.gateway.ts.
Se o user está com is_muted no chat ou tem push desabilitado, alguns disparos são suprimidos seletivamente.
Padrões de uso
- Disparar —
notifySafe({ recipientId, actorId, type, entityId })em todo lugar que cria ação relevante - Listar —
GET /notifications?cursor=...&limit=20 - Marcar como lida —
POST /notifications/:id/readouPOST /notifications/read-all - Contar não-lidas —
GET /notifications/unread-count
Tabelas relacionadas
users— recipient + actor (cascade)push_subscriptions— entrega via push- Polymorphic via
entity_id: posts, item_offers, offer_proposals, friendships, dm_requests, listing_ratings, collections (Steam imports)