* fix(conversations): enforce NOT NULL + FK on contact_id
Conversations had contact_id nullable with no FK to contacts. Combined
with dependent: :destroy_async on Contact#conversations, deleting a
contact could leave conversations pointing to a missing contact,
breaking the conversations#index API with
"undefined method 'additional_attributes' for nil" from the contact
partial.
Changes:
- Migration cleans up existing orphans, sets contact_id NOT NULL and
adds a FK with ON DELETE CASCADE so the invariant is enforced at the
DB level (complements the existing Rails presence validation).
- ContactMergeAction uses update_all for conversations/messages/notes/
contact_inboxes so a failing callback cannot silently leave records
pointing to the mergee contact before it is destroyed.
- Drop the now-redundant orphan filter in Conversations::ResolutionJob
and its spec; the invariant is enforced at the schema level.
* fix: address review feedback
- Drop the ON DELETE CASCADE FK on conversations.contact_id. Several
conversation-owned tables (messages, mentions, conversation_participants,
reporting_events, csat_survey_responses, calls, applied_slas, sla_events,
and polymorphic notifications) still have plain conversation_id references
without FK cascades. The DB-level cascade would skip Conversation's
dependent: cleanup and replace the NULL-contact bug with orphan children,
and would also conflict with the existing non-cascade FKs on
scheduled_messages/recurring_scheduled_messages. Keep the invariant at the
Rails layer (NOT NULL + presence validation + dependent: :destroy_async).
- Clean up orphan conversations in the migration via Rails destroy so
dependent associations are propagated correctly, instead of a raw
DELETE FROM conversations that would orphan all child rows.
- Revert ContactMergeAction.merge_* methods back to per-record update! so
Conversation#after_update_commit still fires (notify_status_change /
CONVERSATION_CONTACT_CHANGED) for contact_id changes. The bang form
still removes the silent-failure risk of the original .update call.
We observed some rare instances on prod where a contact inbox was
getting deleted during the contact merge action. the potential suspect
is contact inbox objects already loaded into the memory getting cleared
through the destroy association job. Reloading the object to avoid this
case !!!
At present, the websocket pubsub tokens are present at the contact objects in chatwoot. A better approach would be to have these tokens at the contact_inbox object instead. This helps chatwoot to deliver the websocket events targetted to the specific widget connection, stop contact events from leaking into other chat sessions from the same contact.
Fixes#1682Fixes#1664
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
When an agent updates a contact email in the dashboard and the contact also update his email via the email collect box,
Merge action receives the same base and mergee contacts which could cause data corruption.