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