Geek Social — Documentação
Módulos

Offers

Sistema de ofertas com counter-propostas (proposal rounds) e dual confirmation.

Visão geral

Offers modelam negociação entre ofertante e dono de um listing. Suporta:

  • Tipo buy (com dinheiro) ou trade (com outro item)
  • Counter-propostas via tabela offer_proposals — apenas 1 proposal pending por offer ao mesmo tempo
  • Dual confirmation antes da transferência efetivar
  • Auto-create de coleção destino se receptor não tem
  • Auto-rejeição quando listing é encerrado

A offer "como um todo" tem status pending → accepted → completed (ou rejected/cancelled/expired). A negociação real (counters) acontece em proposals; quando uma proposal é aceita, a offer evolui pra accepted.

Entidades principais

TabelaPapel
item_offersOffer guarda-chuva (status global, dual confirm timestamps)
offer_proposalsRodadas de negociação (pending/accepted/rejected/superseded)
listingsAnúncio alvo
itemsItem alvo + opcional offered_item_id (em trade)

Endpoints

10 endpoints em /offers:

EndpointResumo
POST /offersCriar oferta
GET /offers/receivedRecebidas (filtrar por status)
GET /offers/sentEnviadas
GET /offers/:idDetalhes (inclui latestProposal)
POST /offers/:id/acceptAceitar a proposal corrente
POST /offers/:id/rejectRejeitar a proposal corrente
POST /offers/:id/cancelOfertante cancela
POST /offers/:id/confirmDual confirm + execute transfer
POST /offers/:id/proposeCounter-proposta
GET /offers/:id/historyTimeline de proposals

Fluxos

Criar oferta

Aceitar proposal corrente

Counter-proposta

Dual confirm + transferência

Cancelar (offerer)

Auto-expiração (cron)

runOffersExpire roda a cada 1h:

UPDATE item_offers
SET status = 'expired'
WHERE status = 'accepted'
  AND (offerer_confirmed_at IS NULL OR owner_confirmed_at IS NULL)
  AND updated_at < now() - INTERVAL '7 days'

Notifica ambos os lados com offer_expired.

Eventos de socket

Eventos via NotificationsService:

  • offer_received, offer_accepted, offer_rejected, offer_cancelled, offer_completed, offer_expired
  • counter_proposal_received, proposal_rejected

Edge cases e regras especiais

  • Pre-check anti-500: oferta duplicada retorna 409 com DUPLICATE_PENDING_OFFER, não erro de unique constraint
  • isMyTurn no frontend — calculado de latestProposal.proposerId !== currentUserId
  • Confirm idempotente — re-chamar não falha; se transfer já rodou, retorna estado atual
  • Auto-create de coleção destino — receptor não precisa ter coleção compatível previamente
  • Hard delete de listing seta listing_id=NULL em offers (preserva offer history)
  • Janela de avaliação 30 dias após offer_completed — após isso, rate retorna RATING_WINDOW_CLOSED
  • offered_item_id set null se item original deletado — preserva offer record
  • Reject só rejeita rodada, não offer inteira. Pra cancelar, ofertante usa /cancel.
  • Trade requires both items present — se algum foi transferido em outra offer entre a criação e o confirm, falha com TRADE_ITEM_UNAVAILABLE.

Dependências entre módulos

  • Listings — auto-reject em close, hard delete preserva via set null
  • Items — alvo + opcional offered + transferência muda collection_id
  • Collections — auto-create destino em transfer
  • Listing Ratings — habilitado após completed
  • Notifications — 8 tipos diferentes
  • Friends — block impede oferta entre user bloqueado/bloqueador

On this page