mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 02:32:57 +00:00
dev day #80
This commit is contained in:
@@ -0,0 +1,908 @@
|
||||
# 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](#core-features)
|
||||
2. [Database Schema](#database-schema)
|
||||
3. [Backend API](#backend-api)
|
||||
4. [Frontend Integration](#frontend-integration)
|
||||
5. [Spam Protection](#spam-protection)
|
||||
6. [Moderation Tools](#moderation-tools)
|
||||
7. [Ban System](#ban-system)
|
||||
8. [Reactions](#reactions)
|
||||
9. [Admin Management](#admin-management)
|
||||
10. [Production Checklist](#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`
|
||||
```sql
|
||||
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`
|
||||
```sql
|
||||
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**:
|
||||
```sql
|
||||
SELECT * FROM comment_bans
|
||||
WHERE user_id = ? AND (until IS NULL OR until > NOW())
|
||||
ORDER BY created_at DESC LIMIT 1
|
||||
```
|
||||
|
||||
### `unban_requests`
|
||||
```sql
|
||||
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`
|
||||
```sql
|
||||
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`
|
||||
```sql
|
||||
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**:
|
||||
```json
|
||||
{
|
||||
"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**:
|
||||
```json
|
||||
{
|
||||
"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**:
|
||||
```json
|
||||
{
|
||||
"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**:
|
||||
```json
|
||||
{
|
||||
"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**:
|
||||
```json
|
||||
{
|
||||
"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**:
|
||||
```json
|
||||
{
|
||||
"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**:
|
||||
```json
|
||||
{
|
||||
"status": "visible" | "hidden"
|
||||
}
|
||||
```
|
||||
|
||||
#### `POST /api/v1/admin/comments/ban`
|
||||
Ban a user from commenting.
|
||||
|
||||
**Request**:
|
||||
```json
|
||||
{
|
||||
"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**:
|
||||
```json
|
||||
{
|
||||
"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**:
|
||||
```tsx
|
||||
<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:
|
||||
|
||||
```go
|
||||
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**:
|
||||
```json
|
||||
["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**:
|
||||
```go
|
||||
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**:
|
||||
```sql
|
||||
SELECT type, COUNT(*) as cnt
|
||||
FROM comment_reactions
|
||||
WHERE comment_id IN (...)
|
||||
GROUP BY type
|
||||
```
|
||||
|
||||
Returns:
|
||||
```json
|
||||
{
|
||||
"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
|
||||
|
||||
- [x] Run migration `20251102000002_create_comments_system.up.sql`
|
||||
- [x] Verify indexes created
|
||||
- [x] Test foreign key constraints
|
||||
- [x] Confirm unique constraints work
|
||||
|
||||
### Backend
|
||||
|
||||
- [x] Comment controller implemented
|
||||
- [x] Spam detection service
|
||||
- [x] Bad words filter
|
||||
- [x] Ban checking on create/edit
|
||||
- [x] Rate limiting applied
|
||||
- [x] Validation helpers
|
||||
|
||||
### Frontend
|
||||
|
||||
- [x] Comments list component
|
||||
- [x] Comment form
|
||||
- [x] Reaction picker
|
||||
- [x] Report modal
|
||||
- [x] Admin moderation page
|
||||
- [x] Helpers & utilities
|
||||
|
||||
### Security
|
||||
|
||||
- [x] Input sanitization (XSS prevention)
|
||||
- [x] SQL injection protection (parameterized queries)
|
||||
- [x] Rate limiting (spam prevention)
|
||||
- [x] Ban enforcement
|
||||
- [x] CSRF protection
|
||||
- [x] 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 ✅
|
||||
Reference in New Issue
Block a user