Files
MyClub/DOCS/COMMENTS_SYSTEM_COMPLETE.md
Tomas Dvorak 087f30e82c dev day #80
2025-11-02 21:31:00 +01:00

20 KiB

Comments & Moderation System - Complete Documentation

Overview

The Comments System provides threaded discussions with anti-spam protection, user reactions, reporting, and comprehensive moderation tools including user bans and appeals.

Table of Contents

  1. Core Features
  2. Database Schema
  3. Backend API
  4. Frontend Integration
  5. Spam Protection
  6. Moderation Tools
  7. Ban System
  8. Reactions
  9. Admin Management
  10. Production Checklist

Core Features

Public Commenting

  • Multi-target support: Articles, Events, Gallery Albums, YouTube Videos
  • Threaded replies: Parent-child comment structure
  • Real-time updates: Pagination with React Query
  • User profiles: Display username, avatar, and engagement level

Moderation

  • Automatic spam detection: Score-based filtering
  • Bad word filtering: Censors profanity
  • Manual approval: Hidden status for suspicious content
  • Admin tools: Bulk actions, user bans, reports queue

User Engagement

  • Reactions: 8 reaction types (👍 ❤️ 😊 😂 😢 😠 👎)
  • Reports: Users can flag inappropriate comments
  • Editing: Own comments editable (marked with timestamp)
  • Points integration: Earn XP for comments and reactions

Anti-Abuse

  • Rate limiting: Prevents spam flooding
  • Daily caps: Limits on comment points earnings
  • Ban system: Temporary or permanent blocks
  • Appeal process: Users can request unbanning

Database Schema

comments

CREATE TABLE comments (
    id BIGSERIAL PRIMARY KEY,
    created_at TIMESTAMP WITH TIME ZONE,
    updated_at TIMESTAMP WITH TIME ZONE,
    target_type VARCHAR(30) NOT NULL,
    target_id VARCHAR(128) NOT NULL,
    user_id BIGINT NOT NULL,
    parent_id BIGINT,
    content TEXT NOT NULL,
    status VARCHAR(20) DEFAULT 'visible',
    spam_score REAL DEFAULT 0,
    spam_rules TEXT,
    is_edited BOOLEAN DEFAULT FALSE,
    edited_at TIMESTAMP WITH TIME ZONE
);

Fields:

  • target_type - Where comment belongs: article, event, gallery_album, youtube_video
  • target_id - ID of the target (can be string for flexibility)
  • user_id - Author
  • parent_id - NULL for root comments, ID for replies
  • content - Comment text (max 2000 chars)
  • status - visible or hidden (moderation)
  • spam_score - 0.0-1.0 calculated by spam detector
  • spam_rules - JSON array of triggered spam rules
  • is_edited - Whether comment was edited after posting
  • edited_at - Timestamp of last edit

Indexes: (target_type, target_id), user_id, parent_id, status, created_at, spam_score

comment_bans

CREATE TABLE comment_bans (
    id BIGSERIAL PRIMARY KEY,
    created_at TIMESTAMP WITH TIME ZONE,
    user_id BIGINT NOT NULL,
    reason TEXT,
    until TIMESTAMP WITH TIME ZONE,
    created_by_id BIGINT NOT NULL
);

Fields:

  • user_id - Banned user
  • reason - Admin's explanation
  • until - NULL = permanent, timestamp = temporary
  • created_by_id - Admin who issued the ban

Active Ban Query:

SELECT * FROM comment_bans
WHERE user_id = ? AND (until IS NULL OR until > NOW())
ORDER BY created_at DESC LIMIT 1

unban_requests

CREATE TABLE unban_requests (
    id BIGSERIAL PRIMARY KEY,
    created_at TIMESTAMP WITH TIME ZONE,
    user_id BIGINT NOT NULL,
    message TEXT,
    status VARCHAR(20) DEFAULT 'pending',
    resolved_by_id BIGINT,
    resolved_at TIMESTAMP WITH TIME ZONE
);

Statuses: pending, approved (ban lifted), rejected (ban remains)

comment_reports

