Files
webgis-lohne/api/contributions.php

318 lines
10 KiB
PHP

<?php
// =====================================================================
// Contributions API Endpoint
// Handles all CRUD Operations for Contributions (Points, Lines, Polygons)
// and Voting. Single Entry Point — Action is determined by the 'action'
// Parameter in the Request.
//
// Supported Actions:
// read — Load all approved Contributions for a Municipality
// create — Insert a new Contribution
// update — Update an existing Contribution
// delete — Delete a Contribution
// vote — Cast a Like or Dislike
// =====================================================================
require_once __DIR__ . '/db.php';
// ---------------------------------------------------------------------
// Read Action Parameter and Route to the correct Handler
// ---------------------------------------------------------------------
$input = get_input();
$action = $input['action'] ?? '';
switch ($action) {
case 'read':
handle_read($input);
break;
case 'create':
handle_create($input);
break;
case 'update':
handle_update($input);
break;
case 'delete':
handle_delete($input);
break;
case 'vote':
handle_vote($input);
break;
default:
error_response('Unknown or missing Action. Supported: read, create, update, delete, vote.');
}
// =====================================================================
// Action Handlers
// =====================================================================
// ---------------------------------------------------------------------
// READ — Load all approved Contributions as GeoJSON FeatureCollection
// Required: municipality_id
// Optional: category (Filter by Category)
// ---------------------------------------------------------------------
function handle_read($input) {
$pdo = get_db();
// Validate Input
$missing = validate_required($input, ['municipality_id']);
if (!empty($missing)) {
error_response('Missing Fields: ' . implode(', ', $missing));
}
$municipality_id = $input['municipality_id'];
// Build Query — optionally filter by Category
$sql = "SELECT *, ST_AsGeoJSON(geom) AS geojson
FROM contributions
WHERE municipality_id = :mid AND status = 'approved'";
$params = [':mid' => $municipality_id];
if (!empty($input['category'])) {
$sql .= " AND category = :cat";
$params[':cat'] = $input['category'];
}
$sql .= " ORDER BY created_at DESC";
try {
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
$rows = $stmt->fetchAll();
} catch (PDOException $e) {
error_response('Database Error: ' . $e->getMessage(), 500);
}
// Build GeoJSON FeatureCollection
$features = [];
foreach ($rows as $row) {
$geometry = json_decode($row['geojson']);
// Remove raw Geometry Columns from Properties
unset($row['geom']);
unset($row['geojson']);
$features[] = [
'type' => 'Feature',
'geometry' => $geometry,
'properties' => $row
];
}
$featureCollection = [
'type' => 'FeatureCollection',
'features' => $features
];
json_response($featureCollection);
}
// ---------------------------------------------------------------------
// CREATE — Insert a new Contribution
// Required: municipality_id, geom, geom_type, category, title, author_name
// Optional: description
// ---------------------------------------------------------------------
function handle_create($input) {
$pdo = get_db();
// Validate Input
$missing = validate_required($input, [
'municipality_id', 'geom', 'geom_type', 'category', 'title', 'author_name'
]);
if (!empty($missing)) {
error_response('Missing Fields: ' . implode(', ', $missing));
}
// Validate Geometry Type
$valid_geom_types = ['point', 'line', 'polygon'];
if (!in_array($input['geom_type'], $valid_geom_types)) {
error_response('Invalid geom_type. Must be: ' . implode(', ', $valid_geom_types));
}
// Validate GeoJSON
$geojson = json_decode($input['geom']);
if (!$geojson || !isset($geojson->type)) {
error_response('Invalid GeoJSON in geom Field.');
}
try {
$stmt = $pdo->prepare("
INSERT INTO contributions
(municipality_id, geom, geom_type, category, title, description, author_name)
VALUES
(:mid, ST_SetSRID(ST_GeomFromGeoJSON(:geom), 4326), :geom_type,
:category, :title, :description, :author_name)
");
$stmt->execute([
':mid' => $input['municipality_id'],
':geom' => $input['geom'],
':geom_type' => $input['geom_type'],
':category' => $input['category'],
':title' => $input['title'],
':description' => $input['description'] ?? '',
':author_name' => $input['author_name']
]);
json_response([
'message' => 'Contribution created successfully.',
'contribution_id' => (int) $pdo->lastInsertId()
], 201);
} catch (PDOException $e) {
error_response('Database Error: ' . $e->getMessage(), 500);
}
}
// ---------------------------------------------------------------------
// UPDATE — Update an existing Contribution
// Required: contribution_id
// Optional: category, title, description, status
// Only provided Fields are updated — others remain unchanged.
// ---------------------------------------------------------------------
function handle_update($input) {
$pdo = get_db();
// Validate Input
$missing = validate_required($input, ['contribution_id']);
if (!empty($missing)) {
error_response('Missing Fields: ' . implode(', ', $missing));
}
$contribution_id = $input['contribution_id'];
// Check if Contribution exists
$stmt = $pdo->prepare("SELECT contribution_id FROM contributions WHERE contribution_id = :id");
$stmt->execute([':id' => $contribution_id]);
if (!$stmt->fetch()) {
error_response('Contribution not found.', 404);
}
// Build dynamic UPDATE Query — only update Fields that were sent
$updatable_fields = ['category', 'title', 'description', 'status'];
$set_clauses = [];
$params = [':id' => $contribution_id];
foreach ($updatable_fields as $field) {
if (isset($input[$field]) && $input[$field] !== '') {
$set_clauses[] = "$field = :$field";
$params[":$field"] = $input[$field];
}
}
if (empty($set_clauses)) {
error_response('No Fields to update. Provide at least one of: ' . implode(', ', $updatable_fields));
}
// Validate Status if provided
if (isset($params[':status'])) {
$valid_statuses = ['pending', 'approved', 'rejected', 'in_progress', 'done'];
if (!in_array($params[':status'], $valid_statuses)) {
error_response('Invalid Status. Must be: ' . implode(', ', $valid_statuses));
}
}
$sql = "UPDATE contributions SET " . implode(', ', $set_clauses) . " WHERE contribution_id = :id";
try {
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
json_response(['message' => 'Contribution updated successfully.']);
} catch (PDOException $e) {
error_response('Database Error: ' . $e->getMessage(), 500);
}
}
// ---------------------------------------------------------------------
// DELETE — Delete a Contribution
// Required: contribution_id
// Note: Associated Votes are deleted automatically (ON DELETE CASCADE).
// ---------------------------------------------------------------------
function handle_delete($input) {
$pdo = get_db();
// Validate Input
$missing = validate_required($input, ['contribution_id']);
if (!empty($missing)) {
error_response('Missing Fields: ' . implode(', ', $missing));
}
$contribution_id = $input['contribution_id'];
// Check if Contribution exists
$stmt = $pdo->prepare("SELECT contribution_id FROM contributions WHERE contribution_id = :id");
$stmt->execute([':id' => $contribution_id]);
if (!$stmt->fetch()) {
error_response('Contribution not found.', 404);
}
try {
$stmt = $pdo->prepare("DELETE FROM contributions WHERE contribution_id = :id");
$stmt->execute([':id' => $contribution_id]);
json_response(['message' => 'Contribution deleted successfully.']);
} catch (PDOException $e) {
error_response('Database Error: ' . $e->getMessage(), 500);
}
}
// ---------------------------------------------------------------------
// VOTE — Cast a Like or Dislike on a Contribution
// Required: contribution_id, voter_name, vote_type (like|dislike)
// The Database Trigger automatically updates likes_count/dislikes_count.
// The UNIQUE Constraint prevents duplicate Votes per Voter.
// ---------------------------------------------------------------------
function handle_vote($input) {
$pdo = get_db();
// Validate Input
$missing = validate_required($input, ['contribution_id', 'voter_name', 'vote_type']);
if (!empty($missing)) {
error_response('Missing Fields: ' . implode(', ', $missing));
}
// Validate Vote Type
$valid_vote_types = ['like', 'dislike'];
if (!in_array($input['vote_type'], $valid_vote_types)) {
error_response('Invalid vote_type. Must be: ' . implode(', ', $valid_vote_types));
}
// Check if Contribution exists
$stmt = $pdo->prepare("SELECT contribution_id FROM contributions WHERE contribution_id = :id");
$stmt->execute([':id' => $input['contribution_id']]);
if (!$stmt->fetch()) {
error_response('Contribution not found.', 404);
}
try {
$stmt = $pdo->prepare("
INSERT INTO votes (contribution_id, voter_name, vote_type)
VALUES (:cid, :voter, :vtype)
");
$stmt->execute([
':cid' => $input['contribution_id'],
':voter' => $input['voter_name'],
':vtype' => $input['vote_type']
]);
json_response(['message' => 'Vote recorded successfully.'], 201);
} catch (PDOException $e) {
// UNIQUE Constraint Violation — Voter already voted on this Contribution
if ($e->getCode() == '23505') {
error_response('You have already voted on this Contribution.', 409);
}
error_response('Database Error: ' . $e->getMessage(), 500);
}
}