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,954 @@
|
||||
# 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 ✅
|
||||
Reference in New Issue
Block a user