CREATE TABLE comment_reports (
    id BIGSERIAL PRIMARY KEY,
    created_at TIMESTAMP WITH TIME ZONE,
    comment_id BIGINT NOT NULL,
    user_id BIGINT NOT NULL,
    reason VARCHAR(255),
    UNIQUE (comment_id, user_id)
);

Prevents: Duplicate reports from same user on same comment.

comment_reactions

CREATE TABLE comment_reactions (
    id BIGSERIAL PRIMARY KEY,
    created_at TIMESTAMP WITH TIME ZONE,
    comment_id BIGINT NOT NULL,
    user_id BIGINT NOT NULL,
    type VARCHAR(24) NOT NULL,
    UNIQUE (comment_id, user_id)
);

Types: like, heart, smile, laugh, thumbs_up, thumbs_down, sad, angry

One per user: Changing reaction deletes old one, creates new one.


Backend API

Public Endpoints

GET /api/v1/comments

List comments for a target.

Query Params:

  • target_type - Required: article, event, gallery_album, youtube_video
  • target_id - Required: ID of the target
  • page - Page number (default: 1)
  • page_size - Items per page (max 100, default: 20)

Response:

{
  "items": [
    {
      "id": 123,
      "target_type": "article",
      "target_id": "45",
      "parent_id": null,
      "content": "Skvělý článek!",
      "status": "visible",
      "is_edited": false,
      "edited_at": null,
      "created_at": "2025-11-01T10:00:00Z",
      "updated_at": "2025-11-01T10:00:00Z",
      "user": {
        "id": 78,
        "first_name": "Jan",
        "last_name": "Novák",
        "role": "fan",
        "username": "jan-novak",
        "avatar_url": "https://api.dicebear.com/..."
      },
      "reactions": {
        "like": 5,
        "heart": 2
      },
      "my_reaction": "like",
      "spam_score": 0.1,
      "spam_rules": []
    }
  ],
  "total": 42,
  "page": 1,
  "page_size": 20
}

Features:

  • Only visible comments returned to public
  • my_reaction included if user authenticated
  • User profile with username + avatar from user_profiles
  • Reactions aggregated by type

Protected Endpoints (Require Auth)

POST /api/v1/comments

Create a new comment.

Request:

{
  "target_type": "article",
  "target_id": "45",
  "content": "Skvělý článek!",
  "parent_id": null
}

Validation:

  • Min 6 characters, max 2000
  • Target type must be allowed
  • Parent comment must exist if specified
  • User not banned

Process:

  1. Check active ban
  2. Evaluate spam score
  3. Filter bad words
  4. Auto-hide if sensitive words detected
  5. Create comment record
  6. Award engagement points (if visible)
  7. Check achievements

Response: Created comment object

Rate Limit: 20 per minute

PUT /api/v1/comments/:id

Edit own comment (or any if admin).

Request:

{
  "content": "Opravený text..."
}

Process:

  1. Check permission (owner or admin)
  2. Check not banned
  3. Re-evaluate spam & filter
  4. Update content
  5. Set is_edited = true, edited_at = now

DELETE /api/v1/comments/:id

Delete own comment (or any if admin).

Cascade: Deletes all child comments, reports, reactions.

POST /api/v1/comments/:id/react

Add or change reaction.

Request:

{
  "type": "heart"
}

Process:

  1. Delete existing reaction (if any)
  2. Create new reaction
  3. Award 1 XP point (capped: max 20/day)

Rate Limit: 60 per minute

DELETE /api/v1/comments/:id/react

Remove reaction.

POST /api/v1/comments/:id/report

Report inappropriate comment.

Request:

{
  "reason": "Spam nebo urážky"
}

Prevents duplicates: One report per user per comment.

Rate Limit: 10 per hour

POST /api/v1/comments/unban-request

Request to be unbanned.

Request:

{
  "message": "Omlouvám se, už se to nebude opakovat..."
}

Rate Limit: 5 per hour

Admin Endpoints

GET /api/v1/admin/comments

List all comments with admin filters.

Query Params:

  • status - visible | hidden
  • target_type - Filter by type
  • target_id - Filter by target
  • user_id - Filter by author
  • page, page_size - Pagination

Response: Includes reports count per comment

