# Architecture Documentation ## Overview ActaLog is a mobile-first **Progressive Web App (PWA)** built using Clean Architecture principles with a Go backend and Vue.js frontend. The system is designed to be modular, testable, scalable, and works offline-first with automatic synchronization. ## Architecture Pattern We follow **Clean Architecture** (also known as Hexagonal Architecture or Ports and Adapters), which provides: - **Independence from frameworks**: Business logic doesn't depend on external libraries - **Testability**: Business rules can be tested without UI, database, or external dependencies - **Independence from UI**: UI can change without affecting business logic - **Independence from database**: Can swap databases without changing business rules - **Independence from external services**: Business rules don't know about the outside world ## System Architecture ```mermaid graph TB subgraph "Client Layer" Web[Web Browser] Mobile[Mobile Browser] Installed[Installed PWA] end subgraph "PWA Layer" SW[Service Worker] IDB[(IndexedDB)] Cache[Cache Storage] Manifest[Web App Manifest] end subgraph "Presentation Layer" VueApp[Vue.js Application] Vuetify[Vuetify Components] OfflineUtil[Offline Storage Utils] end subgraph "API Layer" Router[HTTP Router] Middleware[Middleware Stack] Handlers[HTTP Handlers] end subgraph "Business Logic Layer" Services[Services/Use Cases] Domain[Domain Models] end subgraph "Data Layer" Repos[Repositories] DB[(Database)] end subgraph "Infrastructure" Auth[JWT Authentication] Logger[Structured Logging] Telemetry[OpenTelemetry] end Web --> SW Mobile --> SW Installed --> SW SW --> VueApp SW --> Cache VueApp --> Vuetify VueApp --> OfflineUtil OfflineUtil --> IDB VueApp --> Router Router --> Middleware Middleware --> Handlers Handlers --> Services Services --> Domain Services --> Repos Repos --> DB Middleware --> Auth Middleware --> Logger Services --> Telemetry ``` ## Directory Structure ``` actalog/ ├── cmd/ │ └── actalog/ # Application entry point │ └── main.go ├── internal/ # Private application code │ ├── domain/ # Business entities and interfaces │ │ ├── user.go │ │ ├── workout.go │ │ └── movement.go │ ├── repository/ # Data access implementations │ │ ├── user_repo.go │ │ ├── workout_repo.go │ │ └── movement_repo.go │ ├── service/ # Business logic/use cases │ │ ├── user_service.go │ │ ├── workout_service.go │ │ └── movement_service.go │ └── handler/ # HTTP handlers │ ├── user_handler.go │ ├── workout_handler.go │ └── movement_handler.go ├── pkg/ # Public, reusable packages │ ├── auth/ # Authentication utilities │ ├── middleware/ # HTTP middleware │ ├── utils/ # Utility functions │ └── version/ # Version information ├── api/ # API definitions │ ├── rest/ # REST API routes │ └── models/ # API request/response models ├── configs/ # Configuration management │ └── config.go ├── test/ # Tests │ ├── unit/ # Unit tests │ └── integration/ # Integration tests ├── web/ # Frontend application │ ├── public/ # Static assets │ └── src/ # Vue.js source code ├── docs/ # Documentation ├── design/ # Design assets └── migrations/ # Database migrations ``` ## Layer Responsibilities ### 1. Domain Layer (`internal/domain/`) **Responsibility**: Contains business entities and repository interfaces - Pure Go structs representing business concepts - Repository interfaces (defined here, implemented elsewhere) - No external dependencies - The heart of the application **Core Entities** (Showing planned v0.3.0 schema - not yet implemented): ```go // User represents a system user type User struct { ID int64 Email string PasswordHash string Name string Birthday *time.Time ProfileImage string Role string CreatedAt time.Time UpdatedAt time.Time UpdatedBy *int64 LastLoginAt *time.Time } // WOD represents a predefined CrossFit workout type WOD struct { ID int64 Name string Source string // CrossFit, Other Coach, Self-recorded Type string // Benchmark, Hero, Girl, Notables, Games, Endurance, Self-created Regime string // EMOM, AMRAP, Fastest Time, etc. ScoreType string // Time, Rounds+Reps, Max Weight Description string URL string Notes string CreatedBy *int64 CreatedAt time.Time UpdatedAt time.Time UpdatedBy *int64 } // StrengthMovement represents an exercise/movement type StrengthMovement struct { ID int64 Name string MovementType string // weightlifting, cardio, gymnastics Description string CreatedBy *int64 CreatedAt time.Time UpdatedAt time.Time UpdatedBy *int64 } // Workout represents a workout template (reusable) type Workout struct { ID int64 Name string Notes string CreatedAt time.Time UpdatedAt time.Time UpdatedBy *int64 } // UserWorkout links a user to a workout on a specific date type UserWorkout struct { ID int64 UserID int64 WorkoutID int64 WorkoutDate time.Time Notes string CreatedAt time.Time UpdatedAt time.Time } // WorkoutWOD links a workout to a WOD with scoring type WorkoutWOD struct { ID int64 WorkoutID int64 WODID int64 ScoreValue string OrderIndex int CreatedAt time.Time UpdatedAt time.Time } // WorkoutStrength links a workout to a strength movement with details type WorkoutStrength struct { ID int64 WorkoutID int64 StrengthID int64 Weight *float64 Sets *int Reps *int Notes string OrderIndex int CreatedAt time.Time UpdatedAt time.Time } // UserSetting stores user preferences type UserSetting struct { ID int64 UserID int64 NotificationPreferences string DataExportFormat string Theme string CreatedAt time.Time UpdatedAt time.Time } // AuditLog records significant actions type AuditLog struct { ID int64 UserID *int64 Action string Details string Timestamp time.Time } // Repository Interfaces type UserRepository interface { Create(user *User) error GetByID(id int64) (*User, error) GetByEmail(email string) (*User, error) Update(user *User) error } type WODRepository interface { Create(wod *WOD) error GetByID(id int64) (*WOD, error) GetByName(name string) (*WOD, error) List(filters map[string]interface{}) ([]*WOD, error) Update(wod *WOD) error } type StrengthMovementRepository interface { Create(movement *StrengthMovement) error GetByID(id int64) (*StrengthMovement, error) ListByType(movementType string) ([]*StrengthMovement, error) } type UserWorkoutRepository interface { Create(userWorkout *UserWorkout) error GetByUserAndDate(userID int64, date time.Time) ([]*UserWorkout, error) GetByDateRange(userID int64, start, end time.Time) ([]*UserWorkout, error) } // ... and more repository interfaces ``` ### 2. Repository Layer (`internal/repository/`) **Responsibility**: Implements data access interfaces defined in domain - Implements repository interfaces from domain layer - Handles database queries and data mapping - Isolates persistence logic - Can be easily swapped (e.g., SQLite to PostgreSQL) ### 3. Service Layer (`internal/service/`) **Responsibility**: Contains business logic and use cases - Orchestrates business workflows - Uses repositories for data access - Validates business rules - Transaction management - Independent of delivery mechanism (HTTP, gRPC, etc.) ### 4. Handler Layer (`internal/handler/`) **Responsibility**: HTTP request/response handling - Receives HTTP requests - Validates input - Calls service layer - Formats responses - Error handling and status codes ### 5. API Layer (`api/`) **Responsibility**: API contracts and routing - REST endpoint definitions - Request/response models (DTOs) - API versioning - Route configuration ## Data Flow ```mermaid sequenceDiagram participant Client participant Handler participant Service participant Repository participant Database Client->>Handler: HTTP Request Handler->>Handler: Validate Input Handler->>Service: Call Business Logic Service->>Service: Apply Business Rules Service->>Repository: Data Operation Repository->>Database: SQL Query Database-->>Repository: Result Repository-->>Service: Domain Model Service-->>Handler: Result Handler->>Handler: Format Response Handler-->>Client: HTTP Response ``` ## Dependency Rule Dependencies can only point inward: ``` Handlers → Services → Domain ← Repositories ``` - **Domain** has no dependencies - **Services** depend only on Domain - **Repositories** depend only on Domain - **Handlers** depend on Services and Domain ## Key Design Patterns ### 1. Dependency Injection All dependencies are injected through constructors: ```go type UserService struct { userRepo domain.UserRepository } func NewUserService(repo domain.UserRepository) *UserService { return &UserService{userRepo: repo} } ``` ### 2. Repository Pattern Data access is abstracted through interfaces: ```go // Domain defines the interface type UserRepository interface { GetByID(id int64) (*User, error) } // Repository implements it type PostgresUserRepository struct { ... } func (r *PostgresUserRepository) GetByID(id int64) (*User, error) { ... } ``` ### 3. Interface Segregation Small, focused interfaces instead of large ones: ```go type UserReader interface { GetByID(id int64) (*User, error) } type UserWriter interface { Create(user *User) error } ``` ## Technology Stack ### Backend - **Language**: Go 1.21+ - **Web Framework**: Standard library `net/http` + gorilla/mux or chi - **Database**: SQLite (dev), PostgreSQL (prod) - **ORM/Query Builder**: sqlx or raw SQL - **Authentication**: JWT with golang-jwt - **Password Hashing**: bcrypt - **Observability**: OpenTelemetry - **Testing**: Go standard testing + testify ### Frontend (PWA) - **Framework**: Vue.js 3 - **UI Library**: Vuetify 3 - **State Management**: Pinia - **HTTP Client**: Axios - **Build Tool**: Vite 6 - **PWA Plugin**: vite-plugin-pwa - **Service Worker**: Workbox 7 - **Offline Storage**: IndexedDB (native browser API) - **Testing**: Vitest + Vue Test Utils ### Infrastructure - **Containerization**: Docker + Docker Compose - **Database**: MariaDB/PostgreSQL/SQLite - **Web Server**: Nginx (optional reverse proxy) - **Migrations**: golang-migrate - **HTTPS**: Required for PWA (Let's Encrypt) ## PWA Architecture ActaLog is built as a Progressive Web App, providing native app-like experience with offline capabilities. ### PWA Core Components ```mermaid graph LR subgraph "PWA Components" Manifest[Web App Manifest] SW[Service Worker] Cache[Cache API] IDB[IndexedDB] Sync[Background Sync] end subgraph "User Experience" Install[Install Prompt] Offline[Offline Mode] Updates[Auto Updates] Fast[Fast Loading] end Manifest --> Install SW --> Offline SW --> Updates Cache --> Fast IDB --> Offline Sync --> Offline ``` ### 1. Web App Manifest **Location**: Auto-generated by vite-plugin-pwa **Configuration** (in `vite.config.js`): ```javascript manifest: { name: 'ActaLog - CrossFit Workout Tracker', short_name: 'ActaLog', description: 'Track your CrossFit workouts...', theme_color: '#2c3657', background_color: '#ffffff', display: 'standalone', icons: [...] } ``` **Capabilities**: - Installable to home screen on iOS, Android, and desktop - Custom splash screen with theme colors - Standalone display mode (no browser UI) - Portrait orientation for mobile ### 2. Service Worker (Workbox) **Auto-generated by vite-plugin-pwa with Workbox strategies** **Caching Strategies**: 1. **CacheFirst** (Static Assets) - Fonts (Google Fonts, Material Design Icons) - CSS, JS bundles - Images and icons - Used for rarely-changing resources 2. **NetworkFirst** (API Data) - API responses with 5-minute cache fallback - 10-second network timeout - Falls back to cache if offline 3. **Precaching** - All build assets automatically precached - Updated on new deployment ### 3. Offline Storage (IndexedDB) **Implementation**: `src/utils/offlineStorage.js` **Object Stores**: - `workouts` - Cached workout data - `movements` - Cached movement library - `pendingSync` - Queue for offline operations **Workflow**: ```mermaid sequenceDiagram participant User participant App participant IDB as IndexedDB participant Network participant API User->>App: Log workout (offline) App->>IDB: Save to local DB App->>IDB: Add to sync queue App-->>User: Workout saved locally Note over App: Connection restored App->>IDB: Get pending sync items IDB-->>App: Pending workouts App->>API: POST /api/workouts API-->>App: 201 Created App->>IDB: Remove from sync queue App->>IDB: Mark as synced ``` **Key Functions**: - `saveWorkoutOffline()` - Save workout when offline - `getWorkoutsOffline()` - Retrieve cached workouts - `syncWithServer()` - Sync pending operations - `markWorkoutSynced()` - Update sync status ### 4. Offline-First Strategy **Data Flow**: 1. **Always try network first** for fresh data 2. **Cache successful responses** for offline access 3. **Use cached data** when offline 4. **Queue write operations** for background sync 5. **Sync automatically** when connection restored **Network Detection**: ```javascript // Listen for online/offline events window.addEventListener('online', syncWithServer) window.addEventListener('offline', showOfflineNotice) // Check current status if (navigator.onLine) { // Online - normal operation } else { // Offline - use cache } ``` ### 5. Update Strategy **Auto-update with user notification**: - Service worker checks for updates on page load - New version silently downloaded in background - User prompted to reload when critical updates available - Seamless updates without app store delays **Update Flow**: ```javascript registerSW({ onNeedRefresh() { // Notify user of update if (confirm('New version available. Reload?')) { updateSW(true) } } }) ``` ### PWA Checklist ✅ Web App Manifest with complete metadata ✅ Service Worker for offline functionality ✅ HTTPS in production (required for PWA) ✅ Responsive design (mobile-first) ✅ Fast loading with cache strategies ✅ Offline page/graceful degradation ✅ Add to home screen capability ✅ App icons (72px - 512px) ✅ Background sync for offline operations ⏳ Push notifications (future enhancement) ### Performance Optimizations 1. **Code Splitting**: Lazy-load routes and components 2. **Asset Optimization**: Compress images, minify CSS/JS 3. **Precaching**: Critical resources cached on install 4. **Runtime Caching**: API responses cached intelligently 5. **IndexedDB**: Efficient local data storage ### Browser Support - **Chrome/Edge**: Full support (Desktop & Mobile) - **Safari**: Full support iOS 11.3+ (with limitations on install prompt) - **Firefox**: Full support (Desktop & Android) - **Samsung Internet**: Full support - **Opera**: Full support **Graceful Degradation**: - Works as regular web app in older browsers - Progressive enhancement for modern browsers - No broken functionality in non-PWA browsers ## Audit Logging Architecture ### Comprehensive Audit Trail System ActaLog implements a complete audit logging system that tracks all data modifications across the application. This provides accountability, compliance support, and troubleshooting capabilities. ### Audit Logging Design Patterns **1. Service Layer Integration** - Audit logging is implemented at the service layer, not in repositories or handlers - Every service that modifies data accepts an `auditLogRepo` in its constructor - Services inject user context (userID, userEmail) into audit logs **2. Conditional Logging** ```go if s.auditLogRepo != nil { details, _ := json.Marshal(map[string]interface{}{ "entity_id": entity.ID, "entity_name": entity.Name, "created_by": userEmail, }) detailsStr := string(details) _ = s.auditLogRepo.Create(&domain.AuditLog{ UserID: &userID, TargetUserID: &targetUserID, EventType: domain.EventEntityCreated, Details: &detailsStr, CreatedAt: time.Now(), }) } ``` **3. Fire-and-Forget Pattern** - Audit log failures never block primary operations - Audit logging uses fire-and-forget (errors ignored with `_`) - Primary business operations succeed even if audit logging fails **4. Change Tracking for Updates** ```go // Store old values before update oldName := entity.Name oldValue := entity.Value // Perform update entity.Name = newName entity.Value = newValue // Log with before/after comparison details, _ := json.Marshal(map[string]interface{}{ "entity_id": entity.ID, "changes": map[string]interface{}{ "name_old": oldName, "name_new": entity.Name, "value_old": oldValue, "value_new": entity.Value, }, }) ``` **5. User Attribution** - `UserID`: The user who performed the action - `TargetUserID`: The user who was affected by the action (for user management operations) - `userEmail`: Stored in details JSON for human-readable logs **6. Admin Operation Tracking** ```go details, _ := json.Marshal(map[string]interface{}{ "entity_id": entity.ID, "admin_update": true, // Flag for admin operations "updated_by": adminEmail, }) ``` ### Logged Operations by Entity **Movements** - `movement_created` - New movement creation (custom and standard) - `movement_updated` - Movement attribute updates - `movement_deleted` - Movement deletion **WODs** - `wod_created` - New WOD creation - `wod_updated` - WOD attribute updates - `wod_deleted` - WOD deletion **Workout Templates** - `workout_template_created` - New template creation with movements/WODs - `workout_template_updated` - Template modifications - `workout_template_deleted` - Template deletion **User Workouts** - `user_workout_logged` - User logs a workout - `user_workout_updated` - User edits a logged workout - `user_workout_deleted` - User deletes a workout **User Management** - `profile_updated` - User profile changes (name, email, birthday) - `user_settings_updated` - Settings changes (theme, units, preferences) - `user_created` - User registration - `user_disabled` - User account disabled - `user_enabled` - User account enabled - `user_role_changed` - Role changes (user ↔ admin) - `user_deleted` - User account deletion **Organizations** (v0.14.0) - `organization_created` - Organization creation - `organization_updated` - Organization updates - `organization_deleted` - Organization deletion - `user_organization_added` - User added to organization - `user_organization_removed` - User removed from organization **Subscriptions** (v0.14.0) - `subscription_created` - Subscription creation - `subscription_cancelled` - Subscription cancellation - `subscription_marked_paid` - Payment recorded - `subscription_marked_unpaid` - Payment reversed ### Audit Log Data Structure ```go type AuditLog struct { ID int64 `json:"id"` UserID *int64 `json:"user_id"` // Who performed action TargetUserID *int64 `json:"target_user_id"` // Who was affected EventType string `json:"event_type"` // Event constant Details *string `json:"details"` // JSON-encoded context CreatedAt time.Time `json:"created_at"` // Timestamp } ``` **Details JSON Schema** (varies by event type): ```json { "entity_id": 123, "entity_name": "Back Squat", "entity_type": "movement", "created_by": "user@example.com", "admin_update": false, "changes": { "name_old": "Back Squat", "name_new": "Barbell Back Squat", "type_old": "weightlifting", "type_new": "weightlifting" } } ``` ### Admin Audit Log UI **Admin Data Change Logs View** (`/admin/data-change-logs`) - Filterable by entity type, operation, user email - Paginated table showing all audit events - Detailed dialog showing full before/after JSON - Changed fields diff table for updates - Color-coded operations and entity types **Use Cases:** - Compliance and accountability tracking - Troubleshooting data issues - User action history review - Security incident investigation - Admin operation auditing ### Performance Considerations 1. **Asynchronous Logging**: Audit logs written synchronously but don't block operations 2. **No Transactions**: Audit logs in separate operations (may fail independently) 3. **Indexing**: `audit_logs` table indexed on `event_type`, `user_id`, `target_user_id`, `created_at` 4. **Cleanup**: Admin API endpoint for deleting old audit logs (retention policy) ### Security Considerations - Audit logs are immutable once created (no update/delete from application) - Only admins can view audit logs via API - Sensitive data (passwords) never logged - IP addresses optional (privacy consideration) - GDPR compliance: Audit logs included in user data exports ### Implementation Checklist ✅ Domain layer: Event type constants defined ✅ Repository layer: AuditLogRepository implemented for all databases ✅ Service layer: All CRUD services inject and use auditLogRepo ✅ Handler layer: All handlers extract userEmail from JWT context ✅ Initialization: All services initialized with auditLogRepo in main.go ✅ Admin UI: Complete audit log viewing and filtering interface ✅ Testing: All services tested with and without audit logging enabled --- ## Backup and Restore Architecture ### Overview ActaLog provides a comprehensive backup and restore system that supports multiple restore modes for flexible data management. The system uses a JSON-based backup format with schema metadata for cross-database compatibility. ### Backup Format Backups are stored as ZIP files containing: - `metadata.json` - Backup metadata (version, timestamp, database info, record counts) - `schema_metadata.json` - Database schema information for type-aware restoration - `{table_name}.json` - JSON export of each table's data ### Restore Modes The restore system supports three modes for handling existing data: ```mermaid graph TB subgraph "Restore Modes" Replace[Replace Mode
DELETE all → INSERT all] Merge[Merge Mode
UPDATE existing → INSERT new] Skip[Skip Mode
INSERT only non-existing] end subgraph "Natural Key Matching" Users[Users: email] Movements[Movements: name] WODs[WODs: name] Orgs[Organizations: name] Workouts[User Workouts: user_id + date + name] end Merge --> Users Merge --> Movements Merge --> WODs Merge --> Orgs Merge --> Workouts Skip --> Users Skip --> Movements Skip --> WODs Skip --> Orgs Skip --> Workouts ``` **1. Replace Mode** (Default) - Deletes all existing data in each table - Inserts all records from backup - Fastest but destructive - Use for clean restores to fresh database **2. Merge Mode** - Matches records by natural key (not ID) - Updates existing records with backup data - Inserts new records that don't exist - Preserves records not in backup - ID remapping for foreign key integrity **3. Skip Mode** - Matches records by natural key - Skips records that already exist - Only inserts new records - Safest for incremental imports - ID remapping for foreign key integrity ### Natural Key Matching Strategy Records are matched by business keys rather than database IDs: | Table | Natural Key | Match Strategy | |-------|-------------|----------------| | users | email | Exact match | | movements | name | Exact match | | wods | name | Exact match | | organizations | name | Exact match | | user_workouts | user_id + workout_date + workout_name | Composite match | | workout_movements | workout_id + movement_id + order_index | Composite (after remap) | | workout_wods | workout_id + wod_id + order_index | Composite (after remap) | | user_settings | user_id | After user remap | | notifications | - | Additive only (skip) | | audit_logs | - | Additive only (skip) | ### ID Remapping When using merge or skip mode, IDs may differ between backup and target database. The system maintains ID mappings: ```go type idMappings struct { users map[int64]int64 // backup_id -> target_id movements map[int64]int64 wods map[int64]int64 organizations map[int64]int64 workouts map[int64]int64 userWorkouts map[int64]int64 } ``` **Remapping Flow:** 1. Restore parent tables first (users, movements, wods, organizations) 2. Build ID mapping for each matched/inserted record 3. Restore child tables using remapped foreign keys 4. Update FK references before insert/update ### Restore Result The API returns detailed statistics: ```json { "mode": "merge", "tables_restored": 21, "records_created": 16, "records_updated": 50, "records_skipped": 0, "errors": [] } ``` ### API Usage ```bash # Replace mode (default) curl -X POST /api/admin/backups/{filename}/restore \ -H "Authorization: Bearer $TOKEN" \ -d '{"confirm": true}' # Merge mode curl -X POST /api/admin/backups/{filename}/restore \ -H "Authorization: Bearer $TOKEN" \ -d '{"confirm": true, "mode": "merge"}' # Skip mode curl -X POST /api/admin/backups/{filename}/restore \ -H "Authorization: Bearer $TOKEN" \ -d '{"confirm": true, "mode": "skip"}' ``` ### Import Duplicate Handling All import services support duplicate detection and handling: | Import Type | Skip Duplicates | Update Duplicates | Detection Key | |-------------|-----------------|-------------------|---------------| | WOD Import | ✅ | ✅ | name | | Movement Import | ✅ | ✅ | name | | User Workout Import | ✅ | ✅ | user_id + date + name | | Wodify Import | ✅ | ✅ | workout date | **Import Options:** ```bash # Skip duplicates (keep existing) curl -X POST /api/import/wods/confirm \ -F "file=@wods.csv" \ -F "skip_duplicates=true" # Update duplicates (overwrite existing) curl -X POST /api/import/wods/confirm \ -F "file=@wods.csv" \ -F "update_duplicates=true" ``` ### Security Considerations - Only admin users can create, restore, or delete backups - Backup files stored in configurable directory (default: `./backups`) - Security-sensitive tables (refresh_tokens, password_resets) are skipped during merge/skip - Audit logs are append-only (never overwritten) - All restore operations are logged to audit trail ### Implementation Files - `internal/domain/backup.go` - RestoreMode type, RestoreResult struct, BackupService interface - `internal/service/backup_service.go` - Full backup/restore implementation with merge/skip logic - `internal/handler/backup_handler.go` - REST API endpoints - `internal/service/import_service.go` - Import duplicate handling - `internal/service/wodify_import_service.go` - Wodify-specific import logic --- ## Security Architecture ### Authentication Flow ```mermaid sequenceDiagram participant Client participant API participant AuthService participant UserRepo participant DB Client->>API: POST /api/auth/login API->>AuthService: Authenticate(email, password) AuthService->>UserRepo: GetByEmail(email) UserRepo->>DB: SELECT user DB-->>UserRepo: User data UserRepo-->>AuthService: User AuthService->>AuthService: Verify password (bcrypt) AuthService->>AuthService: Generate JWT AuthService-->>API: JWT token API-->>Client: {token, user} Note over Client,API: Subsequent requests Client->>API: GET /api/workouts (Authorization: Bearer token) API->>API: Validate JWT API->>API: Extract user ID API->>WorkoutService: GetUserWorkouts(userID) ``` ### Security Measures 1. **Password Security**: Bcrypt with cost factor 12+ 2. **SQL Injection Prevention**: Parameterized queries only 3. **XSS Prevention**: Input sanitization and output encoding 4. **CSRF Protection**: CSRF tokens for state-changing operations 5. **Rate Limiting**: Token bucket algorithm 6. **TLS/SSL**: Required in production 7. **CORS**: Configurable allowed origins 8. **Input Validation**: Strict validation at all entry points ## Defense-in-Depth Pattern The protected-user system (v1.3.0) introduced a four-layer defense pattern that's worth using as a reference for future security-critical features: - **L1 — HTTP middleware:** Request-level rejection. Catches every HTTP-level write attempt. - **L2 — Service-layer guard:** Catches in-process callers (cron, jobs, internal APIs) that bypass the HTTP path. - **L3 — Database trigger:** Catches direct DB access (raw SQL, connection-pool bypass, rogue migrations). - **L4 — Audit logging:** Tagged at the rejecting layer (not the layer traversed). Single event per attempt. Plus a **boot-time invariant** that fails closed if any layer is broken. Each layer is independently sufficient. See `docs/security/PROTECTED_USERS.md` for the complete reference implementation; see `docs/security/THREAT_MODEL.md` for the broader threat coverage map. ## Observability ### Three Pillars 1. **Logs**: Structured JSON logging with correlation IDs 2. **Metrics**: Request latency, throughput, error rates 3. **Traces**: Distributed tracing with OpenTelemetry ### Key Metrics - Request duration (p50, p95, p99) - Request rate (requests/second) - Error rate (errors/second) - Database query duration - Active connections - Memory usage ## Testing Strategy ### Test Pyramid ``` /\ / \ E2E Tests (Few) /____\ / \ Integration Tests (Some) /________\ / \ Unit Tests (Many) /____________\ ``` ### Test Types 1. **Unit Tests** (`test/unit/`): Test individual functions/methods 2. **Integration Tests** (`test/integration/`): Test component interactions 3. **E2E Tests**: Test complete user workflows ### Test Practices - Table-driven tests for multiple scenarios - Mocking external dependencies - Test coverage > 80% - Parallel test execution where safe - Test isolation (no shared state) ## Deployment Architecture ### Production Deployment (Docker) ActaLog uses a **single-port architecture** for production deployments: ```mermaid graph TB subgraph "Client Layer" Browser[Web Browser] Mobile[Mobile Browser] PWA[Installed PWA] end subgraph "Docker Container (Port 8080)" GoApp[Go Application] StaticFiles[Static Files
/app/web/dist] subgraph "Routes" APIRoutes[/api/* - API Endpoints] Uploads[/uploads/* - User Files] Frontend[/* - Frontend SPA] end end subgraph "Data Layer" DB[(Database
SQLite/PostgreSQL/MariaDB)] UploadsVol[Uploads Volume] DataVol[Data Volume] end Browser --> GoApp Mobile --> GoApp PWA --> GoApp GoApp --> APIRoutes GoApp --> Uploads GoApp --> Frontend Frontend --> StaticFiles APIRoutes --> DB Uploads --> UploadsVol DB -.-> DataVol ``` **Key Design Points:** 1. **Single Port (8080)**: All traffic (frontend + backend) served from one port 2. **Static File Serving**: Go serves pre-built frontend static files from `/app/web/dist` 3. **Route Priority**: API routes match first, then uploads, then frontend catch-all 4. **SPA Routing**: Non-existent paths serve `index.html` for Vue Router 5. **No Node.js in Production**: Frontend built during Docker image creation 6. **Volume Management**: - `/app/data` - Database files (SQLite) or config - `/app/uploads` - User-uploaded files (avatars, etc.) **Implementation:** See `cmd/actalog/main.go:418-436` for static file serving logic. ### Multi-Instance Production (Scalable) For high-availability deployments: ```mermaid graph LR subgraph "Production Environment" LB[Load Balancer/Nginx] App1[ActaLog Container 1
Port 8080] App2[ActaLog Container 2
Port 8080] DB[(PostgreSQL
Shared Database)] Uploads[Shared Uploads
NFS/S3] Cache[(Redis
Future)] end Client[Clients] --> LB LB --> App1 LB --> App2 App1 --> DB App2 --> DB App1 --> Uploads App2 --> Uploads App1 -.-> Cache App2 -.-> Cache ``` **Scaling Considerations:** - Use PostgreSQL or MariaDB (not SQLite) for shared database - Mount shared uploads volume or use object storage (S3) - Load balancer handles TLS termination - Each container independently serves frontend + API ## Performance Considerations 1. **Database Indexing**: Proper indexes on frequently queried columns 2. **Connection Pooling**: Reuse database connections 3. **Caching Strategy**: - Service Worker cache for static assets - IndexedDB for offline data - Redis for session data (future) 4. **Pagination**: Limit result sets with offset/limit 5. **Lazy Loading**: Load related data on demand 6. **Compression**: gzip compression for API responses 7. **PWA Precaching**: Critical resources cached on app install 8. **Code Splitting**: Lazy-load routes and components ## Future Enhancements 1. **Push Notifications**: Web Push API for workout reminders and achievements 2. **gRPC API**: For enhanced performance on mobile devices 3. **GraphQL**: Flexible querying for complex data requirements 4. **Event Sourcing**: Audit trail and temporal queries 5. **Microservices**: Split into smaller services as needed 6. **Real-time Updates**: WebSockets for live workout tracking 7. **Advanced PWA Features**: - Periodic background sync for data refresh - Web Share API for workout sharing - File System Access API for bulk data operations - Badging API for unsynced workout notifications ## Version History - **v0.3.0**: Planned database schema redesign (documented but not yet implemented) - **v0.2.0-beta**: Multi-database support, workout logging backend (current version) - **v0.1.0**: Initial schema and architecture implementation (current schema) ## Notes on Planned v0.3.0 Schema **Status:** Documented design, not yet implemented in codebase. The database schema redesign is documented to better represent the logical data model: **Key Architectural Changes:** 1. **Workouts as Templates**: Workouts are now reusable templates rather than user-specific instances 2. **WOD Entity**: New first-class entity for CrossFit WODs with comprehensive metadata 3. **Junction Tables**: Proper many-to-many relationships via junction tables (user_workouts, workout_wods, workout_strength) 4. **Audit Trail**: Built-in audit logging for accountability and troubleshooting 5. **User Settings**: Separate table for user preferences **Impact on Application Layers:** - **Domain Layer**: New entities (WOD, StrengthMovement, UserWorkout, etc.) require new repository interfaces - **Service Layer**: Business logic must handle template-based workouts and user instances separately - **API Layer**: Endpoints need refactoring to support new workflow (create template → log user workout) - **Frontend**: UI must distinguish between workout templates and user workout logs **Migration Strategy (When Implemented):** - Database migrations will transform existing data from v0.1.0 schema to v0.3.0 - Existing user workouts will be converted to the new template + user_workout structure - Standard WODs and movements will be seeded during migration