You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
knowfoolery/docs/technical-architecture.md

27 KiB

Know Foolery - Technical Architecture Documentation

Architecture Overview

Know Foolery follows a microservices architecture with clear separation between frontend presentation, backend services, and data persistence layers. The system is designed for cross-platform compatibility, scalability, and maintainability.

System Architecture Diagram

┌────────────────────────────────────────────────────────────────────────────┐
│                            Client Layer                                    │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐    │
│  │   Web App    │  │  Mobile iOS  │  │ Mobile Androi│  │   Desktop ctr│    │
│  │   (React)    │  │(React Native)│  │(React Native)│  │   (Wails)    │    │
│  └──────────────┘  └──────────────┘  └──────────────┘  └──────────────┘    │
└────────────────────────────────────────────────────────────────────────────┘
                                      │
                               HTTPS/WSS
                                      │
┌─────────────────────────────────────────────────────────────────────────────┐
│                          API Gateway Layer                                  │
│  ┌────────────────────────────────────────────────────────────────────────┐ │
│  │  NGINX + API Gateway Service (Go)                                      │ │
│  │  • Authentication & Authorization                                      │ │
│  │  • Rate Limiting & CORS                                                │ │
│  │  • Request Routing & Load Balancing                                    │ │
│  │  • Security Headers & Input Validation                                 │ │
│  └────────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
                                      │
                                 gRPC/HTTP
                                      │
┌─────────────────────────────────────────────────────────────────────────────┐
│                        Microservices Layer                                  │
│  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐            │
│  │    Game     │ │   Question  │ │    User     │ │ Leaderboard │            │
│  │   Service   │ │   Service   │ │   Service   │ │   Service   │            │
│  └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘            │
│  ┌─────────────┐ ┌─────────────┐                                            │
│  │   Session   │ │    Admin    │                                            │
│  │   Service   │ │   Service   │                                            │
│  └─────────────┘ └─────────────┘                                            │
└─────────────────────────────────────────────────────────────────────────────┘
                                      │
                              Database Connections
                                      │
┌─────────────────────────────────────────────────────────────────────────────┐
│                         Data & External Layer                               │
│  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐            │
│  │ PostgreSQL  │ │    Redis    │ │   Zitadel   │ │ Observabi-  │            │
│  │   Primary   │ │   Cache &   │ │   OAuth     │ │    lity     │            │
│  │  Database   │ │  Sessions   │ │  Provider   │ │    Stack    │            │
│  └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘            │
└─────────────────────────────────────────────────────────────────────────────┘

Technology Stack

Frontend Technologies

Web Application:
  Framework: React 18.2+
  Language: TypeScript 5.0+
  Build Tool: Vite 4.0+
  UI Library: Gluestack UI
  Styling: NativeWind/Tailwind CSS 3.0+
  State Management: React Context + useReducer
  Testing: Jest + React Testing Library + Playwright

Mobile Applications:
  Framework: React Native 0.81+
  Language: TypeScript 5.9+
  UI Library: Gluestack UI (Native)
  Navigation: React Navigation 6+
  State Management: React Context + useReducer
  Testing: Jest + React Native Testing Library

Desktop Application:
  Framework: Wails 2.10+
  Template: wails-vite-react-ts-tailwind-template 

Backend Technologies

Microservices:
  Language: Go 1.25+
  HTTP Framework: Fiber 3.0+
  gRPC: Protocol Buffers + gRPC-Go
  Database ORM: Ent (Facebook)
  Authentication: JWT + Zitadel Integration
  Testing: Go testing + testcontainers
  
API Gateway:
  Reverse Proxy: NGINX
  Gateway Service: Go + Fiber
  Load Balancing: Round-robin + Health checks
  Rate Limiting: Redis-based sliding window

Data Technologies

Primary Database:
  Engine: PostgreSQL 15+
  Connection Pooling: pgxpool
  Migrations: Ent migrations
  Backup: pg_dump + Object Storage

Cache Layer:
  Engine: Redis 7+
  Use Cases: Sessions, Rate limiting, Cache
  Clustering: Redis Cluster (Production)
  Persistence: RDB + AOF

