-- Forum Modernization Migration -- Author: Claude Code -- Date: 2026-01-31 -- Description: Adds edit tracking, soft delete, reactions, subscriptions, reports, and edit history -- ============================================================ -- PHASE 1: Add new columns to forum_topics -- ============================================================ -- Edit tracking ALTER TABLE forum_topics ADD COLUMN IF NOT EXISTS edited_at TIMESTAMP; ALTER TABLE forum_topics ADD COLUMN IF NOT EXISTS edited_by INTEGER REFERENCES users(id); ALTER TABLE forum_topics ADD COLUMN IF NOT EXISTS edit_count INTEGER DEFAULT 0; -- Soft delete ALTER TABLE forum_topics ADD COLUMN IF NOT EXISTS is_deleted BOOLEAN DEFAULT FALSE; ALTER TABLE forum_topics ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMP; ALTER TABLE forum_topics ADD COLUMN IF NOT EXISTS deleted_by INTEGER REFERENCES users(id); -- Reactions (JSONB) ALTER TABLE forum_topics ADD COLUMN IF NOT EXISTS reactions JSONB DEFAULT '{}'; -- ============================================================ -- PHASE 2: Add new columns to forum_replies -- ============================================================ -- Edit tracking ALTER TABLE forum_replies ADD COLUMN IF NOT EXISTS edited_at TIMESTAMP; ALTER TABLE forum_replies ADD COLUMN IF NOT EXISTS edited_by INTEGER REFERENCES users(id); ALTER TABLE forum_replies ADD COLUMN IF NOT EXISTS edit_count INTEGER DEFAULT 0; -- Soft delete ALTER TABLE forum_replies ADD COLUMN IF NOT EXISTS is_deleted BOOLEAN DEFAULT FALSE; ALTER TABLE forum_replies ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMP; ALTER TABLE forum_replies ADD COLUMN IF NOT EXISTS deleted_by INTEGER REFERENCES users(id); -- Reactions (JSONB) ALTER TABLE forum_replies ADD COLUMN IF NOT EXISTS reactions JSONB DEFAULT '{}'; -- Solution marking ALTER TABLE forum_replies ADD COLUMN IF NOT EXISTS is_solution BOOLEAN DEFAULT FALSE; ALTER TABLE forum_replies ADD COLUMN IF NOT EXISTS marked_as_solution_by INTEGER REFERENCES users(id); ALTER TABLE forum_replies ADD COLUMN IF NOT EXISTS marked_as_solution_at TIMESTAMP; -- ============================================================ -- PHASE 3: Create forum_topic_subscriptions table -- ============================================================ CREATE TABLE IF NOT EXISTS forum_topic_subscriptions ( id SERIAL PRIMARY KEY, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, topic_id INTEGER NOT NULL REFERENCES forum_topics(id) ON DELETE CASCADE, notify_email BOOLEAN DEFAULT TRUE, notify_app BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT uq_forum_subscription_user_topic UNIQUE (user_id, topic_id) ); -- Indexes for subscriptions CREATE INDEX IF NOT EXISTS idx_forum_subscriptions_user ON forum_topic_subscriptions(user_id); CREATE INDEX IF NOT EXISTS idx_forum_subscriptions_topic ON forum_topic_subscriptions(topic_id); -- ============================================================ -- PHASE 4: Create forum_reports table -- ============================================================ CREATE TABLE IF NOT EXISTS forum_reports ( id SERIAL PRIMARY KEY, reporter_id INTEGER NOT NULL REFERENCES users(id), -- Polymorphic relationship content_type VARCHAR(20) NOT NULL CHECK (content_type IN ('topic', 'reply')), topic_id INTEGER REFERENCES forum_topics(id) ON DELETE CASCADE, reply_id INTEGER REFERENCES forum_replies(id) ON DELETE CASCADE, reason VARCHAR(50) NOT NULL CHECK (reason IN ('spam', 'offensive', 'off-topic', 'other')), description TEXT, status VARCHAR(20) DEFAULT 'pending' CHECK (status IN ('pending', 'reviewed', 'dismissed')), reviewed_by INTEGER REFERENCES users(id), reviewed_at TIMESTAMP, review_note TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Ensure at least one of topic_id or reply_id is set CONSTRAINT chk_forum_report_content CHECK ( (content_type = 'topic' AND topic_id IS NOT NULL AND reply_id IS NULL) OR (content_type = 'reply' AND reply_id IS NOT NULL) ) ); -- Indexes for reports CREATE INDEX IF NOT EXISTS idx_forum_reports_status ON forum_reports(status); CREATE INDEX IF NOT EXISTS idx_forum_reports_reporter ON forum_reports(reporter_id); CREATE INDEX IF NOT EXISTS idx_forum_reports_topic ON forum_reports(topic_id); CREATE INDEX IF NOT EXISTS idx_forum_reports_reply ON forum_reports(reply_id); -- ============================================================ -- PHASE 5: Create forum_edit_history table -- ============================================================ CREATE TABLE IF NOT EXISTS forum_edit_history ( id SERIAL PRIMARY KEY, -- Polymorphic relationship content_type VARCHAR(20) NOT NULL CHECK (content_type IN ('topic', 'reply')), topic_id INTEGER REFERENCES forum_topics(id) ON DELETE CASCADE, reply_id INTEGER REFERENCES forum_replies(id) ON DELETE CASCADE, editor_id INTEGER NOT NULL REFERENCES users(id), old_content TEXT NOT NULL, new_content TEXT NOT NULL, edit_reason VARCHAR(255), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Ensure at least one of topic_id or reply_id is set CONSTRAINT chk_forum_edit_history_content CHECK ( (content_type = 'topic' AND topic_id IS NOT NULL AND reply_id IS NULL) OR (content_type = 'reply' AND reply_id IS NOT NULL) ) ); -- Indexes for edit history CREATE INDEX IF NOT EXISTS idx_forum_edit_history_topic ON forum_edit_history(topic_id); CREATE INDEX IF NOT EXISTS idx_forum_edit_history_reply ON forum_edit_history(reply_id); CREATE INDEX IF NOT EXISTS idx_forum_edit_history_editor ON forum_edit_history(editor_id); -- ============================================================ -- PHASE 6: Additional indexes for performance -- ============================================================ -- Index for soft-deleted topics (admin queries) CREATE INDEX IF NOT EXISTS idx_forum_topics_is_deleted ON forum_topics(is_deleted) WHERE is_deleted = TRUE; -- Index for soft-deleted replies (admin queries) CREATE INDEX IF NOT EXISTS idx_forum_replies_is_deleted ON forum_replies(is_deleted) WHERE is_deleted = TRUE; -- Index for solution replies CREATE INDEX IF NOT EXISTS idx_forum_replies_is_solution ON forum_replies(is_solution) WHERE is_solution = TRUE; -- ============================================================ -- PHASE 7: Grant permissions -- ============================================================ GRANT ALL ON TABLE forum_topic_subscriptions TO nordabiz_app; GRANT ALL ON TABLE forum_reports TO nordabiz_app; GRANT ALL ON TABLE forum_edit_history TO nordabiz_app; GRANT USAGE, SELECT ON SEQUENCE forum_topic_subscriptions_id_seq TO nordabiz_app; GRANT USAGE, SELECT ON SEQUENCE forum_reports_id_seq TO nordabiz_app; GRANT USAGE, SELECT ON SEQUENCE forum_edit_history_id_seq TO nordabiz_app; -- ============================================================ -- VERIFICATION QUERIES -- ============================================================ -- Verify new columns in forum_topics -- SELECT column_name, data_type FROM information_schema.columns -- WHERE table_name = 'forum_topics' AND column_name IN ('edited_at', 'edited_by', 'edit_count', 'is_deleted', 'deleted_at', 'deleted_by', 'reactions'); -- Verify new columns in forum_replies -- SELECT column_name, data_type FROM information_schema.columns -- WHERE table_name = 'forum_replies' AND column_name IN ('edited_at', 'edited_by', 'edit_count', 'is_deleted', 'deleted_at', 'deleted_by', 'reactions', 'is_solution', 'marked_as_solution_by', 'marked_as_solution_at'); -- Verify new tables -- SELECT table_name FROM information_schema.tables WHERE table_name IN ('forum_topic_subscriptions', 'forum_reports', 'forum_edit_history');