-- name: unsnooze-all UPDATE conversations SET snoozed_until = NULL, status_id = (SELECT id FROM conversation_statuses WHERE name = 'Open') WHERE snoozed_until <= NOW(); -- name: insert-conversation WITH status_id AS ( SELECT id FROM conversation_statuses WHERE name = $3 ), reference_number AS ( SELECT generate_reference_number($8) AS reference_number ) INSERT INTO conversations (contact_id, contact_channel_id, status_id, inbox_id, last_message, last_message_at, subject, reference_number) VALUES( $1, $2, (SELECT id FROM status_id), $4, $5, $6, CASE WHEN $9 = TRUE THEN CONCAT($7::text, ' - #', (SELECT reference_number FROM reference_number), '') ELSE $7::text END, (SELECT reference_number FROM reference_number) ) RETURNING id, uuid; -- name: get-conversations -- $1 = viewing user ID for per-agent unread count -- $2 = include mentioned message UUID (true for mentioned inbox, false otherwise) SELECT COUNT(*) OVER() as total, conversations.id, conversations.created_at, conversations.updated_at, conversations.uuid, conversations.waiting_since, users.created_at as "contact.created_at", users.updated_at as "contact.updated_at", users.first_name as "contact.first_name", users.last_name as "contact.last_name", users.avatar_url as "contact.avatar_url", inboxes.channel as inbox_channel, inboxes.name as inbox_name, conversations.sla_policy_id, conversations.first_reply_at, conversations.last_reply_at, conversations.resolved_at, conversations.subject, conversations.last_message, conversations.last_message_at, conversations.last_message_sender, conversations.last_interaction, conversations.last_interaction_at, conversations.last_interaction_sender, conversations.next_sla_deadline_at, conversations.priority_id, ( SELECT CASE WHEN COUNT(*) > 9 THEN 10 ELSE COUNT(*) END FROM ( SELECT 1 FROM conversation_messages WHERE conversation_id = conversations.id AND created_at > COALESCE( (SELECT last_seen_at FROM conversation_last_seen WHERE conversation_id = conversations.id AND user_id = $1), '1970-01-01'::TIMESTAMPTZ ) LIMIT 10 ) t ) as unread_message_count, conversation_statuses.name as status, conversation_priorities.name as priority, as_latest.first_response_deadline_at, as_latest.resolution_deadline_at, as_latest.id as applied_sla_id, nxt_resp_event.deadline_at AS next_response_deadline_at, nxt_resp_event.met_at as next_response_met_at, CASE WHEN $2 = true THEN ( SELECT msg.uuid FROM conversation_mentions cm2 JOIN conversation_messages msg ON msg.id = cm2.message_id WHERE cm2.conversation_id = conversations.id AND (cm2.mentioned_user_id = $1 OR EXISTS( SELECT 1 FROM team_members tm2 WHERE tm2.team_id = cm2.mentioned_team_id AND tm2.user_id = $1 )) ORDER BY cm2.created_at DESC LIMIT 1 ) ELSE NULL END as mentioned_message_uuid FROM conversations JOIN users ON contact_id = users.id JOIN inboxes ON inbox_id = inboxes.id LEFT JOIN conversation_statuses ON status_id = conversation_statuses.id LEFT JOIN conversation_priorities ON priority_id = conversation_priorities.id LEFT JOIN LATERAL ( SELECT id, first_response_deadline_at, resolution_deadline_at FROM applied_slas WHERE conversation_id = conversations.id ORDER BY created_at DESC LIMIT 1 ) as_latest ON true LEFT JOIN LATERAL ( SELECT se.deadline_at, se.met_at FROM sla_events se WHERE se.applied_sla_id = as_latest.id AND se.type = 'next_response' ORDER BY se.created_at DESC LIMIT 1 ) nxt_resp_event ON true WHERE 1=1 %s -- name: get-conversation SELECT c.id, c.created_at, c.updated_at, c.closed_at, c.resolved_at, c.inbox_id, inb.name as inbox_name, COALESCE(inb.from, '') as inbox_mail, COALESCE(inb.channel::TEXT, '') as inbox_channel, c.status_id, c.priority_id, p.name as priority, s.name as status, c.uuid, c.reference_number, c.first_reply_at, c.last_reply_at, c.waiting_since, c.assigned_user_id, c.assigned_team_id, c.subject, c.contact_id, c.sla_policy_id, c.meta, sla.name as sla_policy_name, c.last_message_at, c.last_message_sender, c.last_message, c.last_interaction, c.last_interaction_at, c.last_interaction_sender, c.custom_attributes, (SELECT COALESCE( (SELECT json_agg(t.name) FROM tags t INNER JOIN conversation_tags ct ON ct.tag_id = t.id WHERE ct.conversation_id = c.id), '[]'::json )) AS tags, ct.id as "contact.id", ct.created_at as "contact.created_at", ct.updated_at as "contact.updated_at", ct.first_name as "contact.first_name", ct.last_name as "contact.last_name", ct.email as "contact.email", ct.type as "contact.type", ct.availability_status as "contact.availability_status", ct.avatar_url as "contact.avatar_url", ct.phone_number as "contact.phone_number", ct.phone_number_country_code as "contact.phone_number_country_code", ct.custom_attributes as "contact.custom_attributes", ct.enabled as "contact.enabled", ct.last_active_at as "contact.last_active_at", ct.last_login_at as "contact.last_login_at", as_latest.first_response_deadline_at, as_latest.resolution_deadline_at, as_latest.id as applied_sla_id, nxt_resp_event.deadline_at AS next_response_deadline_at, nxt_resp_event.met_at as next_response_met_at FROM conversations c JOIN users ct ON c.contact_id = ct.id JOIN inboxes inb ON c.inbox_id = inb.id LEFT JOIN sla_policies sla ON c.sla_policy_id = sla.id LEFT JOIN teams at ON at.id = c.assigned_team_id LEFT JOIN conversation_statuses s ON c.status_id = s.id LEFT JOIN conversation_priorities p ON c.priority_id = p.id LEFT JOIN LATERAL ( SELECT id, first_response_deadline_at, resolution_deadline_at FROM applied_slas WHERE conversation_id = c.id ORDER BY created_at DESC LIMIT 1 ) as_latest ON true LEFT JOIN LATERAL ( SELECT se.deadline_at, se.met_at FROM sla_events se WHERE se.applied_sla_id = as_latest.id AND se.type = 'next_response' ORDER BY se.created_at DESC LIMIT 1 ) nxt_resp_event ON true WHERE ($1 > 0 AND c.id = $1) OR ($2::uuid IS NOT NULL AND c.uuid = $2::uuid) OR ($3::TEXT != '' AND c.reference_number = $3::TEXT) -- name: get-conversations-created-after SELECT c.id, c.uuid FROM conversations c WHERE c.created_at > $1; -- name: get-contact-previous-conversations SELECT c.id, c.created_at, c.updated_at, c.uuid, c.subject, u.first_name AS "contact.first_name", u.last_name AS "contact.last_name", u.avatar_url AS "contact.avatar_url", c.last_message, c.last_message_at FROM users u JOIN conversations c ON c.contact_id = u.id WHERE c.contact_id = $1 ORDER BY c.created_at DESC LIMIT $2; -- name: get-conversation-uuid SELECT uuid from conversations where id = $1; -- name: update-conversation-assigned-user UPDATE conversations SET assigned_user_id = $2, updated_at = NOW() WHERE uuid = $1; -- name: update-conversation-assigned-team UPDATE conversations SET assigned_team_id = $2, updated_at = NOW() WHERE uuid = $1; -- name: update-conversation-status UPDATE conversations SET status_id = (SELECT id FROM conversation_statuses WHERE name = $2), resolved_at = COALESCE(resolved_at, CASE WHEN $2 IN ('Resolved', 'Closed') THEN NOW() END), closed_at = COALESCE(closed_at, CASE WHEN $2 = 'Closed' THEN NOW() END), snoozed_until = CASE WHEN $2 = 'Snoozed' THEN $3::timestamptz ELSE snoozed_until END, updated_at = NOW() WHERE uuid = $1; -- name: get-user-active-conversations-count SELECT COUNT(*) FROM conversations WHERE status_id IN (SELECT id FROM conversation_statuses WHERE name NOT IN ('Resolved', 'Closed')) and assigned_user_id = $1; -- name: update-conversation-priority UPDATE conversations SET priority_id = (SELECT id FROM conversation_priorities WHERE name = $2), updated_at = NOW() WHERE uuid = $1; -- name: upsert-user-last-seen INSERT INTO conversation_last_seen (user_id, conversation_id, last_seen_at) VALUES ($1, (SELECT id FROM conversations WHERE uuid = $2), NOW()) ON CONFLICT (conversation_id, user_id) DO UPDATE SET last_seen_at = NOW(), updated_at = NOW(); -- name: update-conversation-last-message -- $1=id, $2=uuid, $3=content, $4=sender_type, $5=timestamp, $6=message_type, $7=private UPDATE conversations SET last_message = $3, last_message_sender = $4, last_message_at = $5, last_interaction = CASE WHEN $6 != 'activity' AND $7 = false THEN $3 ELSE last_interaction END, last_interaction_sender = CASE WHEN $6 != 'activity' AND $7 = false THEN $4 ELSE last_interaction_sender END, last_interaction_at = CASE WHEN $6 != 'activity' AND $7 = false THEN $5 ELSE last_interaction_at END, updated_at = NOW() WHERE CASE WHEN $1 > 0 THEN id = $1 ELSE uuid = $2 END -- name: get-conversation-participants SELECT users.id as id, first_name, last_name, avatar_url FROM conversation_participants INNER JOIN users ON users.id = conversation_participants.user_id WHERE conversation_id = ( SELECT id FROM conversations WHERE uuid = $1 ); -- name: insert-conversation-participant INSERT INTO conversation_participants (user_id, conversation_id) VALUES($1, (SELECT id FROM conversations WHERE uuid = $2)); -- name: get-unassigned-conversations SELECT c.created_at, c.updated_at, c.uuid, c.assigned_team_id, inb.channel as inbox_channel, inb.name as inbox_name FROM conversations c JOIN inboxes inb ON c.inbox_id = inb.id WHERE assigned_user_id IS NULL AND assigned_team_id IS NOT NULL; -- name: update-conversation-first-reply-at UPDATE conversations SET first_reply_at = $2 WHERE first_reply_at IS NULL AND id = $1; -- name: update-conversation-last-reply-at UPDATE conversations SET last_reply_at = $2 WHERE id = $1; -- name: add-conversation-tags -- Insert new tags INSERT INTO conversation_tags (conversation_id, tag_id) SELECT c.id, t.id FROM conversations c, tags t WHERE t.name = ANY($2::text[]) AND c.uuid = $1 ON CONFLICT (conversation_id, tag_id) DO UPDATE SET tag_id = EXCLUDED.tag_id; -- name: set-conversation-tags WITH conversation_id AS ( SELECT id FROM conversations WHERE uuid = $1 ), -- Insert new tags inserted AS ( INSERT INTO conversation_tags (conversation_id, tag_id) SELECT conversation_id.id, t.id FROM conversation_id, tags t WHERE t.name = ANY($2::text[]) ON CONFLICT (conversation_id, tag_id) DO UPDATE SET tag_id = EXCLUDED.tag_id ) -- Delete tags that are not in the new list DELETE FROM conversation_tags WHERE conversation_id = (SELECT id FROM conversation_id) AND tag_id NOT IN ( SELECT id FROM tags WHERE name = ANY($2::text[]) ); -- name: remove-conversation-tags -- Delete tags that are not in the new list DELETE FROM conversation_tags WHERE conversation_id = (SELECT id FROM conversations WHERE uuid = $1) AND tag_id IN ( SELECT id FROM tags WHERE name = ANY($2::text[]) ); -- name: get-conversation-tags SELECT t.name FROM conversation_tags ct JOIN tags t ON ct.tag_id = t.id WHERE ct.conversation_id = (SELECT id FROM conversations WHERE uuid = $1); -- name: get-conversation-uuid-from-message-uuid SELECT c.uuid AS conversation_uuid FROM conversation_messages m JOIN conversations c ON m.conversation_id = c.id WHERE m.uuid = $1; -- name: unassign-open-conversations UPDATE conversations SET assigned_user_id = NULL, updated_at = NOW() WHERE assigned_user_id = $1 AND status_id in (SELECT id FROM conversation_statuses WHERE name NOT IN ('Resolved', 'Closed')); -- name: update-conversation-custom-attributes UPDATE conversations SET custom_attributes = $2, updated_at = NOW() WHERE uuid = $1; -- name: update-conversation-waiting-since UPDATE conversations SET waiting_since = $2, updated_at = NOW() WHERE uuid = $1; -- name: remove-conversation-assignee UPDATE conversations SET assigned_user_id = CASE WHEN $2 = 'user' THEN NULL ELSE assigned_user_id END, assigned_team_id = CASE WHEN $2 = 'team' THEN NULL ELSE assigned_team_id END, updated_at = NOW() WHERE uuid = $1; -- name: re-open-conversation -- Open conversation if it is not already open and unset the assigned user if they are away and reassigning. UPDATE conversations SET status_id = (SELECT id FROM conversation_statuses WHERE name = 'Open'), snoozed_until = NULL, updated_at = NOW(), assigned_user_id = CASE WHEN EXISTS ( SELECT 1 FROM users WHERE users.id = conversations.assigned_user_id AND users.availability_status = 'away_and_reassigning' ) THEN NULL ELSE assigned_user_id END WHERE uuid = $1 AND status_id IN ( SELECT id FROM conversation_statuses WHERE name NOT IN ('Open') ) -- name: get-conversation-by-message-id SELECT c.id, c.uuid, c.assigned_team_id, c.assigned_user_id FROM conversation_messages m JOIN conversations c ON m.conversation_id = c.id WHERE m.id = $1; -- name: delete-conversation DELETE FROM conversations WHERE uuid = $1; -- MESSAGE queries. -- name: get-message-source-ids SELECT source_id FROM conversation_messages WHERE conversation_id = $1 AND type in ('incoming', 'outgoing') and private = false and source_id > '' ORDER BY id DESC LIMIT $2; -- name: get-outgoing-pending-messages SELECT m.id, m.created_at, m.updated_at, m.status, m.type, m.content, m.text_content, m.content_type, m.conversation_id, m.uuid, m.private, m.sender_type, m.sender_id, m.meta, c.uuid as conversation_uuid, m.content_type, m.source_id, ARRAY(SELECT jsonb_array_elements_text(m.meta->'cc')) AS cc, ARRAY(SELECT jsonb_array_elements_text(m.meta->'bcc')) AS bcc, ARRAY(SELECT jsonb_array_elements_text(m.meta->'to')) AS to, c.inbox_id, c.subject FROM conversation_messages m INNER JOIN conversations c ON c.id = m.conversation_id WHERE m.status = 'pending' AND m.type = 'outgoing' AND m.private = false AND NOT(m.id = ANY($1::INT[])) -- name: get-message SELECT m.id, m.created_at, m.updated_at, m.status, m.type, m.content, m.text_content, m.content_type, m.conversation_id, m.uuid, m.private, m.sender_type, m.sender_id, m.meta, c.uuid as conversation_uuid, u.id AS "author.id", u.first_name AS "author.first_name", u.last_name AS "author.last_name", u.avatar_url AS "author.avatar_url", COALESCE( json_agg( json_build_object( 'name', media.filename, 'content_type', media.content_type, 'uuid', media.uuid, 'size', media.size, 'content_id', media.content_id, 'disposition', media.disposition ) ORDER BY media.filename ) FILTER (WHERE media.id IS NOT NULL), '[]'::json ) AS attachments FROM conversation_messages m INNER JOIN conversations c ON c.id = m.conversation_id JOIN users u ON m.sender_id = u.id LEFT JOIN media ON media.model_type = 'messages' AND media.model_id = m.id WHERE m.uuid = $1 GROUP BY m.id, m.created_at, m.updated_at, m.status, m.type, m.content, m.uuid, m.private, m.sender_type, c.uuid, u.id, u.first_name, u.last_name, u.avatar_url ORDER BY m.created_at; -- name: get-messages SELECT COUNT(*) OVER() AS total, m.id, m.created_at, m.updated_at, m.status, m.type, m.content, m.text_content, m.content_type, m.conversation_id, m.uuid, m.private, m.sender_id, m.sender_type, m.meta, $1::uuid AS conversation_uuid, u.id AS "author.id", u.first_name AS "author.first_name", u.last_name AS "author.last_name", u.avatar_url AS "author.avatar_url", COALESCE( (SELECT json_agg( json_build_object( 'name', filename, 'content_type', content_type, 'uuid', uuid, 'size', size, 'content_id', content_id, 'disposition', disposition ) ORDER BY filename ) FROM media WHERE model_type = 'messages' AND model_id = m.id), '[]'::json) AS attachments FROM conversation_messages m JOIN users u ON m.sender_id = u.id WHERE m.conversation_id = ( SELECT id FROM conversations WHERE uuid = $1 LIMIT 1 ) AND ($2::boolean IS NULL OR m.private = $2) AND ($3::text[] IS NULL OR m.type::text = ANY($3)) ORDER BY m.created_at DESC %s -- name: insert-message WITH conversation_id AS ( SELECT id FROM conversations WHERE CASE WHEN $3 > 0 THEN id = $3 ELSE uuid = $4 END ), inserted_msg AS ( INSERT INTO conversation_messages ( "type", status, conversation_id, "content", text_content, sender_id, sender_type, private, content_type, source_id, meta ) VALUES ( $1, $2, (SELECT id FROM conversation_id), $5, $6, $7, $8, $9, $10, $11, $12 ) RETURNING * ) SELECT * FROM inserted_msg; -- name: message-exists-by-source-id SELECT conversation_id FROM conversation_messages WHERE source_id = ANY($1::text []); -- name: update-message-status update conversation_messages set status = $1, updated_at = NOW() where uuid = $2; -- name: get-latest-message SELECT m.created_at, m.updated_at, m.status, m.type, m.content, m.uuid, m.private, m.sender_id, m.sender_type, m.meta FROM conversation_messages m WHERE m.conversation_id = $1 AND m.type = ANY($2) AND m.status = ANY($3) AND m.private = NOT $4 ORDER BY m.created_at DESC LIMIT 1; -- name: upsert-conversation-draft INSERT INTO conversation_drafts (conversation_id, user_id, content, meta, updated_at) VALUES ($1, $2, $3, $4, NOW()) ON CONFLICT (conversation_id, user_id) DO UPDATE SET content = EXCLUDED.content, meta = EXCLUDED.meta, updated_at = NOW() RETURNING *; -- name: get-all-user-drafts SELECT cd.id, cd.conversation_id, cd.user_id, cd.content, cd.meta, cd.created_at, cd.updated_at, c.uuid as conversation_uuid FROM conversation_drafts cd INNER JOIN conversations c ON cd.conversation_id = c.id WHERE cd.user_id = $1 ORDER BY cd.updated_at DESC; -- name: delete-conversation-draft DELETE FROM conversation_drafts WHERE conversation_id IN ( SELECT id FROM conversations WHERE ($1 > 0 AND id = $1) OR ($2::uuid IS NOT NULL AND uuid = $2::uuid) ) AND user_id = $3; -- name: delete-stale-drafts DELETE FROM conversation_drafts WHERE created_at < $1; -- name: insert-mention INSERT INTO conversation_mentions (conversation_id, message_id, mentioned_user_id, mentioned_team_id, mentioned_by_user_id) VALUES ($1, $2, $3, $4, $5); -- name: mark-conversation-unread INSERT INTO conversation_last_seen (user_id, conversation_id, last_seen_at) VALUES ( $1, (SELECT id FROM conversations WHERE uuid = $2), (SELECT created_at - INTERVAL '1 second' FROM conversation_messages WHERE conversation_id = (SELECT id FROM conversations WHERE uuid = $2) ORDER BY created_at DESC LIMIT 1) ) ON CONFLICT (conversation_id, user_id) DO UPDATE SET last_seen_at = (SELECT created_at - INTERVAL '1 second' FROM conversation_messages WHERE conversation_id = (SELECT id FROM conversations WHERE uuid = $2) ORDER BY created_at DESC LIMIT 1), updated_at = NOW();