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

909 lines
20 KiB
Markdown

# 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 ✅