feat: Added way to update the local games from romm when IDs change based on IGDB or Retro Achievement ID
Fixes #2
This commit is contained in:
parent
7948bd24fa
commit
4806f3487a
9 changed files with 143 additions and 52 deletions
|
|
@ -26,15 +26,7 @@ function spawnServer ()
|
||||||
killSignal: 'SIGUSR1',
|
killSignal: 'SIGUSR1',
|
||||||
onExit (subprocess, exitCode, signalCode)
|
onExit (subprocess, exitCode, signalCode)
|
||||||
{
|
{
|
||||||
if (exitCode === 1 && retries <= 3)
|
process.exit();
|
||||||
{
|
|
||||||
server = spawnServer();
|
|
||||||
retries++;
|
|
||||||
} else
|
|
||||||
{
|
|
||||||
process.exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const rl = createInterface({ input: Readable.fromWeb(s.stdout as any) });
|
const rl = createInterface({ input: Readable.fromWeb(s.stdout as any) });
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import { GameListFilterSchema, SERVER_URL } from "@shared/constants";
|
||||||
import { InstallJob } from "../jobs/install-job";
|
import { InstallJob } from "../jobs/install-job";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { convertLocalToFrontend, convertStoreToFrontend, getLocalGameMatch, getSourceGameDetailed } from "./services/utils";
|
import { convertLocalToFrontend, convertStoreToFrontend, getLocalGameMatch, getSourceGameDetailed } from "./services/utils";
|
||||||
import buildStatusResponse, { getValidLaunchCommandsForGame } from "./services/statusService";
|
import buildStatusResponse, { fixSource, getValidLaunchCommandsForGame, validateGameSource } from "./services/statusService";
|
||||||
import { errorToResponse } from "elysia/adapter/bun/handler";
|
import { errorToResponse } from "elysia/adapter/bun/handler";
|
||||||
import { getEmulatorsForSystem, getRomFilePaths, launchCommand } from "./services/launchGameService";
|
import { getEmulatorsForSystem, getRomFilePaths, launchCommand } from "./services/launchGameService";
|
||||||
import { getErrorMessage, SeededRandom } from "@/bun/utils";
|
import { getErrorMessage, SeededRandom } from "@/bun/utils";
|
||||||
|
|
@ -412,6 +412,15 @@ export default new Elysia()
|
||||||
params: z.object({ id: z.string(), source: z.string() }),
|
params: z.object({ id: z.string(), source: z.string() }),
|
||||||
response: z.any()
|
response: z.any()
|
||||||
})
|
})
|
||||||
|
.get('/game/:source/:id/validate', async ({ params: { id, source } }) =>
|
||||||
|
{
|
||||||
|
const valid = await validateGameSource(source, id);
|
||||||
|
return { valid: valid.valid, reason: valid.reason };
|
||||||
|
})
|
||||||
|
.post('/game/:source/:id/fix_source', async ({ params: { id, source } }) =>
|
||||||
|
{
|
||||||
|
return fixSource(source, id);
|
||||||
|
})
|
||||||
.post('/game/:source/:id/play', async ({ params: { id, source }, body, set }) =>
|
.post('/game/:source/:id/play', async ({ params: { id, source }, body, set }) =>
|
||||||
{
|
{
|
||||||
const validCommands = await getValidLaunchCommandsForGame(source, id);
|
const validCommands = await getValidLaunchCommandsForGame(source, id);
|
||||||
|
|
|
||||||
|
|
@ -44,26 +44,53 @@ export async function getLocalGame (source: string, id: string)
|
||||||
return localGame;
|
return localGame;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function validateGameSource (source: string, id: string): Promise<{ valid: boolean, reason?: string; }>
|
export async function fixSource (source: string, id: string)
|
||||||
|
{
|
||||||
|
const valid = await validateGameSource(source, id);
|
||||||
|
if (!valid.valid)
|
||||||
|
{
|
||||||
|
if (!valid.localGame) throw new Error("No Local Game");
|
||||||
|
if (!valid.localGame.source) throw new Error("No Valid Source");
|
||||||
|
|
||||||
|
const foundGame = await plugins.hooks.games.searchGame.promise({
|
||||||
|
igdb_id: valid.localGame.igdb_id ?? undefined,
|
||||||
|
ra_id: valid.localGame.ra_id ?? undefined,
|
||||||
|
source: valid.localGame.source
|
||||||
|
});
|
||||||
|
|
||||||
|
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));
|
||||||
|
return true;
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
throw new Error("Could not find Source Game");
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
throw new Error("Game Source Already Valid");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function validateGameSource (source: string, id: string): Promise<{
|
||||||
|
valid: boolean,
|
||||||
|
localGame?: { id: number; igdb_id: number | null; ra_id: number | null; source: string | null; },
|
||||||
|
reason?: string;
|
||||||
|
}>
|
||||||
{
|
{
|
||||||
const localGame = await getLocalGame(source, id);
|
const localGame = await getLocalGame(source, id);
|
||||||
if (!localGame) throw new Error("Could not find local game");
|
if (!localGame) return { valid: true };
|
||||||
if (localGame.source && localGame.source_id)
|
if (localGame.source && localGame.source_id)
|
||||||
{
|
{
|
||||||
const sourceGame = await plugins.hooks.games.fetchGame.promise({ source: localGame.source, id: localGame.source_id });
|
const sourceGame = await plugins.hooks.games.fetchGame.promise({ source: localGame.source, id: localGame.source_id });
|
||||||
if (!sourceGame) return { valid: false, reason: "Source Missing" };
|
if (!sourceGame) return { valid: false, reason: "Source Missing", localGame };
|
||||||
if (sourceGame.imdb_id !== (localGame.igdb_id ?? undefined))
|
if (sourceGame.imdb_id !== (localGame.igdb_id ?? undefined) && sourceGame.ra_id !== (localGame.ra_id ?? undefined))
|
||||||
{
|
{
|
||||||
return { valid: false, reason: "IGDB Miss Match" };
|
return { valid: false, reason: "Metadata Missmatch", localGame };
|
||||||
}
|
|
||||||
|
|
||||||
if (sourceGame.ra_id !== (localGame.ra_id ?? undefined))
|
|
||||||
{
|
|
||||||
return { valid: false, reason: "RA Miss Match" };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { valid: true };
|
return { valid: true, localGame };
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateLocalLastPlayed (id: number)
|
export async function updateLocalLastPlayed (id: number)
|
||||||
|
|
@ -174,7 +201,7 @@ export default function buildStatusResponse ()
|
||||||
},
|
},
|
||||||
async open (ws)
|
async open (ws)
|
||||||
{
|
{
|
||||||
sendLatests();
|
sendLatests().catch(e => ws.send({ status: 'error', error: JSON.stringify(e) }));
|
||||||
const installJobId = InstallJob.query({ source: ws.data.params.source, id: ws.data.params.id });
|
const installJobId = InstallJob.query({ source: ws.data.params.source, id: ws.data.params.id });
|
||||||
|
|
||||||
async function sendLatests ()
|
async function sendLatests ()
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,11 @@ export class GameHooks
|
||||||
localGame?: FrontEndGameTypeDetailed;
|
localGame?: FrontEndGameTypeDetailed;
|
||||||
id: string;
|
id: string;
|
||||||
}], FrontEndGameTypeDetailed | undefined>(['ctx']);
|
}], FrontEndGameTypeDetailed | undefined>(['ctx']);
|
||||||
|
searchGame = new AsyncSeriesBailHook<[ctx: {
|
||||||
|
source: string;
|
||||||
|
igdb_id?: number;
|
||||||
|
ra_id?: number;
|
||||||
|
}], FrontEndGameTypeDetailed | undefined>(['ctx']);
|
||||||
/** Get download file URLs
|
/** Get download file URLs
|
||||||
* @param ctx.checksum Check if file already exists using checksums
|
* @param ctx.checksum Check if file already exists using checksums
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import { PluginContextType, PluginType } from "@/bun/types/typesc.schema";
|
import { PluginContextType, PluginType } from "@/bun/types/typesc.schema";
|
||||||
import desc from './package.json';
|
import desc from './package.json';
|
||||||
import { DetailedRomSchema, getCollectionApiCollectionsIdGet, getCollectionsApiCollectionsGet, getCurrentUserApiUsersMeGet, getPlatformApiPlatformsIdGet, getPlatformFirmwareApiFirmwareGet, getPlatformsApiPlatformsGet, getRomApiRomsIdGet, getRomContentApiRomsIdContentFileNameGet, getRomsApiRomsGet, getSavesSummaryApiSavesSummaryGet, SimpleRomSchema, updateRomUserApiRomsIdPropsPut } from "@/clients/romm";
|
import { DetailedRomSchema, getCollectionApiCollectionsIdGet, getCollectionsApiCollectionsGet, getCurrentUserApiUsersMeGet, getPlatformApiPlatformsIdGet, getPlatformFirmwareApiFirmwareGet, getPlatformsApiPlatformsGet, getRomApiRomsIdGet, getRomByMetadataProviderApiRomsByMetadataProviderGet, getRomContentApiRomsIdContentFileNameGet, getRomsApiRomsGet, getSavesSummaryApiSavesSummaryGet, SimpleRomSchema, updateRomUserApiRomsIdPropsPut } from "@/clients/romm";
|
||||||
import { config, events } from "@/bun/api/app";
|
import { config, events } from "@/bun/api/app";
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import fs from 'node:fs/promises';
|
import fs from 'node:fs/promises';
|
||||||
|
|
@ -557,5 +557,14 @@ export default class RommIntegration implements PluginType
|
||||||
const platforms = await this.getAllRommPlatforms();
|
const platforms = await this.getAllRommPlatforms();
|
||||||
return platforms.find(p => p.id === Number(id));
|
return platforms.find(p => p.id === Number(id));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ctx.hooks.games.searchGame.tapPromise(desc.name, async ({ source, igdb_id, ra_id }) =>
|
||||||
|
{
|
||||||
|
if (source !== 'romm') return;
|
||||||
|
const roms = await getRomByMetadataProviderApiRomsByMetadataProviderGet({ query: { igdb_id, ra_id } });
|
||||||
|
if (roms.error) throw roms.error;
|
||||||
|
if (!roms.data) return;
|
||||||
|
return this.convertRomToFrontendDetailed(roms.data);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { deleteGameMutation, gameInvalidationQuery } from "@/mainview/scripts/queries/romm";
|
import { deleteGameMutation, fixSourceMutation, gameInvalidationQuery, validateSourceQuery } from "@/mainview/scripts/queries/romm";
|
||||||
import { FocusContext, setFocus, useFocusable } from "@noriginmedia/norigin-spatial-navigation";
|
import { FocusContext, setFocus, useFocusable } from "@noriginmedia/norigin-spatial-navigation";
|
||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||||
import { ContextList, DialogEntry, useContextDialog } from "../ContextDialog";
|
import { ContextList, DialogEntry, useContextDialog } from "../ContextDialog";
|
||||||
import { getErrorMessage } from "react-error-boundary";
|
import { getErrorMessage } from "react-error-boundary";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { Settings, Trash, Trophy } from "lucide-react";
|
import { Hammer, Settings, Trash, Trophy } from "lucide-react";
|
||||||
import MainActions from "./MainActions";
|
import MainActions from "./MainActions";
|
||||||
import ActionButton from "./ActionButton";
|
import ActionButton from "./ActionButton";
|
||||||
import { useLocalStorage } from "usehooks-ts";
|
import { useLocalStorage } from "usehooks-ts";
|
||||||
|
|
@ -33,6 +33,18 @@ export default function ActionButtons (data: { game?: FrontEndGameTypeDetailed,
|
||||||
{
|
{
|
||||||
const [, setDetailsSection] = useLocalStorage('details-section', 'screenshots');
|
const [, setDetailsSection] = useLocalStorage('details-section', 'screenshots');
|
||||||
|
|
||||||
|
const fixMutation = useMutation({
|
||||||
|
...fixSourceMutation, onSuccess (data, variables, onMutateResult, context)
|
||||||
|
{
|
||||||
|
if (onMutateResult) toast.success("Updated Source");
|
||||||
|
context.client.invalidateQueries(gameInvalidationQuery(variables.id, variables.source)).then(() => router.history.back());
|
||||||
|
},
|
||||||
|
onError (error)
|
||||||
|
{
|
||||||
|
toast.error(getErrorMessage(error) ?? "Error While Trying To Fix");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const { data: validation } = useQuery(validateSourceQuery(data.source, data.id));
|
||||||
const { ref, focusKey, hasFocusedChild } = useFocusable({ focusKey: 'actions', forceFocus: true, trackChildren: true, preferredChildFocusKey: 'mainAction' });
|
const { ref, focusKey, hasFocusedChild } = useFocusable({ focusKey: 'actions', forceFocus: true, trackChildren: true, preferredChildFocusKey: 'mainAction' });
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const deleteMutation = useMutation({
|
const deleteMutation = useMutation({
|
||||||
|
|
@ -47,32 +59,41 @@ export default function ActionButtons (data: { game?: FrontEndGameTypeDetailed,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
useBlocker({ shouldBlockFn: () => deleteMutation.isPending });
|
useBlocker({
|
||||||
|
shouldBlockFn: () =>
|
||||||
|
{
|
||||||
|
return deleteMutation.isPending || fixMutation.isPending;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const contextOptions: DialogEntry[] = [];
|
const contextOptions: DialogEntry[] = [];
|
||||||
if (data.game?.local)
|
if (data.game?.local)
|
||||||
{
|
{
|
||||||
if (deleteMutation.isPending)
|
contextOptions.push({
|
||||||
{
|
id: 'delete',
|
||||||
contextOptions.push({
|
action: () =>
|
||||||
id: 'delete',
|
{
|
||||||
icon: <span className="loading loading-spinner loading-lg"></span>,
|
deleteMutation.mutate();
|
||||||
content: "Deleting",
|
},
|
||||||
type: 'error'
|
icon: deleteMutation.isPending ? <span className="loading loading-spinner loading-lg"></span> : <Trash />,
|
||||||
});
|
content: deleteMutation.isPending ? "Deleting" : "Delete",
|
||||||
} else
|
type: 'error'
|
||||||
{
|
});
|
||||||
contextOptions.push({
|
}
|
||||||
id: 'delete',
|
|
||||||
action: () =>
|
if (!validation?.valid)
|
||||||
{
|
{
|
||||||
deleteMutation.mutate();
|
contextOptions.push({
|
||||||
},
|
id: "fix_source",
|
||||||
icon: <Trash />,
|
action (ctx)
|
||||||
content: "Delete",
|
{
|
||||||
type: 'error'
|
if (data.game)
|
||||||
});
|
fixMutation.mutate({ source: data.game.id.source, id: data.game.id.id });
|
||||||
}
|
},
|
||||||
|
icon: fixMutation.isPending ? <span className="loading loading-spinner loading-lg"></span> : <Hammer />,
|
||||||
|
content: "Try Fix Source",
|
||||||
|
type: "warning"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { setOpen, dialog: settingsDialog } = useContextDialog("settings-context", { content: <ContextList disableCloseButton={deleteMutation.isPending} options={contextOptions} />, canClose: !deleteMutation.isPending });
|
const { setOpen, dialog: settingsDialog } = useContextDialog("settings-context", { content: <ContextList disableCloseButton={deleteMutation.isPending} options={contextOptions} />, canClose: !deleteMutation.isPending });
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,13 @@ import { scrollIntoViewHandler } from "@/mainview/scripts/utils";
|
||||||
import { RPC_URL } from "@/shared/constants";
|
import { RPC_URL } from "@/shared/constants";
|
||||||
import { FocusContext, useFocusable } from "@noriginmedia/norigin-spatial-navigation";
|
import { FocusContext, useFocusable } from "@noriginmedia/norigin-spatial-navigation";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { Clock, CloudBackup, CloudDownload, CloudUpload, HardDrive, Store, TriangleAlert } from "lucide-react";
|
import { Clock, CloudBackup, CloudDownload, CloudUpload, Gamepad2, HardDrive, Store, TriangleAlert } from "lucide-react";
|
||||||
import prettyBytes from "pretty-bytes";
|
import prettyBytes from "pretty-bytes";
|
||||||
import { JSX } from "react";
|
import { JSX } from "react";
|
||||||
import ActionButtons from "./ActionButtons";
|
import ActionButtons from "./ActionButtons";
|
||||||
import prettyMilliseconds from 'pretty-ms';
|
import prettyMilliseconds from 'pretty-ms';
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { validateSourceQuery } from "@/mainview/scripts/queries/romm";
|
||||||
|
|
||||||
export function DetailElement (data: { icon: JSX.Element; tooltip?: string | null, children?: any | any[]; })
|
export function DetailElement (data: { icon: JSX.Element; tooltip?: string | null, children?: any | any[]; })
|
||||||
{
|
{
|
||||||
|
|
@ -18,6 +20,12 @@ export function DetailElement (data: { icon: JSX.Element; tooltip?: string | nul
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sourceIconMap: Record<string, any> = {
|
||||||
|
store: <Store />,
|
||||||
|
local: <HardDrive />,
|
||||||
|
romm: <Gamepad2 />
|
||||||
|
};
|
||||||
|
|
||||||
export default function Details (data: {
|
export default function Details (data: {
|
||||||
game?: FrontEndGameTypeDetailed,
|
game?: FrontEndGameTypeDetailed,
|
||||||
source: string,
|
source: string,
|
||||||
|
|
@ -32,6 +40,8 @@ export default function Details (data: {
|
||||||
forceFocus: true
|
forceFocus: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { data: validation } = useQuery(validateSourceQuery(data.source, data.id));
|
||||||
|
|
||||||
const platformCoverImg = data.game?.path_platform_cover ? new URL(`${RPC_URL(__HOST__)}${data.game?.path_platform_cover}`) : undefined;
|
const platformCoverImg = data.game?.path_platform_cover ? new URL(`${RPC_URL(__HOST__)}${data.game?.path_platform_cover}`) : undefined;
|
||||||
if (platformCoverImg)
|
if (platformCoverImg)
|
||||||
platformCoverImg.searchParams.set("width", "64");
|
platformCoverImg.searchParams.set("width", "64");
|
||||||
|
|
@ -70,8 +80,8 @@ export default function Details (data: {
|
||||||
</div>}
|
</div>}
|
||||||
<DetailElement icon={platformCoverImg ? <img className="size-6" src={platformCoverImg.href}></img> : <div className="skeleton size-6 rounded-full shrink-0"></div>} >{data.game?.platform_display_name ?? <div className="skeleton h-4 w-32"></div>}</DetailElement>
|
<DetailElement icon={platformCoverImg ? <img className="size-6" src={platformCoverImg.href}></img> : <div className="skeleton size-6 rounded-full shrink-0"></div>} >{data.game?.platform_display_name ?? <div className="skeleton h-4 w-32"></div>}</DetailElement>
|
||||||
{data.game?.emulators?.some(e => e.integrations.some(i => i.capabilities?.includes('saves'))) && <DetailElement tooltip={"Save Backup"} icon={<CloudUpload />} />}
|
{data.game?.emulators?.some(e => e.integrations.some(i => i.capabilities?.includes('saves'))) && <DetailElement tooltip={"Save Backup"} icon={<CloudUpload />} />}
|
||||||
<DetailElement icon={
|
<DetailElement tooltip={validation?.reason} icon={
|
||||||
<Store />
|
validation ? validation.valid ? sourceIconMap[data.game?.source ?? ''] : <TriangleAlert className="text-error" /> : <span className="loading loading-spinner loading-lg"></span>
|
||||||
} >
|
} >
|
||||||
{data.game?.source ?? data.game?.id.source}
|
{data.game?.source ?? data.game?.id.source}
|
||||||
{data.game?.local && <small className="text-base-content/60 font-semibold">local</small>}</DetailElement>
|
{data.game?.local && <small className="text-base-content/60 font-semibold">local</small>}</DetailElement>
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import { GamesSection } from "@/mainview/components/store/GamesSection";
|
||||||
import Details from "@/mainview/components/game/Details";
|
import Details from "@/mainview/components/game/Details";
|
||||||
import { AutoFocus } from "@/mainview/components/AutoFocus";
|
import { AutoFocus } from "@/mainview/components/AutoFocus";
|
||||||
import SelectMenu from "@/mainview/components/SelectMenu";
|
import SelectMenu from "@/mainview/components/SelectMenu";
|
||||||
|
import { stat } from "node:fs";
|
||||||
|
|
||||||
export const Route = createFileRoute("/game/$source/$id")({
|
export const Route = createFileRoute("/game/$source/$id")({
|
||||||
loader: async ({ params, context }) =>
|
loader: async ({ params, context }) =>
|
||||||
|
|
@ -104,6 +105,8 @@ function Stats (data: { game: FrontEndGameTypeDetailed | undefined; })
|
||||||
stats.push({ label: "Release Date", content: data.game.release_date.toLocaleDateString(), icon: <Calendar /> });
|
stats.push({ label: "Release Date", content: data.game.release_date.toLocaleDateString(), icon: <Calendar /> });
|
||||||
if (data.game.emulators)
|
if (data.game.emulators)
|
||||||
stats.push({ label: "Emulators", content: data.game.emulators.map(e => e.name) });
|
stats.push({ label: "Emulators", content: data.game.emulators.map(e => e.name) });
|
||||||
|
if (data.game.source)
|
||||||
|
stats.push({ label: "Source", content: `${data.game.source} - ${data.game.source_id}` });
|
||||||
const integrations = new Set<string>(data.game.emulators?.flatMap(e => e.integrations).flatMap(i => i.capabilities).filter(c => !!c));
|
const integrations = new Set<string>(data.game.emulators?.flatMap(e => e.integrations).flatMap(i => i.capabilities).filter(c => !!c));
|
||||||
stats.push({ label: "Integrations", content: Array.from(integrations) });
|
stats.push({ label: "Integrations", content: Array.from(integrations) });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { DefaultRommStaleTime, GameListFilterType, RommLoginDataSchema } from "@/shared/constants";
|
import { DefaultRommStaleTime, GameListFilterType, RommLoginDataSchema } from "@/shared/constants";
|
||||||
import { rommApi, settingsApi } from "../clientApi";
|
import { rommApi, settingsApi } from "../clientApi";
|
||||||
import { mutationOptions, QueryFilters, queryOptions } from "@tanstack/react-query";
|
import { mutationOptions, QueryFilters, queryOptions, useMutation } from "@tanstack/react-query";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
import { statsApiStatsGetOptions } from "@/clients/romm/@tanstack/react-query.gen";
|
import { statsApiStatsGetOptions } from "@/clients/romm/@tanstack/react-query.gen";
|
||||||
|
|
||||||
|
|
@ -155,4 +155,19 @@ export const gameInvalidationQuery = (source: string, id: string): QueryFilters
|
||||||
if (query.queryKey.includes(source) && query.queryKey.includes(id)) return true;
|
if (query.queryKey.includes(source) && query.queryKey.includes(id)) return true;
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
export const validateSourceQuery = (source: string, id: string) => queryOptions({
|
||||||
|
queryKey: ["game", source, id, "validate"], queryFn: async () =>
|
||||||
|
{
|
||||||
|
const { data, error } = await rommApi.api.romm.game({ source })({ id }).validate.get();
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
export const fixSourceMutation = mutationOptions({
|
||||||
|
mutationKey: ['game', "fix_source"], mutationFn: async ({ source, id }: { source: string, id: string; }) =>
|
||||||
|
{
|
||||||
|
const { data, error } = await rommApi.api.romm.game({ source })({ id }).fix_source.post();
|
||||||
|
if (error) throw error;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue