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

21 KiB

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
  2. Database Schema
  3. Backend API
  4. Frontend Integration
  5. Points & XP System
  6. Achievements
  7. Rewards Store
  8. Security & Anti-Abuse
  9. Admin Management
  10. 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

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

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

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

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

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:

[
  {
    "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:

{
  "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:

{
  "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:

{
  "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:

{
  "reward_id": 5
}

Response:

{
  "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:

{
  "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:

{
  "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:

{
  "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:

{
  "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:

{
  "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

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:

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:

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:

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:

// 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:

{
  "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:

{
  "sku": "TSHIRT-L-RED",
  "size": "L",
  "color": "Červená"
}

Digital Products

E.g., e-book, wallpaper pack.

Metadata:

{
  "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():

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:

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:

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

  • Run migration 20251102000001_create_engagement_system.up.sql
  • Verify indexes created
  • Default achievements seeded
  • Avatar unlock reward created

Backend

  • Engagement service implemented
  • Controllers with validation
  • Routes registered
  • Rate limiting applied
  • Email templates exist
  • Helper functions created

Frontend

  • User dashboard (SemiAdminPage)
  • Admin panel (EngagementAdminPage)
  • Services configured
  • Utilities available
  • Responsive design

Security

  • Username validation (backend + frontend)
  • Points atomicity (transactions)
  • Rate limits on all endpoints
  • Daily caps per action
  • Avatar upload gating
  • CSRF protection (cookie auth)
  • 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

  • Complete API documentation
  • User guide for Fan Zone
  • Admin guide for management
  • Database schema documented
  • 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