Authentication:
  Provider: Zitadel (Self-hosted)
  Protocol: OAuth 2.0 + OpenID Connect
  Tokens: JWT with RS256 signing
  MFA: TOTP + WebAuthn

Infrastructure Technologies

Containerization:
  Runtime: Docker 24+
  Orchestration: Kubernetes 1.28+
  Registry: Private Docker Registry
  
Observability:
  Metrics: Prometheus + Node Exporter
  Visualization: Grafana + Custom Dashboards
  Tracing: Jaeger + OpenTelemetry
  Logging: Structured logging + Loki
  
CI/CD:
  Platform: GitHub Actions
  Testing: Automated test suites
  Security: SAST/DAST scanning
  Deployment: GitOps with ArgoCD

Microservices Design

Service Boundaries

1. Game Service

// Responsibilities
type GameService struct {
    // Core game logic and session management
    SessionManager  *SessionManager
    ScoreCalculator *ScoreCalculator
    AttemptTracker  *AttemptTracker
    TimerManager    *TimerManager
}

// API Endpoints
POST   /api/v1/game/start              // Start new game session
GET    /api/v1/game/session/{id}       // Get session details
POST   /api/v1/game/answer             // Submit answer
POST   /api/v1/game/hint               // Request hint
POST   /api/v1/game/end                // End session
GET    /api/v1/game/status/{id}        // Get session status

2. Question Service

// Responsibilities
type QuestionService struct {
    QuestionRepo    *QuestionRepository
    ThemeManager    *ThemeManager
    Randomizer      *QuestionRandomizer
    Validator       *AnswerValidator
}

// API Endpoints
GET    /api/v1/questions/random        // Get random question
GET    /api/v1/questions/{id}          // Get specific question
GET    /api/v1/questions/themes        // List available themes
POST   /api/v1/questions               // Create question (admin)
PUT    /api/v1/questions/{id}          // Update question (admin)
DELETE /api/v1/questions/{id}          // Delete question (admin)

3. User Service

// Responsibilities
type UserService struct {
    UserRepo        *UserRepository
    ProfileManager  *ProfileManager
    SessionTracker  *SessionTracker
}

// API Endpoints
POST   /api/v1/users/register          // Register new player
GET    /api/v1/users/profile           // Get user profile
PUT    /api/v1/users/profile           // Update profile
GET    /api/v1/users/sessions          // Get session history
DELETE /api/v1/users/profile           // Delete account (GDPR)

4. Leaderboard Service

// Responsibilities
type LeaderboardService struct {
    ScoreAggregator *ScoreAggregator
    RankingEngine   *RankingEngine
    StatsCalculator *StatsCalculator
}

// API Endpoints
GET    /api/v1/leaderboard/top10       // Get top 10 scores
GET    /api/v1/leaderboard/stats       // Get game statistics
GET    /api/v1/leaderboard/player/{id} // Get player ranking
POST   /api/v1/leaderboard/update      // Update scores (internal)

5. Session Service

// Responsibilities
type SessionService struct {
    SessionStore    *SessionStore
    TimerManager    *TimerManager
    StateManager    *StateManager
}

// API Endpoints
POST   /api/v1/sessions                // Create session
GET    /api/v1/sessions/{id}           // Get session
PUT    /api/v1/sessions/{id}           // Update session
DELETE /api/v1/sessions/{id}           // End session
GET    /api/v1/sessions/{id}/timer     // Get timer status

6. Admin Service

// Responsibilities
type AdminService struct {
    AuthManager     *AuthManager
    QuestionMgmt    *QuestionManagement
    UserMgmt        *UserManagement
    AuditLogger     *AuditLogger
}

// API Endpoints
POST   /api/v1/admin/auth              // Admin authentication
GET    /api/v1/admin/dashboard         // Dashboard data
GET    /api/v1/admin/questions         // List all questions
POST   /api/v1/admin/questions/bulk    // Bulk question import
GET    /api/v1/admin/users             // User management
GET    /api/v1/admin/audit             // Audit logs

