Commit Graph

2 Commits

Author SHA1 Message Date
Arnas Donauskas
faf0f740a4
fix: stop inbound chats fragmenting into duplicate conversations (#363)
An inbound message could create a second conversation for a contact
under a race (Meta retries a delivery, or a batch fans out to concurrent
after() runs). There was no UNIQUE index and no unique-violation
backstop on conversations, unlike contacts (migration 022). Worse, once
two conversations existed the `.single()` lookup errored on every later
message and the webhook created yet another conversation each time,
snowballing into a wall of duplicate chats.

- findOrCreateConversation now resolves oldest-first with .limit(1)
  instead of .single(), so pre-existing duplicates converge, and catches
  23505 on insert to re-resolve the winning row.
- Same hardening applied to the public-API resolver (resolve-conversation).
- Migration 036 merges existing duplicate conversations into the oldest
  thread (re-pointing messages, reactions, deals, flow_runs,
  notifications, ai_usage_log — no data loss) then adds a
  UNIQUE (account_id, contact_id) index as the authoritative guarantee.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:27:46 +03:00
Arnas Donauskas
8cb5792823 feat(api): public REST API v1 — messages, contacts, conversations, broadcasts (#245)
Complete the public API roadmap on top of the existing key-auth layer
(#290). Re-lands POST /api/v1/messages (lost when the stacked #291 never
reached main) by extracting the dashboard send route's core into
`sendMessageToConversation`, then adds:

- Contacts: GET/POST /api/v1/contacts, GET/PATCH /api/v1/contacts/{id}
- Conversations: GET list + /{id} + /{id}/messages
- Broadcasts: POST /api/v1/broadcasts + GET /{id} (persist rows, fan out
  in after(), poll for status)

All list endpoints share one keyset-pagination contract
({ data, meta: { next_cursor } }). No migration required — scopes and
tables already exist. Versioned as 0.3.0 (folded into [Unreleased]);
docs/public-api.md updated; outbound webhooks left as the sole roadmap
item. API stays v1 (additive).

Includes fixes from an adversarial code review: cursor validation
against filter injection, broadcast counts deferred to the DB aggregate
trigger, terminal broadcast status reflects failures, duplicate-phone
de-dup, up-front send-param validation (no orphan conversations),
PATCH null-clear semantics, error-checked tag sync, bounded tag filter,
unified audit-user attribution, and a maxDuration on the broadcast
fan-out.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 14:10:19 +03:00