Files
webgis-lohne/migrations/001_initial_schema.sql

169 lines
7.6 KiB
PL/PgSQL

-- =====================================================================
-- WebGIS Citizen Participation Portal — Initial Schema
-- Migration: 001_initial_schema.sql
-- Description: Creates Core Tables for a multi-tenant Citizen
-- Participation Platform with Point/Line/Polygon
-- Contributions, Voting, and Moderation Workflow.
-- =====================================================================
-- ---------------------------------------------------------------------
-- Block 1: Checks PostGIS Extension
-- ---------------------------------------------------------------------
CREATE EXTENSION IF NOT EXISTS postgis;
-- ---------------------------------------------------------------------
-- Block 2: Creates Table "municipalities"
-- One Row per Municipalitiy using the Portal (multi-tenant setup).
-- ---------------------------------------------------------------------
CREATE TABLE municipalities (
municipality_id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE, -- Municipalitiy Name
slug VARCHAR(50) NOT NULL UNIQUE, -- URL-safe Identifier, e.g. "lohne"
center_lat DOUBLE PRECISION NOT NULL, -- Map Center Latitude
center_lng DOUBLE PRECISION NOT NULL, -- Map Center Longitude
default_zoom SMALLINT NOT NULL DEFAULT 13, -- Map Default Zoom Level
logo_path VARCHAR(255), -- Relative Path to Municipality Logo
primary_color VARCHAR(7) DEFAULT '#6a6a6a', -- HexColor for UI Theme
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE municipalities IS 'Configuration Per Municipality (Tenant) using the Citizen Participation Portal.';
-- ---------------------------------------------------------------------
-- Block 3: Table "contributions"
-- Aitizen and Administration Contributions as Points, Lines, and
-- Polygons stored together in one mixed-geometry Column.
-- ---------------------------------------------------------------------
CREATE TABLE contributions (
contribution_id SERIAL PRIMARY KEY,
municipality_id INTEGER NOT NULL REFERENCES municipalities(municipality_id) ON DELETE CASCADE,
geom GEOMETRY(Geometry, 4326) NOT NULL, -- Mixed Geometry: Point, Line, Polygon, ... (WGS84)
geom_type VARCHAR(20) NOT NULL, -- 'point' | 'line' | 'polygon'
category VARCHAR(50) NOT NULL, -- Contribution Category
title VARCHAR(200) NOT NULL,
description TEXT,
author_name VARCHAR(100) NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'pending',
likes_count INTEGER NOT NULL DEFAULT 0,
dislikes_count INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT contributions_geom_type_check
CHECK (geom_type IN ('point', 'line', 'polygon')),
CONSTRAINT contributions_status_check
CHECK (status IN ('pending', 'approved', 'rejected', 'in_progress', 'done'))
);
COMMENT ON TABLE contributions IS 'Citizen and Administration Contributions with mixed Geometry Types.';
-- ---------------------------------------------------------------------
-- Block 4: Indexes for fast Queries
-- ---------------------------------------------------------------------
CREATE INDEX contributions_geom_idx ON contributions USING GIST (geom);
CREATE INDEX contributions_municipality_idx ON contributions (municipality_id);
CREATE INDEX contributions_status_idx ON contributions (status);
CREATE INDEX contributions_category_idx ON contributions (category);
-- ---------------------------------------------------------------------
-- Block 5: Table "votes"
-- Individual like and dislike Records. UNIQUE Constraint prevents the
-- same voter from liking or disliking the same contribution multiple times.
-- ---------------------------------------------------------------------
CREATE TABLE votes (
vote_id SERIAL PRIMARY KEY,
contribution_id INTEGER NOT NULL REFERENCES contributions(contribution_id) ON DELETE CASCADE,
voter_name VARCHAR(100) NOT NULL, -- ToDo: Replace with user_id once Authentification exists
vote_type VARCHAR(10) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT votes_unique_per_voter UNIQUE (contribution_id, voter_name),
CONSTRAINT votes_vote_type_check CHECK (vote_type IN ('like', 'dislike'))
);
COMMENT ON TABLE votes IS 'Individual Votes to prevent duplicate Likes and Dislikes.';
-- ---------------------------------------------------------------------
-- Block 6: Trigger Functions
-- ---------------------------------------------------------------------
-- Automatically Refresh updated_at on every UPDATE.
CREATE OR REPLACE FUNCTION set_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER contributions_updated_at
BEFORE UPDATE ON contributions
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
CREATE TRIGGER municipalities_updated_at
BEFORE UPDATE ON municipalities
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
-- Keeps likes_count / dislikes_count synchronized with the votes Table.
CREATE OR REPLACE FUNCTION update_vote_counts()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'INSERT' THEN
IF NEW.vote_type = 'like' THEN
UPDATE contributions SET likes_count = likes_count + 1
WHERE contribution_id = NEW.contribution_id;
ELSE
UPDATE contributions SET dislikes_count = dislikes_count + 1
WHERE contribution_id = NEW.contribution_id;
END IF;
ELSIF TG_OP = 'DELETE' THEN
IF OLD.vote_type = 'like' THEN
UPDATE contributions SET likes_count = GREATEST(likes_count - 1, 0)
WHERE contribution_id = OLD.contribution_id;
ELSE
UPDATE contributions SET dislikes_count = GREATEST(dislikes_count - 1, 0)
WHERE contribution_id = OLD.contribution_id;
END IF;
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER votes_count_sync
AFTER INSERT OR DELETE ON votes
FOR EACH ROW EXECUTE FUNCTION update_vote_counts();
-- ---------------------------------------------------------------------
-- Block 7: Typed Geometry Views for QGIS
-- QGIS handles mixed-geometry Tables awkwardly, so one View per
-- Geometry Type is created. Reflects live Data from the Contributions Table.
-- ---------------------------------------------------------------------
CREATE VIEW contributions_points AS
SELECT * FROM contributions WHERE geom_type = 'point';
CREATE VIEW contributions_lines AS
SELECT * FROM contributions WHERE geom_type = 'line';
CREATE VIEW contributions_polygons AS
SELECT * FROM contributions WHERE geom_type = 'polygon';
-- ---------------------------------------------------------------------
-- Block 8: Seed Data — Initial Municipality
-- ---------------------------------------------------------------------
INSERT INTO municipalities (name, slug, center_lat, center_lng, default_zoom, primary_color)
VALUES ('Lohne (Oldenburg)', 'lohne', 52.66639, 8.23306, 14, '#00376D');
-- =====================================================================
-- End of migration 001_initial_schema.sql
-- =====================================================================