Files

625 lines
17 KiB
Markdown

# Database Schema
Complete database schema reference for both Flashpoint and user databases.
## Overview
The backend uses two separate SQLite databases:
1. **Flashpoint Database** (`flashpoint.sqlite`) - Read-only game metadata
2. **User Database** (`user.db`) - Application-specific data
---
## Flashpoint Database (flashpoint.sqlite)
### Purpose
Contains the official Flashpoint Archive game metadata. This database is managed
by the Flashpoint Launcher and should be treated as read-only (except for play
statistics).
### Main Tables
#### game
Primary game metadata table.
```sql
CREATE TABLE game (
id TEXT PRIMARY KEY, -- UUID (e.g., "12345-abcd-6789")
parentGameId TEXT, -- Parent game UUID (for add-ons/DLC)
title TEXT NOT NULL, -- Game title
alternateTitles TEXT, -- Alternate names
series TEXT, -- Game series name
developer TEXT, -- Developer name
publisher TEXT, -- Publisher name
platformName TEXT, -- Platform (Flash, HTML5, etc.)
platformsStr TEXT, -- Comma-separated platforms
platformId INTEGER, -- FK to platform table
playMode TEXT, -- Single Player, Multiplayer, etc.
status TEXT, -- Status
broken BOOLEAN DEFAULT 0, -- Is game broken?
extreme BOOLEAN DEFAULT 0, -- Adult content flag
notes TEXT, -- Notes about game
tagsStr TEXT, -- Semicolon-delimited tags
source TEXT, -- Source URL
applicationPath TEXT, -- Launch application
launchCommand TEXT, -- Launch command/URL
releaseDate TEXT, -- YYYY-MM-DD format
version TEXT, -- Game version
originalDescription TEXT, -- Description
language TEXT, -- Language
library TEXT, -- arcade, theatre, etc.
orderTitle TEXT, -- Normalized title for sorting
dateAdded TEXT, -- When added to Flashpoint
dateModified TEXT, -- Last modified
playtime INTEGER DEFAULT 0, -- Total playtime (seconds)
playCounter INTEGER DEFAULT 0, -- Number of plays
lastPlayed TEXT, -- Last played timestamp
archiveState INTEGER, -- Archive status
logoPath TEXT, -- Logo image path
screenshotPath TEXT -- Screenshot image path
);
CREATE INDEX idx_game_title ON game(title);
CREATE INDEX idx_game_platform ON game(platformName);
CREATE INDEX idx_game_developer ON game(developer);
CREATE INDEX idx_game_library ON game(library);
```
**Key Fields**:
- `id`: Unique game identifier (UUID)
- `platformName`: Platform name (Flash, HTML5, Shockwave, etc.)
- `library`: Game library (arcade, theatre)
- `tagsStr`: Semicolon-delimited tags (e.g., "Action;Adventure;Platformer")
- `broken`: Games that don't work properly
- `extreme`: Adult content requiring age verification
- `playtime`: Total playtime in seconds (updated by backend)
- `playCounter`: Number of times played (updated by backend)
- `lastPlayed`: ISO timestamp of last play (updated by backend)
---
#### platform
Platform definitions.
```sql
CREATE TABLE platform (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE -- Platform name
);
```
---
#### tag
Tag definitions.
```sql
CREATE TABLE tag (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL, -- Tag name
category TEXT -- Tag category
);
```
---
#### game_tags_tag
Many-to-many relationship between games and tags.
```sql
CREATE TABLE game_tags_tag (
gameId TEXT NOT NULL, -- FK to game.id
tagId INTEGER NOT NULL, -- FK to tag.id
FOREIGN KEY (gameId) REFERENCES game(id),
FOREIGN KEY (tagId) REFERENCES tag(id)
);
```
---
#### game_data
Game file data and launch configurations.
```sql
CREATE TABLE game_data (
id INTEGER PRIMARY KEY AUTOINCREMENT,
gameId TEXT NOT NULL, -- FK to game.id
title TEXT, -- Data entry title
dateAdded TEXT, -- When added
sha256 TEXT, -- File hash
crc32 INTEGER, -- CRC32 checksum
presentOnDisk INTEGER, -- NULL=no data, 0=needs download, 1=downloaded
path TEXT, -- File path
size INTEGER, -- File size in bytes
parameters TEXT, -- URL parameters
applicationPath TEXT, -- Launch application
launchCommand TEXT, -- Launch command
FOREIGN KEY (gameId) REFERENCES game(id)
);
CREATE INDEX idx_game_data_gameId ON game_data(gameId);
```
**Key Fields**:
- `presentOnDisk`: Download status (NULL = no file needed, 0 = needs download, 1
= available)
- `launchCommand`: Preferred launch command
- `path`: File path for game content
- `parameters`: URL parameters to append
---
#### playlist
User playlists (shared with Flashpoint Launcher).
```sql
CREATE TABLE playlist (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL, -- Playlist name
description TEXT, -- Playlist description
author TEXT, -- Creator
library TEXT, -- Library filter
icon TEXT -- Icon path
);
```
---
#### playlist_game
Playlist entries.
```sql
CREATE TABLE playlist_game (
id INTEGER PRIMARY KEY AUTOINCREMENT,
playlistId INTEGER NOT NULL, -- FK to playlist.id
gameId TEXT NOT NULL, -- FK to game.id
order_ INTEGER, -- Display order
notes TEXT, -- Entry notes
FOREIGN KEY (playlistId) REFERENCES playlist(id),
FOREIGN KEY (gameId) REFERENCES game(id)
);
```
---
## User Database (user.db)
### Purpose
Stores application-specific data: user accounts, authentication, roles,
permissions, play tracking, and settings.
### Authentication Tables
#### users
User accounts.
```sql
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE COLLATE NOCASE,
email TEXT NOT NULL UNIQUE COLLATE NOCASE,
password_hash TEXT NOT NULL, -- bcrypt hash
role_id INTEGER NOT NULL, -- FK to roles.id
is_active BOOLEAN DEFAULT 1,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
last_login_at TEXT,
FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE RESTRICT
);
CREATE INDEX idx_users_username ON users(username);
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_role_id ON users(role_id);
CREATE INDEX idx_users_is_active ON users(is_active);
```
**Default Users**:
- Username: `admin`, Password: `admin123` (created on first run)
---
#### roles
User roles.
```sql
CREATE TABLE roles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE COLLATE NOCASE,
description TEXT,
priority INTEGER DEFAULT 0, -- Higher = more powerful
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now'))
);
```
**Default Roles**:
- `admin` (id=1, priority=100): Full access
- `user` (id=2, priority=50): Standard access
- `guest` (id=3, priority=0): Read-only access
---
#### permissions
Available permissions.
```sql
CREATE TABLE permissions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE, -- e.g., "games.read"
description TEXT,
resource TEXT NOT NULL, -- e.g., "games"
action TEXT NOT NULL, -- e.g., "read"
created_at TEXT DEFAULT (datetime('now'))
);
CREATE INDEX idx_permissions_resource_action ON permissions(resource, action);
```
**Default Permissions**:
- `games.read`, `games.play`, `games.download`
- `playlists.read`, `playlists.create`, `playlists.update`, `playlists.delete`
- `users.read`, `users.create`, `users.update`, `users.delete`
- `roles.read`, `roles.create`, `roles.update`, `roles.delete`
- `settings.read`, `settings.update`
- `activities.read`
---
#### role_permissions
Role-permission mapping.
```sql
CREATE TABLE role_permissions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
role_id INTEGER NOT NULL,
permission_id INTEGER NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE,
FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE,
UNIQUE(role_id, permission_id)
);
CREATE INDEX idx_role_permissions_role ON role_permissions(role_id);
CREATE INDEX idx_role_permissions_permission ON role_permissions(permission_id);
```
---
#### refresh_tokens
JWT refresh tokens.
```sql
CREATE TABLE refresh_tokens (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
token TEXT NOT NULL UNIQUE,
expires_at TEXT NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
revoked_at TEXT, -- When token was revoked
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
CREATE INDEX idx_refresh_tokens_user_id ON refresh_tokens(user_id);
CREATE INDEX idx_refresh_tokens_token ON refresh_tokens(token);
CREATE INDEX idx_refresh_tokens_expires_at ON refresh_tokens(expires_at);
```
---
#### login_attempts
Failed login tracking for lockout.
```sql
CREATE TABLE login_attempts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL,
ip_address TEXT NOT NULL,
success BOOLEAN DEFAULT 0,
attempted_at TEXT DEFAULT (datetime('now'))
);
CREATE INDEX idx_login_attempts_username ON login_attempts(username);
CREATE INDEX idx_login_attempts_ip ON login_attempts(ip_address);
CREATE INDEX idx_login_attempts_attempted_at ON login_attempts(attempted_at);
```
---
#### system_settings
Global system-wide configuration using key-value storage.
```sql
CREATE TABLE system_settings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key TEXT NOT NULL UNIQUE, -- e.g., 'auth.guest_access_enabled'
value TEXT NOT NULL, -- Stored as string, parsed by data_type
data_type TEXT NOT NULL CHECK (data_type IN ('boolean', 'integer', 'string', 'json', 'float')),
category TEXT NOT NULL, -- auth, app, metadata, features, game, storage, rate_limit
description TEXT, -- Human-readable description
is_public BOOLEAN DEFAULT 0, -- Public settings readable without auth
default_value TEXT, -- Fallback if value is NULL
validation_schema TEXT, -- JSON schema for runtime validation
updated_at TEXT DEFAULT (datetime('now')),
updated_by INTEGER, -- FK to users.id
FOREIGN KEY (updated_by) REFERENCES users(id) ON DELETE SET NULL
);
CREATE INDEX idx_system_settings_key ON system_settings(key);
CREATE INDEX idx_system_settings_category ON system_settings(category);
```
**Categories**: auth, app, metadata, features, game, storage, rate_limit
---
### Play Tracking Tables
#### user_game_plays
Individual play sessions.
```sql
CREATE TABLE user_game_plays (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
game_id TEXT NOT NULL, -- FK to game.id in Flashpoint DB
game_title TEXT,
started_at TEXT NOT NULL DEFAULT (datetime('now')),
ended_at TEXT, -- NULL = session still active
duration_seconds INTEGER, -- Session duration
session_id TEXT NOT NULL UNIQUE, -- Random session identifier
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
CREATE INDEX idx_user_game_plays_user_id ON user_game_plays(user_id);
CREATE INDEX idx_user_game_plays_game_id ON user_game_plays(game_id);
CREATE INDEX idx_user_game_plays_started_at ON user_game_plays(started_at);
CREATE INDEX idx_user_game_plays_session_id ON user_game_plays(session_id);
```
---
#### user_game_stats
Aggregated per-game statistics.
```sql
CREATE TABLE user_game_stats (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
game_id TEXT NOT NULL,
game_title TEXT,
total_plays INTEGER DEFAULT 0,
total_playtime_seconds INTEGER DEFAULT 0,
first_played_at TEXT,
last_played_at TEXT,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
UNIQUE(user_id, game_id)
);
CREATE INDEX idx_user_game_stats_user_id ON user_game_stats(user_id);
CREATE INDEX idx_user_game_stats_game_id ON user_game_stats(game_id);
CREATE INDEX idx_user_game_stats_total_plays ON user_game_stats(total_plays);
CREATE INDEX idx_user_game_stats_total_playtime ON user_game_stats(total_playtime_seconds);
```
---
#### user_stats
Overall user statistics.
```sql
CREATE TABLE user_stats (
user_id INTEGER PRIMARY KEY, -- One row per user
total_games_played INTEGER DEFAULT 0,
total_playtime_seconds INTEGER DEFAULT 0,
total_sessions INTEGER DEFAULT 0,
first_play_at TEXT,
last_play_at TEXT,
updated_at TEXT DEFAULT (datetime('now')),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
CREATE INDEX idx_user_stats_total_playtime ON user_stats(total_playtime_seconds);
```
---
### Activity Logging
#### activity_logs
User activity audit trail.
```sql
CREATE TABLE activity_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER, -- NULL for system actions
username TEXT,
action TEXT NOT NULL, -- e.g., "login", "create", "delete"
resource TEXT, -- e.g., "users", "games"
resource_id TEXT, -- ID of affected resource
details TEXT, -- JSON details
ip_address TEXT,
user_agent TEXT,
created_at TEXT DEFAULT (datetime('now')),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
);
CREATE INDEX idx_activity_logs_user_id ON activity_logs(user_id);
CREATE INDEX idx_activity_logs_action ON activity_logs(action);
CREATE INDEX idx_activity_logs_resource ON activity_logs(resource);
CREATE INDEX idx_activity_logs_created_at ON activity_logs(created_at);
```
**Retention**: Logs older than 90 days are automatically cleaned up.
---
### User Settings
#### user_settings
User preferences and settings.
```sql
CREATE TABLE user_settings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
setting_key TEXT NOT NULL, -- e.g., "theme_mode", "primary_color"
setting_value TEXT, -- Setting value (string)
updated_at TEXT DEFAULT (datetime('now')),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
UNIQUE(user_id, setting_key)
);
CREATE INDEX idx_user_settings_user_id ON user_settings(user_id);
CREATE INDEX idx_user_settings_key ON user_settings(setting_key);
```
**Common Settings**:
- `theme_mode`: "light" | "dark" | "system"
- `primary_color`: Color name (e.g., "blue", "green")
- `sidebar_collapsed`: "true" | "false"
---
## Data Relationships
### Cross-Database Relationships
The backend maintains referential integrity between databases:
```
user_game_plays.game_id -> game.id (Flashpoint DB)
user_game_stats.game_id -> game.id (Flashpoint DB)
```
These are soft foreign keys (not enforced by SQLite) and must be maintained by
application logic.
### Permission Hierarchy
```
User
└─ has Role (role_id)
└─ has Permissions (via role_permissions)
```
### Play Tracking Hierarchy
```
User
├─ user_stats (overall stats)
├─ user_game_stats (per-game aggregated)
└─ user_game_plays (individual sessions)
```
---
## Indexes
All foreign keys and frequently queried columns are indexed for performance:
- User lookups: `username`, `email`, `role_id`
- Play tracking: `user_id`, `game_id`, `started_at`
- Activity logs: `user_id`, `action`, `resource`, `created_at`
- Authentication: `token`, `expires_at`
---
## Data Types
SQLite TEXT fields store:
- **Dates/Times**: ISO 8601 format (`YYYY-MM-DDTHH:MM:SS.sssZ`)
- **Booleans**: 0 (false) or 1 (true)
- **JSON**: Stringified JSON objects in `details` columns
---
## Migration History
See [migrations.md](./migrations.md) for migration system documentation.
Applied migrations:
1. `001_user-schema.sql` - Initial user database schema
2. `002_create-user-settings.sql` - Add user_settings table
3. `003_drop-legacy-theme-columns.sql` - Remove old theme columns from users
table
---
## Query Examples
### Get user with role and permissions
```sql
SELECT u.*, r.name as role_name,
GROUP_CONCAT(p.name) as permissions
FROM users u
JOIN roles r ON u.role_id = r.id
LEFT JOIN role_permissions rp ON r.id = rp.role_id
LEFT JOIN permissions p ON rp.permission_id = p.id
WHERE u.id = ?
GROUP BY u.id;
```
### Get user play statistics
```sql
SELECT
COUNT(DISTINCT game_id) as games_played,
SUM(duration_seconds) as total_playtime,
COUNT(*) as total_sessions
FROM user_game_plays
WHERE user_id = ? AND ended_at IS NOT NULL;
```
### Get top games by playtime
```sql
SELECT game_id, game_title,
total_plays, total_playtime_seconds
FROM user_game_stats
WHERE user_id = ?
ORDER BY total_playtime_seconds DESC
LIMIT 10;
```
---
## Related Documentation
- [UserDatabaseService](../services/user-database-service.md) - Database access
service
- [DatabaseService](../services/database-service.md) - Flashpoint database
service
- [Migrations](./migrations.md) - Migration system
- [Data Models](./data-models.md) - TypeScript types