Public Access
feat(library): add stats page data and pile trend
Log every status change with a trigger, so no code path can miss it. Store IGDB genres with the moods. Write a pile snapshot per user each night. The sync moves an untouched pile game with new playtime to PLAYING in one guarded update, so a status the user picks wins. Old finished games get their year from the ending achievement. GET /api/library/stats serves the groups; the public profile shows only the groups in the profile ticket.
This commit is contained in:
1 parent
b959542e23
commit
0fa2a37f47
48 files changed
+2069
-60
No files matched your search
@@ -0,0 +1,30 @@
|
||||
-- Every status a library game ever had, for "finished per year" and the Pile of Shame trend. Written by a trigger,
|
||||
-- so no code path (entity save, bulk update, native insert) can forget it. The past is not known: the log starts
|
||||
-- on the day of this migration.
|
||||
CREATE TABLE user_game_status_change (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL,
|
||||
appid BIGINT NOT NULL,
|
||||
-- NULL: the game came into the library with to_status.
|
||||
from_status VARCHAR(16),
|
||||
to_status VARCHAR(16) NOT NULL,
|
||||
-- user_game.status_set_by_user after the change: false for the sync and for Core's own FINISHED.
|
||||
by_user BOOLEAN NOT NULL,
|
||||
changed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
FOREIGN KEY (user_id, appid) REFERENCES user_game (user_id, appid) ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX idx_status_change_user_time ON user_game_status_change (user_id, changed_at);
|
||||
|
||||
CREATE FUNCTION log_user_game_status() RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
INSERT INTO user_game_status_change (user_id, appid, from_status, to_status, by_user)
|
||||
VALUES (NEW.user_id, NEW.appid, CASE WHEN TG_OP = 'UPDATE' THEN OLD.status END, NEW.status,
|
||||
NEW.status_set_by_user);
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER user_game_status_insert AFTER INSERT ON user_game
|
||||
FOR EACH ROW EXECUTE FUNCTION log_user_game_status();
|
||||
CREATE TRIGGER user_game_status_update AFTER UPDATE OF status ON user_game
|
||||
FOR EACH ROW WHEN (OLD.status IS DISTINCT FROM NEW.status) EXECUTE FUNCTION log_user_game_status();
|
||||
@@ -0,0 +1,4 @@
|
||||
-- IGDB genre names (e.g. "Role-playing (RPG)") per game, for the stats page. Filled with the moods; the next
|
||||
-- library sync asks IGDB for every game again, so the column gets filled.
|
||||
ALTER TABLE game_mood ADD COLUMN genres VARCHAR(64)[] NOT NULL DEFAULT '{}';
|
||||
UPDATE game_mood SET checked_at = TIMESTAMPTZ 'epoch';
|
||||
@@ -0,0 +1,25 @@
|
||||
-- The size of each user's pile per day (Europe/Berlin), for the Pile of Shame trend. The nightly sync writes one row
|
||||
-- per user with listed games; the past cannot be rebuilt, so the trend starts with the row of the migration day.
|
||||
CREATE TABLE backlog_snapshot (
|
||||
user_id BIGINT NOT NULL,
|
||||
day DATE NOT NULL,
|
||||
backlog_games INTEGER NOT NULL,
|
||||
never_played INTEGER NOT NULL,
|
||||
-- Normal time to beat of the backlog games IGDB knows; it also grows when IGDB learns new times.
|
||||
backlog_minutes BIGINT NOT NULL,
|
||||
listed_games INTEGER NOT NULL,
|
||||
PRIMARY KEY (user_id, day)
|
||||
);
|
||||
|
||||
INSERT INTO backlog_snapshot (user_id, day, backlog_games, never_played, backlog_minutes, listed_games)
|
||||
SELECT ug.user_id, (NOW() AT TIME ZONE 'Europe/Berlin')::date,
|
||||
COUNT(*) FILTER (WHERE ug.status = 'BACKLOG'), COUNT(*) FILTER (WHERE ug.playtime_minutes = 0),
|
||||
COALESCE(SUM(t.normally_minutes) FILTER (WHERE ug.status = 'BACKLOG'), 0), COUNT(*)
|
||||
FROM user_game ug LEFT JOIN game_time_to_beat t ON t.appid = ug.appid
|
||||
WHERE ug.removed_at IS NULL AND ug.hidden_at IS NULL
|
||||
GROUP BY ug.user_id;
|
||||
|
||||
-- When the current Steam account's first sync ran: the games of that sync are the import, no "added" in the trend.
|
||||
-- The accounts of today count from the migration on, like the status history.
|
||||
ALTER TABLE user_steam_sync ADD COLUMN history_since TIMESTAMPTZ;
|
||||
UPDATE user_steam_sync SET history_since = NOW() WHERE history_steam_id IS NOT NULL;
|
||||
@@ -0,0 +1,5 @@
|
||||
-- For a FINISHED or COMPLETED game: when the user unlocked its ending achievement (the rule of EndingAchievement).
|
||||
-- "Finished per year" counts the game in that year, before the status history (V14): a game finished by hand or by
|
||||
-- Core long after its ending, e.g. when the library is first set up, still counts in the right year. Filled by the
|
||||
-- nightly job; NULL when the game has no ending achievement, it is not unlocked, or Steam sent no unlock time.
|
||||
ALTER TABLE user_game ADD COLUMN finished_at_estimate TIMESTAMPTZ;
|
||||
Reference in new issue
Block a user