feat: rework developer/publisher system

This commit is contained in:
DecDuck
2025-05-10 11:59:56 +10:00
parent ac355918ed
commit 90277653cb
12 changed files with 132 additions and 247 deletions

View File

@ -71,8 +71,6 @@ class LibraryManager {
mName: true,
mShortDescription: true,
metadataSource: true,
mDevelopers: true,
mPublishers: true,
mIconObjectId: true,
libraryBasePath: true,
},

View File

@ -1,4 +1,4 @@
import type { Developer, Publisher } from "~/prisma/client";
import type { Company } from "~/prisma/client";
import { MetadataSource } from "~/prisma/client";
import type { MetadataProvider } from ".";
import { MissingMetadataProviderConfig } from ".";
@ -6,9 +6,8 @@ import type {
GameMetadataSearchResult,
_FetchGameMetadataParams,
GameMetadata,
_FetchPublisherMetadataParams,
_FetchCompanyMetadataParams,
CompanyMetadata,
_FetchDeveloperMetadataParams,
} from "./types";
import type { AxiosRequestConfig } from "axios";
import axios from "axios";
@ -168,7 +167,7 @@ export class GiantBombProvider implements MetadataProvider {
? this.turndown.turndown(gameData.description)
: gameData.deck;
const publishers: Publisher[] = [];
const publishers: Company[] = [];
if (gameData.publishers) {
for (const pub of gameData.publishers) {
const res = await publisher(pub.name);
@ -177,7 +176,7 @@ export class GiantBombProvider implements MetadataProvider {
}
}
const developers: Developer[] = [];
const developers: Company[] = [];
if (gameData.developers) {
for (const dev of gameData.developers) {
const res = await developer(dev.name);
@ -222,10 +221,10 @@ export class GiantBombProvider implements MetadataProvider {
return metadata;
}
async fetchPublisher({
async fetchCompany({
query,
createObject,
}: _FetchPublisherMetadataParams): Promise<CompanyMetadata> {
}: _FetchCompanyMetadataParams): Promise<CompanyMetadata> {
const results = await this.request<Array<CompanySearchResult>>(
"search",
"",
@ -255,9 +254,4 @@ export class GiantBombProvider implements MetadataProvider {
return metadata;
}
async fetchDeveloper(
params: _FetchDeveloperMetadataParams,
): Promise<CompanyMetadata> {
return await this.fetchPublisher(params);
}
}

View File

@ -1,4 +1,4 @@
import type { Developer, Publisher } from "~/prisma/client";
import type { Company } from "~/prisma/client";
import { MetadataSource } from "~/prisma/client";
import type { MetadataProvider } from ".";
import { MissingMetadataProviderConfig } from ".";
@ -6,9 +6,8 @@ import type {
GameMetadataSearchResult,
_FetchGameMetadataParams,
GameMetadata,
_FetchPublisherMetadataParams,
_FetchCompanyMetadataParams,
CompanyMetadata,
_FetchDeveloperMetadataParams,
} from "./types";
import type { AxiosRequestConfig } from "axios";
import axios from "axios";
@ -312,8 +311,8 @@ export class IGDBProvider implements MetadataProvider {
}
}
const publishers: Publisher[] = [];
const developers: Developer[] = [];
const publishers: Company[] = [];
const developers: Company[] = [];
for (const involvedCompany of response[i]?.involved_companies ?? []) {
// get details about the involved company
const involved_company_response =
@ -368,10 +367,10 @@ export class IGDBProvider implements MetadataProvider {
throw new Error("No game found on igdb with that id");
}
async fetchPublisher({
async fetchCompany({
query,
createObject,
}: _FetchPublisherMetadataParams): Promise<CompanyMetadata> {
}: _FetchCompanyMetadataParams): Promise<CompanyMetadata> {
const response = await this.request<IGDBCompany>(
"companies",
`where name = "${query}"; fields *; limit 1;`,
@ -407,9 +406,4 @@ export class IGDBProvider implements MetadataProvider {
throw new Error(`igdb failed to find publisher/developer ${query}`);
}
async fetchDeveloper(
params: _FetchDeveloperMetadataParams,
): Promise<CompanyMetadata> {
return await this.fetchPublisher(params);
}
}

View File

@ -1,10 +1,8 @@
import type { Developer, Publisher } from "~/prisma/client";
import { MetadataSource } from "~/prisma/client";
import prisma from "../db/database";
import type {
_FetchDeveloperMetadataParams,
_FetchGameMetadataParams,
_FetchPublisherMetadataParams,
_FetchCompanyMetadataParams,
GameMetadata,
GameMetadataSearchResult,
InternalGameMetadataResult,
@ -35,11 +33,8 @@ export abstract class MetadataProvider {
abstract search(query: string): Promise<GameMetadataSearchResult[]>;
abstract fetchGame(params: _FetchGameMetadataParams): Promise<GameMetadata>;
abstract fetchPublisher(
params: _FetchPublisherMetadataParams,
): Promise<CompanyMetadata | undefined>;
abstract fetchDeveloper(
params: _FetchDeveloperMetadataParams,
abstract fetchCompany(
params: _FetchCompanyMetadataParams,
): Promise<CompanyMetadata | undefined>;
}
@ -138,14 +133,14 @@ export class MetadataHandler {
["internal:read"],
);
let metadata;
let metadata: GameMetadata | undefined = undefined;
try {
metadata = await provider.fetchGame({
id: result.id,
name: result.name,
// wrap in anonymous functions to keep references to this
publisher: (name: string) => this.fetchPublisher(name),
developer: (name: string) => this.fetchDeveloper(name),
publisher: (name: string) => this.fetchCompany(name),
developer: (name: string) => this.fetchCompany(name),
createObject,
});
} catch (e) {
@ -171,77 +166,27 @@ export class MetadataHandler {
mCoverObjectId: metadata.coverId,
mImageLibraryObjectIds: metadata.images,
publishers: {
connect: metadata.publishers,
},
developers: {
connect: metadata.developers,
},
libraryBasePath,
},
});
// relate companies to game
for (const pub of metadata.publishers) {
await prisma.companyGameRelation.upsert({
where: {
companyGame: {
gameId: game.id,
companyId: pub.id,
},
},
create: {
gameId: game.id,
companyId: pub.id,
publisher: true,
},
update: {
publisher: true,
},
});
}
for (const dev of metadata.developers) {
await prisma.companyGameRelation.upsert({
where: {
companyGame: {
gameId: game.id,
companyId: dev.id,
},
},
create: {
gameId: game.id,
companyId: dev.id,
developer: true,
},
update: {
developer: true,
},
});
}
await pullObjects();
return game;
}
async fetchDeveloper(query: string) {
return (await this.fetchDeveloperPublisher(
query,
"fetchDeveloper",
"developer",
)) as Developer | undefined;
}
async fetchPublisher(query: string) {
return (await this.fetchDeveloperPublisher(
query,
"fetchPublisher",
"publisher",
)) as Publisher | undefined;
}
// Careful with this function, it has no typechecking
// Type-checking this thing is impossible
private async fetchDeveloperPublisher(
query: string,
functionName: "fetchDeveloper" | "fetchPublisher",
type: "developer" | "publisher",
) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const existing = await (prisma as any)[type].findFirst({
private async fetchCompany(query: string) {
const existing = await prisma.company.findFirst({
where: {
metadataOriginalQuery: query,
},
@ -258,10 +203,10 @@ export class MetadataHandler {
);
let result: CompanyMetadata | undefined;
try {
result = await provider[functionName]({ query, createObject });
result = await provider.fetchCompany({ query, createObject });
if (result === undefined) {
throw new Error(
`${provider.source()} failed to find a ${type} for "${query}`,
`${provider.source()} failed to find a company for "${query}`,
);
}
} catch (e) {
@ -273,15 +218,8 @@ export class MetadataHandler {
// If we're successful
await pullObjects();
// TODO: dedupe metadata in event that a company with same source and id appears
const object = await prisma.company.upsert({
where: {
metadataKey: {
metadataId: result.id,
metadataSource: provider.source(),
},
},
create: {
const object = await prisma.company.create({
data: {
metadataSource: provider.source(),
metadataId: result.id,
metadataOriginalQuery: query,
@ -293,14 +231,6 @@ export class MetadataHandler {
mBannerObjectId: result.banner,
mWebsite: result.website,
},
update: {
mName: result.name,
mShortDescription: result.shortDescription,
mDescription: result.description,
mLogoObjectId: result.logo,
mBannerObjectId: result.banner,
mWebsite: result.website,
},
});
return object;

View File

@ -3,9 +3,8 @@ import type { MetadataProvider } from ".";
import type {
_FetchGameMetadataParams,
GameMetadata,
_FetchPublisherMetadataParams,
_FetchCompanyMetadataParams,
CompanyMetadata,
_FetchDeveloperMetadataParams,
} from "./types";
import * as jdenticon from "jdenticon";
@ -43,13 +42,8 @@ export class ManualMetadataProvider implements MetadataProvider {
images: [iconId],
};
}
async fetchPublisher(
_params: _FetchPublisherMetadataParams,
): Promise<CompanyMetadata> {
throw new Error("Method not implemented.");
}
async fetchDeveloper(
_params: _FetchDeveloperMetadataParams,
async fetchCompany(
_params: _FetchCompanyMetadataParams,
): Promise<CompanyMetadata> {
throw new Error("Method not implemented.");
}

View File

@ -1,13 +1,12 @@
import type { Developer, Publisher } from "~/prisma/client";
import type { Company } from "~/prisma/client";
import { MetadataSource } from "~/prisma/client";
import type { MetadataProvider } from ".";
import type {
GameMetadataSearchResult,
_FetchGameMetadataParams,
GameMetadata,
_FetchPublisherMetadataParams,
_FetchCompanyMetadataParams,
CompanyMetadata,
_FetchDeveloperMetadataParams,
} from "./types";
import type { AxiosRequestConfig } from "axios";
import axios from "axios";
@ -179,7 +178,7 @@ export class PCGamingWikiProvider implements MetadataProvider {
const game = res.data.cargoquery[0].title;
const publishers: Publisher[] = [];
const publishers: Company[] = [];
if (game.Publishers !== null) {
const pubListClean = this.parseCompanyStr(game.Publishers);
for (const pub of pubListClean) {
@ -189,7 +188,7 @@ export class PCGamingWikiProvider implements MetadataProvider {
}
}
const developers: Developer[] = [];
const developers: Company[] = [];
if (game.Developers !== null) {
const devListClean = this.parseCompanyStr(game.Developers);
for (const dev of devListClean) {
@ -228,10 +227,10 @@ export class PCGamingWikiProvider implements MetadataProvider {
return metadata;
}
async fetchPublisher({
async fetchCompany({
query,
createObject,
}: _FetchPublisherMetadataParams): Promise<CompanyMetadata> {
}: _FetchCompanyMetadataParams): Promise<CompanyMetadata> {
const searchParams = new URLSearchParams({
action: "cargoquery",
tables: "Company",
@ -267,10 +266,4 @@ export class PCGamingWikiProvider implements MetadataProvider {
throw new Error(`pcgamingwiki failed to find publisher/developer ${query}`);
}
async fetchDeveloper(
params: _FetchDeveloperMetadataParams,
): Promise<CompanyMetadata> {
return await this.fetchPublisher(params);
}
}

View File

@ -1,4 +1,4 @@
import type { Developer, Publisher } from "~/prisma/client";
import type { Company } from "~/prisma/client";
import type { TransactionDataType } from "../objects/transactional";
import type { ObjectReference } from "../objects/objectHandler";
@ -27,8 +27,8 @@ export interface GameMetadata {
// These are created using utility functions passed to the metadata loader
// (that then call back into the metadata provider chain)
publishers: Publisher[];
developers: Developer[];
publishers: Company[];
developers: Company[];
reviewCount: number;
reviewRating: number;
@ -55,15 +55,13 @@ export interface _FetchGameMetadataParams {
id: string;
name: string;
publisher: (query: string) => Promise<Publisher | undefined>;
developer: (query: string) => Promise<Developer | undefined>;
publisher: (query: string) => Promise<Company | undefined>;
developer: (query: string) => Promise<Company | undefined>;
createObject: (data: TransactionDataType) => ObjectReference;
}
export interface _FetchPublisherMetadataParams {
export interface _FetchCompanyMetadataParams {
query: string;
createObject: (data: TransactionDataType) => ObjectReference;
}
export type _FetchDeveloperMetadataParams = _FetchPublisherMetadataParams;