Inter-Service Communication

Service Mesh Architecture

Communication Patterns:
  Synchronous: gRPC for real-time operations
  Asynchronous: Event-driven via message queues (future)
  
Service Discovery:
  Registry: Kubernetes DNS
  Health Checks: HTTP /health endpoints
  Load Balancing: Round-robin with health awareness
  
Circuit Breaker:
  Pattern: Hystrix-style circuit breakers
  Fallback: Graceful degradation
  Timeout: Context-based timeouts

gRPC Service Definitions

// game_service.proto
service GameService {
  rpc StartGame(StartGameRequest) returns (StartGameResponse);
  rpc SubmitAnswer(SubmitAnswerRequest) returns (SubmitAnswerResponse);
  rpc GetSession(GetSessionRequest) returns (GetSessionResponse);
  rpc EndGame(EndGameRequest) returns (EndGameResponse);
}

// question_service.proto
service QuestionService {
  rpc GetRandomQuestion(RandomQuestionRequest) returns (Question);
  rpc ValidateAnswer(ValidateAnswerRequest) returns (ValidationResponse);
  rpc GetQuestionHint(HintRequest) returns (HintResponse);
}

// leaderboard_service.proto
service LeaderboardService {
  rpc UpdateScore(UpdateScoreRequest) returns (UpdateScoreResponse);
  rpc GetTopScores(TopScoresRequest) returns (TopScoresResponse);
  rpc GetPlayerRank(PlayerRankRequest) returns (PlayerRankResponse);
}

Database Architecture

Data Model Design

Core Entities (Ent Schemas)

// Question Entity
type Question struct {
    ent.Schema
}

func (Question) Fields() []ent.Field {
    return []ent.Field{
        field.String("theme").NotEmpty(),
        field.Text("text").NotEmpty(),
        field.String("answer").NotEmpty(),
        field.Text("hint").Optional(),
        field.Enum("difficulty").Values("easy", "medium", "hard").Default("medium"),
        field.Bool("is_active").Default(true),
        field.Time("created_at").Default(time.Now),
        field.Time("updated_at").Default(time.Now).UpdateDefault(time.Now),
    }
}

func (Question) Indexes() []ent.Index {
    return []ent.Index{
        index.Fields("theme"),
        index.Fields("difficulty"),
        index.Fields("is_active"),
        index.Fields("created_at"),
    }
}

// GameSession Entity
type GameSession struct {
    ent.Schema
}

func (GameSession) Fields() []ent.Field {
    return []ent.Field{
        field.String("player_name").NotEmpty(),
        field.Int("total_score").Default(0),
        field.Int("questions_asked").Default(0),
        field.Int("questions_correct").Default(0),
        field.Int("hints_used").Default(0),
        field.Time("start_time").Default(time.Now),
        field.Time("end_time").Optional().Nillable(),
        field.Enum("status").Values("active", "completed", "timeout", "abandoned").Default("active"),
        field.String("current_question_id").Optional(),
        field.Int("current_attempts").Default(0),
    }
}

func (GameSession) Edges() []ent.Edge {
    return []ent.Edge{
        edge.To("attempts", QuestionAttempt.Type),
        edge.To("current_question", Question.Type).Unique().Field("current_question_id"),
    }
}

// QuestionAttempt Entity
type QuestionAttempt struct {
    ent.Schema
}

func (QuestionAttempt) Fields() []ent.Field {
    return []ent.Field{
        field.String("session_id"),
        field.String("question_id"),
        field.Int("attempt_number"),
        field.String("submitted_answer"),
        field.Bool("is_correct"),
        field.Bool("used_hint"),
        field.Int("points_awarded"),
        field.Time("submitted_at").Default(time.Now),
        field.Duration("time_taken"),
    }
}

func (QuestionAttempt) Edges() []ent.Edge {
    return []ent.Edge{
        edge.From("session", GameSession.Type).Ref("attempts").Unique().Field("session_id"),
        edge.To("question", Question.Type).Unique().Field("question_id"),
    }
}