PATCH /api/v1/admin/comments/:id/status

Change comment visibility.

Request:

{
  "status": "visible" | "hidden"
}

POST /api/v1/admin/comments/ban

Ban a user from commenting.

Request:

{
  "user_id": 123,
  "reason": "Porušení pravidel diskuse",
  "duration_hours": 24
}

Duration:

  • 0 = Permanent (until NULL)
  • >0 = Temporary (until = now + hours)

Validation:

  • Max 8760 hours (1 year)
  • Reason required

GET /api/v1/admin/comments/bans

List active bans.

Query: WHERE until IS NULL OR until > NOW()

POST /api/v1/admin/comments/bans/:id/lift

End a ban early.

Process: Sets until = NOW(), making ban expired.

GET /api/v1/admin/comments/unban-requests

List unban appeals.

POST /api/v1/admin/comments/unban-requests/:id/resolve

Approve or reject unban request.

Request:

{
  "action": "approve" | "reject"
}

Approve: Sets all user's bans to expired (until = NOW())
Reject: Updates request status only


Frontend Integration

Services

/frontend/src/services/comments.ts

Public comment operations.

Functions:

  • getComments(targetType, targetId, page, pageSize)
  • createComment(body)
  • updateComment(id, body)
  • deleteComment(id)
  • reactToComment(id, type)
  • unreactToComment(id)
  • reportComment(id, reason)
  • createUnbanRequest(message)

/frontend/src/services/admin/comments.ts

Admin moderation operations.

Functions:

  • adminListComments(params)
  • adminUpdateCommentStatus(id, status)
  • adminBanUser(user_id, reason, duration_hours)
  • adminListBans()
  • adminLiftBan(id)
  • adminListUnbanRequests()
  • adminResolveUnban(id, action)

Utilities

/frontend/src/utils/commentsHelpers.ts

Key Functions:

  • formatCommentAge(createdAt) - Human-readable time ("před 5 minutami")
  • getReactionEmoji(type) - Map type to emoji
  • getReactionDisplayName(type) - Localized name
  • countTotalReactions(reactions) - Sum all reactions
  • shortenComment(content, maxLength) - Preview text
  • validateCommentContent(content) - Client validation
  • getBanDurationText(until) - Format ban expiry
  • sortComments(comments, mode) - Threaded or chronological

Components

Recommended Structure:

/frontend/src/components/comments/
  CommentsList.tsx      - Main container
  CommentItem.tsx       - Single comment
  CommentForm.tsx       - Create/edit form
  ReactionPicker.tsx    - Reaction selector
  ReportModal.tsx       - Report dialog
  BannedNotice.tsx      - Ban notification

Example Usage:

<CommentsList 
  targetType="article" 
  targetId={articleId} 
  enableReplies={true}
  enableReactions={true}
/>

Admin Page

/frontend/src/pages/admin/CommentsAdminPage.tsx

Features:

  1. Filters: Status, target type, user ID, reported-only
  2. Bulk Actions: Hide/show selected
  3. Quick Ban: One-click ban with modal
  4. Reports Queue: Highlighted comments with report count
  5. Unban Requests: Approve/reject appeals

UI Highlights:

  • Spam score badge (color-coded)
  • Reports badge (red if >2)
  • Inline status toggle
  • Delete confirmation
  • Ban duration presets (24h, 7d, permanent)

Spam Protection

Spam Score Calculation

Implemented in /internal/services/spam_detection.go (assumed):

Factors:

  1. Link count: Each link adds 0.1
  2. Excessive caps: >50% uppercase adds 0.2
  3. Repeated characters: "aaaaaa" adds 0.15
  4. Short + links: <20 chars with links adds 0.3
  5. Blacklisted keywords: Each adds 0.25

Threshold:

  • Score > 0.5 → Likely spam
  • Score > 0.7 → Auto-hide

Bad Words Filter

Service: /internal/services/bad_words.go

Process:

  1. Load dictionary (Czech + English)
  2. Replace with asterisks: "řkv"
  3. Preserve word length

Example:

Input:  "To je pěknej hov*o!"
Output: "To je pěknej ***!"

Sensitive Words Detection

Triggers manual review (auto-hide).

