From e9fbee43e312399cf5033b3baa295f13e97e67f2 Mon Sep 17 00:00:00 2001 From: patrickzerhusen Date: Wed, 6 May 2026 16:06:43 +0200 Subject: [PATCH] migration for tasks module and reward system --- migrations/009_tasks-module.sql | 181 ++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 migrations/009_tasks-module.sql diff --git a/migrations/009_tasks-module.sql b/migrations/009_tasks-module.sql new file mode 100644 index 0000000..12b656d --- /dev/null +++ b/migrations/009_tasks-module.sql @@ -0,0 +1,181 @@ +-- ===================================================================== +-- Migration 009: Tasks Module — Tasks with Reward System +-- ===================================================================== + + +-- --------------------------------------------------------------------- +-- Block 1: Tasks Table +-- Stores community Tasks with Geometry, Moderation and Completion. +-- Status Flow: pending → rejected | open → completed → verified +-- --------------------------------------------------------------------- +CREATE TABLE IF NOT EXISTS tasks ( + task_id SERIAL PRIMARY KEY, + municipality_id INTEGER NOT NULL REFERENCES municipalities(municipality_id), + geom GEOMETRY(Geometry, 4326) NOT NULL, + geom_type VARCHAR(10) NOT NULL CHECK (geom_type IN ('point', 'line', 'polygon')), + category VARCHAR(50) NOT NULL, + title VARCHAR(200) NOT NULL, + description TEXT DEFAULT '', + points_reward INTEGER NOT NULL DEFAULT 25, + author_name VARCHAR(100) NOT NULL, + browser_id VARCHAR(36), + photo_path VARCHAR(255), + status VARCHAR(20) NOT NULL DEFAULT 'pending' + CHECK (status IN ('pending', 'rejected', 'open', 'completed', 'verified')), + address VARCHAR(255), + + -- Completion Fields (NULL until completed) + completed_by_name VARCHAR(100), + completed_by_browser VARCHAR(36), + completion_photo VARCHAR(255), + completion_comment TEXT, + completed_at TIMESTAMP, + + -- Counters (updated via Triggers) + likes_count INTEGER NOT NULL DEFAULT 0, + dislikes_count INTEGER NOT NULL DEFAULT 0, + comment_count INTEGER NOT NULL DEFAULT 0, + + created_at TIMESTAMP NOT NULL DEFAULT NOW(), + updated_at TIMESTAMP NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_tasks_municipality ON tasks(municipality_id); +CREATE INDEX idx_tasks_status ON tasks(status); +CREATE INDEX idx_tasks_category ON tasks(category); + + +-- --------------------------------------------------------------------- +-- Block 2: User Points Table +-- One Entry per verified Task Completion. Leaderboard via SUM/GROUP BY. +-- --------------------------------------------------------------------- +CREATE TABLE IF NOT EXISTS user_points ( + points_id SERIAL PRIMARY KEY, + municipality_id INTEGER NOT NULL REFERENCES municipalities(municipality_id), + user_name VARCHAR(100) NOT NULL, + points INTEGER NOT NULL DEFAULT 25, + task_id INTEGER NOT NULL REFERENCES tasks(task_id) ON DELETE CASCADE, + created_at TIMESTAMP NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_user_points_municipality ON user_points(municipality_id); +CREATE INDEX idx_user_points_user ON user_points(user_name); + + +-- --------------------------------------------------------------------- +-- Block 3: Extends Votes Table for Tasks +-- Either contribution_id OR task_id is set, not both. +-- --------------------------------------------------------------------- +ALTER TABLE votes + ADD COLUMN task_id INTEGER REFERENCES tasks(task_id) ON DELETE CASCADE; + +CREATE INDEX idx_votes_task ON votes(task_id); + +-- Unique Vote per Browser per Task +ALTER TABLE votes + ADD CONSTRAINT votes_task_browser_unique + UNIQUE (task_id, browser_id); + + +-- --------------------------------------------------------------------- +-- Block 4: Extends Comments Table for Tasks +-- Either contribution_id OR task_id is set, not both. +-- --------------------------------------------------------------------- +ALTER TABLE comments + ADD COLUMN task_id INTEGER REFERENCES tasks(task_id) ON DELETE CASCADE; + +CREATE INDEX idx_comments_task ON comments(task_id); + + +-- --------------------------------------------------------------------- +-- Block 5: Trigger — updated_at Timestamp for Tasks +-- --------------------------------------------------------------------- +CREATE TRIGGER set_tasks_updated_at + BEFORE UPDATE ON tasks + FOR EACH ROW + EXECUTE FUNCTION set_updated_at(); + + +-- --------------------------------------------------------------------- +-- Block 6: Trigger — Vote Counts for Tasks +-- Mirrors the Pattern from Contributions. +-- --------------------------------------------------------------------- +CREATE OR REPLACE FUNCTION update_task_vote_counts() +RETURNS TRIGGER AS $$ +BEGIN + IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN + IF NEW.task_id IS NOT NULL THEN + UPDATE tasks SET + likes_count = (SELECT COUNT(*) FROM votes WHERE task_id = NEW.task_id AND vote_type = 'like'), + dislikes_count = (SELECT COUNT(*) FROM votes WHERE task_id = NEW.task_id AND vote_type = 'dislike') + WHERE task_id = NEW.task_id; + END IF; + END IF; + + IF TG_OP = 'DELETE' OR (TG_OP = 'UPDATE' AND OLD.task_id IS NOT NULL) THEN + UPDATE tasks SET + likes_count = (SELECT COUNT(*) FROM votes WHERE task_id = OLD.task_id AND vote_type = 'like'), + dislikes_count = (SELECT COUNT(*) FROM votes WHERE task_id = OLD.task_id AND vote_type = 'dislike') + WHERE task_id = OLD.task_id; + END IF; + + RETURN NULL; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER trigger_update_task_vote_counts + AFTER INSERT OR DELETE OR UPDATE ON votes + FOR EACH ROW + WHEN (NEW.task_id IS NOT NULL OR OLD.task_id IS NOT NULL) + EXECUTE FUNCTION update_task_vote_counts(); + + +-- --------------------------------------------------------------------- +-- Block 7: Trigger — Comment Count for Tasks +-- Only counts approved Comments. Mirrors Contribution Pattern. +-- --------------------------------------------------------------------- +CREATE OR REPLACE FUNCTION update_task_comment_count() +RETURNS TRIGGER AS $$ +BEGIN + IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN + IF NEW.task_id IS NOT NULL THEN + UPDATE tasks + SET comment_count = ( + SELECT COUNT(*) FROM comments + WHERE task_id = NEW.task_id AND status = 'approved' + ) + WHERE task_id = NEW.task_id; + END IF; + END IF; + + IF TG_OP = 'DELETE' OR (TG_OP = 'UPDATE' AND OLD.task_id IS NOT NULL) THEN + UPDATE tasks + SET comment_count = ( + SELECT COUNT(*) FROM comments + WHERE task_id = OLD.task_id AND status = 'approved' + ) + WHERE task_id = OLD.task_id; + END IF; + + RETURN NULL; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER trigger_update_task_comment_count + AFTER INSERT OR DELETE OR UPDATE OF status ON comments + FOR EACH ROW + WHEN (NEW.task_id IS NOT NULL OR OLD.task_id IS NOT NULL) + EXECUTE FUNCTION update_task_comment_count(); + + +-- --------------------------------------------------------------------- +-- Block 8: Views for QGIS (optional) +-- --------------------------------------------------------------------- +CREATE OR REPLACE VIEW tasks_points AS + SELECT * FROM tasks WHERE geom_type = 'point'; + +CREATE OR REPLACE VIEW tasks_lines AS + SELECT * FROM tasks WHERE geom_type = 'line'; + +CREATE OR REPLACE VIEW tasks_polygons AS + SELECT * FROM tasks WHERE geom_type = 'polygon'; \ No newline at end of file