chore: update documents

This commit is contained in:
Darkraise
2026-02-15 11:50:39 +07:00
parent 4594a66721
commit c270e42d87
19 changed files with 323 additions and 252 deletions
@@ -99,7 +99,6 @@ export class StatisticsService {
// Continue with 0 count
}
// Statistics will be returned
const statistics: Statistics = {
totalGames,
totalAnimations,
-10
View File
@@ -2,9 +2,7 @@
* Authentication and Authorization Types
*/
// ===================================
// User Types
// ===================================
export interface User {
id: number;
@@ -44,9 +42,7 @@ export interface UpdateUserData {
isActive?: boolean;
}
// ===================================
// Authentication Types
// ===================================
export interface LoginCredentials {
username: string;
@@ -65,9 +61,7 @@ export interface AuthTokens {
expiresIn: number;
}
// ===================================
// Role and Permission Types
// ===================================
export interface Permission {
id: number;
@@ -86,9 +80,7 @@ export interface Role {
updatedAt: string;
}
// ===================================
// Activity Log Types
// ===================================
export interface ActivityLog {
id: number;
@@ -114,9 +106,7 @@ export interface LogActivityData {
userAgent?: string;
}
// ===================================
// Auth Settings Types
// ===================================
export interface AuthSettings {
guestAccessEnabled: boolean;
+22 -21
View File
@@ -11,9 +11,9 @@ Flashpoint Web uses JWT-based authentication with role-based access control
graph TB
subgraph "Frontend"
LoginForm[Login Form]
AuthStore[Zustand Auth Store<br/>Access Token Only]
AuthStore[Zustand Auth Store<br/>User Info Only]
APIClient[Axios API Client]
Cookies[HTTP-only Cookies<br/>fp_refresh]
Cookies[HTTP-only Cookies<br/>fp_access + fp_refresh]
end
subgraph "Backend"
@@ -30,15 +30,14 @@ graph TB
end
LoginForm --> AuthStore
AuthRoute -.Set Cookie.-> Cookies
AuthRoute -.accessToken.-> APIClient
AuthRoute -.Set Cookies.-> Cookies
AuthStore --> APIClient
APIClient --> AuthRoute
AuthRoute --> AuthService
AuthService --> UserDB
UserDB -.return user.-> AuthService
AuthService -.JWT tokens.-> AuthRoute
AuthRoute -.accessToken only.-> APIClient
AuthRoute -.user info + expiresIn.-> APIClient
APIClient --> AuthStore
APIClient -.JWT Bearer.-> GameRoutes
@@ -89,9 +88,9 @@ sequenceDiagram
AuthService->>AuthService: Generate JWT tokens
AuthService-->>Backend: {user, accessToken, refreshToken}
Backend->>Backend: Set fp_refresh HTTP-only cookie
Backend-->>API: 201 Created (only accessToken in body)
API->>AuthStore: setAuth(user, accessToken)
Backend->>Backend: Set fp_access + fp_refresh HTTP-only cookies
Backend-->>API: 201 Created (user info + expiresIn only)
API->>AuthStore: setAuth(user)
AuthStore-->>RegisterForm: Success
RegisterForm->>User: Redirect to dashboard
end
@@ -142,13 +141,13 @@ sequenceDiagram
API-->>LoginForm: Show error
else Valid credentials
AuthService->>UserDB: SELECT user permissions
AuthService->>AuthService: Generate JWT access token (15m expiry)
AuthService->>AuthService: Generate JWT access token (1h expiry, configurable)
AuthService->>AuthService: Generate JWT refresh token (30d expiry)
AuthService-->>Backend: {user, accessToken, refreshToken}
Backend->>Backend: Set fp_refresh HTTP-only cookie
Backend-->>API: 200 OK (only accessToken in body)
Backend->>Backend: Set fp_access + fp_refresh HTTP-only cookies
Backend-->>API: 200 OK (user info + expiresIn only)
API->>AuthStore: setAuth(user, accessToken)
API->>AuthStore: setAuth(user)
AuthStore->>ThemeStore: loadThemeFromServer()
AuthStore-->>LoginForm: Success
LoginForm->>User: Redirect to /browse
@@ -158,7 +157,7 @@ sequenceDiagram
**JWT Token Structure**:
```typescript
// Access Token
// Access Token (stored in HTTP-only cookie fp_access, path: /api)
{
userId: 1,
username: "user",
@@ -166,10 +165,10 @@ sequenceDiagram
role: "user",
permissions: ["games.read", "games.play"],
iat: 1705579200,
exp: 1705580100 // 15 minutes
exp: 1705582800 // 1 hour (configurable via JWT_EXPIRES_IN)
}
// Refresh Token (stored in HTTP-only cookie)
// Refresh Token (stored in HTTP-only cookie fp_refresh, path: /api/auth)
{
userId: 1,
type: "refresh",
@@ -473,7 +472,8 @@ export const ProtectedRoute = ({ children, requiredPermission }) => {
## 7. Guest Mode
When `auth.guest_access_enabled` is true in system settings, unauthenticated
users can browse games with limited permissions.
users can browse games with limited permissions. **Note:** Guests cannot play
games — only authenticated users have the `games.play` permission.
```typescript
// Frontend
@@ -481,7 +481,7 @@ const guestUser: User = {
id: 0,
username: 'Guest',
role: 'guest',
permissions: ['games.read', 'games.play'],
permissions: ['games.read', 'playlists.read'], // No games.play
};
// Backend
@@ -501,7 +501,7 @@ export const optionalAuth = async (req, res, next) => {
id: 0,
username: 'guest',
role: 'guest',
permissions: ['games.read'],
permissions: ['games.read', 'playlists.read'], // Read-only access
};
}
@@ -511,12 +511,13 @@ export const optionalAuth = async (req, res, next) => {
## Security Measures
**Password Security**: Bcrypt with 10 rounds
**Password Security**: Bcrypt with 10 rounds (configurable via `BCRYPT_SALT_ROUNDS`)
**JWT Security**:
- Short-lived access tokens (15 minutes) - held in memory only
- Long-lived refresh tokens (30 days) - stored in HTTP-only cookies
- Access tokens (1 hour default, configurable via `JWT_EXPIRES_IN`) - stored in HTTP-only cookie `fp_access` with path `/api`
- Long-lived refresh tokens (30 days) - stored in HTTP-only cookie `fp_refresh` with path `/api/auth`
- Both tokens in HTTP-only cookies prevents XSS token theft
- Refresh tokens are rotated on each use to prevent token reuse attacks
- Signed with random 256-bit JWT_SECRET
- Verify algorithm: HS256
+6 -30
View File
@@ -13,20 +13,14 @@ import dotenv from 'dotenv';
dotenv.config();
export const config = {
// Server configuration
// Server configuration (hardcoded)
nodeEnv: process.env.NODE_ENV || 'development',
port: parseInt(process.env.PORT || '3100', 10),
host: process.env.HOST || '0.0.0.0',
port: 3100, // Hardcoded, not configurable
host: '0.0.0.0', // Hardcoded, not configurable
// Flashpoint paths (only FLASHPOINT_PATH is needed)
flashpointPath: process.env.FLASHPOINT_PATH || 'D:/Flashpoint',
// All other paths are derived automatically:
// - flashpointDbPath: ${flashpointPath}/Data/flashpoint.sqlite
// - htdocsPath: ${flashpointPath}/Legacy/htdocs
// - imagesPath: ${flashpointPath}/Data/Images
// - logosPath: ${flashpointPath}/Data/Logos
// - playlistsPath: ${flashpointPath}/Data/Playlists
// - gamesPath: ${flashpointPath}/Data/Games
// All other paths are derived automatically
// ... more config
};
```
@@ -46,26 +40,8 @@ export const config = {
NODE_ENV=production
```
#### `PORT`
- **Type**: number
- **Default**: `3100`
- **Description**: HTTP server port
- **Usage**:
```bash
PORT=3100
```
#### `HOST`
- **Type**: string
- **Default**: `0.0.0.0`
- **Description**: Server bind address
- **Usage**:
```bash
HOST=0.0.0.0 # Listen on all interfaces
HOST=localhost # Listen on localhost only
```
**Note:** `PORT` and `HOST` are hardcoded to `3100` and `0.0.0.0` respectively
and cannot be changed via environment variables.
### Flashpoint Paths
+42 -11
View File
@@ -13,9 +13,14 @@ hot-reload.
- **Singleton pattern**: Single database connection per process
- **File watching**: Detects when Flashpoint Launcher updates the database
- **Hot-reload**: Automatically reconnects when database changes
- **Read/Write access**: Allows play statistics updates
- **Read-only access**: Flashpoint database is read-only (use UserDatabaseService
for writes)
- **Local copy support**: Optional local database copy for network storage
performance
- **Synchronous API**: Uses better-sqlite3 for better performance than async
wrappers
- **Cache invalidation**: Clears GameSearchCache and filter caches on database
reload
## Public API
@@ -42,24 +47,27 @@ const result = db.prepare('SELECT * FROM game WHERE id = ?').get(gameId);
---
### `get(sql: string, params: any[]): any | null`
### `get<T>(sql: string, params: unknown[]): T | undefined`
Execute a query and return the first result row.
```typescript
const game = DatabaseService.get('SELECT * FROM game WHERE id = ?', [gameId]);
const game = DatabaseService.get<GameRow>(
'SELECT * FROM game WHERE id = ?',
[gameId]
);
```
**Returns**: First result row or null if no results
**Returns**: First result row or undefined if no results
---
### `all(sql: string, params: any[]): any[]`
### `all<T>(sql: string, params: unknown[]): T[]`
Execute a query and return all result rows.
```typescript
const games = DatabaseService.all(
const games = DatabaseService.all<GameRow>(
'SELECT * FROM game WHERE platformName = ? LIMIT ?',
['Flash', 100]
);
@@ -69,19 +77,42 @@ const games = DatabaseService.all(
---
### `run(sql: string, params: any[]): void`
### `exec<T>(sql: string, params: unknown[]): T[]`
Execute an INSERT, UPDATE, or DELETE query.
Execute a query and return results as objects. Same as `all()` but with
different naming convention.
```typescript
DatabaseService.run(
'UPDATE game SET playtime = ?, playCounter = ? WHERE id = ?',
[3600, 5, gameId]
const results = DatabaseService.exec<{ id: number; name: string }>(
'SELECT id, name FROM game',
[]
);
```
---
### `getTableColumns(tableName: string): Set<string>`
Get column names for a table using PRAGMA table_info. Used for runtime schema
introspection.
```typescript
const columns = DatabaseService.getTableColumns('game');
// Returns Set {'id', 'title', 'developer', ...}
```
---
### `getStatus(): DatabaseStatus`
Get current database connection status.
```typescript
const status = DatabaseService.getStatus();
// { connected: true, sourcePath: '...', activePath: '...', usingLocalCopy: false, lastModified: Date }
---
### `isConnected(): boolean`
Check if database is connected.
@@ -138,6 +138,55 @@ const permissions = ['games.read', 'games.play'];
PermissionCache.setUserPermissions(5, permissions);
```
### getRolePermissions(roleId: number): string[] | null
Get role permissions from cache.
**Parameters:**
- `roleId` (number): Role ID to lookup
**Returns:**
- `string[]`: Array of permission strings if cache hit
- `null`: If cache miss or entry expired
**Example:**
```typescript
const cached = PermissionCache.getRolePermissions(2);
if (cached) {
// Use cached permissions
return cached;
} else {
// Query database
const permissions = await queryRolePermissions(2);
PermissionCache.setRolePermissions(2, permissions);
return permissions;
}
```
### setRolePermissions(roleId: number, permissions: string[]): void
Store role permissions in cache.
**Parameters:**
- `roleId` (number): Role ID
- `permissions` (string[]): Array of permission strings
**Side Effects:**
- Creates cache entry with 10-minute TTL
- Logs debug message to console
**Example:**
```typescript
const permissions = ['games.read', 'games.play', 'playlists.create'];
PermissionCache.setRolePermissions(2, permissions);
```
### invalidateUser(userId: number): void
Remove user's permission cache entry.
@@ -235,7 +284,41 @@ for (const user of usersWithRole) {
But for now, the simpler approach of clearing all user cache is more
reliable.
### invalidateAllUsers(): void
Clear all user permission cache entries.
**Use Cases:**
- Bulk user permission changes
- Role structure changes
- System-wide permission refresh
**Example:**
```typescript
// After bulk permission update
await permissionService.bulkUpdate(userIds);
PermissionCache.invalidateAllUsers();
```
### invalidateAllRoles(): void
Clear all role permission cache entries.
**Use Cases:**
- Bulk role permission changes
- Permission structure changes
- System-wide role refresh
**Example:**
```typescript
// After updating permission definitions
await permissionService.rebuildPermissions();
PermissionCache.invalidateAllRoles();
```
### clearAll(): void
@@ -255,6 +338,27 @@ PermissionCache.clearAll();
logger.info('All permission caches cleared');
```
### cleanup(): void
Remove expired cache entries from both user and role caches.
**Behavior:**
- Iterates through all cache entries
- Removes entries where `expiresAt < Date.now()`
- Logs count of removed entries if any were expired
- Called automatically by the cleanup interval
**Note:** This is called automatically every 5 minutes when `startCleanup()` has
been invoked. Manual calls are typically not needed.
**Example:**
```typescript
// Manual cleanup (rarely needed)
PermissionCache.cleanup();
```
### getStats(): CacheStats
Get cache statistics.
@@ -31,8 +31,9 @@ static async initialize(): Promise<void> {
// Open database connection
this.db = new BetterSqlite3(config.userDbPath);
// Enable foreign keys
// Enable foreign keys and WAL mode
this.db.pragma('foreign_keys = ON');
this.db.pragma('journal_mode = WAL');
// Create tables from schema file
await this.createTables();
@@ -40,11 +41,15 @@ static async initialize(): Promise<void> {
// Run migrations
await this.runMigrations();
// Create default admin user
await this.createDefaultAdmin();
// Check if initial setup is needed (first user becomes admin)
this.needsInitialSetup();
}
```
**Note:** UserDatabaseService uses WAL (Write-Ahead Logging) journal mode for
better concurrent access, while DatabaseService (flashpoint.sqlite) uses DELETE
mode.
### 2. Schema Management
Loads schema from SQL file:
+26 -1
View File
@@ -8,7 +8,32 @@ endpoints.
The API client is a centralized Axios instance with request/response
interceptors for authentication and error handling.
**Location:** `frontend/src/lib/api.ts`
**Location:** `frontend/src/lib/api/`
```
lib/api/
├── client.ts # Axios instance with interceptors
├── index.ts # Re-exports all API modules
├── activities.ts # Activity log endpoints
├── auth.ts # Authentication endpoints
├── domains.ts # Domain management
├── favorites.ts # User favorites
├── games.ts # Game search/details
├── github.ts # GitHub integration
├── jobs.ts # Background jobs
├── platforms.ts # Platform list
├── playTracking.ts # Play session tracking
├── playlists.ts # Playlist management
├── roles.ts # Role/permission management
├── ruffle.ts # Ruffle emulator settings
├── settings.ts # System settings
├── sharedPlaylists.ts # Shared playlist access
├── statistics.ts # User statistics
├── tags.ts # Tag list
├── updates.ts # Update checking
├── userPlaylists.ts # User playlist operations
└── users.ts # User management
```
## Client Configuration
@@ -9,21 +9,26 @@ Components are organized by domain and functionality:
```
components/
├── auth/ # Authentication components (4 components)
├── common/ # Shared/utility components (11 components)
├── activities/ # Activity log components (2 components)
├── auth/ # Authentication components (8 components)
├── common/ # Shared/utility components (14 components)
├── dialogs/ # Modal dialogs (1 component)
├── error/ # Error handling components (8 components)
├── game/ # Game-specific components (1 component)
├── layout/ # Layout structure (3 components)
├── library/ # Game browsing/display (9 components)
├── player/ # Game players (2 components)
├── playlist/ # Playlist management (3 components)
├── github/ # GitHub integration (1 component)
├── home/ # Home page components (1 component)
├── jobs/ # Job management (4 components)
├── layout/ # Layout structure (9 components)
├── library/ # Game browsing/display (10 components)
├── player/ # Game players (3 components)
├── playlist/ # Playlist management (10 components)
│ └── SharePlaylistDialog includes domain selector for admins
├── roles/ # Role management (2 components)
├── search/ # Search and filtering (2 components)
├── settings/ # Settings tabs including AppSettingsTab with Domain Settings
├── stats/ # Statistics/charts (4 components)
├── theme/ # Theme controls (2 components)
├── ui/ # Shadcn UI primitives (40+ components)
├── roles/ # Role management (3 components)
├── search/ # Search and filtering (5 components)
├── settings/ # Settings tabs (6 components)
├── stats/ # Statistics/charts (5 components)
├── theme/ # Theme controls (1 component)
├── ui/ # Shadcn UI primitives (36 components)
└── users/ # User management (3 components)
```
+9 -7
View File
@@ -5,16 +5,18 @@ frontend.
## Overview
The application uses 18+ custom hooks organized by functionality:
The application uses 30 custom hooks organized by functionality:
- **Data Fetching** - useGames, usePlaylists, useUsers, useRoles, useActivities
- **Authentication** - useAuth
- **Data Fetching** - useGames, usePlaylists, useUserPlaylists, useUsers,
useRoles, useActivities, useFavorites
- **Authentication** - useAuth, usePublicSettings
- **UI** - useDebounce, useSwipeGesture, useMountEffect, useToast,
useDateTimeFormat
- **Game Data** - useFilterOptions, useTags, usePlatforms, useStatistics,
useDownload
- **Play Tracking** - usePlayTracking
useDateTimeFormat, useMediaQuery, useFeatureFlags
- **Game Data** - useFilterOptions, useFilterDropdowns, useTags, usePlatforms,
useStatistics, useDownload, useMostPlayedGames, useRecentGames
- **Play Tracking** - usePlayTracking, useActivityFilters
- **Community** - useCommunityPlaylists
- **Shared Access** - useSharedAccessToken, useSharedPlaylistAccess, useDomains
## Data Fetching Hooks
+16 -123
View File
@@ -420,7 +420,7 @@ Return null if not found in any ZIP
**Purpose**: Read Flashpoint preferences.json and expose gameDataSources for
auto-downloading game ZIPs.
**Location**: `backend/src/game/services/PreferencesService.ts`
**Location**: `backend/src/services/PreferencesService.ts`
**Architecture**:
@@ -820,66 +820,6 @@ Future improvements:
- Local cache: Sequential writes
- Solution: SSD recommended, RAM disk for cache, bounded resource cleanup
## Code Quality & Security Improvements (Second Pass Review)
The following medium and low severity fixes were implemented to improve code
quality and maintainability:
### HTML Injection & Logging
- `htmlInjector.ts` now uses project logger instead of `console.log`
- Dead code branch removed from polyfill injection logic
- Improves consistency with logging across the module
### PreferencesService Initialization
- Logs warning on re-initialization with different path
- Prevents silent configuration changes that could affect game downloads
- Helps identify configuration issues during development
### Schema Validation
- Hostname schema minimum length corrected to 2 (matches regex requirement)
- Enforces consistency between validation rules and runtime expectations
### Regex Pattern Fixes
- `pathSecurity.ts` regex patterns no longer use `/g` flag with `test()`
- Prevents stateful matching bugs that could skip validation on consecutive calls
- Single-flag patterns are checked per call, not maintained across calls
### Code Cleanup
- Unused exports removed from:
- `mimeTypes.ts` - Cleaning up unused MIME type exports
- `validation/schemas.ts` - Removing unused Zod schema exports
- `utils/cors.ts` - Removing unused utility functions
- `utils/pathSecurity.ts` - Removing unused security utilities
- Reduces module surface area and maintenance burden
### Download Validation
- `GameDataDownloader.getFilename()` now validates timestamps
- Rejects NaN timestamps to prevent corrupted filenames
- Ensures valid download filename format
### Resource Leak Prevention
- `GameDataDownloader` destroys response stream on size limit
- Prevents resource leaks when downloads exceed maximum size
- Proper cleanup when download constraints are violated
### DRY Principle
- `GameDataService` uses `GameDataDownloader.getFilename()` instead of duplicating logic
- Centralizes filename generation, preventing consistency issues
### Additional Low-Priority Improvements (2026-02-07)
**Error Handling & Streaming Safety**:
- `proxy-request-handler` `sendError()` now guards against `headersSent` to prevent write-after-headers errors in streaming scenarios
- `collectRequestBody` now uses `settled` flag to prevent double resolve/reject when both `end` and `error` events fire
**Visibility & Encapsulation**:
- `findActiveDownload` in `gamezipserver.ts` is now private (was publicly accessible but only used internally)
- `PRIVATE_IP_RANGES` in `GameDataDownloader.ts` is now `static readonly`
**Module Boundaries**:
- Removed unused imports and convenience re-exports from service index files for cleaner module boundaries
- Internal-only constants like `CUSTOM_MIME_TYPES` no longer exported, reducing public API surface
## Security Hardening
The game content module implements multiple defense-in-depth security measures:
@@ -1057,82 +997,35 @@ private sendError(res: Response, status: number, message: string) {
## Security Architecture
### Request Body Size Limits
**DoS Protection**: The GameZip server limits request body sizes to prevent
memory exhaustion attacks:
```typescript
// gamezipserver.ts
private readBody(req: http.IncomingMessage, maxSize: number = 1024 * 1024): Promise<string> {
return new Promise((resolve, reject) => {
let body = '';
req.on('data', chunk => {
body += chunk.toString();
if (body.length > maxSize) {
req.destroy(); // Immediately terminate connection
reject(new Error('Request body too large'));
}
});
req.on('end', () => resolve(body));
req.on('error', reject);
});
}
```
**Protection**:
- 1MB maximum request size by default
- Connection destroyed immediately on overflow
- Prevents memory exhaustion attacks
- Configurable per endpoint if needed
### Path Traversal Prevention
All file paths are normalized:
**ZIP Mount ID Validation**: Uses Zod schema to validate game IDs:
```typescript
path.normalize(filePath).toLowerCase();
// validation/schemas.ts
const mountGameSchema = z.object({
gameId: z
.string()
.min(1)
.max(255)
.regex(/^[a-zA-Z0-9\-_]+$/), // Only alphanumeric, hyphens, underscores
});
```
**ZIP Mount ID Validation**:
**ZIP Path Validation**: Resolved paths must stay within the allowed games
directory:
```typescript
// Reject IDs with path traversal sequences
if (
id.includes('/') ||
id.includes('\\') ||
id.includes('..') ||
id.includes('\0')
) {
this.sendError(res, 400, 'Invalid mount ID');
return;
}
```
**ZIP Path Validation**:
```typescript
// Ensure ZIP files are within allowed directory
const resolvedZipPath = path.resolve(normalizedZipPath);
const resolvedGamesPath = path.resolve(allowedGamesPath);
if (!resolvedZipPath.startsWith(resolvedGamesPath)) {
this.sendError(
res,
403,
'Forbidden: ZIP file must be within games directory'
);
return;
if (!resolvedZipPath.startsWith(resolvedGamesPath + path.sep)) {
return { success: false, statusCode: 403 };
}
```
Prevents attacks like:
- `../../../etc/passwd`
- `..\\..\\windows\\system32`
- Null byte injection (`file.txt\0.jpg`)
- Directory escape via mount IDs
Prevents attacks like path traversal (`../../../etc/passwd`), backslash
escapes, and directory escape via mount IDs.
### ZIP Access Control
+26 -13
View File
@@ -38,13 +38,14 @@ Instead of HTTP endpoints, GameZip Server provides direct method calls:
```typescript
// Called from backend
await gameZipServer.mountZip(id: string, params: MountParams)
const result = await gameZipServer.mountZip(params: MountParams)
```
Parameters:
```typescript
interface MountParams {
id: string; // Required: Mount ID (typically game UUID)
zipPath: string; // Required: Absolute path to ZIP file
gameId?: string; // Optional: Game UUID (enables auto-download)
dateAdded?: string; // Optional: ISO date (used for filename)
@@ -54,20 +55,32 @@ interface MountParams {
**Response:**
Throws on error, returns void on success.
```typescript
interface MountResult {
success: boolean; // True if mounted or download started
downloading?: boolean; // True if download in progress (HTTP 202)
statusCode: number; // HTTP status code (200, 202, 400, 403, 404, 500, 503)
}
```
```typescript
// Example usage
try {
await gameZipServer.mountZip('game-abc123', {
zipPath: 'D:/Flashpoint/Data/Games/Flash/G/game.zip',
gameId: 'game-abc123',
dateAdded: '2024-01-15T10:30:00.000Z',
sha256: 'ABC123...'
});
console.log('ZIP mounted successfully');
} catch (error) {
console.error('Mount failed:', error.message);
const result = await gameZipServer.mountZip({
id: 'game-abc123',
zipPath: 'D:/Flashpoint/Data/Games/Flash/G/game.zip',
gameId: 'game-abc123',
dateAdded: '2024-01-15T10:30:00.000Z',
sha256: 'ABC123...'
});
if (result.success) {
if (result.downloading) {
console.log('Download in progress, poll for completion');
} else {
console.log('ZIP mounted successfully');
}
} else {
console.error(`Mount failed with status ${result.statusCode}`);
}
```
@@ -290,7 +303,7 @@ Connection: keep-alive
```http
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS
Access-Control-Allow-Headers: *
```
+1 -1
View File
@@ -226,7 +226,7 @@ private sendError(res: ServerResponse, statusCode: number, message: string) {
// CRITICAL: Set CORS headers even for errors
if (settings.allowCrossDomain) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', '*');
}
+5 -4
View File
@@ -8,10 +8,11 @@ Search, browse, and retrieve game information.
Query params: `search` (title/developer/publisher), `platform`, `series`,
`developers`, `publishers`, `playModes`, `languages`, `library`
(arcade|theatre), `tags`, `yearFrom`, `yearTo`, `sortBy`
(title|releaseDate|dateAdded|developer, default: title), `sortOrder` (asc|desc,
default: asc), `page` (default: 1), `limit` (default: 50, max: 100),
`showBroken` (default: false), `showExtreme` (default: false)
(arcade|theatre), `tags`, `yearFrom`, `yearTo`, `dateAddedSince` (ISO datetime),
`dateModifiedSince` (ISO datetime), `sortBy`
(title|releaseDate|dateAdded|dateModified|developer, default: title),
`sortOrder` (asc|desc, default: asc), `page` (default: 1), `limit` (default: 50,
max: 100), `showBroken` (default: false), `showExtreme` (default: false)
Returns paginated array with essential game fields (id, title, developer,
platformName, library, tags, orderTitle).
+4 -4
View File
@@ -20,7 +20,7 @@ Error: `404 Not Found`
## Create Playlist
`POST /api/playlists` - Optional auth
`POST /api/playlists` - Requires auth + `playlists.create` permission
Body:
`{ "title": "string (required)", "description": "string (optional)", "author": "string (optional)", "icon": "string (optional)", "library": "arcade"|"theatre" (optional), "extreme": boolean (optional) }`
@@ -29,7 +29,7 @@ Returns `201 Created` with new playlist (empty games array).
## Add Games to Playlist
`POST /api/playlists/:id/games` - Optional auth
`POST /api/playlists/:id/games` - Requires auth + `playlists.update` permission
Body: `{ "gameIds": ["uuid1", "uuid2", ...] }`
@@ -39,7 +39,7 @@ Error: `404 Not Found` if playlist not found
## Remove Games from Playlist
`DELETE /api/playlists/:id/games` - Optional auth
`DELETE /api/playlists/:id/games` - Requires auth + `playlists.update` permission
Body: `{ "gameIds": ["uuid1", "uuid2", ...] }`
@@ -47,7 +47,7 @@ Returns updated playlist with remaining games.
## Delete Playlist
`DELETE /api/playlists/:id` - Optional auth
`DELETE /api/playlists/:id` - Requires auth + `playlists.delete` permission
Returns `{ "success": true }`
+3 -1
View File
@@ -7,10 +7,12 @@ Manage system-wide configuration settings.
## Get All Settings
`GET /api/settings` - Requires `settings.read` permission
`GET /api/settings` - Requires `settings.update` permission
Returns grouped settings by category (auth, app, metadata, features, game).
**Note:** This endpoint requires `settings.update` (not `settings.read`) because it returns all settings including sensitive values.
## Get Category Settings
`GET /api/settings/:category` - Requires `settings.read` permission
+12 -6
View File
@@ -41,10 +41,16 @@ backend/
│ │ └── ... (other routes)
│ │
│ ├── game/ # Game service (merged from game-service/)
│ │ ├── handlers/ # Request handlers
│ │ ├── services/ # Game-related services
│ │ ├── utils/ # Game utilities
│ │ └── ... (game-specific code)
│ │ ├── gamezipserver.ts # ZIP mount/serve manager
│ │ ├── legacy-server.ts # Legacy content fallback chain
│ │ ├── zip-manager.ts # ZIP file handling with LRU cache
│ │ ├── proxy-request-handler.ts # Proxy request routing
│ │ ├── config.ts # Game service configuration
│ │ ├── mimeTypes.ts # MIME type mappings
│ │ ├── cgi/ # CGI script execution
│ │ ├── services/ # Game-related services
│ │ ├── utils/ # Game utilities (CORS, path security)
│ │ └── validation/ # Zod schemas for validation
│ │
│ ├── services/ # Business logic layer
│ │ ├── DatabaseService.ts # Flashpoint DB (read-only)
@@ -124,8 +130,8 @@ frontend/
│ ├── store/ # Zustand stores
│ │ ├── auth.ts # User & auth state
│ │ ├── theme.ts # Theme preferences
│ │ ├── ui.ts # UI state
│ │ └── preferences.ts # User preferences
│ │ ├── ui.ts # UI state (sidebar, etc.)
│ │ └── sharedAccess.ts # Shared playlist access tokens
│ │
│ ├── lib/ # Library code
│ │ ├── api.ts # Axios client (authenticated)
@@ -280,6 +280,22 @@ Aggregated play statistics per user per game.
**Indexes:** user_id, game_id, total_plays, total_playtime_seconds
### user_stats
Aggregate user statistics (overall totals across all games).
| Column | Type | Default | Description |
| -------------------- | ------- | ------- | ----------------------------- |
| user_id | INTEGER | - | Primary key, foreign key to users.id |
| total_games_played | INTEGER | 0 | Total unique games played |
| total_playtime_seconds | INTEGER | 0 | Total playtime across all games |
| total_sessions | INTEGER | 0 | Total play sessions |
| first_play_at | TEXT | NULL | First ever play timestamp |
| last_play_at | TEXT | NULL | Most recent play timestamp |
| updated_at | TEXT | now() | ISO 8601 |
**Foreign Key:** user_id references users(id) ON DELETE CASCADE
### user_settings
Flexible user preference storage (key-value).
+6 -4
View File
@@ -70,12 +70,14 @@ web application for browsing and playing games from the Flashpoint Archive.
- [HTML5 Player](04-frontend/player-implementation/html5-player.md) - HTML5
games
### 05. Game Service
### 05. Game Service (Integrated into Backend)
- [Game Service Overview](05-game-service/README.md) - Game service introduction
- [Architecture](05-game-service/architecture.md) - Dual-server design
- [Proxy Server](05-game-service/proxy-server.md) - HTTP proxy (port 22500)
- [GameZip Server](05-game-service/gamezip-server.md) - ZIP server (port 22501)
- [Architecture](05-game-service/architecture.md) - Integrated single-service
design
- [Proxy Server](05-game-service/proxy-server.md) - HTTP proxy (/game-proxy/\*)
- [GameZip Server](05-game-service/gamezip-server.md) - ZIP server
(/game-zip/\*)
- [Legacy Server](05-game-service/legacy-server.md) - Legacy content serving
- [ZIP Manager](05-game-service/zip-manager.md) - ZIP mounting
- [MIME Types](05-game-service/mime-types.md) - MIME type handling