feat: Implemented filtering and searching
This commit is contained in:
parent
4806f3487a
commit
444d8c4c27
49 changed files with 841 additions and 290 deletions
|
|
@ -1,6 +1,6 @@
|
|||
import Elysia, { status } from "elysia";
|
||||
import { config, db, emulatorsDb, plugins, taskQueue } from "../app";
|
||||
import { and, eq, getTableColumns, inArray, sql } from "drizzle-orm";
|
||||
import { and, eq, getTableColumns, ilike, inArray, like, sql } from "drizzle-orm";
|
||||
import z from "zod";
|
||||
import * as schema from "@schema/app";
|
||||
import fs from "node:fs/promises";
|
||||
|
|
@ -20,6 +20,7 @@ import { buildStoreFrontendEmulatorSystems, getShuffledStoreGames, getStoreEmula
|
|||
import { convertStoreEmulatorToFrontend } from "../store/services/emulatorsService";
|
||||
import { host } from "@/bun/utils/host";
|
||||
import { LaunchGameJob } from "../jobs/launch-game-job";
|
||||
import { cores } from "../emulatorjs/emulatorjs";
|
||||
|
||||
// A custom jimp that supports webp
|
||||
const Jimp = createJimp({
|
||||
|
|
@ -134,12 +135,24 @@ export default new Elysia()
|
|||
.get('/games', async ({ query, set }) =>
|
||||
{
|
||||
const games: FrontEndGameType[] = [];
|
||||
const filterSets: FrontEndFilterSets = {
|
||||
age_ratings: new Set(),
|
||||
player_counts: new Set(),
|
||||
languages: new Set(),
|
||||
companies: new Set(),
|
||||
genres: new Set()
|
||||
};
|
||||
|
||||
if (query.source === 'store')
|
||||
{
|
||||
const shuffledGames = await getShuffledStoreGames();
|
||||
set.headers['x-max-items'] = shuffledGames.length;
|
||||
const storeGames = await Promise.all(shuffledGames
|
||||
const storeGames = await Promise.all(shuffledGames.filter(g =>
|
||||
{
|
||||
if (query.search)
|
||||
return path.basename(g.path).toLocaleLowerCase().includes(query.search.toLocaleLowerCase());
|
||||
return true;
|
||||
})
|
||||
.slice(query.offset ?? 0, Math.min((query.offset ?? 0) + (query.limit ?? 50), shuffledGames.length))
|
||||
.map(async (e) =>
|
||||
{
|
||||
|
|
@ -185,6 +198,11 @@ export default new Elysia()
|
|||
}
|
||||
}
|
||||
|
||||
if (query.search)
|
||||
{
|
||||
where.push(like(schema.games.name, query.search));
|
||||
}
|
||||
|
||||
if (query.source)
|
||||
{
|
||||
where.push(eq(schema.games.source, query.source));
|
||||
|
|
@ -218,7 +236,7 @@ export default new Elysia()
|
|||
{
|
||||
// Collections are just a remote thing for now.
|
||||
const remoteGames: FrontEndGameTypeWithIds[] = [];
|
||||
await plugins.hooks.games.fetchGames.promise({ query, games: remoteGames }).catch(e => console.error(e));
|
||||
await plugins.hooks.games.fetchGames.promise({ query, games: remoteGames, filters: filterSets }).catch(e => console.error(e));
|
||||
games.push(...remoteGames.map(g =>
|
||||
{
|
||||
if (localGameExistsPredicate(g))
|
||||
|
|
@ -233,37 +251,74 @@ export default new Elysia()
|
|||
|
||||
} else
|
||||
{
|
||||
games.push(...localGames.slice(query.offset, query.limit ? query.offset ?? 0 + query.limit : undefined).map(g =>
|
||||
games.push(...localGames.slice(query.offset, query.limit ? query.offset ?? 0 + query.limit : undefined).filter(g =>
|
||||
{
|
||||
if (query.genres && query.genres.length > 0)
|
||||
{
|
||||
if (!g.metadata) return false;
|
||||
if (!g.metadata.genres) return false;
|
||||
if (query.genres.some(genre => !g.metadata?.genres?.includes(genre))) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}).map(g =>
|
||||
{
|
||||
return convertLocalToFrontend(g);
|
||||
}));
|
||||
|
||||
const remoteGames: FrontEndGameTypeWithIds[] = [];
|
||||
const remoteGameSet = new Set<string>();
|
||||
await plugins.hooks.games.fetchGames.promise({ query, games: remoteGames }).catch(e => console.error(e));
|
||||
games.push(...remoteGames.filter(g =>
|
||||
if (query.localOnly !== true)
|
||||
{
|
||||
if (localGameExistsPredicate(g))
|
||||
const remoteGames: FrontEndGameTypeWithIds[] = [];
|
||||
const remoteGameSet = new Set<string>();
|
||||
await plugins.hooks.games.fetchGames.promise({ query, games: remoteGames, filters: filterSets }).catch(e => console.error(e));
|
||||
games.push(...remoteGames.filter(g =>
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (localGameExistsPredicate(g))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (g.igdb_id)
|
||||
if (g.igdb_id)
|
||||
{
|
||||
const igdbId = `igdb@${g.igdb_id}`;
|
||||
if (remoteGameSet.has(igdbId)) return false;
|
||||
remoteGameSet.add(igdbId);
|
||||
}
|
||||
|
||||
if (g.ra_id)
|
||||
{
|
||||
const raId = `ra@${g.ra_id}`;
|
||||
if (remoteGameSet.has(raId)) return false;
|
||||
remoteGameSet.add(raId);
|
||||
}
|
||||
|
||||
return true;
|
||||
}));
|
||||
} else
|
||||
{
|
||||
await plugins.hooks.games.fetchFilters.promise({ filters: filterSets }).catch(e => console.error(e));
|
||||
}
|
||||
|
||||
localGames.map(g =>
|
||||
{
|
||||
const metadata: any = g.metadata;
|
||||
if (metadata.genres && Array.isArray(metadata.genres))
|
||||
{
|
||||
const igdbId = `igdb@${g.igdb_id}`;
|
||||
if (remoteGameSet.has(igdbId)) return false;
|
||||
remoteGameSet.add(igdbId);
|
||||
metadata.genres.forEach((g: string) => filterSets.genres.add(g));
|
||||
}
|
||||
|
||||
if (g.ra_id)
|
||||
if (metadata.age_ratings && Array.isArray(metadata.age_ratings))
|
||||
{
|
||||
const raId = `ra@${g.ra_id}`;
|
||||
if (remoteGameSet.has(raId)) return false;
|
||||
remoteGameSet.add(raId);
|
||||
metadata.age_ratings.forEach((g: string) => filterSets.age_ratings.add(g));
|
||||
}
|
||||
|
||||
return true;
|
||||
}));
|
||||
if (metadata.companies && Array.isArray(metadata.companies))
|
||||
{
|
||||
metadata.companies.forEach((g: string) => filterSets.companies.add(g));
|
||||
}
|
||||
if (metadata.player_count)
|
||||
{
|
||||
filterSets.player_counts.add(metadata.player_count);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -280,11 +335,22 @@ export default new Elysia()
|
|||
case 'name':
|
||||
games.sort((a, b) => (a.name ?? '').localeCompare(b.name ?? ''));
|
||||
break;
|
||||
case "release":
|
||||
games.sort((a, b) => (b.metadata.first_release_date?.getTime() ?? 0) - (a.metadata.first_release_date?.getTime() ?? 0));
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return { games };
|
||||
const filterLists: FrontEndFilterLists = {
|
||||
age_ratings: Array.from(filterSets.age_ratings),
|
||||
player_counts: Array.from(filterSets.player_counts),
|
||||
languages: Array.from(filterSets.languages),
|
||||
companies: Array.from(filterSets.companies),
|
||||
genres: Array.from(filterSets.genres)
|
||||
};
|
||||
|
||||
return { games, filters: filterLists };
|
||||
}, {
|
||||
query: GameListFilterSchema,
|
||||
})
|
||||
|
|
@ -341,8 +407,22 @@ export default new Elysia()
|
|||
return {
|
||||
name: 'EMULATORJS',
|
||||
validSources: [{ binPath: SERVER_URL(host), type: 'embedded', exists: true }],
|
||||
logo: `/api/romm/image?url=${encodeURIComponent('https://emulatorjs.org/logo/EmulatorJS.png')}`,
|
||||
systems: [],
|
||||
logo: 'https://emulatorjs.org/logo/EmulatorJS.png',
|
||||
systems: await Promise.all(Object.keys(cores).map(async c =>
|
||||
{
|
||||
const mapping = await emulatorsDb.query.systemMappings.findFirst({
|
||||
where (fields, operators)
|
||||
{
|
||||
return operators.and(operators.eq(fields.source, "romm"), operators.eq(fields.system, c));
|
||||
}, columns: { sourceSlug: true }
|
||||
});
|
||||
const system: EmulatorSystem = {
|
||||
id: c,
|
||||
name: c,
|
||||
iconUrl: `/api/romm/image/romm/assets/platforms/${mapping?.sourceSlug}.svg`
|
||||
};
|
||||
return system;
|
||||
})),
|
||||
gameCount: 0,
|
||||
integrations: []
|
||||
} satisfies FrontEndGameTypeDetailedEmulator;
|
||||
|
|
@ -536,8 +616,8 @@ export default new Elysia()
|
|||
const sourceData = await getSourceGameDetailed(source, id);
|
||||
if (!sourceData) return status("Not Found");
|
||||
|
||||
const sourceCompaniesSet = new Set(sourceData.companies);
|
||||
const sourceGenresSet = new Set(sourceData.genres);
|
||||
const sourceCompaniesSet = new Set(sourceData.metadata.companies);
|
||||
const sourceGenresSet = new Set(sourceData.metadata.genres);
|
||||
|
||||
const esSystem = sourceData.platform_slug ? await emulatorsDb.query.systemMappings.findFirst({ where: and(eq(emulatorSchema.systemMappings.source, 'romm'), eq(emulatorSchema.systemMappings.sourceSlug, sourceData.platform_slug)), columns: { system: true } }) : undefined;
|
||||
|
||||
|
|
@ -550,7 +630,7 @@ export default new Elysia()
|
|||
|
||||
const localGamesSourceSet = new Set(localGames.filter(g => g.source).map(g => `${g.source}@${g.source_id}`));
|
||||
|
||||
games.push(...localGames.map(g => ({ ...convertLocalToFrontend(g), metadata: g.metadata })));
|
||||
games.push(...localGames.map(g => convertLocalToFrontend(g)));
|
||||
|
||||
const shuffledGames = await getShuffledStoreGames();
|
||||
const storeGames = await Promise.all(shuffledGames
|
||||
|
|
@ -559,7 +639,7 @@ export default new Elysia()
|
|||
const system = path.dirname(g.path);
|
||||
const id = path.basename(g.path, path.extname(g.path));
|
||||
|
||||
if (localGamesSourceSet.has(`${system}@${id}`))
|
||||
if (localGamesSourceSet.has(`store@${system}@${id}`))
|
||||
return false;
|
||||
|
||||
if (esSystem)
|
||||
|
|
|
|||
|
|
@ -60,7 +60,19 @@ export async function fixSource (source: string, id: string)
|
|||
|
||||
if (foundGame)
|
||||
{
|
||||
await db.update(appSchema.games).set({ source: foundGame.id.source, source_id: foundGame.id.id }).where(eq(appSchema.games.id, valid.localGame.id));
|
||||
await db.update(appSchema.games).set({
|
||||
source: foundGame.id.source,
|
||||
source_id: foundGame.id.id,
|
||||
metadata: {
|
||||
age_ratings: foundGame.metadata.age_ratings,
|
||||
genres: foundGame.metadata.genres,
|
||||
player_count: foundGame.metadata.player_count ?? undefined,
|
||||
companies: foundGame.metadata.companies,
|
||||
game_modes: foundGame.metadata.game_modes,
|
||||
average_rating: foundGame.metadata.average_rating ?? undefined,
|
||||
first_release_date: foundGame.metadata.first_release_date?.getTime() ?? undefined,
|
||||
}
|
||||
}).where(eq(appSchema.games.id, valid.localGame.id));
|
||||
return true;
|
||||
} else
|
||||
{
|
||||
|
|
@ -82,6 +94,9 @@ export async function validateGameSource (source: string, id: string): Promise<{
|
|||
if (!localGame) return { valid: true };
|
||||
if (localGame.source && localGame.source_id)
|
||||
{
|
||||
// Store should be immutable
|
||||
if (localGame.source === 'store') return { valid: true, localGame };
|
||||
|
||||
const sourceGame = await plugins.hooks.games.fetchGame.promise({ source: localGame.source, id: localGame.source_id });
|
||||
if (!sourceGame) return { valid: false, reason: "Source Missing", localGame };
|
||||
if (sourceGame.imdb_id !== (localGame.igdb_id ?? undefined) && sourceGame.ra_id !== (localGame.ra_id ?? undefined))
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ export function convertLocalToFrontend (g: typeof schema.games.$inferSelect & {
|
|||
})
|
||||
{
|
||||
const game: FrontEndGameType = {
|
||||
platform_display_name: g.platform?.name ?? "Local",
|
||||
platform_display_name: g.platform?.name ?? null,
|
||||
id: { id: String(g.id), source: 'local' },
|
||||
updated_at: g.created_at,
|
||||
path_cover: `/api/romm/game/local/${g.id}/cover`,
|
||||
|
|
@ -45,17 +45,24 @@ export function convertLocalToFrontend (g: typeof schema.games.$inferSelect & {
|
|||
slug: g.slug,
|
||||
name: g.name,
|
||||
platform_id: g.platform_id,
|
||||
platform_slug: g.platform?.slug ?? null
|
||||
platform_slug: g.platform?.slug ?? null,
|
||||
metadata: {
|
||||
first_release_date: g.metadata?.first_release_date !== undefined ? new Date(g.metadata?.first_release_date) : null
|
||||
}
|
||||
};
|
||||
|
||||
return game;
|
||||
}
|
||||
|
||||
export function convertLocalToFrontendDetailed (g: typeof schema.games.$inferSelect & {
|
||||
platform?: typeof schema.platforms.$inferSelect | null;
|
||||
export async function convertLocalToFrontendDetailed (g: typeof schema.games.$inferSelect & {
|
||||
platform?: { name: string | null, slug: string | null; } | null;
|
||||
screenshotIds?: number[];
|
||||
})
|
||||
{
|
||||
|
||||
const exists = await checkInstalled(g.path_fs);
|
||||
const fileSize = await calculateSize(g.path_fs);
|
||||
|
||||
const game: FrontEndGameTypeDetailed = {
|
||||
platform_display_name: g.platform?.name ?? "Local",
|
||||
id: { id: String(g.id), source: 'local' },
|
||||
|
|
@ -72,9 +79,18 @@ export function convertLocalToFrontendDetailed (g: typeof schema.games.$inferSel
|
|||
platform_id: g.platform_id,
|
||||
platform_slug: g.platform?.slug ?? null,
|
||||
summary: g.summary,
|
||||
fs_size_bytes: 0,
|
||||
missing: false,
|
||||
local: true
|
||||
fs_size_bytes: fileSize,
|
||||
missing: !exists,
|
||||
local: true,
|
||||
metadata: {
|
||||
genres: g.metadata.genres ?? [],
|
||||
companies: g.metadata.companies ?? [],
|
||||
game_modes: g.metadata.game_modes ?? [],
|
||||
age_ratings: g.metadata.age_ratings ?? [],
|
||||
player_count: g.metadata.player_count ?? null,
|
||||
average_rating: g.metadata.average_rating ?? null,
|
||||
first_release_date: g.metadata.first_release_date ? new Date(g.metadata.first_release_date) : null
|
||||
}
|
||||
};
|
||||
|
||||
return game;
|
||||
|
|
@ -107,7 +123,10 @@ export async function convertStoreToFrontend (system: string, id: string, storeG
|
|||
name: storeGame.title,
|
||||
platform_id: null,
|
||||
platform_slug: rommSystem?.sourceSlug ?? system,
|
||||
paths_screenshots: storeGame.pictures.screenshots?.map((s: string) => `/api/romm/image?url=${encodeURIComponent(s)}`) ?? []
|
||||
paths_screenshots: storeGame.pictures.screenshots?.map((s: string) => `/api/romm/image?url=${encodeURIComponent(s)}`) ?? [],
|
||||
metadata: {
|
||||
first_release_date: null
|
||||
}
|
||||
};
|
||||
|
||||
return game;
|
||||
|
|
@ -131,6 +150,15 @@ export async function convertStoreToFrontendDetailed (system: string, id: string
|
|||
fs_size_bytes: size,
|
||||
missing: false,
|
||||
local: false,
|
||||
metadata: {
|
||||
genres: storeGame.tags,
|
||||
companies: [],
|
||||
game_modes: [],
|
||||
age_ratings: [],
|
||||
player_count: "",
|
||||
average_rating: null,
|
||||
first_release_date: null
|
||||
}
|
||||
};
|
||||
|
||||
return detailed;
|
||||
|
|
@ -148,29 +176,7 @@ export async function getLocalGameDetailed (match: any)
|
|||
|
||||
if (localGame)
|
||||
{
|
||||
const exists = await checkInstalled(localGame.path_fs);
|
||||
const fileSize = await calculateSize(localGame.path_fs);
|
||||
const game: FrontEndGameTypeDetailed = {
|
||||
path_cover: `/api/romm/game/local/${localGame.id}/cover`,
|
||||
updated_at: localGame.created_at,
|
||||
id: { id: String(localGame.id), source: 'local' },
|
||||
path_platform_cover: `/api/romm/platform/local/${localGame.platform_id}/cover`,
|
||||
fs_size_bytes: fileSize ?? null,
|
||||
paths_screenshots: localGame.screenshots.map(s => `/api/romm/screenshot/${s.id}`),
|
||||
local: true,
|
||||
missing: !exists,
|
||||
platform_display_name: localGame.platform?.name,
|
||||
summary: localGame.summary,
|
||||
source: localGame.source,
|
||||
source_id: localGame.source_id,
|
||||
path_fs: localGame.path_fs,
|
||||
last_played: localGame.last_played,
|
||||
slug: localGame.slug,
|
||||
name: localGame.name,
|
||||
platform_id: localGame.platform_id,
|
||||
platform_slug: localGame.platform.slug
|
||||
};
|
||||
return game;
|
||||
return convertLocalToFrontendDetailed({ ...localGame, screenshotIds: localGame.screenshots.map(s => s.id) });
|
||||
}
|
||||
|
||||
return undefined;
|
||||
|
|
|
|||
|
|
@ -37,6 +37,10 @@ export class GameHooks
|
|||
fetchGames = new AsyncSeriesHook<[ctx: {
|
||||
query: GameListFilterType;
|
||||
games: FrontEndGameTypeWithIds[];
|
||||
filters: FrontEndFilterSets;
|
||||
}]>(['ctx']);
|
||||
fetchFilters = new AsyncSeriesHook<[ctx: {
|
||||
filters: FrontEndFilterSets;
|
||||
}]>(['ctx']);
|
||||
fetchGame = new AsyncSeriesBailHook<[ctx: {
|
||||
source: string;
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ export class LaunchGameJob implements IJob<z.infer<typeof LaunchGameJob.dataSche
|
|||
if (typeof filename === 'string')
|
||||
{
|
||||
console.log("Save File Changed", filename);
|
||||
this.changedSaveFiles.set(filename, { subPath: filename, cwd: commandArgs.savesPath! });
|
||||
this.changedSaveFiles.set(filename, { subPath: filename, cwd: commandArgs.savesPath!, shared: true });
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import { PluginContextType, PluginType } from "@/bun/types/typesc.schema";
|
||||
import desc from './package.json';
|
||||
import { DetailedRomSchema, getCollectionApiCollectionsIdGet, getCollectionsApiCollectionsGet, getCurrentUserApiUsersMeGet, getPlatformApiPlatformsIdGet, getPlatformFirmwareApiFirmwareGet, getPlatformsApiPlatformsGet, getRomApiRomsIdGet, getRomByMetadataProviderApiRomsByMetadataProviderGet, getRomContentApiRomsIdContentFileNameGet, getRomsApiRomsGet, getSavesSummaryApiSavesSummaryGet, SimpleRomSchema, updateRomUserApiRomsIdPropsPut } from "@/clients/romm";
|
||||
import { DetailedRomSchema, getCollectionApiCollectionsIdGet, getCollectionsApiCollectionsGet, getCurrentUserApiUsersMeGet, getPlatformApiPlatformsIdGet, getPlatformFirmwareApiFirmwareGet, getPlatformsApiPlatformsGet, getRomApiRomsIdGet, getRomByMetadataProviderApiRomsByMetadataProviderGet, getRomContentApiRomsIdContentFileNameGet, getRomFiltersApiRomsFiltersGet, getRomsApiRomsGet, getSavesSummaryApiSavesSummaryGet, SimpleRomSchema, updateRomUserApiRomsIdPropsPut } from "@/clients/romm";
|
||||
import { config, events } from "@/bun/api/app";
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs/promises';
|
||||
|
|
@ -16,6 +16,12 @@ import { validateGameSource } from "@/bun/api/games/services/statusService";
|
|||
export default class RommIntegration implements PluginType
|
||||
{
|
||||
isSteamDeck = false;
|
||||
orderByMap: Record<string, string> = {
|
||||
added: "created_at",
|
||||
activity: "created_at",
|
||||
name: "name",
|
||||
release: "metadatum.first_release_date"
|
||||
};
|
||||
|
||||
async updateClient ()
|
||||
{
|
||||
|
|
@ -49,8 +55,11 @@ export default class RommIntegration implements PluginType
|
|||
const game: FrontEndGameType = {
|
||||
id: { id: String(rom.id), source: 'romm' },
|
||||
path_cover: `/api/romm/image/romm${this.isSteamDeck ? rom.path_cover_small : rom.path_cover_large}`,
|
||||
last_played: rom.rom_user.last_played ? new Date(rom.rom_user.last_played) : null,
|
||||
last_played: rom.rom_user.last_played !== null ? new Date(rom.rom_user.last_played) : null,
|
||||
updated_at: new Date(rom.created_at),
|
||||
metadata: {
|
||||
first_release_date: rom.metadatum.first_release_date !== null ? new Date(rom.metadatum.first_release_date) : null,
|
||||
},
|
||||
slug: rom.slug,
|
||||
platform_id: rom.platform_id,
|
||||
platform_display_name: rom.platform_display_name,
|
||||
|
|
@ -74,11 +83,17 @@ export default class RommIntegration implements PluginType
|
|||
fs_size_bytes: rom.fs_size_bytes,
|
||||
local: false,
|
||||
missing: rom.missing_from_fs,
|
||||
genres: rom.metadatum.genres,
|
||||
companies: rom.metadatum.companies,
|
||||
release_date: rom.metadatum.first_release_date ? new Date(rom.metadatum.first_release_date) : undefined,
|
||||
imdb_id: rom.igdb_id ?? undefined,
|
||||
ra_id: rom.ra_id ?? undefined
|
||||
ra_id: rom.ra_id ?? undefined,
|
||||
metadata: {
|
||||
age_ratings: rom.metadatum.age_ratings,
|
||||
genres: rom.metadatum.genres,
|
||||
companies: rom.metadatum.companies,
|
||||
game_modes: rom.metadatum.game_modes,
|
||||
player_count: rom.metadatum.player_count,
|
||||
average_rating: rom.metadatum.average_rating,
|
||||
first_release_date: rom.metadatum.first_release_date ? new Date(rom.metadatum.first_release_date) : null
|
||||
}
|
||||
};
|
||||
|
||||
const userData = await getCurrentUserApiUsersMeGet();
|
||||
|
|
@ -119,26 +134,32 @@ export default class RommIntegration implements PluginType
|
|||
|
||||
load (ctx: PluginContextType)
|
||||
{
|
||||
ctx.hooks.games.fetchGames.tapPromise(desc.name, async ({ query, games }) =>
|
||||
ctx.hooks.games.fetchGames.tapPromise(desc.name, async ({ query, games, filters }) =>
|
||||
{
|
||||
if (((!query.platform_source || query.platform_source === 'romm') || !!query.collection_id) && (!query.source || query.source === 'romm'))
|
||||
{
|
||||
|
||||
const orderByMap: Record<string, string> = {
|
||||
added: "created_at",
|
||||
activity: "created_at",
|
||||
name: "name"
|
||||
};
|
||||
|
||||
const rommGames = await getRomsApiRomsGet({
|
||||
query: {
|
||||
platform_ids: query.platform_id ? [query.platform_id] : undefined,
|
||||
collection_id: query.collection_id,
|
||||
limit: query.limit,
|
||||
offset: query.offset,
|
||||
order_by: orderByMap[query.orderBy ?? '']
|
||||
order_by: this.orderByMap[query.orderBy ?? ''],
|
||||
with_filter_values: true,
|
||||
genres: query.genres,
|
||||
genres_logic: "all",
|
||||
age_ratings: query.age_ratings,
|
||||
search_term: query.search,
|
||||
}, throwOnError: true
|
||||
});
|
||||
|
||||
rommGames.data.filter_values.age_ratings.forEach(r => filters.age_ratings.add(r));
|
||||
rommGames.data.filter_values.companies.forEach(r => filters.companies.add(r));
|
||||
rommGames.data.filter_values.languages.forEach(r => filters.languages.add(r));
|
||||
rommGames.data.filter_values.player_counts.forEach(r => filters.player_counts.add(r));
|
||||
rommGames.data.filter_values.genres.forEach(r => filters.genres.add(r));
|
||||
|
||||
games.push(...rommGames.data.items.map(g =>
|
||||
{
|
||||
const game: FrontEndGameTypeWithIds = {
|
||||
|
|
@ -151,6 +172,16 @@ export default class RommIntegration implements PluginType
|
|||
}
|
||||
});
|
||||
|
||||
ctx.hooks.games.fetchFilters.tapPromise(desc.name, async ({ filters }) =>
|
||||
{
|
||||
const rommFilters = await getRomFiltersApiRomsFiltersGet({ throwOnError: true });
|
||||
rommFilters.data.age_ratings.forEach(r => filters.age_ratings.add(r));
|
||||
rommFilters.data.companies.forEach(r => filters.companies.add(r));
|
||||
rommFilters.data.languages.forEach(r => filters.languages.add(r));
|
||||
rommFilters.data.player_counts.forEach(r => filters.player_counts.add(r));
|
||||
rommFilters.data.genres.forEach(r => filters.genres.add(r));
|
||||
});
|
||||
|
||||
ctx.hooks.auth.loginComplete.tapPromise(desc.name, async ({ service }) =>
|
||||
{
|
||||
if (service !== 'romm') return;
|
||||
|
|
@ -277,10 +308,10 @@ export default class RommIntegration implements PluginType
|
|||
const rommPlatform = rommPlatforms.find(p => p.slug === game.platform_slug);
|
||||
if (rommPlatform)
|
||||
{
|
||||
const rommGames = await getRomsApiRomsGet({ query: { genres: game.genres, genres_logic: 'any' } });
|
||||
const rommGames = await getRomsApiRomsGet({ query: { genres: game.metadata.genres, genres_logic: 'any' } });
|
||||
if (rommGames.data)
|
||||
{
|
||||
games.push(...rommGames.data.items.map(g => ({ ...this.convertRomToFrontend(g), metadata: g.metadatum })));
|
||||
games.push(...rommGames.data.items.map(g => ({ ...this.convertRomToFrontend(g) })));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,15 @@ export const games = sqliteTable('games', {
|
|||
path_fs: text("path_fs"),
|
||||
last_played: integer("last_played", { mode: 'timestamp' }),
|
||||
created_at: integer("created_at", { mode: 'timestamp' }).default(sql`(unixepoch())`).notNull(),
|
||||
metadata: text("metadata", { mode: 'json' }).default(sql`'{}'`),
|
||||
metadata: text("metadata", { mode: 'json' }).default(sql`'{}'`).$type<{
|
||||
genres?: string[],
|
||||
companies?: string[],
|
||||
game_modes?: string[],
|
||||
age_ratings?: string[];
|
||||
player_count?: string;
|
||||
first_release_date?: number;
|
||||
average_rating?: number;
|
||||
}>().notNull(),
|
||||
slug: text("slug").unique(),
|
||||
platform_id: integer("platform_id").references(() => platforms.id, { onUpdate: 'cascade' }).notNull(),
|
||||
cover: blob("cover", { mode: 'buffer' }),
|
||||
|
|
|
|||
|
|
@ -25,7 +25,22 @@ export const store = new Elysia({ prefix: '/api/store' })
|
|||
});
|
||||
const emulatesParsed = await getAllStoreEmulatorPackages();
|
||||
let frontEndEmulators = await Promise.all(emulatesParsed
|
||||
.filter(e => e.os.includes(process.platform as any))
|
||||
.filter(e =>
|
||||
{
|
||||
if (!e.os.includes(process.platform as any)) return false;
|
||||
if (query.search)
|
||||
{
|
||||
const lowerCaseSearch = query.search.toLocaleLowerCase();
|
||||
|
||||
if (e.name.toLocaleLowerCase().includes(lowerCaseSearch) || e.systems.some(s => s.toLocaleLowerCase().includes(lowerCaseSearch)) || e.keywords?.some(k => k.toLocaleLowerCase().includes(lowerCaseSearch)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.map(async (emulator) =>
|
||||
{
|
||||
const systems = await buildStoreFrontendEmulatorSystems(emulator);
|
||||
|
|
@ -77,7 +92,8 @@ export const store = new Elysia({ prefix: '/api/store' })
|
|||
limit: z.coerce.number().optional(),
|
||||
missing: z.stringbool().optional().describe("Show Only Non Installed emulators"),
|
||||
orderBy: z.enum(['name', 'recently_updated', 'importance']).optional(),
|
||||
related: z.string().optional()
|
||||
related: z.string().optional(),
|
||||
search: z.string().optional()
|
||||
})
|
||||
})
|
||||
.get('/games/featured', async () =>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue