feat(import): import many games with background matching, review and undo

This commit is contained in:
lobo committed 2026-09-29 10:20:54 +02:00
1 parent 3bd1eda0ec
commit 2a5fcc1c00
20 files changed
+1639 -24

No files matched your search

@@ -0,0 +1,51 @@
-- Imports of many games at once (item 23): a list or a CSV, matched with IGDB in the background, then saved as
-- games added by hand. A game an import added keeps its import_id, so the import can be undone.
-- The table is also the queue of the two workers (GameImportWorker):
-- QUEUED -> MATCHING -> READY (the user reviews) -> SAVING -> SAVED (-> UNDONE), or FAILED after 3 tries.
-- queued_at orders each queue. progress_at is set while a worker runs the import and after every line; NULL while
-- it waits. attempts counts the tries a watchdog or a restart stopped.
CREATE TABLE game_import (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL,
source VARCHAR(16) NOT NULL,
status VARCHAR(16) NOT NULL,
total INT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
queued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
progress_at TIMESTAMPTZ,
attempts INT NOT NULL DEFAULT 0,
-- While SAVING: NULL = adding the games, DETAILS = loading their time to beat and moods.
step VARCHAR(16),
saved_at TIMESTAMPTZ
);
CREATE INDEX game_import_user ON game_import (user_id);
CREATE INDEX game_import_queue ON game_import (status, queued_at);
-- One import in the matching queue per user; an import of 1000 lines takes minutes of the shared IGDB budget.
CREATE UNIQUE INDEX game_import_one_matching ON game_import (user_id) WHERE status IN ('QUEUED', 'MATCHING');
-- One row per line of the input. igdb_id: the game the user already picked in the search (source SEARCH).
-- matched_at NULL: not matched yet. The chosen_* columns hold the user's review, save_result what the save did.
CREATE TABLE game_import_row (
import_id BIGINT NOT NULL REFERENCES game_import (id) ON DELETE CASCADE,
line_no INT NOT NULL,
title VARCHAR(200),
platform VARCHAR(32),
playtime_minutes INT,
status VARCHAR(16),
igdb_id BIGINT,
matched_at TIMESTAMPTZ,
search_failed BOOLEAN NOT NULL DEFAULT FALSE,
exact BOOLEAN NOT NULL DEFAULT FALSE,
-- The IGDB hits, best first, kept whole (IgdbApiService.Game), so the save needs no IGDB call.
candidates JSONB NOT NULL DEFAULT '[]',
chosen_igdb_id BIGINT,
chosen_platform VARCHAR(32),
chosen_status VARCHAR(16),
chosen_minutes INT,
-- ADDED or SKIPPED (in the library already, or IGDB does not know the game).
save_result VARCHAR(8),
PRIMARY KEY (import_id, line_no)
);
ALTER TABLE user_game ADD COLUMN import_id BIGINT REFERENCES game_import (id) ON DELETE SET NULL;
CREATE INDEX user_game_import ON user_game (import_id) WHERE import_id IS NOT NULL;