Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactoring/NR-65 Refactoring game identifier #207

Merged
merged 78 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
f31dfa2
Halfway done with refactoring game identifier
lukatarman Dec 18, 2023
098f3ea
refactored get untried filtered steam apps method
lukatarman Dec 19, 2023
2b85362
added first part of tryIfGameViaSource test
lukatarman Dec 19, 2023
8b05b8d
fixed test
lukatarman Jan 14, 2024
3fe45fd
added second test case
lukatarman Jan 15, 2024
1569b81
added all tryIfGameViaSource tests
lukatarman Jan 16, 2024
7adf85e
added .copy() method to game class
lukatarman Jan 16, 2024
5fe657b
added missing tests to game.service.js file
lukatarman Jan 16, 2024
378231d
finished refactoring game identifier
lukatarman Jan 16, 2024
8078f29
refactored game identifier and game service slightly
lukatarman Jan 17, 2024
7a79a0d
updated imports to reflect new folder structure
lukatarman Jan 19, 2024
cce9f5f
removed unnecessary logger messages
lukatarman Jan 19, 2024
bfe588d
added ValidDataSources test
lukatarman Jan 20, 2024
8f37ea9
fixed bug causing repeat adds of failed/tried via
lukatarman Jan 26, 2024
d33d876
added todo
lukatarman Feb 6, 2024
7f2fe0c
created GamesAggregator
lukatarman Feb 13, 2024
71bd5d8
added logger to steamAppsRepository
lukatarman Feb 15, 2024
ccbc45a
added steamAppsAggregate data model
lukatarman Feb 15, 2024
730c290
added tests for steamAppsAggregate
lukatarman Feb 16, 2024
58c83d2
added initial games.aggregate methods
lukatarman Feb 16, 2024
a4cdb19
added todos
lukatarman Feb 16, 2024
15dbc1d
added using GamesAggregate to games repository
lukatarman Feb 16, 2024
c8ce0a3
Added message argument to games aggregate method
lukatarman Feb 19, 2024
8d6fade
implemented new data model to game identifier
lukatarman Feb 19, 2024
fe8e42d
added identify types to steam apps aggregate data model
lukatarman Feb 20, 2024
d1fa78e
used new method in game identifier
lukatarman Feb 20, 2024
5f3f56d
moved steam app methods into data model
lukatarman Feb 21, 2024
f695ee2
changed date in asset file
lukatarman Feb 22, 2024
7ddcf9e
moved set of service functions into game data model
lukatarman Feb 22, 2024
6d321e2
renamed tryIfGameViaSource method
lukatarman Feb 22, 2024
0e51d5e
renamed method
lukatarman Feb 22, 2024
acae142
refactored slightly due to new datamodel method usage
lukatarman Feb 22, 2024
25e94ce
moved identifyTypes method call into steamApps aggregate model
lukatarman Feb 23, 2024
2a47075
moved loop and some logic into steam client.js
lukatarman Feb 23, 2024
6d5f5a2
removed fdescribe
lukatarman Feb 24, 2024
984ed3c
added getIds method to games aggregate
lukatarman Feb 26, 2024
b679495
getSteamAppsById now returns instance of SteamAppsAggregate
lukatarman Feb 27, 2024
302a05c
removed getGameIds function
lukatarman Feb 27, 2024
3d888e5
reverted changes to steam client
lukatarman Feb 28, 2024
07423a8
added functions to html details mocks
lukatarman Feb 29, 2024
f0481f5
passed in parseHTML through main
lukatarman Mar 3, 2024
70a75de
moved the parsing of html pages into identifier
lukatarman Mar 4, 2024
c238382
moved steam.identifyType into separate public method
lukatarman Mar 5, 2024
497d8d4
moved logger back into game.identifier
lukatarman Mar 5, 2024
b4e3ffe
renamed checkIfEmpty methods to isEmpty
lukatarman Mar 6, 2024
4a074dc
refactored updateGamesWithoutReleaseDates
lukatarman Mar 6, 2024
527e239
added comment
lukatarman Mar 7, 2024
4c5c27c
removed unused code
lukatarman Mar 8, 2024
6da85db
moved updating game's details into game data model
lukatarman Mar 14, 2024
1176ec4
added updateGamesMissingDetails to gamesAggregate
lukatarman Mar 14, 2024
c326ab7
added new code into game identifier
lukatarman Mar 14, 2024
0c41540
renamed variable
lukatarman Mar 14, 2024
dec36a9
remolved todos
lukatarman Mar 18, 2024
3a334b9
removed todo
lukatarman Mar 20, 2024
b1a45e0
fixed method duplication
lukatarman Mar 20, 2024
3647273
added todo
lukatarman Mar 20, 2024
973dc07
extracted reusable code into separate private method
lukatarman Mar 25, 2024
c928d55
optimized some tests
lukatarman Mar 25, 2024
ecddf68
reorganized method order
lukatarman Mar 25, 2024
348a34f
removed fdescribe
lukatarman Mar 26, 2024
7f29a48
removed unused method
lukatarman Mar 26, 2024
62a60d5
added values for readability
lukatarman Mar 26, 2024
1b1d2d2
fixed bug running collectSteamApps twice
lukatarman Mar 26, 2024
2011408
fixed bug incorrectly persisting steamapps/games
lukatarman Mar 26, 2024
a867e63
changed isEmpty in games aggregate into getter
lukatarman Apr 7, 2024
722700e
moved getSourceUrl into steam client
lukatarman Apr 7, 2024
e7bd160
changed getIds method to a getterr
lukatarman Apr 7, 2024
1ff3e59
renamed method to extractGameDetailsFrom
lukatarman Apr 8, 2024
e2d1af1
made methods in fromSteamApp method private
lukatarman Apr 9, 2024
15d0999
renamed methods
lukatarman Apr 9, 2024
900e877
changed games aggregate games property to private
lukatarman Apr 10, 2024
c855f4a
changed static method to standard constructor in games aggregate
lukatarman Apr 10, 2024
87f4166
changed games aggregate isEmpty method into getter
lukatarman Apr 10, 2024
d316d19
changed SteamApp isGame method into getter
lukatarman Apr 10, 2024
fd2017d
made some methods in steam app private
lukatarman Apr 10, 2024
b4256b0
made steam apps aggregate content private. Used content getter
lukatarman Apr 10, 2024
4b2ef79
changed steam apps aggregate static method into constructor
lukatarman Apr 10, 2024
024ae51
passed only steam app id into private method in steam apps aggregate
lukatarman Apr 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions backend/assets/html.details.pages.mock.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
import { parseHTML } from "linkedom";