Database Performance Optimization

-- Indexes for optimal query performance
CREATE INDEX idx_questions_theme_active ON questions(theme, is_active);
CREATE INDEX idx_sessions_status_start_time ON game_sessions(status, start_time);
CREATE INDEX idx_attempts_session_question ON question_attempts(session_id, question_id);
CREATE INDEX idx_leaderboard_score_time ON game_sessions(total_score DESC, end_time ASC) WHERE status = 'completed';

-- Partitioning for large datasets (future scaling)
CREATE TABLE game_sessions_y2024m01 PARTITION OF game_sessions 
    FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');

Database Connection Management

// Database configuration
type DatabaseConfig struct {
    Primary  DatabaseInstance
    Replicas []DatabaseInstance
    Pool     PoolConfig
}

type DatabaseInstance struct {
    Host     string
    Port     int
    Database string
    Username string
    Password string
    SSLMode  string
}

type PoolConfig struct {
    MaxOpenConns    int           // 25
    MaxIdleConns    int           // 5
    ConnMaxLifetime time.Duration // 30 minutes
    ConnMaxIdleTime time.Duration // 15 minutes
}

// Connection pooling with read/write splitting
func NewDatabaseClient(config DatabaseConfig) (*ent.Client, error) {
    primaryDSN := formatDSN(config.Primary)
    
    // Primary database for writes
    primaryDB, err := sql.Open("postgres", primaryDSN)
    if err != nil {
        return nil, err
    }
    
    configurePools(primaryDB, config.Pool)
    
    // Create Ent client with instrumented driver
    drv := entsql.OpenDB("postgres", primaryDB)
    return ent.NewClient(ent.Driver(drv)), nil
}

Security Architecture

Authentication Flow

sequenceDiagram
    participant C as Client
    participant G as API Gateway
    participant Z as Zitadel
    participant S as Service
    
    C->>G: Request with credentials
    G->>Z: Validate credentials
    Z->>G: Return JWT token
    G->>C: Return token + user info
    C->>G: API request with JWT
    G->>G: Validate JWT signature
    G->>S: Forward request with user context
    S->>G: Return response
    G->>C: Return response

Authorization Strategy

// Role-based access control
type Role string

const (
    RolePlayer Role = "player"
    RoleAdmin  Role = "admin"
)

type Permissions struct {
    CanPlayGame      bool
    CanViewScores    bool
    CanManageQuestions bool
    CanViewAuditLogs bool
    CanManageUsers   bool
}

var RolePermissions = map[Role]Permissions{
    RolePlayer: {
        CanPlayGame:   true,
        CanViewScores: true,
    },
    RoleAdmin: {
        CanPlayGame:        true,
        CanViewScores:      true,
        CanManageQuestions: true,
        CanViewAuditLogs:   true,
        CanManageUsers:     true,
    },
}

// JWT middleware with role checking
func AuthMiddleware() fiber.Handler {
    return func(c *fiber.Ctx) error {
        token := extractToken(c.Get("Authorization"))
        
        claims, err := validateJWT(token)
        if err != nil {
            return c.Status(401).JSON(fiber.Map{"error": "Invalid token"})
        }
        
        c.Locals("user_id", claims.Subject)
        c.Locals("user_role", claims.Role)
        c.Locals("permissions", RolePermissions[Role(claims.Role)])
        
        return c.Next()
    }
}

func RequirePermission(permission func(Permissions) bool) fiber.Handler {
    return func(c *fiber.Ctx) error {
        perms := c.Locals("permissions").(Permissions)
        if !permission(perms) {
            return c.Status(403).JSON(fiber.Map{"error": "Insufficient permissions"})
        }
        return c.Next()
    }
}

Input Validation & Security

// Comprehensive input validation
type InputValidator struct {
    maxAnswerLength int
    maxNameLength   int
    allowedChars    *regexp.Regexp
}

func NewInputValidator() *InputValidator {
    return &InputValidator{
        maxAnswerLength: 500,
        maxNameLength:   50,
        allowedChars:    regexp.MustCompile(`^[a-zA-Z0-9\s\-_.]+$`),
    }
}

