feat(ollama): implement case creation rewrite with strict rules fix(sync): include phone number in economic customer sync fix(migrations): clean up orphan links before enforcing foreign keys in email threading schema feat(migrations): add support for physical patch-panel layouts and update related constraints feat(migrations): add start port number for physical patch panels feat(migrations): introduce display order for physical panels feat(migrations): link wall outlets to switch hardware feat(migrations): allow optional label and customer for wall outlets feat(migrations): create hardware network links table feat(migrations): add location display order for hardware assets feat(migrations): create UISP devices and link to hardware assets
115 lines
3.8 KiB
SQL
115 lines
3.8 KiB
SQL
-- Migration 159: Repair SAG email threading schema (idempotent)
|
|
-- Purpose:
|
|
-- 1) Ensure sag_emails link table exists and is constrained correctly.
|
|
-- 2) Ensure email_messages has threading columns used by SAG email tab.
|
|
-- 3) Backfill thread_key where missing.
|
|
|
|
-- Ensure link table exists
|
|
CREATE TABLE IF NOT EXISTS sag_emails (
|
|
sag_id INTEGER REFERENCES sag_sager(id) ON DELETE CASCADE,
|
|
email_id INTEGER REFERENCES email_messages(id) ON DELETE CASCADE,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- Ensure required columns on email_messages exist
|
|
ALTER TABLE email_messages
|
|
ADD COLUMN IF NOT EXISTS in_reply_to VARCHAR(500),
|
|
ADD COLUMN IF NOT EXISTS email_references TEXT,
|
|
ADD COLUMN IF NOT EXISTS thread_key VARCHAR(500);
|
|
|
|
-- Cleanup duplicates before adding unique constraint/PK
|
|
-- Old installations can contain links to emails or cases that have since
|
|
-- been deleted. Remove those orphan links before enforcing foreign keys.
|
|
DELETE FROM sag_emails se
|
|
WHERE NOT EXISTS (
|
|
SELECT 1 FROM email_messages em WHERE em.id = se.email_id
|
|
)
|
|
OR NOT EXISTS (
|
|
SELECT 1 FROM sag_sager s WHERE s.id = se.sag_id
|
|
);
|
|
|
|
WITH ranked AS (
|
|
SELECT ctid,
|
|
ROW_NUMBER() OVER (PARTITION BY sag_id, email_id ORDER BY created_at NULLS LAST, ctid) AS rn
|
|
FROM sag_emails
|
|
)
|
|
DELETE FROM sag_emails se
|
|
USING ranked r
|
|
WHERE se.ctid = r.ctid
|
|
AND r.rn > 1;
|
|
|
|
-- Ensure PK exists
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (
|
|
SELECT 1
|
|
FROM pg_constraint
|
|
WHERE conname = 'sag_emails_pkey'
|
|
AND conrelid = 'sag_emails'::regclass
|
|
) THEN
|
|
ALTER TABLE sag_emails
|
|
ADD CONSTRAINT sag_emails_pkey PRIMARY KEY (sag_id, email_id);
|
|
END IF;
|
|
END $$;
|
|
|
|
-- Ensure FKs exist
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (
|
|
SELECT 1
|
|
FROM pg_constraint
|
|
WHERE conname = 'sag_emails_sag_id_fkey'
|
|
AND conrelid = 'sag_emails'::regclass
|
|
) THEN
|
|
ALTER TABLE sag_emails
|
|
ADD CONSTRAINT sag_emails_sag_id_fkey
|
|
FOREIGN KEY (sag_id) REFERENCES sag_sager(id) ON DELETE CASCADE;
|
|
END IF;
|
|
|
|
IF NOT EXISTS (
|
|
SELECT 1
|
|
FROM pg_constraint
|
|
WHERE conname = 'sag_emails_email_id_fkey'
|
|
AND conrelid = 'sag_emails'::regclass
|
|
) THEN
|
|
ALTER TABLE sag_emails
|
|
ADD CONSTRAINT sag_emails_email_id_fkey
|
|
FOREIGN KEY (email_id) REFERENCES email_messages(id) ON DELETE CASCADE;
|
|
END IF;
|
|
END $$;
|
|
|
|
-- Helpful indexes for case email tab
|
|
CREATE INDEX IF NOT EXISTS idx_sag_emails_sag_id ON sag_emails(sag_id);
|
|
CREATE INDEX IF NOT EXISTS idx_sag_emails_email_id ON sag_emails(email_id);
|
|
CREATE INDEX IF NOT EXISTS idx_email_messages_thread_key ON email_messages(thread_key) WHERE thread_key IS NOT NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_email_messages_in_reply_to ON email_messages(in_reply_to) WHERE in_reply_to IS NOT NULL;
|
|
|
|
-- Backfill thread_key for rows where it is missing
|
|
UPDATE email_messages
|
|
SET thread_key = LOWER(
|
|
REGEXP_REPLACE(
|
|
COALESCE(
|
|
NULLIF(
|
|
SPLIT_PART(
|
|
REGEXP_REPLACE(COALESCE(email_references, ''), '^[\s<>,]+', ''),
|
|
' ',
|
|
1
|
|
),
|
|
''
|
|
),
|
|
NULLIF(in_reply_to, ''),
|
|
NULLIF(message_id, '')
|
|
),
|
|
'[<>\s]',
|
|
'',
|
|
'g'
|
|
)
|
|
)
|
|
WHERE (thread_key IS NULL OR TRIM(thread_key) = '')
|
|
AND COALESCE(NULLIF(email_references, ''), NULLIF(in_reply_to, ''), NULLIF(message_id, '')) IS NOT NULL;
|
|
|
|
COMMENT ON TABLE sag_emails IS 'Emails linked to the Case (SAG).';
|
|
COMMENT ON COLUMN email_messages.in_reply_to IS 'Raw In-Reply-To header used for SAG threading lookup';
|
|
COMMENT ON COLUMN email_messages.email_references IS 'Raw References header used for SAG threading lookup';
|
|
COMMENT ON COLUMN email_messages.thread_key IS 'Stable normalized thread key for grouping email conversations';
|