export function createHtmlDetailsPage(page) {
return { page, id: 1 };
}

export function createHtmlDetailsPages(pages) {
return pages.map((page, index) => {
return { page, id: index + 1 };
});
}

export function getParsedHtmlPage(page) {
return parseHTML(page).document;
}

export function getParsedHtmlPages(pages) {
return pages.map((page, index) => {
return { page: parseHTML(page).document, id: index + 1 };
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -821,7 +821,7 @@ export const feartressGameHtmlDetailsPage = `<!DOCTYPE html>

<div class="grid_label grid_date">Release</div>
<div class="grid_content grid_date">
TBD </div>
10.10.2001 </div>
</div>


Expand All @@ -842,7 +842,7 @@ export const feartressGameHtmlDetailsPage = `<!DOCTYPE html>

<div class="release_date">
<div class="subtitle column">Release Date:</div>
<div class="date">TBD</div>
<div class="date">1 January, 2001</div>
</div>

<div class="dev_row">
Expand Down
345 changes: 345 additions & 0 deletions backend/assets/steamdb-details-pages/elden.ring.html.details.page.js

Large diffs are not rendered by default.

36 changes: 13 additions & 23 deletions backend/src/adapters/driven/http/steam.client.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { SteamApp } from "../../../core/models/steam.app.js";
import { ValidDataSources } from "../../../core/models/valid.data.sources.js";

export class SteamClient {
#httpClient;
Expand Down Expand Up @@ -33,38 +34,27 @@ export class SteamClient {
).map((player) => (player ? player.data.response.player_count : 0));
}

async getSteamAppHtmlDetailsPage(id) {
// TODO https://github.com/lukatarman/steam-game-stats/issues/192
// We add the try catch block here to not crash the whole backend when our application comes accross
// errors written into the Steam API
try {
const url = `https://store.steampowered.com/app/${id}`;

return (await this.#httpClient.get(url)).data;
} catch (err) {
return "";
}
}
// TODO https://github.com/lukatarman/steam-game-stats/issues/192
async getSourceHtmlDetailsPage(id, source) {
const url = this.getSourceUrl(id, source);

async getSteamchartsGameHtmlDetailsPage(id) {
// TODO https://github.com/lukatarman/steam-game-stats/issues/192
try {
const url = `https://steamcharts.com/app/${id}`;

return (await this.#httpClient.get(url)).data;
} catch (err) {
return "";
}
}

async getSteamDbHtmlDetailsPage(id) {
// TODO https://github.com/lukatarman/steam-game-stats/issues/192
try {
const url = `https://steamdb.info/app/${id}/info/`;
getSourceUrl(id, source) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use private methods (encapsulation) if no client needs access

switch (source) {
case ValidDataSources.validDataSources.steamWeb:
return `https://store.steampowered.com/app/${id}`;

return (await this.#httpClient.get(url)).data;
} catch (error) {
return "";
case ValidDataSources.validDataSources.steacharts:
return `https://steamcharts.com/app/${id}`;

case ValidDataSources.validDataSources.steamDb:
return `https://steamdb.info/app/${id}/info/`;
}
}
}
195 changes: 70 additions & 125 deletions backend/src/core/features/game-identifier/game.identifier.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
import {
discoverGamesFromSteamWeb,
updateTypeSideEffectFree,
identifyGames,
assignType,
updateMissingDetails,
updateMissingReleaseDates,
recordAttemptsViaSteamDb,
} from "../../services/game.service.js";
import { delay } from "../../../common/time.utils.js";
import { HistoryCheck } from "../../models/history.check.js";
import { ValidDataSources } from "../../models/valid.data.sources.js";

export class GameIdentifier {
#steamClient;
Expand All @@ -17,6 +9,7 @@ export class GameIdentifier {
#historyChecksRepository;
#logger;
#options;
#htmlParser;

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good! 👍 You are using dependency injection. Now you are inverting dependencies which means the game identifier is independent from the htmlParser and whoever is instantiating the GameIdentifier is dependent on it because it has to provide it to the GameIdentifier on creation.

constructor(
steamClient,
Expand All @@ -25,203 +18,155 @@ export class GameIdentifier {
historyChecksRepository,
logger,
options,
htmlParser,
) {
this.#steamClient = steamClient;
this.#steamAppsRepository = steamAppsRepository;
this.#gamesRepository = gamesRepository;
this.#historyChecksRepository = historyChecksRepository;
this.#logger = logger;
this.#options = options;
this.#htmlParser = htmlParser;
}

tryViaSteamWeb = async () => {
this.#logger.debugc("identifying games via steam web");
checkIfGameViaSource = async (source) => {
this.#logger.debugc(`identifying games via ${source}`);

const steamApps = await this.#steamAppsRepository.getSteamWebUntriedFilteredSteamApps(
const steamApps = await this.#steamAppsRepository.getSourceUntriedFilteredSteamApps(
this.#options.batchSize,
source,
);
if (steamApps.length === 0) {

if (steamApps.isEmpty) {
this.#logger.debugc(
`no steam apps in db, retry in: ${this.#options.globalIterationDelay} ms`,
);

return;
}

const [games, updatedSteamApps] = await this.#identifyViaSteamWeb(steamApps);

this.#persist(games, updatedSteamApps);
};
const htmlDetailsPages = await this.#getSteamAppsHtmlDetailsPages(
steamApps.content,
source,
);

async #identifyViaSteamWeb(steamApps) {
const htmlDetailsPages = await this.#getSteamAppsHtmlDetailsPages(steamApps);
steamApps.identifyTypes(htmlDetailsPages, source);

const games = discoverGamesFromSteamWeb(steamApps, htmlDetailsPages);
const games = steamApps.extractGames(htmlDetailsPages, source);

const updatedSteamApps = updateTypeSideEffectFree(steamApps, htmlDetailsPages);
await this.#persistGameCheckUpdates(games, steamApps.content);
};

return [games, updatedSteamApps];
}
async #getSteamAppsHtmlDetailsPages(steamApps, source) {
const htmlDetailsPages = [];

async #getSteamAppsHtmlDetailsPages(steamApps) {
const detailsPages = [];
for (let steamApp of steamApps) {
detailsPages.push(
// TODO https://github.com/lukatarman/steam-game-stats/issues/192
await this.#steamClient.getSteamAppHtmlDetailsPage(steamApp.appid),
// TODO https://github.com/lukatarman/steam-game-stats/issues/192
const htmlPage = await this.#steamClient.getSourceHtmlDetailsPage(
steamApp.appid,
source,
);

htmlDetailsPages.push({
page: this.#htmlParser(htmlPage).document,
id: steamApp.appid,
});

await delay(this.#options.unitDelay);
}
return detailsPages;

return htmlDetailsPages;
}

async #persist(games, updatedSteamApps) {
async #persistGameCheckUpdates(games, steamApps) {
if (games.length !== 0) {
this.#logger.debugc(`persiting ${games.length} identified games`);

await this.#gamesRepository.insertManyGames(games);
await this.#historyChecksRepository.insertManyHistoryChecks(
HistoryCheck.manyFromGames(games),
);
}
await this.#steamAppsRepository.updateSteamAppsById(updatedSteamApps);
}

tryViaSteamchartsWeb = async () => {
this.#logger.debugc("identifying games via steamcharts web");

const steamApps =
await this.#steamAppsRepository.getSteamchartsUntriedFilteredSteamApps(
this.#options.batchSize,
);
if (steamApps.length === 0) {
this.#logger.debugc(
`no steam apps in db, retry in: ${this.#options.globalIterationDelay} ms`,
);
return;
}

const updatedSteamApps = await this.#updateStatusViaSteamchartsWeb(steamApps);
const games = identifyGames(updatedSteamApps);

this.#persist(games, updatedSteamApps);
};

// this method is a mess right now, including the tests. Will be fixed in issue: #198
async #updateStatusViaSteamchartsWeb(steamApps) {
const updatedSteamApps = [];

for (let steamApp of steamApps) {
const steamAppCopy = steamApp.copy();
steamAppCopy.triedViaSteamchartsWeb();

// TODO https://github.com/lukatarman/steam-game-stats/issues/192
const result = await this.#steamClient.getSteamchartsGameHtmlDetailsPage(
steamApp.appid,
);

if (result === "") {
steamAppCopy.failedViaSteamchartsWeb();
this.#logger.debugc(`no entry on steamcharts web for appid: ${steamApp.appid}`);
}

assignType(result, steamAppCopy);

await delay(this.#options.unitDelay);
updatedSteamApps.push(steamAppCopy);
}

return updatedSteamApps;
await this.#steamAppsRepository.updateSteamAppsById(steamApps);
}

updateGamesWithoutDetails = async () => {
const source = ValidDataSources.validDataSources.steamDb;
this.#logger.debugc("updating games without details");

const games = await this.#gamesRepository.getGamesWithoutDetails(
this.#options.batchSize,
);

if (games.length === 0) {
if (games.isEmpty) {
this.#logger.debugc(
`no games without details in db, retrying in ${
`no games without details in db, retry in: ${
this.#options.globalIterationDelay
} ms`,
);
return;
}

const steamApps = await this.#steamAppsRepository.getSteamAppsById(
games.map((game) => game.id),
);
const steamApps = await this.#steamAppsRepository.getSteamAppsById(games.ids);

const htmlDetailsPages = await this.#getSteamDbHtmlDetailsPages(games);
const htmlDetailsPages = await this.#getSteamAppsHtmlDetailsPages(
steamApps.content,
source,
);

const updatedApps = recordAttemptsViaSteamDb(steamApps, htmlDetailsPages);
steamApps.recordAttemptsViaSource(htmlDetailsPages, source);

updateMissingDetails(games, htmlDetailsPages);
games.updateGameDetailsFrom(htmlDetailsPages);

this.#persistMissingProperties(games, updatedApps);
this.#persistUpdatedDetails(games.content, steamApps.content);
};

async #getSteamDbHtmlDetailsPages(games) {
const htmlDetailsPages = [];

for (let game of games) {
// TODO https://github.com/lukatarman/steam-game-stats/issues/192
const htmlPage = await this.#steamClient.getSteamDbHtmlDetailsPage(game.id);
htmlDetailsPages.push({ page: htmlPage, id: game.id });

await delay(this.#options.unitDelay);
}

return htmlDetailsPages;
}

async #persistMissingProperties(games, appsWithoutPages) {
if (appsWithoutPages.length !== 0) {
this.#logger.debugc(`persisting ${appsWithoutPages.length} apps without pages`);
this.#steamAppsRepository.updateSteamAppsById(appsWithoutPages);
}

async #persistUpdatedDetails(games, steamApps) {
this.#logger.debugc(`persisting ${steamApps.length} apps with updated html attempts`);
this.#logger.debugc(`persisting ${games.length} games with updated details`);
await this.#gamesRepository.updateGameDetails(games);

await this.#steamAppsRepository.updateSteamAppsById(steamApps);
await this.#gamesRepository.updateGameDetailsFrom(games);
}

updateGamesWithoutReleaseDates = async () => {
this.#logger.debugc("updating games without details");
const source = ValidDataSources.validDataSources.steamDb;
this.#logger.debugc(`updating games without release dates via ${source}`);

const games = await this.#gamesRepository.getGamesWithoutReleaseDates(
this.#options.batchSize,
);

if (games.length === 0) {
if (games.isEmpty) {
this.#logger.debugc(
`no games without release dates in db, retrying in ${
this.#options.iterationDelay
`no games without release dates in db, retry in: ${
this.#options.globalIterationDelay
} ms`,
);

return;
}

const steamApps = await this.#steamAppsRepository.getSteamAppsById(
games.map((game) => game.id),
);
const steamApps = await this.#steamAppsRepository.getSteamAppsById(games.ids);

const htmlDetailsPages = await this.#getSteamDbHtmlDetailsPages(games);
const htmlDetailsPages = await this.#getSteamAppsHtmlDetailsPages(
steamApps.content,
source,
);

const updatedApps = recordAttemptsViaSteamDb(steamApps, htmlDetailsPages);
steamApps.recordAttemptsViaSource(htmlDetailsPages, source);

updateMissingReleaseDates(games, htmlDetailsPages);
games.extractReleaseDatesFrom(htmlDetailsPages);

this.#persistReleaseDates(games, updatedApps);
this.#persistReleaseDates(games.content, steamApps.content);
};

#persistReleaseDates = async (games, appsWithoutPages) => {
if (appsWithoutPages.length !== 0) {
this.#logger.debugc(`persisting ${appsWithoutPages.length} apps without pages`);
this.#steamAppsRepository.updateSteamAppsById(appsWithoutPages);
}

async #persistReleaseDates(games, steamApps) {
this.#logger.debugc(`persisting ${steamApps.length} apps with updated html attempts`);
this.#logger.debugc(`persisting ${games.length} games with updated release dates`);

await this.#steamAppsRepository.updateSteamAppsById(steamApps);
await this.#gamesRepository.updateReleaseDates(games);
};
}
}
Loading
Loading