func (v *InputValidator) ValidateAnswer(answer string) error {
    if len(answer) == 0 {
        return errors.New("answer cannot be empty")
    }
    
    if len(answer) > v.maxAnswerLength {
        return errors.New("answer too long")
    }
    
    // Sanitize HTML and potential XSS
    clean := html.EscapeString(strings.TrimSpace(answer))
    if clean != answer {
        return errors.New("invalid characters in answer")
    }
    
    return nil
}

func (v *InputValidator) ValidatePlayerName(name string) error {
    if len(name) < 2 || len(name) > v.maxNameLength {
        return errors.New("name must be 2-50 characters")
    }
    
    if !v.allowedChars.MatchString(name) {
        return errors.New("name contains invalid characters")
    }
    
    return nil
}

// Rate limiting implementation
type RateLimiter struct {
    redis  *redis.Client
    limits map[string]RateLimit
}

type RateLimit struct {
    Requests int           // Number of requests
    Window   time.Duration // Time window
}

func (rl *RateLimiter) Allow(key string, limit RateLimit) bool {
    current, err := rl.redis.Incr(key).Result()
    if err != nil {
        return false // Fail closed
    }
    
    if current == 1 {
        rl.redis.Expire(key, limit.Window)
    }
    
    return current <= int64(limit.Requests)
}

// Usage in middleware
func RateLimitMiddleware() fiber.Handler {
    limiter := NewRateLimiter()
    
    return func(c *fiber.Ctx) error {
        userID := c.Locals("user_id").(string)
        clientIP := c.IP()
        
        // Per-user rate limiting
        userKey := fmt.Sprintf("rate_limit:user:%s", userID)
        if !limiter.Allow(userKey, RateLimit{Requests: 60, Window: time.Minute}) {
            return c.Status(429).JSON(fiber.Map{"error": "Rate limit exceeded"})
        }
        
        // Per-IP rate limiting
        ipKey := fmt.Sprintf("rate_limit:ip:%s", clientIP)
        if !limiter.Allow(ipKey, RateLimit{Requests: 100, Window: time.Minute}) {
            return c.Status(429).JSON(fiber.Map{"error": "Rate limit exceeded"})
        }
        
        return c.Next()
    }
}

Cross-Platform Frontend Architecture

Gluestack UI Integration

// Shared component library structure
// packages/ui-components/src/index.ts

export { GameCard } from './GameCard'
export { Leaderboard } from './Leaderboard'
export { Timer } from './Timer'
export { ScoreDisplay } from './ScoreDisplay'
export { AdminPanel } from './AdminPanel'

// Component example with Gluestack UI
// packages/ui-components/src/GameCard/GameCard.tsx
import {
  Card,
  VStack,
  HStack,
  Text,
  Input,
  Button,
  Badge,
  Progress,
  Box
} from '@gluestack-ui/themed'

export interface GameCardProps {
  question: string
  theme: string
  timeRemaining: number
  attemptsLeft: number
  currentScore: number
  onSubmitAnswer: (answer: string) => void
  onRequestHint: () => void
  isLoading?: boolean
}

export const GameCard: React.FC<GameCardProps> = ({
  question,
  theme,
  timeRemaining,
  attemptsLeft,
  currentScore,
  onSubmitAnswer,
  onRequestHint,
  isLoading = false
}) => {
  const [answer, setAnswer] = useState('')
  
  const handleSubmit = () => {
    if (answer.trim()) {
      onSubmitAnswer(answer.trim())
      setAnswer('')
    }
  }
  
  return (
    <Card size="lg" variant="elevated" m="$4">
      <VStack space="md" p="$4">
        {/* Header with theme and timer */}
        <HStack justifyContent="space-between" alignItems="center">
          <Badge size="sm" variant="solid" action="info">
            <Text color="$white" fontSize="$sm" fontWeight="$semibold">
              {theme}
            </Text>
          </Badge>
          <HStack space="sm" alignItems="center">
            <Text fontSize="$sm" color="$textLight600">
              ⏱️ {Math.floor(timeRemaining / 60)}:{(timeRemaining % 60).toString().padStart(2, '0')}
            </Text>
          </HStack>
        </HStack>
        
        {/* Progress indicator */}
        <Progress value={((30 * 60 - timeRemaining) / (30 * 60)) * 100} size="sm">
          <ProgressFilledTrack />
        </Progress>
        
        {/* Question */}
        <Box>
          <Text fontSize="$lg" fontWeight="$semibold" lineHeight="$xl">
            {question}
          </Text>
        </Box>
        
        {/* Answer input */}
        <Input size="lg" isDisabled={isLoading}>
          <InputField
            placeholder="Enter your answer..."
            value={answer}
            onChangeText={setAnswer}
            autoCapitalize="none"
            autoCorrect={false}
          />
        </Input>
        
        {/* Game stats */}
        <HStack justifyContent="space-between" alignItems="center">
          <Text fontSize="$sm" color="$textLight600">
            Attempts left: {attemptsLeft}/3
          </Text>
          <Text fontSize="$sm" color="$textLight600">
            Score: {currentScore} points
          </Text>
        </HStack>
        
        {/* Action buttons */}
        <HStack space="sm">
          <Button
            size="lg"
            variant="solid"
            action="primary"
            flex={1}
            onPress={handleSubmit}
            isDisabled={!answer.trim() || isLoading}
          >
            <ButtonText>Submit Answer</ButtonText>
          </Button>
          <Button
            size="lg"
            variant="outline"
            action="secondary"
            onPress={onRequestHint}
            isDisabled={isLoading}
          >
            <ButtonText>💡 Hint</ButtonText>
          </Button>
        </HStack>
      </VStack>
    </Card>
  )
}

Platform-Specific Adaptations

// Platform detection and adaptation
// packages/shared-logic/src/platform.ts

export interface PlatformCapabilities {
  hasTouch: boolean
  hasKeyboard: boolean
  hasCamera: boolean
  canVibrate: boolean
  supportsNotifications: boolean
  isOfflineCapable: boolean
}

export const getPlatformCapabilities = (): PlatformCapabilities => {
  // Web platform
  if (typeof window !== 'undefined') {
    return {
      hasTouch: 'ontouchstart' in window,
      hasKeyboard: true,
      hasCamera: !!navigator.mediaDevices?.getUserMedia,
      canVibrate: !!navigator.vibrate,
      supportsNotifications: 'Notification' in window,
      isOfflineCapable: 'serviceWorker' in navigator
    }
  }
  
  // React Native platform
  if (typeof require !== 'undefined') {
    try {
      const { Platform } = require('react-native')
      return {
        hasTouch: true,
        hasKeyboard: Platform.OS === 'ios' || Platform.OS === 'android',
        hasCamera: true,
        canVibrate: true,
        supportsNotifications: true,
        isOfflineCapable: true
      }
    } catch {
      // Fallback for other environments
    }
  }
  
  // Default capabilities
  return {
    hasTouch: false,
    hasKeyboard: true,
    hasCamera: false,
    canVibrate: false,
    supportsNotifications: false,
    isOfflineCapable: false
  }
}

// Responsive design utilities
export const useResponsiveValue = <T>(values: {
  mobile: T
  tablet: T
  desktop: T
}) => {
  const [screenSize, setScreenSize] = useState<'mobile' | 'tablet' | 'desktop'>('desktop')
  
  useEffect(() => {
    const updateScreenSize = () => {
      const width = window.innerWidth
      if (width < 768) {
        setScreenSize('mobile')
      } else if (width < 1024) {
        setScreenSize('tablet')
      } else {
        setScreenSize('desktop')
      }
    }
    
    updateScreenSize()
    window.addEventListener('resize', updateScreenSize)
    return () => window.removeEventListener('resize', updateScreenSize)
  }, [])
  
  return values[screenSize]
}

This technical architecture provides a solid foundation for building a scalable, secure, and maintainable quiz game platform with cross-platform capabilities.