mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 10:42:57 +00:00
955 lines
21 KiB
Markdown
955 lines
21 KiB
Markdown
# Engagement System - Complete Documentation
|
|
|
|
## Overview
|
|
|
|
The Engagement System is a comprehensive gamification platform that rewards users for participation through XP, levels, points, achievements, and redeemable rewards.
|
|
|
|
## Table of Contents
|
|
|
|
1. [Core Concepts](#core-concepts)
|
|
2. [Database Schema](#database-schema)
|
|
3. [Backend API](#backend-api)
|
|
4. [Frontend Integration](#frontend-integration)
|
|
5. [Points & XP System](#points--xp-system)
|
|
6. [Achievements](#achievements)
|
|
7. [Rewards Store](#rewards-store)
|
|
8. [Security & Anti-Abuse](#security--anti-abuse)
|
|
9. [Admin Management](#admin-management)
|
|
10. [Production Checklist](#production-checklist)
|
|
|
|
---
|
|
|
|
## Core Concepts
|
|
|
|
### Points
|
|
- **Currency** for redeeming rewards
|
|
- Can be manually adjusted by admins
|
|
- Awarded for user actions (commenting, voting, etc.)
|
|
- NOT deducted when spent on XP-only rewards
|
|
|
|
### XP (Experience Points)
|
|
- **Progression metric** for leveling up
|
|
- Mirrors points by default (except admin adjustments)
|
|
- Determines user level
|
|
- Cannot be spent, only earned
|
|
|
|
### Levels
|
|
- Automatically calculated from total XP
|
|
- Formula: `Total XP to Level L = 50 * (L-1) * L`
|
|
- Each level requires: `100 * L` additional XP
|
|
- Visual progression with colored badges
|
|
- Titles: Začátečník → Nováček → Aktivní člen → Veterán → Expert → Mistr → Legenda
|
|
|
|
### Achievements
|
|
- One-time milestones that award points + XP
|
|
- Automatically checked and granted
|
|
- Examples: first comment, 10 votes, newsletter subscription
|
|
|
|
### Rewards
|
|
- Items users can redeem with points
|
|
- Types: avatars, merchandise coupons, custom unlocks
|
|
- Limited or unlimited stock
|
|
- Redemption workflow with approval system
|
|
|
|
---
|
|
|
|
## Database Schema
|
|
|
|
### `user_profiles`
|
|
```sql
|
|
CREATE TABLE user_profiles (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
created_at TIMESTAMP WITH TIME ZONE,
|
|
updated_at TIMESTAMP WITH TIME ZONE,
|
|
user_id BIGINT UNIQUE NOT NULL,
|
|
points BIGINT DEFAULT 0,
|
|
level INTEGER DEFAULT 1,
|
|
xp BIGINT DEFAULT 0,
|
|
username VARCHAR(32) UNIQUE NOT NULL,
|
|
avatar_url VARCHAR(500),
|
|
animated_avatar_url VARCHAR(500),
|
|
avatar_upload_unlocked BOOLEAN DEFAULT FALSE
|
|
);
|
|
```
|
|
|
|
**Indexes**: `user_id`, `points DESC`, `level DESC`, `xp DESC`, `username`
|
|
|
|
### `points_transactions`
|
|
```sql
|
|
CREATE TABLE points_transactions (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
created_at TIMESTAMP WITH TIME ZONE,
|
|
user_id BIGINT NOT NULL,
|
|
delta BIGINT NOT NULL,
|
|
xp_delta BIGINT DEFAULT 0,
|
|
reason VARCHAR(64) NOT NULL,
|
|
meta JSONB
|
|
);
|
|
```
|
|
|
|
**Common Reasons**:
|
|
- `comment_create` - User posted a comment (5 pts/XP)
|
|
- `comment_reacted` - User reacted to a comment (1 pt/XP)
|
|
- `poll_vote` - User voted in a poll (3 pts/XP)
|
|
- `newsletter_subscribe` - User subscribed to newsletter (12 pts/XP)
|
|
- `redeem` - User redeemed a reward (negative points)
|
|
- `redeem_refund` - Redemption rejected (positive points)
|
|
- `admin_adjust` - Manual adjustment (points only, no XP)
|
|
- `achievement:CODE` - Achievement unlocked
|
|
|
|
### `achievements`
|
|
```sql
|
|
CREATE TABLE achievements (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
code VARCHAR(64) UNIQUE NOT NULL,
|
|
title VARCHAR(255) NOT NULL,
|
|
description TEXT,
|
|
points BIGINT DEFAULT 0,
|
|
xp BIGINT DEFAULT 0,
|
|
icon VARCHAR(255),
|
|
active BOOLEAN DEFAULT TRUE
|
|
);
|
|
```
|
|
|
|
**Default Achievements**:
|
|
- `first_comment` - První komentář (10 pts/XP)
|
|
- `first_vote` - První hlasování (8 pts/XP)
|
|
- `newsletter_sub` - Odběr novinek (12 pts/XP)
|
|
- `comments_10` - Komentátor (20 pts/XP)
|
|
- `votes_10` - Hlasující (20 pts/XP)
|
|
- `comments_50` - Aktivní člen (50 pts/XP)
|
|
- `votes_50` - Věrný fanoušek (50 pts/XP)
|
|
- `comments_100` - Veterán diskuzí (100 pts/XP)
|
|
|
|
### `user_achievements`
|
|
Junction table tracking which achievements each user has unlocked.
|
|
|
|
### `reward_items`
|
|
```sql
|
|
CREATE TABLE reward_items (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
name VARCHAR(255) NOT NULL,
|
|
type VARCHAR(32) NOT NULL,
|
|
cost_points BIGINT NOT NULL,
|
|
image_url VARCHAR(500),
|
|
stock INTEGER DEFAULT 0,
|
|
active BOOLEAN DEFAULT TRUE,
|
|
metadata JSONB
|
|
);
|
|
```
|
|
|
|
**Types**:
|
|
- `avatar_static` - Static image avatar (auto-applied)
|
|
- `avatar_animated` - Animated GIF avatar (auto-applied)
|
|
- `avatar_upload_unlock` - Unlock custom avatar upload
|
|
- `merch_coupon` - Merchandise discount code
|
|
- `merch_physical` - Physical item (requires fulfillment)
|
|
- `merch_digital` - Digital download
|
|
- `custom` - Admin-defined
|
|
|
|
**Stock**:
|
|
- `-1` = Unlimited
|
|
- `0` = Out of stock
|
|
- `>0` = Limited quantity
|
|
|
|
### `reward_redemptions`
|
|
```sql
|
|
CREATE TABLE reward_redemptions (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
user_id BIGINT NOT NULL,
|
|
reward_id BIGINT NOT NULL,
|
|
status VARCHAR(24) DEFAULT 'pending'
|
|
);
|
|
```
|
|
|
|
**Statuses**:
|
|
- `pending` - Awaiting admin approval (manual rewards)
|
|
- `approved` - Admin approved but not yet fulfilled
|
|
- `fulfilled` - Item delivered to user
|
|
- `rejected` - Admin rejected (points refunded)
|
|
|
|
---
|
|
|
|
## Backend API
|
|
|
|
### Public Endpoints
|
|
|
|
#### `GET /api/v1/engagement/rewards`
|
|
List all active rewards available for redemption.
|
|
|
|
**Response**:
|
|
```json
|
|
[
|
|
{
|
|
"id": 1,
|
|
"name": "Avatar Blue #1",
|
|
"type": "avatar_static",
|
|
"cost_points": 50,
|
|
"image_url": "/uploads/avatars/blue-1.png",
|
|
"stock": 5,
|
|
"active": true
|
|
}
|
|
]
|
|
```
|
|
|
|
### Protected Endpoints (Require Auth)
|
|
|
|
#### `GET /api/v1/engagement/profile`
|
|
Get current user's engagement profile.
|
|
|
|
**Response**:
|
|
```json
|
|
{
|
|
"user_id": 123,
|
|
"points": 1250,
|
|
"level": 12,
|
|
"xp": 7800,
|
|
"username": "fan-superstar",
|
|
"avatar_url": "https://api.dicebear.com/7.x/pixel-art/svg?seed=fan-superstar",
|
|
"animated_avatar_url": null,
|
|
"avatar_upload_unlocked": true,
|
|
"achievements": 8
|
|
}
|
|
```
|
|
|
|
#### `PATCH /api/v1/engagement/profile`
|
|
Update username.
|
|
|
|
**Request**:
|
|
```json
|
|
{
|
|
"username": "new-username"
|
|
}
|
|
```
|
|
|
|
**Validation**:
|
|
- 3-32 characters
|
|
- Only lowercase letters, numbers, `-`, `_`, `.`
|
|
- No consecutive special chars
|
|
- Cannot start/end with special chars
|
|
- Reserved words blocked
|
|
|
|
#### `PATCH /api/v1/engagement/avatar`
|
|
Update avatar URLs.
|
|
|
|
**Request**:
|
|
```json
|
|
{
|
|
"avatar_url": "/uploads/my-avatar.png",
|
|
"animated_avatar_url": "/uploads/my-avatar.gif"
|
|
}
|
|
```
|
|
|
|
**Note**: Custom uploads require `avatar_upload_unlocked = true`.
|
|
|
|
#### `POST /api/v1/engagement/redeem`
|
|
Redeem a reward.
|
|
|
|
**Request**:
|
|
```json
|
|
{
|
|
"reward_id": 5
|
|
}
|
|
```
|
|
|
|
**Response**:
|
|
```json
|
|
{
|
|
"ok": true,
|
|
"status": "approved"
|
|
}
|
|
```
|
|
|
|
**Process**:
|
|
1. Check user has enough points
|
|
2. Check stock availability
|
|
3. Deduct points atomically
|
|
4. Decrement stock
|
|
5. Create redemption record
|
|
6. Auto-apply for avatar types
|
|
7. Send confirmation email
|
|
8. For manual rewards: notify admin
|
|
|
|
**Rate Limit**: 5 requests per hour
|
|
|
|
#### `GET /api/v1/engagement/achievements`
|
|
List all achievements with user progress.
|
|
|
|
**Response**:
|
|
```json
|
|
{
|
|
"achievements": [
|
|
{
|
|
"id": 1,
|
|
"code": "first_comment",
|
|
"title": "První komentář",
|
|
"description": "Napsal/a jste první komentář.",
|
|
"points": 10,
|
|
"xp": 10,
|
|
"achieved": true,
|
|
"achieved_at": "2025-10-15T14:30:00Z"
|
|
}
|
|
],
|
|
"counters": {
|
|
"comments": 25,
|
|
"votes": 18,
|
|
"newsletter": true
|
|
}
|
|
}
|
|
```
|
|
|
|
#### `GET /api/v1/engagement/leaderboard`
|
|
Get top users.
|
|
|
|
**Query Params**:
|
|
- `metric`: `points` | `level` | `xp` (default: `points`)
|
|
- `limit`: 1-100 (default: 20)
|
|
|
|
**Response**:
|
|
```json
|
|
{
|
|
"items": [
|
|
{
|
|
"rank": 1,
|
|
"user_id": 456,
|
|
"first_name": "Jan",
|
|
"last_name": "Novák",
|
|
"username": "fan-456",
|
|
"role": "fan",
|
|
"points": 5420,
|
|
"level": 28,
|
|
"xp": 39200,
|
|
"avatar_url": "...",
|
|
"animated_avatar_url": null
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
#### `GET /api/v1/engagement/transactions`
|
|
Get user's points transaction history.
|
|
|
|
**Query Params**:
|
|
- `limit`: 1-200 (default: 50)
|
|
- `reason`: filter by reason
|
|
|
|
**Response**:
|
|
```json
|
|
{
|
|
"items": [
|
|
{
|
|
"id": 789,
|
|
"user_id": 123,
|
|
"delta": 5,
|
|
"xp_delta": 5,
|
|
"reason": "comment_create",
|
|
"meta": {"comment_id": 42},
|
|
"created_at": "2025-11-01T10:15:00Z"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Admin Endpoints
|
|
|
|
#### `GET /admin/engagement/rewards`
|
|
List all rewards (including inactive).
|
|
|
|
**Query**: `?active=true|false`
|
|
|
|
#### `POST /admin/engagement/rewards`
|
|
Create a new reward.
|
|
|
|
#### `PUT /admin/engagement/rewards/:id`
|
|
Update reward details.
|
|
|
|
#### `DELETE /admin/engagement/rewards/:id`
|
|
Delete a reward.
|
|
|
|
#### `GET /admin/engagement/redemptions`
|
|
List all redemptions.
|
|
|
|
**Query**: `?status=pending|approved|rejected|fulfilled`
|
|
|
|
#### `PATCH /admin/engagement/redemptions/:id`
|
|
Update redemption status.
|
|
|
|
**Request**:
|
|
```json
|
|
{
|
|
"action": "approve" | "reject" | "fulfill"
|
|
}
|
|
```
|
|
|
|
**Reject Logic**:
|
|
- Refunds points to user
|
|
- Restores stock
|
|
- Logs refund transaction
|
|
- Sends notification email
|
|
|
|
#### `GET /admin/engagement/leaderboard`
|
|
Admin leaderboard (includes email, higher limits).
|
|
|
|
#### `GET /admin/engagement/transactions`
|
|
Admin transaction log.
|
|
|
|
**Query**: `?user_id=&reason=&limit=`
|
|
|
|
#### `POST /admin/engagement/adjust`
|
|
Manually adjust user points.
|
|
|
|
**Request**:
|
|
```json
|
|
{
|
|
"user_id": 123,
|
|
"delta": 100,
|
|
"reason": "admin_adjust",
|
|
"meta": {"note": "Compensation for bug"}
|
|
}
|
|
```
|
|
|
|
**Note**: Admin adjustments affect points only, not XP.
|
|
|
|
#### `GET /admin/engagement/profile/:user_id`
|
|
View any user's profile.
|
|
|
|
---
|
|
|
|
## Frontend Integration
|
|
|
|
### Services
|
|
|
|
#### `/frontend/src/services/engagement.ts`
|
|
Public API client for engagement features.
|
|
|
|
**Functions**:
|
|
- `getProfile()`
|
|
- `patchProfile(body)`
|
|
- `patchAvatar(body)`
|
|
- `getRewards()`
|
|
- `redeemReward(id)`
|
|
- `getAchievements()`
|
|
- `getLeaderboard(metric, limit)`
|
|
|
|
#### `/frontend/src/services/admin/engagement.ts`
|
|
Admin API client.
|
|
|
|
**Functions**:
|
|
- `adminListRewards(params)`
|
|
- `adminCreateReward(body)`
|
|
- `adminUpdateReward(id, body)`
|
|
- `adminDeleteReward(id)`
|
|
- `adminListRedemptions(params)`
|
|
- `adminUpdateRedemptionStatus(id, action)`
|
|
- `adminGetLeaderboard(metric, limit)`
|
|
- `adminListTransactions(params)`
|
|
- `adminAdjustPoints(body)`
|
|
- `adminGetUserProfile(user_id)`
|
|
|
|
### Utilities
|
|
|
|
#### `/frontend/src/utils/engagementHelpers.ts`
|
|
|
|
**Key Functions**:
|
|
- `computeLevelInfo(xp, level)` - Calculate level progress
|
|
- `computeLevelFromXP(xp)` - Determine level from XP
|
|
- `getLevelTitle(level)` - Get level name
|
|
- `getLevelColor(level)` - Get badge color
|
|
- `formatPoints(points)` - Format with k/M suffix
|
|
- `validateUsername(username)` - Client-side validation
|
|
- `generateUsernameSuggestion(first, last)` - Auto-suggest username
|
|
|
|
### Pages
|
|
|
|
#### `/frontend/src/pages/SemiAdminPage.tsx`
|
|
**Fan Zone** - User engagement profile dashboard.
|
|
|
|
**Features**:
|
|
- Profile stats (points, level, XP progress)
|
|
- Username editor
|
|
- Avatar management (upload, randomize)
|
|
- Level badge with colored tier
|
|
- Achievements viewer
|
|
- Leaderboard integration
|
|
- Rewards store
|
|
|
|
**Access**: Any authenticated user
|
|
|
|
#### `/frontend/src/pages/admin/EngagementAdminPage.tsx`
|
|
**Admin Panel** - Complete engagement management.
|
|
|
|
**Sections**:
|
|
1. **Leaderboards** - Top users by points/level/XP
|
|
2. **Create Reward** - Form with quick presets
|
|
3. **Rewards List** - Edit, delete, toggle active
|
|
4. **Redemptions** - Approve/reject/fulfill requests
|
|
5. **Transactions** - View and filter all transactions
|
|
6. **Manual Adjustments** - Add/remove points
|
|
|
|
**Features**:
|
|
- Batch reward creation (bulk avatars)
|
|
- Image upload for rewards
|
|
- Metadata editor for coupons/merch
|
|
- Real-time stock management
|
|
- Email notifications
|
|
|
|
**Access**: Admin only
|
|
|
|
---
|
|
|
|
## Points & XP System
|
|
|
|
### Earning Points & XP
|
|
|
|
| Action | Points | XP | Daily Cap |
|
|
|--------|--------|-----|-----------|
|
|
| Comment create | 5 | 5 | 10 comments |
|
|
| Comment reaction | 1 | 1 | 20 reactions |
|
|
| Poll vote | 3 | 3 | 1 vote |
|
|
| Newsletter subscribe | 12 | 12 | Once |
|
|
| Achievement unlock | Varies | Varies | - |
|
|
|
|
**Anti-Abuse**:
|
|
- Daily caps per reason (tracked in `PointsTransaction`)
|
|
- Rate limiting on endpoints
|
|
- Spam detection for comments
|
|
- Ban system prevents abuse
|
|
|
|
### Spending Points
|
|
|
|
Points are spent to redeem rewards. XP is never deducted.
|
|
|
|
**Redemption Flow**:
|
|
1. User browses rewards store
|
|
2. Clicks "Redeem" on affordable item
|
|
3. System checks: points ≥ cost, stock > 0
|
|
4. **Atomic transaction**:
|
|
- Deduct points from profile
|
|
- Decrement stock
|
|
- Create redemption record
|
|
- Log transaction
|
|
5. Auto-apply for avatar types
|
|
6. Email confirmation
|
|
7. Admin notification if manual fulfillment needed
|
|
|
|
**Refund on Rejection**:
|
|
- Admin clicks "Reject" on pending redemption
|
|
- System refunds full points
|
|
- Restores stock
|
|
- Logs refund transaction
|
|
- Notifies user
|
|
|
|
### Level Calculation
|
|
|
|
```go
|
|
func ComputeLevel(xp int64) int {
|
|
lvl := 1
|
|
threshold := int64(100)
|
|
remaining := xp
|
|
|
|
for remaining >= threshold && lvl < 200 {
|
|
remaining -= threshold
|
|
lvl++
|
|
threshold += int64(100)
|
|
}
|
|
|
|
return max(1, lvl)
|
|
}
|
|
```
|
|
|
|
**Examples**:
|
|
- Level 1: 0 XP
|
|
- Level 2: 100 XP (100 more)
|
|
- Level 3: 300 XP (200 more)
|
|
- Level 4: 600 XP (300 more)
|
|
- Level 10: 4500 XP
|
|
- Level 20: 19000 XP
|
|
- Level 50: 122500 XP
|
|
|
|
---
|
|
|
|
## Achievements
|
|
|
|
### Built-in Achievements
|
|
|
|
Defined in migration `20251102000001_create_engagement_system.up.sql`:
|
|
|
|
```sql
|
|
INSERT INTO achievements (code, title, description, points, xp, active) VALUES
|
|
('first_comment', 'První komentář', 'Napsal/a jste první komentář.', 10, 10, TRUE),
|
|
('first_vote', 'První hlasování', 'Poprvé jste hlasoval/a v anketě.', 8, 8, TRUE),
|
|
('newsletter_sub', 'Odběr novinek', 'Přihlášení k odběru newsletteru.', 12, 12, TRUE),
|
|
('comments_10', 'Komentátor', '10 komentářů!', 20, 20, TRUE),
|
|
('votes_10', 'Hlasující', '10 hlasování!', 20, 20, TRUE),
|
|
('comments_50', 'Aktivní člen', '50 komentářů!', 50, 50, TRUE),
|
|
('votes_50', 'Věrný fanoušek', '50 hlasování!', 50, 50, TRUE),
|
|
('comments_100', 'Veterán diskuzí', '100 komentářů!', 100, 100, TRUE);
|
|
```
|
|
|
|
### Achievement Checking
|
|
|
|
Automatically triggered:
|
|
- After comment creation
|
|
- After poll vote
|
|
- After newsletter subscription
|
|
- On manual admin points adjustment
|
|
|
|
**Service Method**:
|
|
```go
|
|
func (s *EngagementService) CheckAndAwardAchievements(userID uint) error
|
|
```
|
|
|
|
**Process**:
|
|
1. Load user's completed achievements
|
|
2. Count relevant actions (comments, votes, newsletter)
|
|
3. Check each achievement condition
|
|
4. Award if not already unlocked:
|
|
- Create `UserAchievement` record
|
|
- Add both points AND xp via `AwardPointsAndXP()`
|
|
- Transaction logged with reason `achievement:CODE`
|
|
|
|
### Adding Custom Achievements
|
|
|
|
**Via SQL**:
|
|
```sql
|
|
INSERT INTO achievements (code, title, description, points, xp, icon, active)
|
|
VALUES ('super_fan', 'Super Fanoušek', 'Dosáhl/a jste úrovně 50!', 500, 500, '⭐', TRUE);
|
|
```
|
|
|
|
**Logic in Service**:
|
|
```go
|
|
// In CheckAndAwardAchievements
|
|
if up.Level >= 50 {
|
|
awardByCode("super_fan")
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Rewards Store
|
|
|
|
### Creating Rewards
|
|
|
|
**Quick Presets** (Admin UI):
|
|
- Avatar (static) - 50 points
|
|
- Avatar (animated) - 100 points
|
|
- Merch coupon - 200 points
|
|
|
|
**Batch Creation**:
|
|
Useful for importing avatar packs.
|
|
|
|
**Settings**:
|
|
- Base URL template: `https://cdn.example.com/avatars/avatar-{i}.png`
|
|
- Count: 10
|
|
- Start index: 1
|
|
- Generates: avatar-1.png through avatar-10.png
|
|
|
|
### Reward Types
|
|
|
|
#### Avatar Static/Animated
|
|
**Auto-applied on redemption**:
|
|
- `avatar_static` → Updates `UserProfile.avatar_url`
|
|
- `avatar_animated` → Updates `UserProfile.animated_avatar_url`
|
|
- Status: `approved` (instant)
|
|
|
|
#### Avatar Upload Unlock
|
|
Special reward type that unlocks custom upload.
|
|
- Cost: typically 100 points
|
|
- Stock: -1 (unlimited)
|
|
- Sets `UserProfile.avatar_upload_unlocked = true`
|
|
- One per user
|
|
|
|
#### Merchandise Coupons
|
|
Requires manual fulfillment.
|
|
|
|
**Metadata Example**:
|
|
```json
|
|
{
|
|
"coupon_code": "SUPERFAN10",
|
|
"expires_at": "2025-12-31",
|
|
"discount": "10%",
|
|
"note": "Vyzvednout na recepci"
|
|
}
|
|
```
|
|
|
|
**Workflow**:
|
|
1. User redeems → Status `pending`
|
|
2. Admin reviews → Clicks "Approve"
|
|
3. Admin delivers → Clicks "Fulfill"
|
|
|
|
#### Physical Merchandise
|
|
Like coupons but requires shipping.
|
|
|
|
**Metadata**:
|
|
```json
|
|
{
|
|
"sku": "TSHIRT-L-RED",
|
|
"size": "L",
|
|
"color": "Červená"
|
|
}
|
|
```
|
|
|
|
#### Digital Products
|
|
E.g., e-book, wallpaper pack.
|
|
|
|
**Metadata**:
|
|
```json
|
|
{
|
|
"download_url": "https://...",
|
|
"license_key": "XXXX-YYYY-ZZZZ"
|
|
}
|
|
```
|
|
|
|
### Stock Management
|
|
|
|
**Unlimited**: `stock = -1`
|
|
**Out of stock**: `stock = 0` (reward hidden to users)
|
|
**Limited**: `stock > 0` (decrements on redemption, restores on rejection)
|
|
|
|
**Admin can**:
|
|
- Update stock inline in rewards table
|
|
- Toggle `active` to hide/show without deleting
|
|
|
|
---
|
|
|
|
## Security & Anti-Abuse
|
|
|
|
### Rate Limiting
|
|
|
|
Applied to all engagement endpoints:
|
|
- Redeem: 5 requests / hour
|
|
- Comment create: 20 / minute
|
|
- Poll vote: 60 / minute
|
|
- Reactions: 60 / minute
|
|
- Unban request: 5 / hour
|
|
|
|
### Daily Caps
|
|
|
|
Implemented in `EngagementService.AwardPointsCapped()`:
|
|
|
|
```go
|
|
switch reason {
|
|
case "poll_vote":
|
|
return cnt < 1 // Max 1 per day
|
|
case "comment_create":
|
|
return cnt < 10 // Max 10 per day
|
|
case "comment_reacted":
|
|
return cnt < 20 // Max 20 per day
|
|
case "newsletter_subscribe":
|
|
return cnt == 0 // Once per lifetime
|
|
}
|
|
```
|
|
|
|
### Username Validation
|
|
|
|
**Backend** (`pkg/validation/engagement.go`):
|
|
- Length: 3-32 characters
|
|
- Charset: `[a-z0-9\-_.]`
|
|
- No consecutive specials
|
|
- No leading/trailing specials
|
|
- Reserved word check
|
|
|
|
**Frontend** (`utils/engagementHelpers.ts`):
|
|
Pre-validation with instant feedback.
|
|
|
|
### Points Atomicity
|
|
|
|
All points operations use database transactions:
|
|
|
|
```go
|
|
tx := ec.DB.Begin()
|
|
if res := tx.Model(&models.UserProfile{}).
|
|
Where("user_id = ? AND points >= ?", userID, cost).
|
|
UpdateColumn("points", gorm.Expr("points - ?", cost));
|
|
res.RowsAffected == 0 {
|
|
tx.Rollback()
|
|
return error
|
|
}
|
|
tx.Commit()
|
|
```
|
|
|
|
Prevents:
|
|
- Double spending
|
|
- Race conditions
|
|
- Negative balances
|
|
|
|
### Avatar Upload Security
|
|
|
|
Users must first unlock via reward redemption.
|
|
|
|
**Check**:
|
|
```go
|
|
if strings.HasPrefix(url, "/uploads/") {
|
|
if !up.AvatarUploadUnlocked {
|
|
return errors.New("locked")
|
|
}
|
|
}
|
|
```
|
|
|
|
External URLs (Dicebear, etc.) allowed without unlock.
|
|
|
|
---
|
|
|
|
## Admin Management
|
|
|
|
### Dashboard Features
|
|
|
|
1. **Leaderboards** - Monitor top performers
|
|
2. **Reward CRUD** - Full management interface
|
|
3. **Redemption Queue** - Approve/reject/fulfill
|
|
4. **Transaction Log** - Audit all point changes
|
|
5. **Manual Adjustments** - Add/remove points
|
|
|
|
### Batch Operations
|
|
|
|
**Rewards**:
|
|
- Create multiple avatars from URL template
|
|
- Bulk activate/deactivate
|
|
|
|
**Transactions**:
|
|
- Filter by user, reason, date
|
|
- Export capability (future)
|
|
|
|
### Email Notifications
|
|
|
|
**To Users**:
|
|
- Reward redeemed confirmation
|
|
- Redemption status updates (fulfilled/rejected)
|
|
- Achievement unlocked (future)
|
|
|
|
**To Admins**:
|
|
- New pending redemption alert
|
|
- Includes user info and manage link
|
|
|
|
**Templates**:
|
|
- `/templates/emails/reward_redeemed_user.html`
|
|
- `/templates/emails/reward_redeemed_admin.html`
|
|
|
|
---
|
|
|
|
## Production Checklist
|
|
|
|
### Database
|
|
|
|
- [x] Run migration `20251102000001_create_engagement_system.up.sql`
|
|
- [x] Verify indexes created
|
|
- [x] Default achievements seeded
|
|
- [x] Avatar unlock reward created
|
|
|
|
### Backend
|
|
|
|
- [x] Engagement service implemented
|
|
- [x] Controllers with validation
|
|
- [x] Routes registered
|
|
- [x] Rate limiting applied
|
|
- [x] Email templates exist
|
|
- [x] Helper functions created
|
|
|
|
### Frontend
|
|
|
|
- [x] User dashboard (SemiAdminPage)
|
|
- [x] Admin panel (EngagementAdminPage)
|
|
- [x] Services configured
|
|
- [x] Utilities available
|
|
- [x] Responsive design
|
|
|
|
### Security
|
|
|
|
- [x] Username validation (backend + frontend)
|
|
- [x] Points atomicity (transactions)
|
|
- [x] Rate limits on all endpoints
|
|
- [x] Daily caps per action
|
|
- [x] Avatar upload gating
|
|
- [x] CSRF protection (cookie auth)
|
|
- [x] Input sanitization
|
|
|
|
### Testing
|
|
|
|
- [ ] Create test user profile
|
|
- [ ] Award points for comment
|
|
- [ ] Redeem avatar reward
|
|
- [ ] Test level progression
|
|
- [ ] Unlock achievement
|
|
- [ ] Admin adjust points
|
|
- [ ] Approve/reject redemption
|
|
- [ ] Test daily caps
|
|
- [ ] Verify email delivery
|
|
- [ ] Load test leaderboard
|
|
|
|
### Configuration
|
|
|
|
- [ ] Set `SMTP_*` environment variables
|
|
- [ ] Configure canonical base URL for emails
|
|
- [ ] Review default achievement values
|
|
- [ ] Set initial reward catalog
|
|
- [ ] Configure avatar upload limits
|
|
|
|
### Monitoring
|
|
|
|
- [ ] Track redemption rate
|
|
- [ ] Monitor points inflation
|
|
- [ ] Check for abuse patterns
|
|
- [ ] Review transaction logs
|
|
- [ ] Monitor email delivery
|
|
|
|
### Documentation
|
|
|
|
- [x] Complete API documentation
|
|
- [x] User guide for Fan Zone
|
|
- [x] Admin guide for management
|
|
- [x] Database schema documented
|
|
- [x] Helper functions documented
|
|
|
|
---
|
|
|
|
## Future Enhancements
|
|
|
|
### Phase 2
|
|
- [ ] Seasonal events (double XP weekends)
|
|
- [ ] Team/guild system
|
|
- [ ] Achievement categories
|
|
- [ ] Leaderboard seasons
|
|
- [ ] Profile customization (banners, badges)
|
|
|
|
### Phase 3
|
|
- [ ] Referral rewards
|
|
- [ ] Daily login streaks
|
|
- [ ] Special challenges
|
|
- [ ] Limited-time rewards
|
|
- [ ] Trading system (?)
|
|
|
|
### Integration Ideas
|
|
- [ ] Match prediction rewards
|
|
- [ ] Attendance check-in points
|
|
- [ ] Social media sharing bonuses
|
|
- [ ] Newsletter engagement tracking
|
|
|
|
---
|
|
|
|
## Support
|
|
|
|
For issues or questions:
|
|
1. Check admin transaction log for debugging
|
|
2. Review user profile directly in database
|
|
3. Check email logs for notification delivery
|
|
4. Verify migration ran successfully
|
|
5. Consult `/DOCS/` for additional guides
|
|
|
|
**Migration Files**:
|
|
- `database/migrations/20251102000001_create_engagement_system.up.sql`
|
|
- `database/migrations/20251102000001_create_engagement_system.down.sql`
|
|
|
|
**Key Files**:
|
|
- Backend: `internal/services/engagement.go`
|
|
- Backend: `internal/controllers/engagement_controller.go`
|
|
- Frontend: `frontend/src/pages/SemiAdminPage.tsx`
|
|
- Frontend: `frontend/src/pages/admin/EngagementAdminPage.tsx`
|
|
- Utils: `frontend/src/utils/engagementHelpers.ts`
|
|
- Validation: `pkg/validation/engagement.go`
|
|
- Helpers: `internal/helpers/engagement_helpers.go`
|
|
|
|
---
|
|
|
|
**Last Updated**: November 2, 2025
|
|
**Status**: Production Ready ✅
|