mirror of
https://github.com/Drop-OSS/drop.git
synced 2025-11-13 16:22:39 +10:00
initial work on metadata system
This commit is contained in:
76
prisma/migrations/20240929000950_add_game_data/migration.sql
Normal file
76
prisma/migrations/20240929000950_add_game_data/migration.sql
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "MetadataSource" AS ENUM ('Custom', 'GiantBomb');
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Game" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"metadataSource" "MetadataSource" NOT NULL,
|
||||||
|
"metadataId" TEXT NOT NULL,
|
||||||
|
"mName" TEXT NOT NULL,
|
||||||
|
"mShortDescription" TEXT NOT NULL,
|
||||||
|
"mDescription" TEXT NOT NULL,
|
||||||
|
"mReviewCount" INTEGER NOT NULL,
|
||||||
|
"mReviewRating" DOUBLE PRECISION NOT NULL,
|
||||||
|
"mIconId" TEXT NOT NULL,
|
||||||
|
"mBannerId" TEXT NOT NULL,
|
||||||
|
"mArt" TEXT[],
|
||||||
|
"mScreenshots" TEXT[],
|
||||||
|
|
||||||
|
CONSTRAINT "Game_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Developer" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"metadataSource" "MetadataSource" NOT NULL,
|
||||||
|
"metadataId" TEXT NOT NULL,
|
||||||
|
"mName" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Developer_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Publisher" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"metadataSource" "MetadataSource" NOT NULL,
|
||||||
|
"metadataId" TEXT NOT NULL,
|
||||||
|
"mName" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Publisher_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "_GameToPublisher" (
|
||||||
|
"A" TEXT NOT NULL,
|
||||||
|
"B" TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "_DeveloperToGame" (
|
||||||
|
"A" TEXT NOT NULL,
|
||||||
|
"B" TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "_GameToPublisher_AB_unique" ON "_GameToPublisher"("A", "B");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "_GameToPublisher_B_index" ON "_GameToPublisher"("B");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "_DeveloperToGame_AB_unique" ON "_DeveloperToGame"("A", "B");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "_DeveloperToGame_B_index" ON "_DeveloperToGame"("B");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_GameToPublisher" ADD CONSTRAINT "_GameToPublisher_A_fkey" FOREIGN KEY ("A") REFERENCES "Game"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_GameToPublisher" ADD CONSTRAINT "_GameToPublisher_B_fkey" FOREIGN KEY ("B") REFERENCES "Publisher"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_DeveloperToGame" ADD CONSTRAINT "_DeveloperToGame_A_fkey" FOREIGN KEY ("A") REFERENCES "Developer"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_DeveloperToGame" ADD CONSTRAINT "_DeveloperToGame_B_fkey" FOREIGN KEY ("B") REFERENCES "Game"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- Added the required column `mBanner` to the `Developer` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Added the required column `mDescription` to the `Developer` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Added the required column `mLogo` to the `Developer` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Added the required column `mShortDescription` to the `Developer` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Added the required column `mBanner` to the `Publisher` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Added the required column `mDescription` to the `Publisher` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Added the required column `mLogo` to the `Publisher` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Added the required column `mShortDescription` to the `Publisher` table without a default value. This is not possible if the table is not empty.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Developer" ADD COLUMN "mBanner" TEXT NOT NULL,
|
||||||
|
ADD COLUMN "mDescription" TEXT NOT NULL,
|
||||||
|
ADD COLUMN "mLogo" TEXT NOT NULL,
|
||||||
|
ADD COLUMN "mShortDescription" TEXT NOT NULL;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Publisher" ADD COLUMN "mBanner" TEXT NOT NULL,
|
||||||
|
ADD COLUMN "mDescription" TEXT NOT NULL,
|
||||||
|
ADD COLUMN "mLogo" TEXT NOT NULL,
|
||||||
|
ADD COLUMN "mShortDescription" TEXT NOT NULL;
|
||||||
@ -34,3 +34,61 @@ model LinkedAuthMec {
|
|||||||
|
|
||||||
@@id([userId, mec])
|
@@id([userId, mec])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum MetadataSource {
|
||||||
|
Custom
|
||||||
|
GiantBomb
|
||||||
|
}
|
||||||
|
|
||||||
|
model Game {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
|
||||||
|
metadataSource MetadataSource
|
||||||
|
metadataId String
|
||||||
|
|
||||||
|
// Any field prefixed with m is filled in from metadata
|
||||||
|
// Acts as a cache so we can search and filter it
|
||||||
|
mName String // Name of game
|
||||||
|
mShortDescription String // Short description
|
||||||
|
mDescription String // Supports markdown
|
||||||
|
mDevelopers Developer[]
|
||||||
|
mPublishers Publisher[]
|
||||||
|
|
||||||
|
mReviewCount Int
|
||||||
|
mReviewRating Float
|
||||||
|
|
||||||
|
mIconId String // linked to objects in s3
|
||||||
|
mBannerId String // linked to objects in s3
|
||||||
|
mArt String[] // linked to objects in s3
|
||||||
|
mScreenshots String[] // linked to objects in s3
|
||||||
|
}
|
||||||
|
|
||||||
|
model Developer {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
|
||||||
|
metadataSource MetadataSource
|
||||||
|
metadataId String
|
||||||
|
|
||||||
|
mName String
|
||||||
|
mShortDescription String
|
||||||
|
mDescription String
|
||||||
|
mLogo String
|
||||||
|
mBanner String
|
||||||
|
|
||||||
|
games Game[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model Publisher {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
|
||||||
|
metadataSource MetadataSource
|
||||||
|
metadataId String
|
||||||
|
|
||||||
|
mName String
|
||||||
|
mShortDescription String
|
||||||
|
mDescription String
|
||||||
|
mLogo String
|
||||||
|
mBanner String
|
||||||
|
|
||||||
|
games Game[]
|
||||||
|
}
|
||||||
|
|||||||
56
server/internal/metadata/index.ts
Normal file
56
server/internal/metadata/index.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { _FetchDeveloperMetadataParams, _FetchGameMetadataParams, _FetchPublisherMetadataParams, DeveloperMetadata, GameMetadata, GameMetadataSearchResult, InternalGameMetadataResult, PublisherMetadata } from "./types";
|
||||||
|
|
||||||
|
|
||||||
|
export abstract class MetadataProvider {
|
||||||
|
abstract id(): string;
|
||||||
|
abstract name(): string;
|
||||||
|
|
||||||
|
abstract search(query: string): Promise<GameMetadataSearchResult[]>;
|
||||||
|
abstract fetchGame(params: _FetchGameMetadataParams): Promise<GameMetadata>;
|
||||||
|
abstract fetchPublisher(params: _FetchPublisherMetadataParams): Promise<PublisherMetadata>;
|
||||||
|
abstract fetchDeveloper(params: _FetchDeveloperMetadataParams): Promise<DeveloperMetadata>;
|
||||||
|
}
|
||||||
|
|
||||||
|
class MetadataHandler {
|
||||||
|
// Ordered by priority
|
||||||
|
private providers: Map<string, MetadataProvider> = new Map();
|
||||||
|
private createObject: (url: string) => Promise<string>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.createObject = async () => "";
|
||||||
|
}
|
||||||
|
|
||||||
|
async search(query: string) {
|
||||||
|
const promises: Promise<InternalGameMetadataResult[]>[] = [];
|
||||||
|
for (const provider of this.providers.values()) {
|
||||||
|
const queryTransformationPromise = new Promise<InternalGameMetadataResult[]>(async (resolve, reject) => {
|
||||||
|
const results = await provider.search(query);
|
||||||
|
const mappedResults: InternalGameMetadataResult[] = results.map((result) => Object.assign(
|
||||||
|
{},
|
||||||
|
result,
|
||||||
|
{
|
||||||
|
sourceId: provider.id(),
|
||||||
|
sourceName: provider.name()
|
||||||
|
}
|
||||||
|
));
|
||||||
|
resolve(mappedResults);
|
||||||
|
});
|
||||||
|
promises.push(queryTransformationPromise);
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = await Promise.allSettled(promises);
|
||||||
|
const successfulResults = results.filter((result) => result.status === 'fulfilled').map((result) => result.value).flat();
|
||||||
|
|
||||||
|
return successfulResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchGame(game: InternalGameMetadataResult) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchDeveloper(query: string) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new MetadataHandler();
|
||||||
64
server/internal/metadata/types.d.ts
vendored
Normal file
64
server/internal/metadata/types.d.ts
vendored
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { Developer, Publisher } from "@prisma/client";
|
||||||
|
|
||||||
|
export interface GameMetadataSearchResult {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
icon: string;
|
||||||
|
description: string;
|
||||||
|
year: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GameMetadataSource {
|
||||||
|
sourceId: string;
|
||||||
|
sourceName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type InternalGameMetadataResult = GameMetadataSearchResult & GameMetadataSource;
|
||||||
|
export type RemoteObject = string;
|
||||||
|
|
||||||
|
export interface GameMetadata {
|
||||||
|
name: string;
|
||||||
|
shortDescription: string;
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
// These are created using utility functions passed to the metadata loader
|
||||||
|
// (that then call back into the metadata provider chain)
|
||||||
|
publishers: Publisher[]
|
||||||
|
developers: Developer[]
|
||||||
|
|
||||||
|
reviewCount: number;
|
||||||
|
reviewRating: number;
|
||||||
|
|
||||||
|
// Created with another utility function
|
||||||
|
icon: RemoteObject,
|
||||||
|
banner: RemoteObject,
|
||||||
|
art: RemoteObject[],
|
||||||
|
screenshots: RemoteObject[],
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PublisherMetadata {
|
||||||
|
name: string;
|
||||||
|
shortDescription: string;
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
logo: RemoteObject;
|
||||||
|
banner: RemoteObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DeveloperMetadata = PublisherMetadata;
|
||||||
|
|
||||||
|
export interface _FetchGameMetadataParams {
|
||||||
|
id: string,
|
||||||
|
|
||||||
|
publisher: (query: string) => Promise<Publisher>
|
||||||
|
developer: (query: string) => Promise<Developer>
|
||||||
|
|
||||||
|
createObject: (url: string) => Promise<RemoteObject>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface _FetchPublisherMetadataParams {
|
||||||
|
query: string;
|
||||||
|
createObject: (url: string) => Promise<RemoteObject>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type _FetchDeveloperMetadataParams = _FetchPublisherMetadataParams;
|
||||||
Reference in New Issue
Block a user