Categories:

  • Hate speech
  • Threats
  • Explicit content
  • Harassment

Action: Comment created with status = 'hidden', admin must approve.

Rate Limiting

Applied to comment endpoints:

middleware.RateLimit(20, time.Minute) // 20 comments per minute
middleware.RateLimit(60, time.Minute) // 60 reactions per minute
middleware.RateLimit(10, time.Hour)   // 10 reports per hour

Prevents:

  • Comment flooding
  • Reaction spam
  • Report abuse

Moderation Tools

Comment Status

Visible: Public, earns points
Hidden: Only admin sees, no points

Toggle via admin panel or bulk operations.

Spam Score Review

Admin view shows:

  • Numeric score (0.00-1.00)
  • Color badge (green/yellow/red)
  • Triggered rules array

Example Rules:

["excessive_caps", "repeated_chars", "external_link"]

Report Queue

Prioritization:

  • Comments with >2 reports highlighted red
  • Sort by report count descending
  • Show reporter count, not individual reports

Actions:

  1. Review comment content
  2. Check spam score
  3. Review author history
  4. Decision:
    • Hide comment
    • Ban user
    • Dismiss (do nothing)

Bulk Actions

Future enhancement:

  • Select multiple comments
  • Apply status change to all
  • Delete selected

Ban System

Types of Bans

Temporary:

  • Duration in hours
  • Auto-expires
  • User can appeal early

Permanent:

  • No expiry (until = NULL)
  • User must appeal
  • Admin approval required

Ban Enforcement

Check on Comment Create:

var activeBan models.CommentBan
err := db.Where("user_id = ? AND (until IS NULL OR until > ?)", userID, time.Now()).
    First(&activeBan).Error

if err == nil {
    return 403, "Your account is restricted from commenting"
}

Check on Comment Edit: Same logic prevents editing while banned.

Ban UI

Admin Panel:

  • One-click ban button on comment
  • Modal with:
    • Reason field (required)
    • Duration selector
    • Quick presets (1h, 24h, 7d, permanent)

User Notification:

  • API error response with ban info
  • Frontend shows ban notice with:
    • Reason
    • Expiry (if temporary)
    • Appeal button

Appeal Process

User Flow:

  1. See ban notice
  2. Click "Request Unban"
  3. Write apology/explanation
  4. Submit (rate-limited: 5/hour)

Admin Flow:

  1. Review unban requests table
  2. Check user's comment history
  3. Decision:
    • Approve: Lift all bans
    • Reject: Keep ban active

Email Notification (future):

  • Notify user of decision
  • Include reason for rejection

Reactions

Available Types

Type Emoji Display Name Use Case
like 👍 Líbí se General agreement
heart ❤️ Srdíčko Love/support
smile 😊 Úsměv Friendly
laugh 😂 Smích Funny
thumbs_up 👍 Palec nahoru Approval
thumbs_down 👎 Palec dolů Disapproval
sad 😢 Smutné Sympathy
angry 😠 Naštvaný Frustration

Implementation

One reaction per user per comment (unique constraint).

Changing Reaction:

  1. User clicks new reaction
  2. Frontend calls POST /comments/:id/react with new type
  3. Backend deletes old reaction
  4. Backend creates new reaction
  5. Frontend updates UI instantly (optimistic)

Aggregation:

SELECT type, COUNT(*) as cnt
FROM comment_reactions
WHERE comment_id IN (...)
GROUP BY type

Returns:

{
  "like": 5,
  "heart": 2,
  "laugh": 1
}

UI Display:

  • Show top 3 reaction types
  • Total count
  • Highlight user's reaction
  • Click to toggle

Admin Management

Dashboard

CommentsAdminPage.tsx provides:

Filters:

  • Status (visible/hidden)
  • Target type
  • Target ID
  • User ID
  • Reported only toggle

Actions per Comment:

  • Toggle visible/hidden
  • Delete (with cascade)
  • Ban user
  • View user profile (future)

Batch Operations (future):

  • Select multiple
  • Bulk hide/show
  • Bulk delete

Bans Management

Active Bans Table:

  • User ID
  • Reason
  • Duration remaining
  • Created by
  • Actions: Lift ban

