← back to Professional Directory

db/migrations/005_community.sql

104 lines

-- Phase 4 — community threads + private DMs.
-- Apply: psql -d doctor_professional_directory -f db/migrations/005_community.sql

BEGIN;

-- ─── threads ──────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS threads (
  id              bigserial PRIMARY KEY,
  title           text NOT NULL,
  body            text,
  city            text,
  zip             text,
  organization_id bigint REFERENCES organizations(id) ON DELETE SET NULL,   -- optional anchor to a practice
  category        text NOT NULL DEFAULT 'general'
                   CHECK (category IN ('general','recommendations','specialty','insurance','pediatrics','eldercare','mental_health','dental','optometry','question','rant')),
  created_by_user_id bigint REFERENCES users(id) ON DELETE SET NULL,
  hidden_by_admin boolean NOT NULL DEFAULT false,
  pinned          boolean NOT NULL DEFAULT false,
  reply_count     integer NOT NULL DEFAULT 0,
  last_post_at    timestamptz NOT NULL DEFAULT now(),
  created_at      timestamptz NOT NULL DEFAULT now(),
  updated_at      timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_threads_visible ON threads (last_post_at DESC) WHERE hidden_by_admin = false;
CREATE INDEX IF NOT EXISTS idx_threads_city    ON threads (city) WHERE city IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_threads_org     ON threads (organization_id) WHERE organization_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_threads_cat     ON threads (category);

DROP TRIGGER IF EXISTS threads_set_updated_at ON threads;
CREATE TRIGGER threads_set_updated_at BEFORE UPDATE ON threads
  FOR EACH ROW EXECUTE FUNCTION trigger_set_timestamp();

-- ─── thread_posts ─────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS thread_posts (
  id              bigserial PRIMARY KEY,
  thread_id       bigint NOT NULL REFERENCES threads(id) ON DELETE CASCADE,
  author_user_id  bigint REFERENCES users(id) ON DELETE SET NULL,
  body            text NOT NULL,
  hidden_by_admin boolean NOT NULL DEFAULT false,
  created_at      timestamptz NOT NULL DEFAULT now(),
  updated_at      timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_posts_thread ON thread_posts (thread_id, created_at);
CREATE INDEX IF NOT EXISTS idx_posts_author ON thread_posts (author_user_id) WHERE author_user_id IS NOT NULL;

DROP TRIGGER IF EXISTS posts_set_updated_at ON thread_posts;
CREATE TRIGGER posts_set_updated_at BEFORE UPDATE ON thread_posts
  FOR EACH ROW EXECUTE FUNCTION trigger_set_timestamp();

-- Bump thread.last_post_at + reply_count on every new post.
CREATE OR REPLACE FUNCTION trigger_bump_thread_on_post() RETURNS trigger AS $$
BEGIN
  UPDATE threads
     SET last_post_at = NEW.created_at,
         reply_count  = reply_count + 1,
         updated_at   = now()
   WHERE id = NEW.thread_id;
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

DROP TRIGGER IF EXISTS posts_bump_thread ON thread_posts;
CREATE TRIGGER posts_bump_thread AFTER INSERT ON thread_posts
  FOR EACH ROW EXECUTE FUNCTION trigger_bump_thread_on_post();

-- ─── dm_pairs ─────────────────────────────────────────────────────────────
-- Canonical pair representation: user_a_id < user_b_id (always).
CREATE TABLE IF NOT EXISTS dm_pairs (
  id              bigserial PRIMARY KEY,
  user_a_id       bigint NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  user_b_id       bigint NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  last_message_at timestamptz,
  created_at      timestamptz NOT NULL DEFAULT now(),
  CHECK (user_a_id < user_b_id),
  UNIQUE (user_a_id, user_b_id)
);
CREATE INDEX IF NOT EXISTS idx_dmp_a ON dm_pairs (user_a_id, last_message_at DESC NULLS LAST);
CREATE INDEX IF NOT EXISTS idx_dmp_b ON dm_pairs (user_b_id, last_message_at DESC NULLS LAST);

-- ─── messages ─────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS messages (
  id              bigserial PRIMARY KEY,
  dm_pair_id      bigint NOT NULL REFERENCES dm_pairs(id) ON DELETE CASCADE,
  author_user_id  bigint NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  body            text NOT NULL,
  sent_at         timestamptz NOT NULL DEFAULT now(),
  read_at         timestamptz
);
CREATE INDEX IF NOT EXISTS idx_msgs_pair ON messages (dm_pair_id, sent_at);
CREATE INDEX IF NOT EXISTS idx_msgs_unread ON messages (dm_pair_id) WHERE read_at IS NULL;

CREATE OR REPLACE FUNCTION trigger_bump_pair_on_msg() RETURNS trigger AS $$
BEGIN
  UPDATE dm_pairs SET last_message_at = NEW.sent_at WHERE id = NEW.dm_pair_id;
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

DROP TRIGGER IF EXISTS msgs_bump_pair ON messages;
CREATE TRIGGER msgs_bump_pair AFTER INSERT ON messages
  FOR EACH ROW EXECUTE FUNCTION trigger_bump_pair_on_msg();

COMMIT;