gameflow-deck/src/bun/api/games/platforms.ts
Simeon Radivoev 06b7e4074d
feat: Implemented local game import (with a wizard)
feat: Implemented a radial virtual gamepad keyboard.
fix: Fixed shortcuts for file explorer
2026-05-04 14:59:43 +03:00

184 lines
No EOL
7.9 KiB
TypeScript

import Elysia, { status } from "elysia";
import z from "zod";
import { and, count, eq, getTableColumns, not, notExists, or } from "drizzle-orm";
import { config, db, plugins } from "../app";
import * as schema from "@schema/app";
import { findPlatform } from "./services/utils";
export default new Elysia()
.get('/platforms', async () =>
{
const localPlatforms = await db.select({ ...getTableColumns(schema.platforms), game_count: count(schema.games.id) })
.from(schema.platforms)
.leftJoin(schema.games, eq(schema.games.platform_id, schema.platforms.id))
.groupBy(schema.platforms.id);
const localPlatformSet = new Set(localPlatforms.filter(p => p.game_count > 0).map(p => p.slug));
const remotePlatforms: FrontEndPlatformType[] = [];
await plugins.hooks.games.fetchPlatforms.promise({ platforms: remotePlatforms });
await Promise.all(remotePlatforms.map(async p =>
{
p.hasLocal = localPlatformSet.has(p.slug);
if (p.paths_screenshots.length <= 0)
{
const localScreenshots = await db.select({ id: schema.screenshots.id }).from(schema.games).leftJoin(schema.platforms, eq(schema.platforms.id, schema.games.platform_id)).where(eq(schema.platforms.slug, p.slug)).leftJoin(schema.screenshots, eq(schema.screenshots.game_id, schema.games.id)).limit(1);
if (localScreenshots)
p.paths_screenshots.push(...localScreenshots.map(s => `/api/romm/screenshot/${s.id}`));
}
const localGames = await db.select({ id: schema.games.id, source: schema.games.source, souceId: schema.games.source_id }).from(schema.games).leftJoin(schema.platforms, eq(schema.platforms.id, schema.games.platform_id)).where(and(eq(schema.platforms.slug, p.slug), not(eq(schema.games.source, 'romm')))).groupBy(schema.games.id);
p.game_count += localGames.length;
}));
const platformSlugSet = new Set(remotePlatforms.map(p => p.slug));
const platforms: FrontEndPlatformType[] = [];
platforms.push(...remotePlatforms);
platforms.push(...await Promise.all(localPlatforms.filter(p => !platformSlugSet?.has(p.slug)).map(async p =>
{
const game = await db.query.games.findFirst({ where: eq(schema.games.platform_id, p.id) });
let screenshots: { id: number; }[] = [];
if (game)
{
screenshots = await db.query.screenshots.findMany({ where: eq(schema.screenshots.game_id, game.id), columns: { id: true } });
}
const platform: FrontEndPlatformType = {
slug: p.slug,
name: p.name,
family_name: p.family_name,
path_cover: `/api/romm/platform/local/${p.id}/cover`,
game_count: p.game_count,
updated_at: p.created_at,
id: { source: 'local', id: String(p.id) },
hasLocal: true,
paths_screenshots: screenshots?.map(s => `/api/romm/screenshot/${s.id}`) ?? []
};
return platform;
})));
return { platforms };
}).get('/platforms/:source/:id', async ({ params: { source, id } }) =>
{
if (source === 'local')
{
const localPlatform = await db.query.platforms.findFirst({ where: eq(schema.platforms.id, Number(id)) });
if (localPlatform)
{
const platform: FrontEndPlatformType = {
slug: localPlatform.slug,
name: localPlatform.name,
family_name: localPlatform.family_name,
path_cover: `/api/romm/platform/local/${localPlatform.id}/cover`,
game_count: 0,
updated_at: localPlatform.created_at,
id: { source: 'local', id: String(localPlatform.id) },
hasLocal: true,
paths_screenshots: []
};
return platform;
}
return status("Not Found");
} else
{
const remotePlatform = await plugins.hooks.games.fetchPlatform.promise({ source, id });
if (!remotePlatform) return status("Not Found");
const local = await db.query.platforms.findFirst({ where: or(eq(schema.platforms.slug, remotePlatform?.slug), eq(schema.platforms.name, remotePlatform?.name)) });
return { ...remotePlatform, hasLocal: !!local };
}
}, { params: z.object({ source: z.string(), id: z.string() }) })
.get('/platform/local/:id/cover', async ({ params: { id }, set }) =>
{
set.headers["cross-origin-resource-policy"] = 'cross-origin';
const coverBlob = await db.query.platforms.findFirst({
columns: {
cover: true, cover_type: true
}, where: eq(schema.platforms.id, id)
});
if (!coverBlob || !coverBlob.cover)
{
return status(404);
}
if (coverBlob.cover_type)
{
set.headers["content-type"] = coverBlob.cover_type;
}
return status(200, coverBlob.cover);
}, { response: { 200: z.instanceof(Buffer<ArrayBufferLike>), 404: z.any() }, params: z.object({ id: z.coerce.number() }) })
.post('/platform/:source/:id/update', async ({ params: { source, id } }) =>
{
const where: any[] = [];
if (source === 'local')
{
where.push(eq(schema.platforms.id, Number(id)));
} else
{
const remotePlatform = await plugins.hooks.games.fetchPlatform.promise({ source, id });
if (remotePlatform)
{
where.push(eq(schema.platforms.slug, remotePlatform.slug));
}
}
const localPlatform = await db.query.platforms.findFirst({
where: or(...where)
});
if (!localPlatform) return status("Not Found");
const platformLookup = await plugins.hooks.games.platformLookup.promise({
slug: localPlatform.slug
});
let platformCover = await fetch(`${config.get('rommAddress') ?? 'https://demo.romm.app'}/assets/platforms/${localPlatform.slug}.svg`);
if (!platformCover.ok && platformLookup?.url_logo)
{
platformCover = await fetch(platformLookup.url_logo);
}
await db.update(schema.platforms).set({
name: platformLookup?.name,
cover: Buffer.from(await platformCover.arrayBuffer()),
cover_type: platformCover.headers.get('content-type'),
}).where(eq(schema.platforms.id, localPlatform.id));
})
.delete('/platform/local/:id', async ({ params: { id } }) =>
{
const deleted = await db.delete(schema.platforms).where(and(eq(schema.platforms.id, Number(id)),
notExists(
db
.select()
.from(schema.games)
.where(eq(schema.games.platform_id, Number(id)))
))).returning();
if (deleted.length <= 0) return status("Not Found");
})
.get('/platform/lookup/match/:source/:id', async ({ params: { source, id } }) =>
{
const platformLookup = await plugins.hooks.games.platformLookup.promise({ source, id });
if (!platformLookup) return status("Not Found");
const match = await findPlatform({
system_slug: platformLookup.slug,
platform: {
source_slug: platformLookup.slug,
source_id: Number(id),
source: source,
name: platformLookup.name
}
});
return { details: platformLookup, match };
}, {
detail: {
description: "Find matches of remote platform lookups. Returns the operations for each platform if it were to be imported. If platform locally exists. Will a new local platform be created from say romm. Unknown is returned if no match is found."
}
});