nordabiz/database/migrations/024_forum_modernization.sql
Maciej Pienczyn f22342ea37 feat: Add forum modernization with reactions, subscriptions, and moderation
- Add edit tracking (24h limit), soft delete, and JSONB reactions to ForumTopic/ForumReply
- Create ForumTopicSubscription, ForumReport, ForumEditHistory models
- Add 15 new API endpoints for user actions and admin moderation
- Implement reactions (👍❤️🎉), topic subscriptions, content reporting
- Add solution marking, restore deleted content, edit history for admins
- Create forum_reports.html and forum_deleted.html admin templates
- Integrate notifications for replies, reactions, solutions, and reports
- Add SQL migration 024_forum_modernization.sql

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 18:55:40 +01:00

169 lines
7.6 KiB
SQL

-- 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');