$municipality_id]; // Optional: Filters by Category if (!empty($input['category'])) { $sql .= " AND category = :cat"; $params[':cat'] = $input['category']; } $sql .= " ORDER BY created_at DESC"; try { // Prepared Statement to prevent SQL Injection $stmt = $pdo->prepare($sql); $stmt->execute($params); // Fetches Results as PHP-Array $rows = $stmt->fetchAll(); } catch (PDOException $e) { error_response('Database Error: ' . $e->getMessage(), 500); } // Builds GeoJSON FeatureCollection $features = []; foreach ($rows as $row) { $geometry = json_decode($row['geojson']); // Removes 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: Inserts new Contributions // Required: municipality_id, geom, geom_type, category, title, author_name // Optional: description // --------------------------------------------------------------------- function handle_create($input) { $pdo = get_db(); // Validates Input $missing = validate_required($input, [ 'municipality_id', 'geom', 'geom_type', 'category', 'title', 'author_name' ]); if (!empty($missing)) { error_response('Missing Fields: ' . implode(', ', $missing)); } // Validates Geometry Type $valid_geom_types = ['point', 'line', 'polygon']; if (!in_array($input['geom_type'], $valid_geom_types)) { error_response('Invalid Geometry Type. Must be: ' . implode(', ', $valid_geom_types)); } // Validates GeoJSON $geojson = json_decode($input['geom']); if (!$geojson || !isset($geojson->type)) { error_response('Invalid GeoJSON in Geometry Field.'); } // Prepared SQL Statement 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: Updates existing Contributions // Required: contribution_id // Optional: category, title, description, status // Provided Fields are updated. Others remain unchanged. // --------------------------------------------------------------------- function handle_update($input) { $pdo = get_db(); // Validates Input $missing = validate_required($input, ['contribution_id']); if (!empty($missing)) { error_response('Missing Fields: ' . implode(', ', $missing)); } $contribution_id = $input['contribution_id']; // Checks 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); } // Builds dynamic SQL Query to only update sent Fields $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)); } // Validates Status 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)); } } // Builds SQL Statement $sql = "UPDATE contributions SET " . implode(', ', $set_clauses) . " WHERE contribution_id = :id"; // Prepared SQL Statement 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: Deletes existing Contributions // Required: contribution_id // Associated Votes are deleted automatically // --------------------------------------------------------------------- function handle_delete($input) { $pdo = get_db(); // Validates Input $missing = validate_required($input, ['contribution_id']); if (!empty($missing)) { error_response('Missing Fields: ' . implode(', ', $missing)); } $contribution_id = $input['contribution_id']; // Checks 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); } // Prepared SQL Statement 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: Likes or Dislikes a Contribution // Required: contribution_id, voter_name, vote_type // Database Trigger automatically updates Likes and Dislikes Count // UNIQUE Constraint prevents duplicate Votes per Voter. // --------------------------------------------------------------------- function handle_vote($input) { $pdo = get_db(); // Validates Input $missing = validate_required($input, ['contribution_id', 'voter_name', 'vote_type']); if (!empty($missing)) { error_response('Missing Fields: ' . implode(', ', $missing)); } // Validates 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)); } // Checks 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); } // Prepared SQL Statement 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); } }