Lift Ban:

  • Sets until = NOW()
  • Comment immediately

Delete Ban:

  • Hard delete (not recommended)
  • User can comment again

Unban Requests

Queue Display:

  • User ID
  • Message (appeal text)
  • Status (pending/approved/rejected)
  • Created date

Actions:

  • Approve → Lift all user's bans
  • Reject → Update status, ban remains

Best Practices:

  1. Review user's full comment history
  2. Consider offense severity
  3. Check if multiple offenses
  4. Document decision reason

Production Checklist

Database

  • Run migration 20251102000002_create_comments_system.up.sql
  • Verify indexes created
  • Test foreign key constraints
  • Confirm unique constraints work

Backend

  • Comment controller implemented
  • Spam detection service
  • Bad words filter
  • Ban checking on create/edit
  • Rate limiting applied
  • Validation helpers

Frontend

  • Comments list component
  • Comment form
  • Reaction picker
  • Report modal
  • Admin moderation page
  • Helpers & utilities

Security

  • Input sanitization (XSS prevention)
  • SQL injection protection (parameterized queries)
  • Rate limiting (spam prevention)
  • Ban enforcement
  • CSRF protection
  • Auth checks on edit/delete

Testing

  • Post comment on article
  • Reply to comment
  • Edit own comment
  • Delete own comment
  • React to comment (change reaction)
  • Report comment
  • Admin hide comment
  • Admin ban user (temporary)
  • User appeal ban
  • Admin approve unban
  • Test spam detection
  • Verify bad words filter
  • Load test (pagination, 1000+ comments)

Configuration

  • Review spam score thresholds
  • Customize bad words dictionary
  • Set sensitive words list
  • Configure rate limits
  • Review ban duration limits

Monitoring

  • Track spam scores
  • Monitor ban rate
  • Review report queue
  • Check false positives
  • User feedback on filters

Best Practices

For Users

  1. Be respectful - Follow community guidelines
  2. No spam - Avoid excessive links, caps
  3. Report wisely - Use for genuine violations only
  4. Appeal fairly - Provide honest explanation

For Moderators

  1. Review context - Read full conversation
  2. Be consistent - Apply rules uniformly
  3. Document decisions - Use reason fields
  4. Respond promptly - Check queue daily
  5. Communicate - Explain bans clearly

For Developers

  1. Log everything - Track all moderation actions
  2. Preserve evidence - Don't hard-delete flagged content
  3. Monitor metrics - Spam rate, ban appeals, etc.
  4. Iterate filters - Update based on new patterns
  5. User feedback - Collect and review regularly

Future Enhancements

Phase 2

  • Upvote/downvote separate from reactions
  • Comment sorting (newest, oldest, top)
  • Notification system (mentions, replies)
  • Rich text support (links, formatting)
  • Image attachments

Phase 3

  • Moderator role (between fan and admin)
  • Auto-mod rules (configurable triggers)
  • Appeal workflow automation
  • Comment analytics dashboard
  • User reputation score

Integration Ideas

  • Slack/Discord webhooks for reports
  • ML-based spam detection
  • Sentiment analysis
  • Language detection
  • Automated translation

Support

For issues or questions:

  1. Check spam score for false positives
  2. Review ban table for active restrictions
  3. Verify rate limits not blocking legitimate use
  4. Check email logs for notifications
  5. Consult transaction log for points issues

Migration Files:

  • database/migrations/20251102000002_create_comments_system.up.sql
  • database/migrations/20251102000002_create_comments_system.down.sql

Key Files:

  • Backend: internal/controllers/comment_controller.go
  • Backend: internal/services/spam_detection.go (assumed)
  • Backend: internal/services/bad_words.go (assumed)
  • Frontend: frontend/src/pages/admin/CommentsAdminPage.tsx
  • Frontend: frontend/src/services/comments.ts
  • Frontend: frontend/src/services/admin/comments.ts
  • Utils: frontend/src/utils/commentsHelpers.ts
  • Validation: pkg/validation/comments.go
  • Helpers: internal/helpers/comments_helpers.go

Last Updated: November 2, 2025
Status: Production Ready