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