diff --git a/api/contributions.php b/api/contributions.php index 55ac14b..7e23ebb 100644 --- a/api/contributions.php +++ b/api/contributions.php @@ -64,7 +64,7 @@ function handle_read($input) { $municipality_id = $input['municipality_id']; - // Builds SQL Query + // Builds SQL Query with Placeholders for prepared Statement $sql = "SELECT *, ST_AsGeoJSON(geom) AS geojson FROM contributions WHERE municipality_id = :mid AND status = 'approved'"; @@ -79,8 +79,10 @@ function handle_read($input) { $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); @@ -113,14 +115,14 @@ function handle_read($input) { // --------------------------------------------------------------------- -// CREATE — Insert a new Contribution +// CREATE: Inserts new Contributions // Required: municipality_id, geom, geom_type, category, title, author_name // Optional: description // --------------------------------------------------------------------- function handle_create($input) { $pdo = get_db(); - // Validate Input + // Validates Input $missing = validate_required($input, [ 'municipality_id', 'geom', 'geom_type', 'category', 'title', 'author_name' ]); @@ -128,18 +130,19 @@ function handle_create($input) { error_response('Missing Fields: ' . implode(', ', $missing)); } - // Validate Geometry Type + // Validates 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)); + error_response('Invalid Geometry Type. Must be: ' . implode(', ', $valid_geom_types)); } - // Validate GeoJSON + // Validates GeoJSON $geojson = json_decode($input['geom']); if (!$geojson || !isset($geojson->type)) { - error_response('Invalid GeoJSON in geom Field.'); + error_response('Invalid GeoJSON in Geometry Field.'); } + // Prepared SQL Statement try { $stmt = $pdo->prepare(" INSERT INTO contributions @@ -171,15 +174,15 @@ function handle_create($input) { // --------------------------------------------------------------------- -// UPDATE — Update an existing Contribution +// UPDATE: Updates existing Contributions // Required: contribution_id // Optional: category, title, description, status -// Only provided Fields are updated — others remain unchanged. +// Provided Fields are updated. Others remain unchanged. // --------------------------------------------------------------------- function handle_update($input) { $pdo = get_db(); - // Validate Input + // Validates Input $missing = validate_required($input, ['contribution_id']); if (!empty($missing)) { error_response('Missing Fields: ' . implode(', ', $missing)); @@ -187,14 +190,14 @@ function handle_update($input) { $contribution_id = $input['contribution_id']; - // Check if Contribution exists + // 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); } - // Build dynamic UPDATE Query — only update Fields that were sent + // Builds dynamic SQL Query to only update sent Fields $updatable_fields = ['category', 'title', 'description', 'status']; $set_clauses = []; $params = [':id' => $contribution_id]; @@ -210,16 +213,18 @@ function handle_update($input) { error_response('No Fields to update. Provide at least one of: ' . implode(', ', $updatable_fields)); } - // Validate Status if provided + // 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); @@ -233,14 +238,14 @@ function handle_update($input) { // --------------------------------------------------------------------- -// DELETE — Delete a Contribution +// DELETE: Deletes existing Contributions // Required: contribution_id -// Note: Associated Votes are deleted automatically (ON DELETE CASCADE). +// Associated Votes are deleted automatically // --------------------------------------------------------------------- function handle_delete($input) { $pdo = get_db(); - // Validate Input + // Validates Input $missing = validate_required($input, ['contribution_id']); if (!empty($missing)) { error_response('Missing Fields: ' . implode(', ', $missing)); @@ -248,13 +253,14 @@ function handle_delete($input) { $contribution_id = $input['contribution_id']; - // Check if Contribution exists + // 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]); @@ -268,33 +274,34 @@ function handle_delete($input) { // --------------------------------------------------------------------- -// 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. +// 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(); - // Validate Input + // Validates Input $missing = validate_required($input, ['contribution_id', 'voter_name', 'vote_type']); if (!empty($missing)) { error_response('Missing Fields: ' . implode(', ', $missing)); } - // Validate Vote Type + // 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)); } - // Check if Contribution exists + // 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) @@ -310,7 +317,7 @@ function handle_vote($input) { json_response(['message' => 'Vote recorded successfully.'], 201); } catch (PDOException $e) { - // UNIQUE Constraint Violation — Voter already voted on this Contribution + // UNIQUE Constraint Violation - Voter already voted on this Contribution if ($e->getCode() == '23505') { error_response('You have already voted on this Contribution.', 409); }