docs/admin/README.md
Rama main · réplica de lectura del repositorio original.
# ActaLog - Administrator Documentation
**Version:** 1.2.1
**Last Updated:** 2026-02-28
This guide provides comprehensive instructions for ActaLog system administrators. It covers user management, system configuration, backup/restore operations, security best practices, and troubleshooting.
## Table of Contents
1. [Administrator Overview](#administrator-overview)
2. [System Metrics Dashboard](#system-metrics-dashboard)
3. [User Account Management](#user-account-management)
4. [User Import/Export](#user-importexport)
5. [Subscription Management](#subscription-management)
6. [Class Scheduling Administration](#class-scheduling-administration)
7. [Database Backup and Restore](#database-backup-and-restore)
8. [Audit Log Monitoring](#audit-log-monitoring)
9. [Data Change Logs](#data-change-logs)
10. [System Configuration](#system-configuration)
11. [Security Best Practices](#security-best-practices)
12. [Database Management](#database-management)
13. [Troubleshooting](#troubleshooting)
14. [API Reference](#api-reference)
15. [Admin FAQ](#admin-faq)
---
## Administrator Overview
### What is an Administrator?
Administrators have elevated privileges in ActaLog, including:
- Full user account management (view, unlock, disable, delete, role changes to athlete/coach/admin)
- System metrics dashboard (user stats, workout counts, system health)
- Database backup creation and restore capabilities
- Audit log access and cleanup
- Data change log monitoring (track WOD/Movement modifications)
- Manual email verification overrides
- System data cleanup operations
- Access to admin-only API endpoints
### How to Become an Administrator
- The **first user** to register in a new ActaLog installation automatically becomes an administrator
- Additional administrators can be promoted by existing administrators via the Admin Users panel
- Admin status is controlled by the `role` field in the users table. ActaLog has three roles: `athlete` (default), `coach`, and `admin`
### Accessing Admin Features
Navigate to **Profile** → **Admin** to access:
- **System Metrics** - Real-time dashboard with user stats, workout counts, and system health
- **Admin Users** - User account management
- **Backups** - Database backup and restore
- **Audit Logs** - System activity monitoring (authentication, admin actions)
- **Data Change Logs** - Track modifications to WODs, Movements, and other entities
- **Data Cleanup** - Maintenance operations
---
## System Metrics Dashboard
The System Metrics Dashboard provides real-time statistics about your ActaLog instance. Access it via **Profile** → **Admin** → **System Metrics**.
### Dashboard Sections
#### User Statistics
- **Total Users** - Total registered users in the system
- **Active This Month** - Users who have logged at least one workout this month
- **New This Month** - Users who registered this month
- **Disabled Users** - Accounts that have been disabled by administrators
#### Workout Statistics
- **Total Workouts** - Total workouts logged across all users
- **Workouts This Month** - Workouts logged in the current month
- **Avg Per User** - Average number of workouts per user
#### Content Statistics
- **Total Movements** - All movements in the system (standard + user-created)
- **Total WODs** - All WODs in the system (standard + user-created)
- **Workout Templates** - All workout templates (standard + user-created)
- Each category shows a breakdown of user-created vs standard content
#### Subscription Statistics
- **Active Subscriptions** - Currently active paid subscriptions
- **Expiring Soon** - Subscriptions expiring within 7 days
- **Expired** - Subscriptions that have expired
#### System Health
- **Audit Events** - Recent audit log entries (security monitoring)
- **Email Success Rate** - Percentage of successfully sent emails
- **Emails Sent** - Total emails sent by the system
- **Failed Emails** - Emails that failed to send
#### Workout Trends Chart
A 30-day bar chart showing daily workout activity across all users. Includes:
- Total workouts in the period
- Average workouts per day
- Peak day (highest single-day count)
### API Endpoint
```
GET /api/admin/metrics
```
Returns all dashboard metrics in a single response. Requires admin authentication.
**Response Example:**
```json
{
"user_stats": {
"total_users": 150,
"active_this_month": 42,
"new_this_month": 8,
"disabled_users": 2
},
"workout_stats": {
"total_workouts": 3456,
"workouts_this_month": 234,
"avg_workouts_per_user": 23.04
},
"content_stats": {
"total_movements": 182,
"user_created_movements": 12,
"total_wods": 314,
"user_created_wods": 25,
"total_workout_templates": 45,
"user_created_templates": 15
},
"subscription_stats": {
"active_subscriptions": 45,
"expiring_soon": 3,
"expired_subscriptions": 8
},
"system_health": {
"recent_audit_events_24h": 156,
"email_success_rate": 98.5,
"total_emails_sent": 1234,
"failed_emails": 12
},
"workout_trends": [
{"period": "2026-01-01", "count": 12},
{"period": "2026-01-02", "count": 8},
...
]
}
```
---
## User Account Management
```mermaid
flowchart TD
A[User Account] --> B{Account Status}
B -->|Active| C[Normal Access]
B -->|Locked| D[Temporary Lock]
B -->|Disabled| E[Permanent Block]
D --> F[Auto-Unlock After 15 min]
D --> G[Admin Manual Unlock]
F --> C
G --> C
E --> H[Admin Enable]
H --> C
C --> I{Email Verified?}
I -->|Yes| J[Full Access]
I -->|No| K[Limited Access]
K --> L[Admin Manual Verify]
L --> J
```
### Accessing User Management
1. Navigate to **Profile** → **Admin** → **Admin Users**
2. View the complete user list with status indicators
### User List View
The Admin Users view displays:
- **Email:** User's email address
- **Role:** User or Admin badge
- **Email Verified:** Green checkmark or red X
- **Account Status:** Lock icon if locked
- **Account Enabled:** Green checkmark or red X if disabled
- **Actions:** Quick action buttons for each user
### Status Indicators
**Icons and Colors:**
- 🔒 **Lock (Red):** Account is temporarily locked due to failed login attempts
- ✅ **Verified (Green):** Email is verified
- ❌ **Not Verified (Red):** Email not verified
- 👤 **Athlete (Blue):** Regular athlete role
- 🎓 **Coach (Teal):** Coach role (class management access)
- 🛡️ **Admin (Purple):** Administrator role
- ✅ **Enabled (Green):** Account is active
- ❌ **Disabled (Red):** Account is permanently disabled
### How Do I Unlock a Locked User Account?
**When Accounts Get Locked:**
- After 5 failed login attempts within 15 minutes
- Account locked until `locked_until` timestamp (15 minutes from last failed attempt)
- Can be manually unlocked by admin immediately
**To Unlock an Account:**
1. Navigate to **Admin Users** view
2. Find the user with a red lock icon
3. Click the **🔓 Unlock** button
4. Confirmation: "Account unlocked successfully"
5. User can now log in immediately
**Technical Details:**
- Sets `failed_login_attempts = 0`
- Clears `locked_at` and `locked_until` timestamps
- Audit log entry: `user_unlocked` event
### How Do I Disable/Enable User Accounts?
**Disabling an Account:**
Permanent account suspension (e.g., for policy violations, terminated employees, security concerns).
1. Navigate to **Admin Users** view
2. Find the user to disable
3. Click the **🚫 Disable** button
4. **Confirmation dialog appears:**
- Enter a reason for disabling (required)
- Example: "Violation of terms of service" or "Employee terminated"
5. Click **Confirm**
6. User account is disabled immediately
**What Happens When Disabled:**
- User cannot log in (all login attempts rejected)
- Existing sessions are revoked
- Data is preserved (workouts, PRs, profile)
- Account can be re-enabled by admin
**Enabling a Disabled Account:**
1. Navigate to **Admin Users** view
2. Find the user with red ❌ in Enabled column
3. Click the **✅ Enable** button
4. Confirmation: "Account enabled successfully"
5. User can now log in again
**Technical Details:**
- `account_disabled` boolean flag
- `disabled_at` timestamp
- `disabled_by_user_id` tracks which admin disabled the account
- `disable_reason` text field stores justification
- Audit log entries: `user_disabled` and `user_enabled` events
### How Do I Change User Roles (Admin/User)?
**Promoting a User to Admin:**
1. Navigate to **Admin Users** view
2. Find the regular user (blue 👤 icon)
3. Click the **🛡️ Make Admin** button
4. Confirmation dialog appears
5. Click **Confirm**
6. User is now an administrator
**Demoting an Admin to User:**
1. Navigate to **Admin Users** view
2. Find the admin user (purple 🛡️ icon)
3. Click the **👤 Make User** button
4. Confirmation dialog appears
5. Click **Confirm**
6. User is now a regular user (loses admin privileges)
**Important Notes:**
- You cannot change your own role (prevents accidental lockout)
- At least one admin should always exist in the system
- Role changes take effect immediately
- User may need to log out and back in to see updated permissions
**Technical Details:**
- `role` field in users table: `"admin"`, `"coach"`, or `"athlete"`
- Audit log entries: `user_role_changed` event
### How Do I Verify User Emails Manually?
**When Manual Verification is Needed:**
- User didn't receive verification email
- Email service is temporarily down
- Migrating users from another system
- Troubleshooting registration issues
**To Manually Verify an Email:**
1. Navigate to **Admin Users** view
2. Find the user with red ❌ in Email Verified column
3. Click the **✉️ Verify Email** button
4. Confirmation: "Email verified successfully"
5. User now has full access (no verification banner)
**Technical Details:**
- Sets `email_verified = true`
- Sets `email_verified_at = CURRENT_TIMESTAMP`
- Audit log entry: `user_email_verified` event
### How Do I Delete a User Account?
**⚠️ WARNING: This action is PERMANENT and CANNOT be undone!**
**What Gets Deleted:**
- User account and profile
- All workouts and performance history
- All personal records (PRs)
- Custom movements and WODs created by the user
- Profile pictures and uploaded files
- All user sessions
**To Delete a User:**
1. Navigate to **Admin Users** view
2. Find the user to delete
3. Click the **🗑️ Delete** button
4. **Confirmation dialog appears with warning:**
- Shows what will be deleted
- Requires explicit confirmation
5. Click **Delete User** to confirm
6. User and all associated data is permanently removed
**Important Notes:**
- You cannot delete your own account (prevents accidental lockout)
- Consider disabling instead of deleting if data preservation is important
- Database cascading deletes handle related data automatically
- Audit log entry: `user_deleted` event (logged before deletion)
**Technical Details:**
- Cascading deletes configured in database schema
- Deletes from tables: `users`, `user_workouts`, `workout_movements`, `workout_wods`, `refresh_tokens`, `password_resets`, `email_verification_tokens`, `sessions`
- Foreign key constraints ensure data integrity
---
## User Import/Export
The User Import/Export feature provides bulk user management capabilities for administrators. Access it via **Profile** → **Admin** → **User Import/Export**.
### Overview
This feature enables administrators to:
- **Import users from CSV** - Bulk create new user accounts with predefined passwords
- **Export users to CSV** - Download a list of all users (email, name)
- **Batch password reset** - Send password reset emails to multiple users at once
### Importing Users from CSV
Import new users in bulk by uploading a CSV file.

#### CSV Format Requirements
The CSV file must have these columns in the header row:
- `email` - User's email address (required, must be unique)
- `name` - User's display name (required)
- `password` - Initial password (required, minimum 8 characters)
**Example CSV:**
```csv
email,name,password
john.doe@example.com,John Doe,SecurePass123
jane.smith@example.com,Jane Smith,Password456
```
#### Import Process
1. Navigate to **User Import/Export** → **Import** tab
2. Click the upload zone or drag and drop a CSV file
3. Click **Preview Import** to validate the file
4. Review the preview results:

- **Total Rows** - Number of data rows in the CSV
- **Valid** - Rows that will be imported successfully
- **Duplicates** - Emails that already exist in the system
- **Invalid** - Rows with validation errors (bad email format, short password, etc.)
5. Optionally check **Skip duplicate emails** (recommended)
6. Click **Import X Users** to create the accounts
#### What Gets Created
For each valid user imported:
- User account with role `athlete`
- Password hashed with bcrypt (cost 12)
- Email marked as verified
- Permanent free subscription created
### Exporting Users to CSV
Download a CSV file containing all user email addresses and names.

#### Export Process
1. Navigate to **User Import/Export** → **Export** tab
2. Click **Download Users CSV**
3. Save the file to your computer
#### Export Format
The exported CSV contains:
```csv
email,name
john.doe@example.com,John Doe
jane.smith@example.com,Jane Smith
```
**Note:** Passwords are NOT included in the export for security reasons.
#### Use Cases
- Create a backup of user email list
- Use as a template for bulk user import
- Audit current user accounts
- Migration to another system
### Batch Password Reset
Send password reset emails to multiple users at once. Useful for:
- Onboarding users who need to set their own passwords
- Security incidents requiring password rotation
- Helping users who have forgotten their passwords

#### Password Reset Process
1. Navigate to **User Import/Export** → **Password Reset** tab
2. Use the filters to find users:
- **Search** - Filter by name or email
- **Created after/before** - Filter by registration date
3. Select users using checkboxes (or use header checkbox to select all visible)
4. Click **Send Reset Emails**
5. Review the results:
- **Successfully Sent** - Number of emails sent
- **Failed** - Number of failures (with error details)
#### What Happens
For each selected user:
1. A secure password reset token is generated (32 bytes, 1-hour expiry)
2. An email is sent with a link to set a new password
3. The user clicks the link and enters a new password
**Note:** Email service must be configured for this feature to work. If email is disabled, tokens are generated but no emails are sent.
### API Endpoints
#### Preview User Import
```
POST /api/admin/user-management/import/preview
Authorization: Bearer <admin-jwt-token>
Content-Type: multipart/form-data
file: <users.csv>
```
#### Confirm User Import
```
POST /api/admin/user-management/import/confirm
Authorization: Bearer <admin-jwt-token>
Content-Type: multipart/form-data
file: <users.csv>
skip_duplicates: true
```
#### Export Users
```
GET /api/admin/user-management/export
Authorization: Bearer <admin-jwt-token>
```
#### List Users with Filter
```
GET /api/admin/user-management/filter?search=john&limit=50&offset=0
Authorization: Bearer <admin-jwt-token>
```
**Query Parameters:**
- `search` - Search in name and email
- `created_after` - Filter by creation date (YYYY-MM-DD)
- `created_before` - Filter by creation date (YYYY-MM-DD)
- `limit` - Results per page (default: 50, max: 1000)
- `offset` - Pagination offset
#### Batch Password Reset
```
POST /api/admin/user-management/batch-password-reset
Authorization: Bearer <admin-jwt-token>
Content-Type: application/json
{
"user_ids": [1, 2, 3, 4, 5]
}
```
**Response:**
```json
{
"successfully_sent": 4,
"failed_count": 1,
"errors": ["User ID 3: email service disabled"]
}
```
---
---
## Subscription Management
ActaLog includes a built-in subscription billing system. Admins control all subscription operations manually; there is no automated payment processor by default.
### Access
Navigate to **Profile** → **Admin** → **Subscriptions**.
### Subscription Types
| Type | Description |
|------|-------------|
| **User Subscription** | Applies to a single athlete account |
| **Organization Subscription** | Applies to all members of a gym organization |
| **Permanent Free** | Never expires; used for special accounts |
### Subscription States
- **Active** — Athlete has full read/write access to all features
- **Expired / None** — Athlete enters read-only mode (HTTP 402 on write operations). They can still view all their historical data.
Coaches and Admins are **never** subject to subscription enforcement regardless of subscription state.
### Common Admin Tasks
**Create a subscription:**
1. Go to **Subscriptions** tab
2. Click **Create Subscription**
3. Select user or organization, set expiration date, choose type
4. Click **Save**
**Mark a subscription as paid:**
1. Find the subscription in the list
2. Click the **Mark as Paid** button
3. Confirm the action
**Cancel a subscription:**
1. Find the subscription
2. Click **Cancel**
3. Enter a cancellation reason (for audit trail)
**View expiring subscriptions:**
- The **Expiring Soon** tab shows subscriptions expiring within 30 days
**View expired subscriptions:**
- The **Expired** tab lists all lapsed subscriptions
### Subscription Enforcement
When an athlete's subscription expires:
- All `POST`, `PUT`, `PATCH`, `DELETE` requests to feature routes return **HTTP 402 Payment Required**
- `GET` requests continue to succeed — athletes never lose access to their data
- A banner appears in the UI prompting renewal
- Routes exempt from enforcement: profile, settings, password change, notifications, subscription status, login/logout
---
## Class Scheduling Administration
The class scheduling system allows gyms to manage locations, class templates, session schedules, coach assignments, reservations, waitlists, credit packages, and required documents.
Access scheduling administration via **Profile** → **Admin** → **Scheduling**.
### Gym Locations
Locations represent physical gym facilities. Each organization can have multiple locations.
- **Create:** Click **Add Location**, enter name and address
- **Edit:** Click location row → edit form
- **Delete:** Available if no active sessions are attached
### Class Templates
Templates define the blueprint for a recurring class type (e.g., "6am CrossFit", "Open Gym").
Fields:
- **Name** — Display name for the class
- **Location** — Which gym location
- **Capacity** — Maximum athletes per session
- **Duration** — Minutes
- **Description** — Optional notes shown to athletes
**Delete modes:** When deleting a template, choose:
- `Template only` — Sessions become orphaned (still visible in history)
- `With future sessions` — Deletes template and all future sessions; past sessions preserved
- `With all sessions` — Deletes template and every associated session (use with caution)
Credit refunds are issued automatically for unconfirmed reservations on deleted sessions.
### Schedule Slots
Slots attach a template to a recurring time (e.g., every Monday/Wednesday/Friday at 6:00am). The system generates individual class sessions from slots.
### Class Sessions
Individual occurrences of a class. Each session has:
- Date/time, capacity, coach assignment(s)
- Reservation list, waitlist, attendance records
**Session actions:**
- View roster and check-in status
- Add/remove coaches
- Cancel individual sessions
### Coach Assignments
Coaches must be assigned to an organization before they can manage its sessions. Assign coaches via **Admin** → **Users** → select user → change role to **Coach**, then assign to organization(s) via the scheduling panel.
A coach must have:
1. The `coach` role (set in Admin > Users)
2. An assignment to the specific organization (set in Scheduling admin)
### Credit Packages
Packages define how athletes purchase class access (e.g., "10-Class Pack at $100", "Monthly Unlimited").
- **Create:** Click **Add Package**, set name, credit count, price, expiration days
- **Issue credits to user:** Admin > Users > select user > issue credits from package
### User Documents
Documents are gym-defined forms that athletes must complete before attending classes (waivers, liability forms, health questionnaires).
- **Create document type:** Set name, whether required, expiration period
- **Mark as completed:** Admin can mark a user's document as completed on their behalf
- Athletes view their document status in **My Credits**
### Waitlist Management
When a class is at capacity, athletes can join the waitlist. If a reservation is cancelled, the first waitlisted athlete is automatically promoted and notified.
---
## Database Backup and Restore
```mermaid
flowchart TD
A[Backup Request] --> B[Collect Database Data]
B --> C[Export to JSON]
C --> D[Create SQLite Dump]
D --> E[Copy Uploaded Files]
E --> F[Create ZIP Archive]
F --> G[Save to Backups Directory]
G --> H[Log Audit Event]
I[Restore Request] --> J[Extract ZIP Archive]
J --> K[Validate Backup Structure]
K --> L[Clear Current Database]
L --> M[Restore Tables from JSON]
M --> N[Restore Files]
N --> O[Log Audit Event]
O --> P[Application Ready]
```
### Creating a Database Backup
**What's Included in a Backup:**
- All user accounts and profiles
- All workouts and performance data
- All movements (standard and custom)
- All WODs (standard and custom)
- All PRs and historical data
- Profile pictures and uploaded files
- Complete SQLite database dump (for quick restore)
- Backup metadata (version, timestamp, table row counts)
**To Create a Backup:**
1. Navigate to **Profile** → **Admin** → **Backups**
2. Click **Create Backup** button
3. Enter an optional description (recommended):
- Example: "Pre-migration backup"
- Example: "Weekly backup - 2025-01-15"
- Example: "Before v0.9.0 upgrade"
4. Click **Create**
5. Wait for backup to complete (progress indicator shown)
6. Backup appears in the list with:
- Filename (timestamped)
- Description
- Created date/time
- File size
- Row counts per table
**Downloading a Backup:**
1. Find the backup in the list
2. Click the **💾 Download** button
3. Save the ZIP file to a secure location
4. **Store backups in multiple locations** (local + cloud storage)
**Backup File Structure:**
```
backup_20250122_143000.zip
├── metadata.json # Backup info, version, table counts
├── database.db # Complete SQLite dump (quick restore)
├── users.json # User accounts
├── user_workouts.json # Workout sessions
├── workout_movements.json # Movement performances
├── workout_wods.json # WOD performances
├── movements.json # Movement library
├── wods.json # WOD library
├── uploads/ # Uploaded files (profile pictures, etc.)
│ └── ...
```
**Technical Details:**
- Backups stored in `backups/` directory (excluded from git)
- Filename format: `backup_YYYYMMDD_HHMMSS.zip`
- Metadata includes ActaLog version for compatibility checking
- SQLite dump created using `.dump` command (for SQLite databases only)
- JSON exports work with all database drivers (SQLite, PostgreSQL, MySQL)
### Restoring from a Backup
**⚠️ WARNING: Restore REPLACES ALL CURRENT DATA!**
**Before Restoring:**
- Create a backup of current data (if any data worth preserving exists)
- Verify the backup file is valid and complete
- Ensure no users are actively using the system
- Test restore on a staging environment first (if possible)
**To Restore a Backup:**
1. Navigate to **Profile** → **Admin** → **Backups**
2. Find the backup to restore
3. Click the **🔄 Restore** button
4. **Confirmation dialog appears with strong warning:**
- "This will REPLACE ALL current data"
- "This action cannot be undone"
- "Create a backup of current data first if needed"
5. Click **Restore** to confirm
6. Wait for restore to complete (progress indicator shown)
7. **Application reloads automatically**
8. Verify data is restored correctly
**What Happens During Restore:**
1. Existing database is cleared (all tables truncated)
2. Data is restored from JSON files in backup
3. Uploaded files are restored to uploads directory
4. Foreign key constraints are re-established
5. Audit log entry created: `backup_restored` event
**Post-Restore Verification:**
- Check user count matches backup metadata
- Verify workout count matches backup metadata
- Test login with a known user account
- Check a few workout entries for data accuracy
- Verify profile pictures and uploads are accessible
**Cross-Version Compatibility:**
- Backups from v0.6.0+ are compatible with v0.8.0
- Metadata includes version info for validation
- Restore handles missing tables gracefully (forward compatibility)
- Newer backups can be restored to older versions (with potential data loss for new features)
### Uploading Backups from Another System
If you're migrating from another ActaLog instance or restoring from external storage:
1. Navigate to **Profile** → **Admin** → **Backups**
2. Click **Upload Backup** button
3. Select the backup ZIP file from your device
4. Click **Upload**
5. Backup is validated and added to the backup list
6. You can now restore from this uploaded backup
**Validation Checks:**
- File must be a valid ZIP archive
- Must contain `metadata.json` file
- Filename is sanitized for security
- File size limits may apply (check system configuration)
### Deleting Old Backups
To free up disk space, you can delete old backups:
1. Navigate to **Profile** → **Admin** → **Backups**
2. Find the backup to delete
3. Click the **🗑️ Delete** button
4. Confirmation dialog appears
5. Click **Delete**
6. Backup file is permanently removed from server
**Important Notes:**
- Deleted backups cannot be recovered
- Download important backups before deleting
- Keep at least one recent backup at all times
- Consider automated backup rotation (future feature)
---
## Audit Log Monitoring
### What is the Audit Log?
The audit log tracks all significant system events for security and accountability:
- User authentication events (login, logout, failed attempts)
- Admin actions (user unlock, disable, delete, role changes)
- Backup operations (create, download, restore)
- Session management (token refresh, session revoke)
- Data modifications (user updates, email verification)
### Accessing the Audit Log
1. Navigate to **Profile** → **Admin** → **Audit Logs**
2. View chronological list of events
### Audit Log Event Types
**Authentication Events:**
- `user_login` - Successful login
- `user_logout` - User logout
- `failed_login` - Failed login attempt
- `account_locked` - Account locked due to failed attempts
- `token_refreshed` - JWT token refreshed
**Admin Actions:**
- `user_unlocked` - Admin unlocked user account
- `user_disabled` - Admin disabled user account
- `user_enabled` - Admin re-enabled user account
- `user_role_changed` - Admin changed user role
- `user_email_verified` - Admin manually verified email
- `user_deleted` - Admin deleted user account
**Backup Operations:**
- `backup_created` - Backup created
- `backup_downloaded` - Backup downloaded
- `backup_restored` - Backup restored
- `backup_uploaded` - External backup uploaded
- `backup_deleted` - Backup deleted
**Session Management:**
- `session_revoked` - Single session revoked
- `all_sessions_revoked` - All user sessions revoked
**User Events:**
- `user_registered` - New user registration
- `user_updated` - Profile updated
- `verification_email_sent` - Email verification sent
- `password_reset_requested` - Password reset requested
- `password_reset_completed` - Password successfully reset
### Audit Log Fields
Each audit log entry contains:
- **Event Type:** Category of event
- **User Email:** User who triggered the event (if applicable)
- **IP Address:** Source IP of the request
- **Timestamp:** When the event occurred
- **Details:** JSON object with event-specific data
### Filtering and Search
The audit log can be filtered by:
- **Event Type:** Select specific event categories
- **User:** Filter by user email
- **Date Range:** View events within a time period
- **IP Address:** Track activity from specific IPs
### Audit Log Cleanup
Over time, audit logs can grow large. To clean up old entries:
1. Navigate to **Profile** → **Admin** → **Audit Logs**
2. Click **Cleanup** button
3. Select retention period:
- Last 30 days
- Last 90 days
- Last 365 days
- All time (no cleanup)
4. Click **Confirm**
5. Older entries are permanently deleted
**Recommended Retention:**
- Production systems: 365 days minimum
- Compliance requirements: Check your industry regulations
- Security incidents: Preserve logs until investigation complete
---
## Data Change Logs
### What are Data Change Logs?
Data Change Logs provide a complete audit trail for modifications to core application data. Unlike Audit Logs (which track authentication and admin actions), Data Change Logs track changes to:
- **WODs** (Workouts of the Day) - Create, Update, Delete operations
- **Movements** - Create, Update, Delete operations
- **Future:** Other entity types as the system expands
Each log entry captures:
- **Before/After Values:** Complete JSON snapshots showing exactly what changed
- **User Context:** Who made the change (user ID, email)
- **Request Context:** IP address, timestamp
- **Operation Type:** INSERT, UPDATE, or DELETE
### Accessing Data Change Logs
1. Navigate to **Profile** → **Admin** → **Data Change Logs**
2. View chronological list of data modifications
### Data Change Log View
The Data Change Logs view provides:
- **Paginated Table:** Browse through all change logs
- **Filtering Options:**
- **Entity Type:** Filter by wod, movement, etc.
- **Operation:** Filter by INSERT, UPDATE, or DELETE
- **User Email:** Search by partial email match
- **Date Range:** Start and end date filters
- **Quick Info Columns:**
- Entity type and ID
- Operation (color-coded: green=INSERT, blue=UPDATE, red=DELETE)
- User who made the change
- Timestamp
- Truncated before/after values
### Viewing Change Details
To see full details of a change:
1. Find the log entry in the table
2. Click the **Details** button (or the row)
3. **Details Dialog** shows:
- Full before value (JSON formatted)
- Full after value (JSON formatted)
- For UPDATE operations: Side-by-side comparison
- User information and IP address
- Exact timestamp
### Understanding Operations
**INSERT Operations:**
- `before_value`: NULL (no previous data)
- `after_value`: Complete JSON of new entity
- Example: User creates a new custom WOD
**UPDATE Operations:**
- `before_value`: Entity state before modification
- `after_value`: Entity state after modification
- Example: User edits a movement's description
**DELETE Operations:**
- `before_value`: Entity state before deletion
- `after_value`: NULL (entity removed)
- Example: User deletes a custom WOD
### Filtering and Search
**By Entity Type:**
- Select "WOD" to see only WOD changes
- Select "Movement" to see only Movement changes
**By Operation:**
- Filter to see only creates, updates, or deletes
**By User Email:**
- Enter partial email to find changes by specific user
- Example: "john" finds john@example.com, john.doe@example.com
**By Date Range:**
- Set start date to see changes after a specific date
- Set end date to see changes before a specific date
- Combine for a specific time window
### Data Change Log Cleanup
To manage storage and comply with retention policies:
1. Navigate to **Profile** → **Admin** → **Data Change Logs**
2. Click **Cleanup** button
3. Enter retention period in days (e.g., 365 for 1 year)
4. Click **Confirm**
5. Logs older than the retention period are permanently deleted
**Recommended Retention:**
- Development: 30-90 days
- Production: 365 days minimum
- Compliance: Check your industry requirements (GDPR, HIPAA, etc.)
### Use Cases
**Security Investigation:**
- Track who modified or deleted data
- See exact before/after values for suspicious changes
- Correlate with IP addresses
**Debugging:**
- Understand what changed when a user reports an issue
- Trace data inconsistencies back to the source
**Compliance:**
- Demonstrate audit trail for regulatory requirements
- Prove who made changes and when
**Data Recovery:**
- View before values to understand what was lost in a deletion
- Reference for manual data restoration if needed
---
## System Configuration
### Environment Variables
ActaLog is configured via environment variables in the `.env` file. Copy `.env.example` to `.env` and customize for your deployment.
#### Application Settings
```bash
# Application environment
APP_ENV=production # development, staging, or production
APP_URL=https://your-domain.com # Used for email links
PORT=8080 # Server port
# JWT Authentication
JWT_SECRET=your-secret-key-here-change-this # MUST change in production
JWT_EXPIRATION=900 # Access token lifetime (seconds) - 15 minutes
REFRESH_TOKEN_EXPIRATION=2592000 # Refresh token lifetime (seconds) - 30 days
# Security
CORS_ORIGINS=https://your-frontend-domain.com # Comma-separated allowed origins
```
**CRITICAL:**
- **JWT_SECRET:** MUST be changed from default! Use a long random string.
- **APP_URL:** Must match your actual frontend URL for email links to work
- **CORS_ORIGINS:** Whitelist only your frontend domain(s)
#### Database Configuration
**SQLite (Development/Small Deployments):**
```bash
DB_DRIVER=sqlite3
DB_NAME=actalog.db
```
**PostgreSQL (Production Recommended):**
```bash
DB_DRIVER=postgres
DB_HOST=localhost
DB_PORT=5432
DB_USER=actalog
DB_PASSWORD=your-db-password
DB_NAME=actalog
DB_SSLMODE=disable # or require/verify-full for production
DB_SCHEMA=public # Optional: schema isolation for multi-tenant
DB_MAX_OPEN_CONNS=25 # Connection pool: max open connections
DB_MAX_IDLE_CONNS=5 # Connection pool: max idle connections
DB_CONN_MAX_LIFETIME=5m # Connection pool: max lifetime
```
**MySQL/MariaDB (Alternative):**
```bash
DB_DRIVER=mysql
DB_HOST=localhost
DB_PORT=3306
DB_USER=actalog
DB_PASSWORD=your-db-password
DB_NAME=actalog
DB_MAX_OPEN_CONNS=25 # Connection pool: max open connections
DB_MAX_IDLE_CONNS=5 # Connection pool: max idle connections
DB_CONN_MAX_LIFETIME=5m # Connection pool: max lifetime
```
#### Email Configuration
```bash
# Email service (SMTP)
EMAIL_FROM=noreply@your-domain.com
SMTP_HOST=smtp.gmail.com # Your SMTP server
SMTP_PORT=587 # Usually 587 for TLS, 465 for SSL
SMTP_USER=your-email@gmail.com # SMTP username
SMTP_PASS=your-app-password # SMTP password or app-specific password
```
**Common SMTP Providers:**
- **Gmail:** `smtp.gmail.com:587` (requires app-specific password if 2FA enabled)
- **SendGrid:** `smtp.sendgrid.net:587`
- **AWS SES:** `email-smtp.us-east-1.amazonaws.com:587`
- **Mailgun:** `smtp.mailgun.org:587`
#### Frontend Configuration
The frontend uses environment variables prefixed with `VITE_`. Create `web/.env` for production deployments:
```bash
# API base URL (only needed if backend is on different domain/port)
VITE_API_BASE_URL=https://api.your-domain.com
# Or leave empty to use relative URLs (recommended when backend and frontend on same domain)
VITE_API_BASE_URL=
```
### Database Driver Selection
ActaLog supports three database drivers with automatic detection:
| Driver | Use Case | Connection Pooling | Schema Isolation |
|--------|----------|-------------------|------------------|
| **sqlite3** | Development, single-user, small deployments | ❌ No | ❌ No |
| **postgres** | Production, multi-user, high performance | ✅ Yes | ✅ Yes |
| **mysql** | Alternative production option, MariaDB compatible | ✅ Yes | ❌ No |
**PostgreSQL (Recommended for Production):**
- Uses **pgx/v5 driver** (actively maintained, 10-30% faster than lib/pq)
- Supports schema isolation via `DB_SCHEMA` parameter
- Advanced connection pooling with tunable parameters
- Best for multi-user production deployments
**Migration from SQLite to PostgreSQL:**
See `docs/POSTGRESQL_MIGRATION.md` for detailed migration guide.
### Connection Pooling Tuning
For PostgreSQL and MySQL, tune connection pooling for your workload:
**Conservative (Low Traffic):**
```bash
DB_MAX_OPEN_CONNS=10
DB_MAX_IDLE_CONNS=2
DB_CONN_MAX_LIFETIME=5m
```
**Moderate (Typical Production):**
```bash
DB_MAX_OPEN_CONNS=25
DB_MAX_IDLE_CONNS=5
DB_CONN_MAX_LIFETIME=5m
```
**Aggressive (High Traffic):**
```bash
DB_MAX_OPEN_CONNS=100
DB_MAX_IDLE_CONNS=25
DB_CONN_MAX_LIFETIME=1m
```
**Tuning Guidelines:**
- `MaxOpenConns`: Total concurrent database connections
- `MaxIdleConns`: Kept-alive connections (should be < MaxOpenConns)
- `ConnMaxLifetime`: How long a connection can be reused before recycling
- Monitor database connection count to avoid exceeding database limits
- PostgreSQL default max connections: 100 (adjust `max_connections` in postgresql.conf if needed)
---
## Security Best Practices
### Password Security
**Password Policy Recommendations:**
- Minimum 8 characters (enforced by application)
- Require mix of uppercase, lowercase, numbers, and symbols
- Implement password complexity checks (future enhancement)
- Educate users on password manager usage
**Password Storage:**
- All passwords hashed with bcrypt (cost factor 12)
- Never store plaintext passwords
- Password reset tokens expire after 1 hour
- Reset tokens are single-use only
### JWT Secret Key Management
**CRITICAL:** The `JWT_SECRET` must be:
- Changed from the default value before production deployment
- Long and random (minimum 32 characters, recommend 64+)
- Kept secure (never commit to version control)
- Rotated periodically (requires all users to re-login)
**Generating a Secure JWT Secret:**
```bash
# Linux/Mac
openssl rand -base64 64
# Or using Node.js
node -e "console.log(require('crypto').randomBytes(64).toString('base64'))"
```
### Account Lockout Protection
**Automatic Lockout:**
- Triggered after 5 failed login attempts within 15 minutes
- Account locked for 15 minutes
- Admin can manually unlock immediately
**Failed Login Tracking:**
- `failed_login_attempts` counter in users table
- `locked_at` and `locked_until` timestamps
- Audit log entry for each failed attempt
- Counter resets on successful login
### CORS Configuration
**Production CORS Setup:**
```bash
# Single origin
CORS_ORIGINS=https://app.your-domain.com
# Multiple origins (comma-separated)
CORS_ORIGINS=https://app.your-domain.com,https://www.your-domain.com
```
**Security Notes:**
- Never use `*` (allow all origins) in production
- Only whitelist your actual frontend domain(s)
- HTTPS required for production (HTTP only for local development)
- Verify CORS headers in browser dev tools
### Email Service Security
**SMTP Best Practices:**
- Use app-specific passwords (not main account password)
- Enable 2FA on email service account
- Use TLS/SSL for SMTP connections (port 587 or 465)
- Monitor email sending for unusual activity
- Set up SPF, DKIM, and DMARC DNS records for deliverability
### Database Backup Security
**Backup Protection:**
- Store backups in secure location (encrypted storage)
- Backups contain sensitive user data (email, hashed passwords, personal workouts)
- Restrict backup file access (chmod 600 or equivalent)
- Use encrypted backup archives (future enhancement)
- Off-site backup storage recommended
**Backup Rotation:**
- Keep multiple backup versions
- Implement automated backup schedule (daily/weekly)
- Test restore procedure regularly
- Document disaster recovery plan
### Session Management
**Refresh Token Security:**
- 30-day expiration (configurable via `REFRESH_TOKEN_EXPIRATION`)
- Revoke all sessions on password change (recommended)
- Allow users to view active sessions
- Admins can revoke user sessions if compromised
### User Access Monitoring
**Regular Security Audits:**
- Review audit logs for suspicious activity
- Monitor failed login patterns by IP
- Check for unusual admin actions
- Verify active sessions for all users
- Investigate account lockouts
---
## Database Management
### Running Database Migrations
Migrations are applied automatically on application startup. To manually run migrations:
```bash
# Run migrations
make migrate
# Or use the migrate tool directly
go run cmd/migrate/main.go
```
**Migration Files:**
- Located in `migrations/` directory
- Format: `YYYYMMDDHHMMSS_description.up.sql` (apply) and `.down.sql` (rollback)
- Applied in chronological order
- Migration status tracked in `schema_migrations` table
### Creating New Migrations
```bash
# Create new migration
make migrate-create name=add_new_feature
# Edit the generated files:
# migrations/YYYYMMDDHHMMSS_add_new_feature.up.sql
# migrations/YYYYMMDDHHMMSS_add_new_feature.down.sql
```
### Verifying Database Schema
To check if database schema is up-to-date:
```bash
# Check schema
make check-schema
# Or run the tool directly
go run cmd/check-schema/main.go
```
### Database Schema Isolation (PostgreSQL Only)
PostgreSQL supports schema isolation for multi-tenant deployments:
**Setup:**
```bash
# Set schema in environment
DB_SCHEMA=tenant1
# ActaLog will use search_path=tenant1 for all operations
```
**Benefits:**
- Multiple isolated ActaLog instances in one PostgreSQL database
- Each tenant's data in separate schema
- Easier database administration
- Cost-effective for SaaS deployments
**Limitations:**
- PostgreSQL only (not supported on SQLite or MySQL)
- All tables must be created in the schema (migrations handle this)
- Requires PostgreSQL user to have schema creation privileges
### Database Performance
**Monitoring:**
- Check connection pool utilization (log output)
- Monitor query execution time (enable SQL logging)
- Watch for slow queries in PostgreSQL logs
- Profile database load with `pg_stat_statements` (PostgreSQL)
**Optimization:**
- Add indexes on frequently queried columns
- Analyze query plans for slow queries
- Tune connection pool parameters
- Consider read replicas for high-traffic deployments
- Implement caching layer (future enhancement)
---
## Troubleshooting
### Database Connection Issues
**Problem:** `failed to connect to database`
**Solutions:**
- Verify database is running: `systemctl status postgresql` (or `mysql`)
- Check database credentials in `.env`
- Verify network connectivity to database host
- Check firewall rules allow connection on database port
- For PostgreSQL: verify `pg_hba.conf` allows connections from application host
- Check database logs for connection errors
**Problem:** `too many connections` (PostgreSQL/MySQL)
**Solutions:**
- Reduce `DB_MAX_OPEN_CONNS` in `.env`
- Increase database `max_connections` setting
- Check for connection leaks (connections not being closed)
- Monitor active connections: `SELECT count(*) FROM pg_stat_activity;` (PostgreSQL)
### Migration Failures
**Problem:** Migration fails mid-execution
**Solutions:**
- Check database logs for specific error
- Verify database user has sufficient privileges (CREATE TABLE, ALTER TABLE, etc.)
- Check if migration was partially applied (inspect database)
- Manually rollback partial migration if needed
- Fix migration SQL and retry
**Problem:** `migration version mismatch`
**Solutions:**
- Check `schema_migrations` table for current version
- Ensure all migration files are present in `migrations/` directory
- Run migrations: `make migrate`
- If corrupt, reset migrations (⚠️ data loss): drop `schema_migrations` table and run migrations
### Email Delivery Issues
**Problem:** Verification emails not being received
**Solutions:**
- Check SMTP configuration in `.env`
- Verify SMTP credentials are correct
- Test SMTP connection manually: `telnet smtp.gmail.com 587`
- Check email service logs for bounce/rejection messages
- Verify sender email (`EMAIL_FROM`) is authorized to send
- Check spam/junk folders
- Verify firewall allows outbound SMTP connections
- For Gmail: ensure "Less secure app access" is enabled OR use app-specific password
**Problem:** Email links point to wrong domain
**Solutions:**
- Set `APP_URL` in `.env` to your actual frontend URL
- Ensure HTTPS is used in production
- Restart application after changing `APP_URL`
### Backup and Restore Issues
**Problem:** Backup creation fails
**Solutions:**
- Check disk space: `df -h`
- Verify `backups/` directory exists and is writable
- Check database connectivity (backups query all tables)
- Review application logs for specific error
- Ensure no locks on database (e.g., long-running queries)
**Problem:** Restore fails with "table does not exist"
**Solutions:**
- Verify backup file is complete and not corrupted
- Check backup `metadata.json` for version compatibility
- Restore may be from newer version (forward compatibility issue)
- Manually inspect backup JSON files
- Contact support if backup is critical
### Performance Issues
**Problem:** Application is slow
**Solutions:**
- Check database connection pool settings
- Monitor database query performance (enable query logging)
- Review audit logs for unusual activity patterns
- Check system resources (CPU, memory, disk I/O)
- Optimize database indexes if needed
- Consider upgrading database instance or resources
**Problem:** High memory usage
**Solutions:**
- Check for memory leaks (restart application as temporary fix)
- Reduce connection pool size
- Monitor goroutine count (Go applications)
- Review large query result sets (pagination needed?)
### Authentication and Session Issues
**Problem:** Users getting logged out frequently
**Solutions:**
- Check `JWT_EXPIRATION` setting (default 15 minutes)
- Verify refresh token mechanism is working
- Check browser cookie settings (cookies must be enabled)
- Verify frontend is properly handling token refresh
- Check for clock skew between server and client
**Problem:** "Invalid token" errors
**Solutions:**
- Verify `JWT_SECRET` hasn't changed (changing secret invalidates all tokens)
- Check token expiration times
- Ensure frontend is sending token in `Authorization: Bearer <token>` header
- Verify token format is valid JWT
- Check for special characters in JWT_SECRET (use base64-encoded strings)
---
## API Reference
### Admin User Management Endpoints
All admin endpoints require authentication and admin role.
#### List All Users
```
GET /api/admin/users
Authorization: Bearer <admin-jwt-token>
```
**Response:**
```json
[
{
"id": 1,
"email": "user@example.com",
"role": "athlete",
"email_verified": true,
"email_verified_at": "2025-01-15T10:30:00Z",
"failed_login_attempts": 0,
"locked_at": null,
"locked_until": null,
"account_disabled": false,
"disabled_at": null,
"created_at": "2025-01-01T00:00:00Z"
}
]
```
#### Unlock User Account
```
POST /api/admin/users/{id}/unlock
Authorization: Bearer <admin-jwt-token>
```
#### Disable User Account
```
POST /api/admin/users/{id}/disable
Authorization: Bearer <admin-jwt-token>
Content-Type: application/json
{
"reason": "Violation of terms of service"
}
```
#### Enable User Account
```
POST /api/admin/users/{id}/enable
Authorization: Bearer <admin-jwt-token>
```
#### Change User Role
```
PUT /api/admin/users/{id}/role
Authorization: Bearer <admin-jwt-token>
Content-Type: application/json
{
"role": "admin" // or "coach" or "athlete"
}
```
#### Toggle Email Verification
```
POST /api/admin/users/{id}/toggle-email-verification
Authorization: Bearer <admin-jwt-token>
```
#### Delete User
```
DELETE /api/admin/users/{id}
Authorization: Bearer <admin-jwt-token>
```
### Backup Management Endpoints
#### Create Backup
```
POST /api/admin/backups
Authorization: Bearer <admin-jwt-token>
Content-Type: application/json
{
"description": "Weekly backup before upgrade"
}
```
#### List Backups
```
GET /api/admin/backups
Authorization: Bearer <admin-jwt-token>
```
#### Download Backup
```
GET /api/admin/backups/{filename}
Authorization: Bearer <admin-jwt-token>
```
#### Upload Backup
```
POST /api/admin/backups/upload
Authorization: Bearer <admin-jwt-token>
Content-Type: multipart/form-data
file: <backup.zip>
```
#### Restore Backup
```
POST /api/admin/backups/{filename}/restore
Authorization: Bearer <admin-jwt-token>
```
#### Delete Backup
```
DELETE /api/admin/backups/{filename}
Authorization: Bearer <admin-jwt-token>
```
### Audit Log Endpoints
#### Get Audit Logs
```
GET /api/admin/audit-logs?limit=100&offset=0&event_type=user_login
Authorization: Bearer <admin-jwt-token>
```
#### Clean Up Audit Logs
```
POST /api/admin/audit-logs/cleanup
Authorization: Bearer <admin-jwt-token>
Content-Type: application/json
{
"older_than_days": 365
}
```
### Data Change Log Endpoints
#### List Data Change Logs
```
GET /api/data-change-logs?entity_type=wod&operation=UPDATE&user_email=john&limit=50&offset=0
Authorization: Bearer <admin-jwt-token>
```
**Query Parameters:**
- `entity_type` - Filter by entity type (wod, movement)
- `entity_id` - Filter by specific entity ID
- `operation` - Filter by operation (INSERT, UPDATE, DELETE)
- `user_id` - Filter by user ID
- `user_email` - Filter by partial email match
- `start_date` - Start of date range (YYYY-MM-DD or RFC3339)
- `end_date` - End of date range (YYYY-MM-DD or RFC3339)
- `limit` - Maximum results (default: 50)
- `offset` - Pagination offset
**Response:**
```json
{
"logs": [
{
"id": 1,
"entity_type": "wod",
"entity_id": 42,
"operation": "UPDATE",
"user_id": 1,
"user_email": "admin@example.com",
"before_value": "{\"name\":\"Old Name\",\"description\":\"...\"}",
"after_value": "{\"name\":\"New Name\",\"description\":\"...\"}",
"ip_address": "192.168.1.100",
"created_at": "2025-11-26T10:30:00Z"
}
],
"total": 150,
"limit": 50,
"offset": 0
}
```
#### Get Single Data Change Log
```
GET /api/data-change-logs/{id}
Authorization: Bearer <admin-jwt-token>
```
#### Get Entity Change History
```
GET /api/data-change-logs/entity/{entity_type}/{entity_id}?limit=50&offset=0
Authorization: Bearer <admin-jwt-token>
```
#### Clean Up Old Data Change Logs
```
POST /api/admin/data-change-logs/cleanup
Authorization: Bearer <admin-jwt-token>
Content-Type: application/json
{
"retention_days": 365
}
```
**Response:**
```json
{
"deleted_count": 1250,
"message": "Old data change logs deleted successfully"
}
```
---
## Admin FAQ
**Q: How do I add another administrator?**
A: Navigate to Admin Users, find the user, click "Make Admin" button. The user must already have a registered account.
**Q: Can I demote myself from admin?**
A: No, you cannot change your own role to prevent accidental lockout. Another admin must demote you.
**Q: What happens if all admins are deleted or demoted?**
A: You would lose admin access. Always ensure at least one admin account exists. Recovery requires database-level changes.
**Q: How often should I create backups?**
A: Recommended schedule:
- Daily automated backups for active production systems
- Weekly backups for low-activity systems
- Before any major upgrade or migration
- Before bulk data operations
**Q: Can I restore a backup from an older version of ActaLog?**
A: Generally yes, backups from v0.6.0+ are forward compatible. The restore process handles missing tables gracefully. Test on staging first.
**Q: How do I migrate from SQLite to PostgreSQL?**
A: See `docs/POSTGRESQL_MIGRATION.md` for the complete migration guide. Overview: create backup, set up PostgreSQL, change `.env`, run migrations, restore data.
**Q: What's the difference between disabling and deleting a user?**
A: Disabling preserves all user data (can be re-enabled later). Deleting permanently removes the account and all data (cannot be undone).
**Q: How do I handle a security incident (compromised account)?**
A: Immediately disable the account, revoke all sessions, review audit logs for suspicious activity, reset password if re-enabling, investigate how compromise occurred.
**Q: Can I customize email templates?**
A: Currently, email templates are hardcoded in the application. Future versions may support customizable templates. Contact support for urgent customization needs.
**Q: How do I monitor application health?**
A: Check the health endpoint: `GET /health` (no authentication required). Returns status and version info. Set up monitoring/alerting on this endpoint.
**Q: What logs should I monitor?**
A: Monitor:
- Application logs (stdout/stderr)
- Audit logs (via Admin interface)
- Database logs (PostgreSQL/MySQL logs)
- Web server logs (nginx/apache if using reverse proxy)
- Failed login patterns
- Backup success/failure
---
**Document Version:** 1.2.1
**Last Updated:** 2026-02-28
**Applicable Versions:** ActaLog 0.10.0-beta and later