Store overhaul (#142)

* feat: small library tweaks + company page

* feat: new store view

* fix: ci merge error

* feat: add genres to store page

* feat: sorting

* feat: lock game/version imports while their tasks are running

* feat: feature games

* feat: tag based filtering

* fix: make tags alphabetical

* refactor: move a bunch of i18n to common

* feat: add localizations for everything

* fix: title description on panel

* fix: feature carousel text

* fix: i18n footer strings

* feat: add tag page

* fix: develop merge

* feat: offline games support (don't error out if provider throws)

* feat: tag management

* feat: show library next to game import + small fixes

* feat: most of the company and tag managers

* feat: company text field editing

* fix: small fixes + tsgo experiemental

* feat: upload icon and banner

* feat: store infinite scrolling and bulk import mode

* fix: lint

* fix: add drop-base to prettier ignore
This commit is contained in:
DecDuck
2025-07-30 13:40:49 +10:00
committed by GitHub
parent 1ae051f066
commit 8363de2eed
97 changed files with 3506 additions and 524 deletions
+10 -7
View File
@@ -65,6 +65,11 @@ interface GameResult {
reviews?: Array<{
api_detail_url: string;
}>;
genres?: Array<{
name: string;
id: number;
}>;
}
interface ReviewResult {
@@ -189,7 +194,7 @@ export class GiantBombProvider implements MetadataProvider {
context?.logger.warn(`Failed to import publisher "${pub}"`);
continue;
}
context?.logger.info(`Imported publisher "${pub}"`);
context?.logger.info(`Imported publisher "${pub.name}"`);
publishers.push(res);
}
}
@@ -224,11 +229,7 @@ export class GiantBombProvider implements MetadataProvider {
const releaseDate = gameData.original_release_date
? DateTime.fromISO(gameData.original_release_date).toJSDate()
: DateTime.fromISO(
`${gameData.expected_release_year ?? new Date().getFullYear()}-${
gameData.expected_release_month ?? 1
}-${gameData.expected_release_day ?? 1}`,
).toJSDate();
: new Date();
context?.progress(85);
@@ -249,6 +250,8 @@ export class GiantBombProvider implements MetadataProvider {
}
}
const tags = (gameData.genres ?? []).map((e) => e.name);
const metadata: GameMetadata = {
id: gameData.guid,
name: gameData.name,
@@ -256,7 +259,7 @@ export class GiantBombProvider implements MetadataProvider {
description: longDescription,
released: releaseDate,
tags: [],
tags,
reviews,
+3 -2
View File
@@ -450,7 +450,7 @@ export class IGDBProvider implements MetadataProvider {
mReviewHref: currentGame.url,
};
const tags = await this.getGenres(currentGame.genres);
const genres = await this.getGenres(currentGame.genres);
const deck = this.trimMessage(currentGame.summary, 280);
@@ -461,12 +461,13 @@ export class IGDBProvider implements MetadataProvider {
description: currentGame.summary,
released,
genres,
reviews: [review],
publishers,
developers,
tags,
tags: [],
icon,
bannerId: banner,
+24 -13
View File
@@ -18,6 +18,8 @@ import taskHandler, { wrapTaskContext } from "../tasks";
import { randomUUID } from "crypto";
import { fuzzy } from "fast-fuzzy";
import { logger } from "~/server/internal/logging";
import libraryManager from "../library";
import type { GameTagModel } from "~/prisma/client/models";
export class MissingMetadataProviderConfig extends Error {
private providerName: string;
@@ -124,19 +126,22 @@ export class MetadataHandler {
);
}
private parseTags(tags: string[]) {
const results: Array<Prisma.TagCreateOrConnectWithoutGamesInput> = [];
private async parseTags(tags: string[]) {
const results: Array<GameTagModel> = [];
tags.forEach((t) =>
results.push({
where: {
name: t,
},
create: {
name: t,
},
}),
);
for (const tag of tags) {
const rawResults: GameTagModel[] =
await prisma.$queryRaw`SELECT * FROM "GameTag" WHERE SIMILARITY(name, ${tag}) > 0.45;`;
let resultTag = rawResults.at(0);
if (!resultTag) {
resultTag = await prisma.gameTag.create({
data: {
name: tag,
},
});
}
results.push(resultTag);
}
return results;
}
@@ -180,6 +185,8 @@ export class MetadataHandler {
});
if (existing) return undefined;
await libraryManager.lockGame(libraryId, libraryPath);
const gameId = randomUUID();
const taskId = `import:${gameId}`;
@@ -262,7 +269,7 @@ export class MetadataHandler {
connectOrCreate: metadataHandler.parseRatings(metadata.reviews),
},
tags: {
connectOrCreate: metadataHandler.parseTags(metadata.tags),
connect: await metadataHandler.parseTags(metadata.tags),
},
libraryId,
@@ -271,6 +278,10 @@ export class MetadataHandler {
});
logger.info(`Finished game import.`);
progress(100);
},
async finally() {
await libraryManager.unlockGame(libraryId, libraryPath);
},
});