diff --git a/migrations/007_comment_moderation.sql b/migrations/007_comment_moderation.sql
new file mode 100644
index 0000000..97af7d4
--- /dev/null
+++ b/migrations/007_comment_moderation.sql
@@ -0,0 +1,14 @@
+-- =====================================================================
+-- Migration 007: Adds Status Column to Comments for Moderation
+-- =====================================================================
+
+-- Adds Status Column with Default 'pending'
+ALTER TABLE comments
+ ADD COLUMN status VARCHAR(20) NOT NULL DEFAULT 'pending'
+ CHECK (status IN ('pending', 'approved', 'rejected'));
+
+-- Index for fast Status Filtering
+CREATE INDEX idx_comments_status ON comments(status);
+
+-- Approves existing Comments
+UPDATE comments SET status = 'approved';
\ No newline at end of file
diff --git a/migrations/008_comment_count_trigger.sql b/migrations/008_comment_count_trigger.sql
new file mode 100644
index 0000000..04dba3b
--- /dev/null
+++ b/migrations/008_comment_count_trigger.sql
@@ -0,0 +1,65 @@
+-- =====================================================================
+-- Migration 008: Adds comment_count Column with automatic Trigger
+-- Mirrors Pattern from likes_count and dislikes_count.
+-- =====================================================================
+
+
+-- ---------------------------------------------------------------------
+-- Block 1: Adds comment_count Column to Contributions
+-- ---------------------------------------------------------------------
+ALTER TABLE contributions
+ ADD COLUMN comment_count INTEGER NOT NULL DEFAULT 0;
+
+
+-- ---------------------------------------------------------------------
+-- Block 2: Backfills existing Comment Counts
+-- ---------------------------------------------------------------------
+UPDATE contributions c
+SET comment_count = (
+ SELECT COUNT(*)
+ FROM comments cm
+ WHERE cm.contribution_id = c.contribution_id
+ AND cm.status = 'approved'
+);
+
+
+-- ---------------------------------------------------------------------
+-- Block 3: Trigger Function to update comment_count
+-- Fires on Status Change on comments. Only counts approved Comments
+-- ---------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION update_comment_count()
+RETURNS TRIGGER AS $$
+BEGIN
+ IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN
+ UPDATE contributions
+ SET comment_count = (
+ SELECT COUNT(*) FROM comments
+ WHERE contribution_id = NEW.contribution_id
+ AND status = 'approved'
+ )
+ WHERE contribution_id = NEW.contribution_id;
+ END IF;
+
+ IF TG_OP = 'DELETE' OR (TG_OP = 'UPDATE' AND OLD.contribution_id != NEW.contribution_id) THEN
+ UPDATE contributions
+ SET comment_count = (
+ SELECT COUNT(*) FROM comments
+ WHERE contribution_id = OLD.contribution_id
+ AND status = 'approved'
+ )
+ WHERE contribution_id = OLD.contribution_id;
+ END IF;
+
+ RETURN NULL;
+END;
+$$ LANGUAGE plpgsql;
+
+
+-- ---------------------------------------------------------------------
+-- Block 4: Attaches Trigger to comments Table
+-- ---------------------------------------------------------------------
+CREATE TRIGGER trigger_update_comment_count
+ AFTER INSERT OR DELETE OR UPDATE OF status
+ ON comments
+ FOR EACH ROW
+ EXECUTE FUNCTION update_comment_count();
\ No newline at end of file
diff --git a/public/admin.php b/public/admin.php
index ce77702..7ec8fd4 100644
--- a/public/admin.php
+++ b/public/admin.php
@@ -3,12 +3,6 @@
// Moderation Page
// Lists Contributions for Review. Moderators can approve, reject,
// edit and delete Contributions. Includes Map Preview and Filtering.
-//
-// ToDo's:
-// - Comment Moderation Tab
-// - News Management Tab
-// - User Management Tab
-// - Analytics Tab
// =====================================================================
// Reads Environment Configfile
@@ -57,6 +51,7 @@ $stmt = $pdo->prepare("SELECT * FROM municipalities WHERE slug = :slug");
$stmt->execute([':slug' => getenv('MUNICIPALITY_SLUG')]);
$municipality = $stmt->fetch();
+
// Loads News for Moderation
$stmt = $pdo->prepare("
SELECT news_id, title, content, author_name, published_at, created_at
@@ -67,6 +62,30 @@ $stmt = $pdo->prepare("
$stmt->execute([':mid' => $municipality['municipality_id']]);
$news_items = $stmt->fetchAll();
+
+// Loads all Comments with Contribution Titles for Moderation
+$stmt = $pdo->prepare("
+ SELECT cm.comment_id, cm.contribution_id, cm.author_name, cm.browser_id,
+ cm.content, cm.status, cm.created_at,
+ co.title AS contribution_title, co.category AS contribution_category
+ FROM comments cm
+ JOIN contributions co ON cm.contribution_id = co.contribution_id
+ WHERE co.municipality_id = :mid
+ ORDER BY cm.created_at DESC
+");
+$stmt->execute([':mid' => $municipality['municipality_id']]);
+$all_comments = $stmt->fetchAll();
+
+// Counts Comments per Status
+$comment_counts = ['pending' => 0, 'approved' => 0, 'rejected' => 0];
+foreach ($all_comments as $c) {
+ if (isset($comment_counts[$c['status']])) {
+ $comment_counts[$c['status']]++;
+ }
+}
+$comment_counts['total'] = count($all_comments);
+
+
// Shows Login Page if not authenticated
if ($page === 'login' || !is_admin()) {
show_login_page($municipality, $login_error ?? null);
@@ -84,8 +103,8 @@ $categories = get_categories();
// Loads all Contributions for Municipality
$stmt = $pdo->prepare("
- SELECT contribution_id, title, category, description, author_name,
- geom_type, status, likes_count, dislikes_count, created_at, updated_at
+ SELECT contribution_id, title, category, description, author_name, photo_path,
+ geom_type, status, likes_count, dislikes_count, comment_count, created_at, updated_at
FROM contributions
WHERE municipality_id = :mid
ORDER BY created_at DESC
@@ -159,6 +178,9 @@ $counts['total'] = count($all_contributions);
+
@@ -176,27 +198,6 @@ $counts['total'] = count($all_contributions);
-
-
-
-
= $counts['total'] ?>
-
Alle
-
-
-
= $counts['pending'] ?>
-
Ausstehend
-
-
-
= $counts['approved'] ?>
-
Akzeptiert
-
-
-
= $counts['rejected'] ?>
-
Abgelehnt
-
-
-
-