Skip to content

Commit 09159cb

Browse files
committed
added documentation
1 parent af71723 commit 09159cb

File tree

3 files changed

+1225
-0
lines changed

3 files changed

+1225
-0
lines changed

docs/database-schema.md

Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
# Database Schema Documentation
2+
3+
## Overview
4+
5+
Backlogia uses SQLite as its database engine. The database consolidates game libraries from multiple stores (Steam, Epic, GOG, itch.io, Humble Bundle, Battle.net, EA, Amazon Games, Xbox, and local folders) into a centralized location.
6+
7+
**Database Path**: Configured via `DATABASE_PATH` in `config.py`
8+
9+
## Tables
10+
11+
### 1. games
12+
13+
The main table storing all games from all sources.
14+
15+
| Column | Type | Nullable | Description |
16+
|--------|------|----------|-------------|
17+
| `id` | INTEGER | No | Primary key, auto-incremented |
18+
| `name` | TEXT | No | Game title |
19+
| `store` | TEXT | No | Source store (steam, epic, gog, itch, humble, battlenet, ea, amazon, xbox, local, ubisoft) |
20+
| `store_id` | TEXT | Yes | Unique identifier from the source store |
21+
| `description` | TEXT | Yes | Game description/summary |
22+
| `developers` | TEXT | Yes | JSON array of developer names |
23+
| `publishers` | TEXT | Yes | JSON array of publisher names |
24+
| `genres` | TEXT | Yes | JSON array of genre/theme tags |
25+
| `cover_image` | TEXT | Yes | URL or path to cover/box art image |
26+
| `background_image` | TEXT | Yes | URL or path to background/hero image |
27+
| `icon` | TEXT | Yes | URL or path to icon/logo image |
28+
| `supported_platforms` | TEXT | Yes | JSON array of platform names (Windows, Mac, Linux, Android, etc.) |
29+
| `release_date` | TEXT | Yes | Release date in ISO format or timestamp |
30+
| `created_date` | TEXT | Yes | Creation date from store |
31+
| `last_modified` | TEXT | Yes | Last modification date from store |
32+
| `playtime_hours` | REAL | Yes | Total hours played (Steam only) |
33+
| `critics_score` | REAL | Yes | Critic/user score from store (0-100 scale) |
34+
| `average_rating` | REAL | Yes | Computed average across all available ratings (0-100 scale) |
35+
| `can_run_offline` | BOOLEAN | Yes | Whether game can run without internet connection |
36+
| `dlcs` | TEXT | Yes | JSON array of DLC information |
37+
| `extra_data` | TEXT | Yes | JSON object for store-specific additional data |
38+
| `added_at` | TIMESTAMP | No | When the game was first added to database (default: current timestamp) |
39+
| `updated_at` | TIMESTAMP | No | When the game was last updated (default: current timestamp) |
40+
| `hidden` | BOOLEAN | Yes | User flag to hide game from main views (default: 0) |
41+
| `nsfw` | BOOLEAN | Yes | User flag to mark game as NSFW (default: 0) |
42+
| `cover_url_override` | TEXT | Yes | User-specified cover image URL override |
43+
| `igdb_id` | TEXT | Yes | IGDB identifier for the game |
44+
| `igdb_rating` | REAL | Yes | IGDB rating (0-100 scale) |
45+
| `aggregated_rating` | REAL | Yes | IGDB aggregated rating (0-100 scale) |
46+
| `total_rating` | REAL | Yes | IGDB total rating (0-100 scale) |
47+
| `metacritic_score` | REAL | Yes | Metacritic critic score (0-100 scale) |
48+
| `metacritic_user_score` | REAL | Yes | Metacritic user score (0-10 scale) |
49+
| `metacritic_url` | TEXT | Yes | URL to Metacritic page |
50+
| `protondb_tier` | TEXT | Yes | ProtonDB compatibility tier (platinum, gold, silver, bronze, borked) |
51+
| `protondb_score` | REAL | Yes | ProtonDB score (0-100 scale) |
52+
| `ubisoft_id` | TEXT | Yes | Ubisoft Connect game identifier |
53+
54+
**Indexes:**
55+
- `idx_games_store` on `store`
56+
- `idx_games_name` on `name`
57+
58+
**Unique Constraint:** `(store, store_id)` - ensures no duplicate games per store
59+
60+
#### Average Rating Calculation
61+
62+
The `average_rating` column is computed from all available rating sources:
63+
- `critics_score` (Steam reviews, 0-100)
64+
- `igdb_rating` (IGDB rating, 0-100)
65+
- `aggregated_rating` (IGDB aggregated, 0-100)
66+
- `total_rating` (IGDB total, 0-100)
67+
- `metacritic_score` (Metacritic critics, 0-100)
68+
- `metacritic_user_score` (Metacritic users, normalized from 0-10 to 0-100)
69+
70+
All ratings are normalized to a 0-100 scale, then averaged. Returns `None` if no ratings are available.
71+
72+
### 2. collections
73+
74+
User-created game collections for organizing games.
75+
76+
| Column | Type | Nullable | Description |
77+
|--------|------|----------|-------------|
78+
| `id` | INTEGER | No | Primary key, auto-incremented |
79+
| `name` | TEXT | No | Collection name |
80+
| `description` | TEXT | Yes | Collection description |
81+
| `created_at` | TIMESTAMP | No | When the collection was created (default: current timestamp) |
82+
| `updated_at` | TIMESTAMP | No | When the collection was last modified (default: current timestamp) |
83+
84+
### 3. collection_games
85+
86+
Junction table linking games to collections (many-to-many relationship).
87+
88+
| Column | Type | Nullable | Description |
89+
|--------|------|----------|-------------|
90+
| `collection_id` | INTEGER | No | Foreign key to collections.id (CASCADE on delete) |
91+
| `game_id` | INTEGER | No | Foreign key to games.id (CASCADE on delete) |
92+
| `added_at` | TIMESTAMP | No | When the game was added to collection (default: current timestamp) |
93+
94+
**Primary Key:** `(collection_id, game_id)`
95+
96+
**Foreign Keys:**
97+
- `collection_id``collections(id)` ON DELETE CASCADE
98+
- `game_id``games(id)` ON DELETE CASCADE
99+
100+
### 4. settings
101+
102+
Application settings storage (key-value pairs).
103+
104+
| Column | Type | Nullable | Description |
105+
|--------|------|----------|-------------|
106+
| `key` | TEXT | No | Setting key (primary key) |
107+
| `value` | TEXT | Yes | Setting value (stored as text, JSON for complex values) |
108+
| `updated_at` | TIMESTAMP | No | When the setting was last updated (default: current timestamp) |
109+
110+
## Store-Specific Data
111+
112+
### Steam
113+
- `store_id`: Steam AppID
114+
- `cover_image`: `https://cdn.cloudflare.steamstatic.com/steam/apps/{appid}/library_600x900_2x.jpg`
115+
- `background_image`: `https://cdn.cloudflare.steamstatic.com/steam/apps/{appid}/library_hero.jpg`
116+
- `playtime_hours`: Total playtime
117+
- `critics_score`: User review score (percentage)
118+
119+
### Epic Games Store
120+
- `store_id`: Epic app_name
121+
- `can_run_offline`: Offline capability
122+
- `dlcs`: List of DLCs
123+
124+
### GOG
125+
- `store_id`: GOG product_id
126+
- `genres`: Combined genres and themes (deduplicated, case-insensitive)
127+
- `release_date`: Unix timestamp converted to ISO format
128+
129+
### itch.io
130+
- `store_id`: itch.io game ID
131+
- `supported_platforms`: Built from platform flags (windows, mac, linux, android)
132+
133+
### Humble Bundle
134+
- `store_id`: Humble machine_name
135+
- `publishers`: Contains payee information
136+
137+
### Battle.net
138+
- `store_id`: Blizzard title_id
139+
- `extra_data`: Contains raw Battle.net data
140+
141+
### EA
142+
- `store_id`: EA offer_id
143+
144+
### Amazon Games
145+
- `store_id`: Amazon product_id
146+
147+
### Xbox
148+
- `store_id`: Xbox store ID
149+
- `extra_data`: Contains:
150+
- `is_streaming`: Whether it's a cloud streaming game
151+
- `acquisition_type`: How the game was acquired
152+
- `title_id`: Xbox title ID
153+
- `pfn`: Package family name
154+
155+
### Local
156+
- `store_id`: Generated from folder path
157+
- `extra_data`: Contains:
158+
- `folder_path`: Path to game folder
159+
- `manual_igdb_id`: User-specified IGDB ID for metadata matching
160+
161+
### Ubisoft Connect
162+
- `store_id`: Ubisoft game ID
163+
- `ubisoft_id`: Alternative Ubisoft identifier
164+
165+
## Database Connection
166+
167+
The `database.py` module provides:
168+
- `get_db()`: Returns a connection with `row_factory = sqlite3.Row` for dict-like access
169+
170+
## Migration Functions
171+
172+
The following functions handle database schema migrations:
173+
174+
- `ensure_extra_columns()`: Adds `hidden`, `nsfw`, and `cover_url_override` columns
175+
- `ensure_collections_tables()`: Creates `collections` and `collection_games` tables
176+
- `add_average_rating_column()`: Adds `average_rating` column
177+
178+
## Import Pipeline
179+
180+
The `database_builder.py` module contains functions to import games from each store:
181+
182+
1. `create_database()`: Initialize all tables and indexes
183+
2. `import_steam_games(conn)`
184+
3. `import_epic_games(conn)`
185+
4. `import_gog_games(conn)`
186+
5. `import_itch_games(conn)`
187+
6. `import_humble_games(conn)`
188+
7. `import_battlenet_games(conn)`
189+
8. `import_ea_games(conn)`
190+
9. `import_amazon_games(conn)`
191+
10. `import_xbox_games(conn)`
192+
11. `import_local_games(conn)`
193+
194+
Each import function:
195+
- Returns the count of imported games
196+
- Uses `ON CONFLICT(store, store_id) DO UPDATE` to handle duplicates
197+
- Updates the `updated_at` timestamp
198+
- Prints progress messages with `[OK]` style indicators
199+
200+
## Utility Functions
201+
202+
### Rating Management
203+
204+
```python
205+
calculate_average_rating(
206+
critics_score=None,
207+
igdb_rating=None,
208+
aggregated_rating=None,
209+
total_rating=None,
210+
metacritic_score=None,
211+
metacritic_user_score=None
212+
) -> float | None
213+
```
214+
215+
Computes average rating from available sources (0-100 scale).
216+
217+
```python
218+
update_average_rating(conn, game_id) -> float | None
219+
```
220+
221+
Updates the `average_rating` for a specific game by fetching all rating fields and computing the average.
222+
223+
### Statistics
224+
225+
```python
226+
get_stats(conn) -> dict
227+
```
228+
229+
Returns:
230+
```json
231+
{
232+
"total": 1234,
233+
"by_store": {
234+
"steam": 500,
235+
"epic": 200,
236+
"gog": 300,
237+
...
238+
}
239+
}
240+
```
241+
242+
## JSON Fields
243+
244+
Several columns store JSON arrays or objects as TEXT:
245+
246+
- `developers`: `["Studio A", "Studio B"]`
247+
- `publishers`: `["Publisher A"]`
248+
- `genres`: `["Action", "RPG", "Adventure"]`
249+
- `supported_platforms`: `["Windows", "Linux"]`
250+
- `dlcs`: Array of DLC objects
251+
- `extra_data`: Store-specific additional information
252+
253+
Always use `json.loads()` and `json.dumps()` when reading/writing these fields.
254+
255+
## Best Practices
256+
257+
1. **Always use parameterized queries** to prevent SQL injection
258+
2. **Commit after batch operations** for performance
259+
3. **Handle exceptions per-game** during imports to avoid losing entire batch
260+
4. **Update `updated_at`** whenever modifying game records
261+
5. **Call `update_average_rating()`** after updating any rating field
262+
6. **Use `get_db()`** for row factory access to treat rows as dictionaries
263+
7. **Run migration functions** (`ensure_extra_columns()`, `ensure_collections_tables()`) on startup
264+
265+
## Error Handling
266+
267+
Import functions print errors but continue processing:
268+
```python
269+
try:
270+
# import game
271+
except Exception as e:
272+
print(f" Error importing {game.get('name')}: {e}")
273+
```
274+
275+
This ensures one failing game doesn't block the entire import process.
276+
277+
## Example Queries
278+
279+
### Get all games from a specific store
280+
```python
281+
cursor.execute("SELECT * FROM games WHERE store = ?", ("steam",))
282+
```
283+
284+
### Get games with ratings above 80
285+
```python
286+
cursor.execute("SELECT * FROM games WHERE average_rating >= 80 ORDER BY average_rating DESC")
287+
```
288+
289+
### Get games in a collection
290+
```python
291+
cursor.execute("""
292+
SELECT g.* FROM games g
293+
JOIN collection_games cg ON g.id = cg.game_id
294+
WHERE cg.collection_id = ?
295+
""", (collection_id,))
296+
```
297+
298+
### Search games by name
299+
```python
300+
cursor.execute("SELECT * FROM games WHERE name LIKE ? ORDER BY name", (f"%{search_term}%",))
301+
```
302+
303+
### Get hidden/NSFW games
304+
```python
305+
cursor.execute("SELECT * FROM games WHERE hidden = 1")
306+
cursor.execute("SELECT * FROM games WHERE nsfw = 1")
307+
```

0 commit comments

Comments
 (0)