From 3750e9ed8fc1c0919aade9e45a0189838f12b16d Mon Sep 17 00:00:00 2001 From: Simeon Radivoev Date: Sun, 22 Mar 2026 01:11:21 +0200 Subject: [PATCH] feat: Implemented emulator installation feat: Updated romm API version feat: Updated es-de rules feat: Added tabs to game details refactor: returned to global query definitions to help with typescript performance --- bun.lock | 5 + package.json | 7 +- scripts/dev.ts | 7 +- scripts/generate-es-de-mapping.ts | 6 +- scripts/romm/openapi.json | 2 +- src/bun/api/app.ts | 3 + src/bun/api/cache.ts | 11 + src/bun/api/games/games.ts | 469 ++++++--- src/bun/api/games/platforms.ts | 26 +- .../api/games/services/launchGameService.ts | 275 ++++-- src/bun/api/games/services/statusService.ts | 147 +-- src/bun/api/games/services/utils.ts | 133 ++- src/bun/api/jobs/emulator-download-job.ts | 105 ++ src/bun/api/jobs/install-job.ts | 280 +++--- src/bun/api/jobs/jobs.ts | 52 +- src/bun/api/jobs/login-job.ts | 6 +- src/bun/api/jobs/twitch-login-job.ts | 6 +- src/bun/api/jobs/update-store.ts | 10 +- src/bun/api/schema/emulators.ts | 1 + src/bun/api/settings/services.ts | 41 +- .../api/store/services/emulatorsService.ts | 31 + src/bun/api/store/services/gamesService.ts | 70 +- src/bun/api/store/store.ts | 167 ++-- src/bun/api/system.ts | 2 +- src/bun/api/task-queue.ts | 89 +- src/bun/index.ts | 6 +- src/bun/server.ts | 38 +- src/bun/types/types.d.ts | 2 +- src/bun/utils.ts | 44 +- src/bun/utils/downloader.ts | 222 +++++ src/clients/romm/@tanstack/react-query.gen.ts | 522 +++++++++- src/clients/romm/index.ts | 4 +- src/clients/romm/sdk.gen.ts | 321 ++++++- src/clients/romm/types.gen.ts | 900 +++++++++++++++++- src/mainview/components/CardElement.tsx | 5 +- src/mainview/components/CollectionList.tsx | 11 +- src/mainview/components/CollectionsDetail.tsx | 4 +- src/mainview/components/ContextDialog.tsx | 58 +- src/mainview/components/FilePicker.tsx | 8 +- src/mainview/components/Filters.tsx | 66 +- src/mainview/components/FocusDots.tsx | 3 +- src/mainview/components/FrontEndGameCard.tsx | 34 +- src/mainview/components/GameList.tsx | 11 +- src/mainview/components/Header.tsx | 32 +- src/mainview/components/LoadMoreButton.tsx | 3 +- src/mainview/components/Notifications.tsx | 9 +- src/mainview/components/PlatformsList.tsx | 4 +- src/mainview/components/Screenshots.tsx | 78 +- src/mainview/components/StatList.tsx | 50 + src/mainview/components/game/Achievements.tsx | 35 + .../options/DownloadDirectoryOption.tsx | 4 +- .../components/options/PathSettingsOption.tsx | 6 +- .../components/options/SettingsOption.tsx | 6 +- .../components/store/EmulatorsSection.tsx | 2 +- .../components/store/GamesSection.tsx | 44 +- .../components/store/StatsSection.tsx | 5 +- .../components/store/StoreEmulatorCard.tsx | 26 +- src/mainview/emulatorjs/emulator.ts | 1 + src/mainview/gen/static-icon-assets.gen.ts | 2 +- src/mainview/index.css | 23 + src/mainview/index.tsx | 37 +- src/mainview/routes/collection.$id.tsx | 4 +- src/mainview/routes/embedded.$source.$id.tsx | 6 +- src/mainview/routes/game/$source.$id.tsx | 464 ++++++--- src/mainview/routes/index.tsx | 47 +- src/mainview/routes/launcher.$source.$id.tsx | 6 +- src/mainview/routes/platform.$source.$id.tsx | 4 +- src/mainview/routes/settings/about.tsx | 5 +- src/mainview/routes/settings/accounts.tsx | 25 +- src/mainview/routes/settings/directories.tsx | 6 +- src/mainview/routes/settings/emulators.tsx | 16 +- src/mainview/routes/settings/route.tsx | 34 +- .../routes/store/details.emulator.$id.tsx | 299 ++++-- src/mainview/routes/store/tab/emulators.tsx | 4 +- src/mainview/routes/store/tab/games.tsx | 6 +- src/mainview/routes/store/tab/index.tsx | 32 +- src/mainview/routes/store/tab/route.tsx | 77 +- src/mainview/scripts/brandIcons.tsx | 4 +- src/mainview/scripts/contexts.ts | 4 + src/mainview/scripts/queries.ts | 11 - src/mainview/scripts/queries/romm.ts | 186 ++-- src/mainview/scripts/queries/settings.ts | 248 +++-- src/mainview/scripts/queries/store.ts | 126 +-- src/mainview/scripts/queries/system.ts | 92 +- src/mainview/scripts/spatialNavigation.ts | 42 +- src/mainview/scripts/types.ts | 4 +- src/mainview/scripts/utils.ts | 42 +- src/mainview/types.d.ts | 1 + src/shared/constants.ts | 76 +- tsconfig.json | 3 + vendors/es-de/emulators.darwin.x64.sqlite | Bin 176128 -> 180224 bytes vendors/es-de/emulators.haiku.x64.sqlite | Bin 135168 -> 135168 bytes vendors/es-de/emulators.linux.arm.sqlite | Bin 180224 -> 184320 bytes vendors/es-de/emulators.linux.x64.sqlite | Bin 217088 -> 221184 bytes vendors/es-de/emulators.win32.x64.sqlite | Bin 208896 -> 212992 bytes .../es-de/systems/android/es_find_rules.xml | 25 + vendors/es-de/systems/android/es_systems.xml | 19 +- vendors/es-de/systems/linux/es_systems.xml | 4 +- vendors/es-de/systems/linuxarm/es_systems.xml | 2 +- vendors/es-de/systems/macos/es_systems.xml | 5 +- vendors/es-de/systems/unix/es_systems.xml | 4 +- vendors/es-de/systems/windows/es_systems.xml | 6 +- vite.config.ts | 4 +- 103 files changed, 4888 insertions(+), 1632 deletions(-) create mode 100644 src/bun/api/jobs/emulator-download-job.ts create mode 100644 src/bun/api/store/services/emulatorsService.ts create mode 100644 src/bun/utils/downloader.ts create mode 100644 src/mainview/components/StatList.tsx create mode 100644 src/mainview/components/game/Achievements.tsx delete mode 100644 src/mainview/scripts/queries.ts diff --git a/bun.lock b/bun.lock index 9bf51a2..e4c0aff 100644 --- a/bun.lock +++ b/bun.lock @@ -5,6 +5,7 @@ "": { "name": "electrobun-hello-world", "dependencies": { + "7zip-min": "^3.0.1", "@auth/core": "^0.34.3", "@elysiajs/cors": "^1.4.1", "@elysiajs/eden": "^1.4.6", @@ -83,6 +84,10 @@ }, }, "packages": { + "7zip-bin": ["7zip-bin@5.1.1", "", {}, "sha512-sAP4LldeWNz0lNzmTird3uWfFDWWTeg6V/MsmyyLR9X1idwKBWIgt/ZvinqQldJm3LecKEs1emkbquO6PCiLVQ=="], + + "7zip-min": ["7zip-min@3.0.1", "", { "dependencies": { "7zip-bin": "5.1.1" } }, "sha512-WB4VCA/KSKzxhj+BAp8fI3ZYMMAftclkXlUTckuiDacsqyubQxxG3lGcpBcgzWWuJqnfQncEq1xrJpPLSxqsxw=="], + "@ap0nia/eden": ["@ap0nia/eden@1.0.0-next.22", "", { "peerDependencies": { "elysia": "^1.3.1" } }, "sha512-9iH09koK29Yuem80fz8nCt9iHVcJqxUo2QHAr4psI02PhvL70n6aWVo/hlHyYXwOSsSgRQlLl1vPmiulFOUFoA=="], "@ap0nia/eden-tanstack-query": ["@ap0nia/eden-tanstack-query@1.0.0-next.22", "", {}, "sha512-eSQ98G4TYzrAdsfRekrvqIrTqrAUFy+YpibZ5fj5KL6/R6FcrS2U2F51iML98baXT4MTpOJARY9p+7x0hiA8Qw=="], diff --git a/package.json b/package.json index 21b34f8..a0070eb 100644 --- a/package.json +++ b/package.json @@ -13,9 +13,9 @@ "packageManager": "bun@1.3.9", "type": "module", "scripts": { - "dev": "NODE_ENV=development bun run build:vite && bun run ./scripts/dev.ts", + "dev": " NODE_ENV=development bun run build:vite && conc 'bun run ./scripts/dev.ts'", "dev:hmr": "PUBLIC_ACCESS=true conc -k 'bun run hmr' 'bun run ./scripts/dev.ts'", - "build:vite": "vite build", + "build:vite": "bun run --bun vite build", "build:prod:vite": "NODE_ENV=production bun run build:vite", "build:dev:vite": "NODE_ENV=development bun run build:vite", "build": "bun run build:vite && bun run ./scripts/package-bun.ts", @@ -24,7 +24,7 @@ "build:linux": "TARGET=bun-linux-x64 bun run build", "openapi-ts": "bun run ./scripts/romm/openapi-ts.ts", "run:build-action": "act workflow_dispatch --artifact-server-path artifacts --env ACTIONS_RUNTIME_TOKEN=foo -W .forgejo/workflows/build.yml", - "hmr": "vite --port 5173", + "hmr": "bun run --bun vite --port 5173", "drizzle:generate": "bunx drizzle-kit generate", "test": "bun test", "mappings:generate": "bun run drizzle-kit generate --dialect=sqlite --schema=./src/bun/api/schema/emulators.ts --out=./scripts/drizzle/es-de && bun run ./scripts/generate-es-de-mapping.ts", @@ -40,6 +40,7 @@ "package:Windows": "bun run build:prod" }, "dependencies": { + "7zip-min": "^3.0.1", "@auth/core": "^0.34.3", "@elysiajs/cors": "^1.4.1", "@elysiajs/eden": "^1.4.6", diff --git a/scripts/dev.ts b/scripts/dev.ts index 96ac9f2..436c615 100644 --- a/scripts/dev.ts +++ b/scripts/dev.ts @@ -1,4 +1,3 @@ -// watcher.ts - run this instead of --watch import EventEmitter from "events"; import browser from '../src/bun/browser'; import { tmpdir } from "os"; @@ -13,9 +12,9 @@ let retries = 0; function spawnServer () { - return Bun.spawn(["bun", "run", '--watch', "--inspect=127.0.0.1:9228/fixed-session", "./src/bun/index.ts"], { + return Bun.spawn(["bun", '--watch', '--install=fallback', '--smol', "run", "--inspect=127.0.0.1:9228/fixed-session", "./src/bun/index.ts"], { env: { - ...Bun.env, + ...process.env, HEADLESS: "true", }, stdout: "inherit", @@ -50,7 +49,7 @@ function spawnBrowser () try { - return browser(events, Bun.env.FORCE_BROWSER === "true", { configPath: path.join(tmpdir(), 'gameflow') }); + return browser(events, process.env.FORCE_BROWSER === "true", { configPath: path.join(tmpdir(), 'gameflow') }); } catch (error) { console.error(error); diff --git a/scripts/generate-es-de-mapping.ts b/scripts/generate-es-de-mapping.ts index 3c0aa10..115c19f 100644 --- a/scripts/generate-es-de-mapping.ts +++ b/scripts/generate-es-de-mapping.ts @@ -6,8 +6,6 @@ import { Database } from "bun:sqlite"; import * as schema from '../src/bun/api/schema/emulators'; import { migrate } from "drizzle-orm/bun-sqlite/migrator"; import { drizzle } from "drizzle-orm/bun-sqlite"; -import path from 'node:path'; -import { ensureDir } from 'fs-extra'; /** get all latest supported romm platforms */ const rommPlatforms = await getSupportedPlatformsEndpointApiPlatformsSupportedGet({ baseUrl: "https://demo.romm.app" }); @@ -57,6 +55,7 @@ await Promise.all(platforms.map(async ([platform, arch]) => const emulators = $r('ruleList emulator').toArray().map(s => { const $emulator = $r(s); + const comment = $emulator.contents().toArray().find((node) => node.type === 'comment'); const $systempath = $emulator.find('rule[type=systempath] entry'); const $staticpath = $emulator.find('rule[type=staticpath] entry'); const $corepath = $emulator.find('rule[type=corepath] entry'); @@ -66,12 +65,14 @@ await Promise.all(platforms.map(async ([platform, arch]) => const emulatorName = $emulator.attr('name'); const emulator: typeof schema.emulators.$inferInsert = { name: emulatorName!, + fullname: comment?.data.trim(), systempath: $systempath.toArray().map(p => $r(p).text()), staticpath: $staticpath.toArray().map(p => $r(p).text()), corepath: $corepath.toArray().map(p => $r(p).text()), androidpackage: $androidpackage.toArray().map(p => $r(p).text()), winregistrypath: $winregistrypath.toArray().map(p => $r(p).text()), }; + return emulator; }); @@ -143,6 +144,7 @@ await Promise.all(platforms.map(async ([platform, arch]) => commands, mappings }; + return system; })); diff --git a/scripts/romm/openapi.json b/scripts/romm/openapi.json index 86d3921..4214bca 100644 --- a/scripts/romm/openapi.json +++ b/scripts/romm/openapi.json @@ -1 +1 @@ -{"openapi":"3.1.0","info":{"title":"RomM API","version":"4.6.1"},"paths":{"/api/heartbeat":{"get":{"tags":["system"],"summary":"Heartbeat","description":"Endpoint to set the CSRF token in cache and return all the basic RomM config\n\nReturns:\n HeartbeatReturn: TypedDict structure with all the defined values in the HeartbeatReturn class.","operationId":"heartbeat_api_heartbeat_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HeartbeatResponse"}}}}}}},"/api/heartbeat/metadata/{source}":{"get":{"tags":["system"],"summary":"Metadata Heartbeat","description":"Endpoint to return the heartbeat of the metadata sources","operationId":"metadata_heartbeat_api_heartbeat_metadata__source__get","parameters":[{"name":"source","in":"path","required":true,"schema":{"type":"string","title":"Source"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"boolean","title":"Response Metadata Heartbeat Api Heartbeat Metadata Source Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/setup/library":{"get":{"tags":["system"],"summary":"Get Setup Library Info","description":"Get library structure information for setup wizard.\n\nOnly accessible during initial setup (no admin users) or with authentication.\n\nReturns:\n - detected_structure: \"struct_a\" (roms/{platform}), \"struct_b\" ({platform}/roms), or None\n - existing_platforms: list of objects with fs_slug and rom_count\n - supported_platforms: list of all supported platforms with metadata","operationId":"get_setup_library_info_api_setup_library_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]},{"HTTPBasic":[]}]}},"/api/setup/platforms":{"post":{"tags":["system"],"summary":"Create Setup Platforms","description":"Create platform folders during setup wizard.\n\nOnly accessible during initial setup (no admin users) or with authentication.\n\nArgs:\n platform_slugs: List of platform fs_slugs to create\n\nReturns:\n - success: bool\n - created_count: number of platforms created\n - message: success or error message","operationId":"create_setup_platforms_api_setup_platforms_post","requestBody":{"content":{"application/json":{"schema":{"items":{"type":"string"},"type":"array","title":"Platform Slugs"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]},{"HTTPBasic":[]}]}},"/api/login":{"post":{"tags":["auth"],"summary":"Login","description":"Session login endpoint\n\nArgs:\n request (Request): Fastapi Request object\n credentials: Defaults to Depends(HTTPBasic()).\n\nRaises:\n CredentialsException: Invalid credentials\n UserDisabledException: Auth is disabled","operationId":"login_api_login_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBasic":[]}]}},"/api/logout":{"post":{"tags":["auth"],"summary":"Logout","description":"Session logout endpoint\n\nArgs:\n request (Request): Fastapi Request object","operationId":"logout_api_logout_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/token":{"post":{"tags":["auth"],"summary":"Token","description":"OAuth2 token endpoint\n\nArgs:\n form_data (Annotated[OAuth2RequestForm, Depends): Form Data with OAuth2 info\n\nRaises:\n HTTPException: Missing refresh token\n HTTPException: Invalid refresh token\n HTTPException: Missing username or password\n HTTPException: Invalid username or password\n HTTPException: Client credentials are not yet supported\n HTTPException: Invalid or unsupported grant type\n HTTPException: Insufficient scope\n\nReturns:\n TokenResponse: TypedDict with the new generated token info","operationId":"token_api_token_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_token_api_token_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/login/openid":{"get":{"tags":["auth"],"summary":"Login Via Openid","description":"OIDC login endpoint\n\nArgs:\n request (Request): Fastapi Request object\n\nRaises:\n OIDCDisabledException: OAuth is disabled\n OIDCNotConfiguredException: OAuth not configured\n\nReturns:\n RedirectResponse: Redirect to OIDC provider","operationId":"login_via_openid_api_login_openid_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/oauth/openid":{"get":{"tags":["auth"],"summary":"Auth Openid","description":"OIDC callback endpoint\n\nArgs:\n request (Request): Fastapi Request object\n\nRaises:\n OIDCDisabledException: OAuth is disabled\n OIDCNotConfiguredException: OAuth not configured\n AuthCredentialsException: Invalid credentials\n UserDisabledException: Auth is disabled\n\nReturns:\n RedirectResponse: Redirect to home page","operationId":"auth_openid_api_oauth_openid_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/forgot-password":{"post":{"tags":["auth"],"summary":"Request Password Reset","description":"Request a password reset link for the user.\n\nArgs:\n username (str): Username of the user requesting the reset\nReturns:\n None: Returns 200 OK status","operationId":"request_password_reset_api_forgot_password_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_request_password_reset_api_forgot_password_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/reset-password":{"post":{"tags":["auth"],"summary":"Reset Password","description":"Reset password using the token.\n\nArgs:\n token (str): Reset token from the URL\n new_password (str): New user password\n\nReturns:\n None: Returns 200 OK status","operationId":"reset_password_api_reset_password_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_reset_password_api_reset_password_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/users":{"get":{"tags":["users"],"summary":"Get Users","description":"Get all users endpoint\n\nArgs:\n request (Request): Fastapi Request object\n\nReturns:\n list[UserSchema]: All users stored in the RomM's database","operationId":"get_users_api_users_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/UserSchema"},"type":"array","title":"Response Get Users Api Users Get"}}}}},"security":[{"OAuth2PasswordBearer":["users.read"]},{"HTTPBasic":[]}]},"post":{"tags":["users"],"summary":"Add User","description":"Create user endpoint\n\nArgs:\n request (Request): Fastapi Requests object\n username (str): User username\n password (str): User password\n email (str): User email\n role (str): RomM Role object represented as string\n\nReturns:\n UserSchema: Newly created user","operationId":"add_user_api_users_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_add_user_api_users_post"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]},{"HTTPBasic":[]}]}},"/api/users/invite-link":{"post":{"tags":["users"],"summary":"Create Invite Link","description":"Create an invite link for a user.\n\nArgs:\n request (Request): FastAPI Request object\n role (str): The role of the user\n\nReturns:\n InviteLinkSchema: Invite link","operationId":"create_invite_link_api_users_invite_link_post","security":[{"OAuth2PasswordBearer":[]},{"HTTPBasic":[]}],"parameters":[{"name":"role","in":"query","required":true,"schema":{"type":"string","title":"Role"}}],"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InviteLinkSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/users/register":{"post":{"tags":["users"],"summary":"Create User From Invite","description":"Create user endpoint with invite link\n\nArgs:\n username (str): User username\n email (str): User email\n password (str): User password\n token (str): Invite link token\n\nReturns:\n UserSchema: Newly created user","operationId":"create_user_from_invite_api_users_register_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_create_user_from_invite_api_users_register_post"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/users/me":{"get":{"tags":["users"],"summary":"Get Current User","description":"Get current user endpoint\n\nArgs:\n request (Request): Fastapi Request object\n\nReturns:\n UserSchema | None: Current user","operationId":"get_current_user_api_users_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/UserSchema"},{"type":"null"}],"title":"Response Get Current User Api Users Me Get"}}}}},"security":[{"OAuth2PasswordBearer":["me.read"]},{"HTTPBasic":[]}]}},"/api/users/{id}":{"get":{"tags":["users"],"summary":"Get User","description":"Get user endpoint\n\nArgs:\n request (Request): Fastapi Request object\n\nReturns:\n UserSchem: User stored in the RomM's database","operationId":"get_user_api_users__id__get","security":[{"OAuth2PasswordBearer":["users.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","title":"Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["users"],"summary":"Update User","description":"Update user endpoint\n\nArgs:\n request (Request): Fastapi Requests object\n user_id (int): User internal id\n form_data (Annotated[UserUpdateForm, Depends): Form Data with user updated info\n\nRaises:\n HTTPException: User is not found in database\n HTTPException: Username already in use by another user\n\nReturns:\n UserSchema: Updated user info","operationId":"update_user_api_users__id__put","security":[{"OAuth2PasswordBearer":["me.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","title":"Id"}}],"requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/UserForm"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["users"],"summary":"Delete User","description":"Delete a user by ID.\n\nRaises:\n HTTPException: User is not found in database\n HTTPException: User deleting itself\n HTTPException: User is the last admin user","operationId":"delete_user_api_users__id__delete","security":[{"OAuth2PasswordBearer":["users.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"User internal id.","title":"Id"},"description":"User internal id."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"400":{"description":"Bad Request"},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/users/{id}/ra/refresh":{"post":{"tags":["users"],"summary":"Refresh RetroAchievements","description":"Refresh RetroAchievements progression data for a user.","operationId":"refresh_retro_achievements_api_users__id__ra_refresh_post","security":[{"OAuth2PasswordBearer":["me.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"User internal id.","title":"Id"},"description":"User internal id."}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_refresh_retro_achievements_api_users__id__ra_refresh_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/platforms":{"post":{"tags":["platforms"],"summary":"Add Platform","description":"Create a platform.","operationId":"add_platform_api_platforms_post","security":[{"OAuth2PasswordBearer":["platforms.write"]},{"HTTPBasic":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_add_platform_api_platforms_post"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlatformSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["platforms"],"summary":"Get Platforms","description":"Retrieve platforms.","operationId":"get_platforms_api_platforms_get","security":[{"OAuth2PasswordBearer":["platforms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"updated_after","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Filter platforms updated after this datetime (ISO 8601 format with timezone information).","title":"Updated After"},"description":"Filter platforms updated after this datetime (ISO 8601 format with timezone information)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PlatformSchema"},"title":"Response Get Platforms Api Platforms Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/platforms/supported":{"get":{"tags":["platforms"],"summary":"Get Supported Platforms Endpoint","description":"Retrieve the list of supported platforms.","operationId":"get_supported_platforms_endpoint_api_platforms_supported_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/PlatformSchema"},"type":"array","title":"Response Get Supported Platforms Endpoint Api Platforms Supported Get"}}}}},"security":[{"OAuth2PasswordBearer":["platforms.read"]},{"HTTPBasic":[]}]}},"/api/platforms/{id}":{"get":{"tags":["platforms"],"summary":"Get Platform","description":"Retrieve a platform by ID.","operationId":"get_platform_api_platforms__id__get","security":[{"OAuth2PasswordBearer":["platforms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Platform id.","title":"Id"},"description":"Platform id."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlatformSchema"}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["platforms"],"summary":"Update Platform","description":"Update a platform.","operationId":"update_platform_api_platforms__id__put","security":[{"OAuth2PasswordBearer":["platforms.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Platform id.","title":"Id"},"description":"Platform id."}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_update_platform_api_platforms__id__put"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlatformSchema"}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["platforms"],"summary":"Delete Platform","description":"Delete a platform by ID.","operationId":"delete_platform_api_platforms__id__delete","security":[{"OAuth2PasswordBearer":["platforms.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Platform id.","title":"Id"},"description":"Platform id."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/roms":{"post":{"tags":["roms"],"summary":"Add Rom","description":"Upload a single rom.","operationId":"add_rom_api_roms_post","security":[{"OAuth2PasswordBearer":["roms.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"x-upload-platform","in":"header","required":true,"schema":{"type":"integer","minimum":1,"description":"Platform internal id.","title":"X-Upload-Platform"},"description":"Platform internal id."},{"name":"x-upload-filename","in":"header","required":true,"schema":{"type":"string","description":"The name of the file being uploaded.","title":"X-Upload-Filename"},"description":"The name of the file being uploaded."}],"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"400":{"description":"Bad Request"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["roms"],"summary":"Get Roms","description":"Retrieve roms.","operationId":"get_roms_api_roms_get","security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"with_char_index","in":"query","required":false,"schema":{"type":"boolean","description":"Whether to get the char index.","default":true,"title":"With Char Index"},"description":"Whether to get the char index."},{"name":"with_filter_values","in":"query","required":false,"schema":{"type":"boolean","description":"Whether to return filter values.","default":true,"title":"With Filter Values"},"description":"Whether to return filter values."},{"name":"search_term","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search term to filter roms.","title":"Search Term"},"description":"Search term to filter roms."},{"name":"platform_ids","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"integer"}},{"type":"null"}],"description":"Platform internal ids. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned.","title":"Platform Ids"},"description":"Platform internal ids. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned."},{"name":"collection_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}],"description":"Collection internal id.","title":"Collection Id"},"description":"Collection internal id."},{"name":"virtual_collection_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Virtual collection internal id.","title":"Virtual Collection Id"},"description":"Virtual collection internal id."},{"name":"smart_collection_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}],"description":"Smart collection internal id.","title":"Smart Collection Id"},"description":"Smart collection internal id."},{"name":"matched","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Whether the rom matched at least one metadata source.","title":"Matched"},"description":"Whether the rom matched at least one metadata source."},{"name":"favorite","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Whether the rom is marked as favorite.","title":"Favorite"},"description":"Whether the rom is marked as favorite."},{"name":"duplicate","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Whether the rom is marked as duplicate.","title":"Duplicate"},"description":"Whether the rom is marked as duplicate."},{"name":"last_played","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Whether the rom has a last played value for the current user.","title":"Last Played"},"description":"Whether the rom has a last played value for the current user."},{"name":"playable","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Whether the rom is playable from the browser.","title":"Playable"},"description":"Whether the rom is playable from the browser."},{"name":"missing","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Whether the rom is missing from the filesystem.","title":"Missing"},"description":"Whether the rom is missing from the filesystem."},{"name":"has_ra","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Whether the rom has RetroAchievements data.","title":"Has Ra"},"description":"Whether the rom has RetroAchievements data."},{"name":"verified","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Whether the rom is verified by Hasheous.","title":"Verified"},"description":"Whether the rom is verified by Hasheous."},{"name":"group_by_meta_id","in":"query","required":false,"schema":{"type":"boolean","description":"Whether to group roms by metadata ID (IGDB / Moby / ScreenScraper / RetroAchievements / LaunchBox).","default":false,"title":"Group By Meta Id"},"description":"Whether to group roms by metadata ID (IGDB / Moby / ScreenScraper / RetroAchievements / LaunchBox)."},{"name":"genres","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Associated genre. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned.","title":"Genres"},"description":"Associated genre. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned."},{"name":"franchises","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Associated franchise. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned.","title":"Franchises"},"description":"Associated franchise. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned."},{"name":"collections","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Associated collection. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned.","title":"Collections"},"description":"Associated collection. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned."},{"name":"companies","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Associated company. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned.","title":"Companies"},"description":"Associated company. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned."},{"name":"age_ratings","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Associated age rating. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned.","title":"Age Ratings"},"description":"Associated age rating. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned."},{"name":"statuses","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Game status, set by the current user. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned.","title":"Statuses"},"description":"Game status, set by the current user. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned."},{"name":"regions","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Associated region tag. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned.","title":"Regions"},"description":"Associated region tag. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned."},{"name":"languages","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Associated language tag. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned.","title":"Languages"},"description":"Associated language tag. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned."},{"name":"player_counts","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Associated player count. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned.","title":"Player Counts"},"description":"Associated player count. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned."},{"name":"genres_logic","in":"query","required":false,"schema":{"type":"string","description":"Logic operator for genres filter: 'any' (OR), 'all' (AND) or 'none' (NOT).","default":"any","title":"Genres Logic"},"description":"Logic operator for genres filter: 'any' (OR), 'all' (AND) or 'none' (NOT)."},{"name":"franchises_logic","in":"query","required":false,"schema":{"type":"string","description":"Logic operator for franchises filter: 'any' (OR), 'all' (AND) or 'none' (NOT).","default":"any","title":"Franchises Logic"},"description":"Logic operator for franchises filter: 'any' (OR), 'all' (AND) or 'none' (NOT)."},{"name":"collections_logic","in":"query","required":false,"schema":{"type":"string","description":"Logic operator for collections filter: 'any' (OR), 'all' (AND) or 'none' (NOT).","default":"any","title":"Collections Logic"},"description":"Logic operator for collections filter: 'any' (OR), 'all' (AND) or 'none' (NOT)."},{"name":"companies_logic","in":"query","required":false,"schema":{"type":"string","description":"Logic operator for companies filter: 'any' (OR), 'all' (AND) or 'none' (NOT).","default":"any","title":"Companies Logic"},"description":"Logic operator for companies filter: 'any' (OR), 'all' (AND) or 'none' (NOT)."},{"name":"age_ratings_logic","in":"query","required":false,"schema":{"type":"string","description":"Logic operator for age ratings filter: 'any' (OR), 'all' (AND) or 'none' (NOT).","default":"any","title":"Age Ratings Logic"},"description":"Logic operator for age ratings filter: 'any' (OR), 'all' (AND) or 'none' (NOT)."},{"name":"regions_logic","in":"query","required":false,"schema":{"type":"string","description":"Logic operator for regions filter: 'any' (OR), 'all' (AND) or 'none' (NOT).","default":"any","title":"Regions Logic"},"description":"Logic operator for regions filter: 'any' (OR), 'all' (AND) or 'none' (NOT)."},{"name":"languages_logic","in":"query","required":false,"schema":{"type":"string","description":"Logic operator for languages filter: 'any' (OR), 'all' (AND) or 'none' (NOT).","default":"any","title":"Languages Logic"},"description":"Logic operator for languages filter: 'any' (OR), 'all' (AND) or 'none' (NOT)."},{"name":"statuses_logic","in":"query","required":false,"schema":{"type":"string","description":"Logic operator for statuses filter: 'any' (OR), 'all' (AND) or 'none' (NOT).","default":"any","title":"Statuses Logic"},"description":"Logic operator for statuses filter: 'any' (OR), 'all' (AND) or 'none' (NOT)."},{"name":"player_counts_logic","in":"query","required":false,"schema":{"type":"string","description":"Logic operator for player counts filter: 'any' (OR), 'all' (AND) or 'none' (NOT).","default":"any","title":"Player Counts Logic"},"description":"Logic operator for player counts filter: 'any' (OR), 'all' (AND) or 'none' (NOT)."},{"name":"order_by","in":"query","required":false,"schema":{"type":"string","description":"Field to order results by.","default":"name","title":"Order By"},"description":"Field to order results by."},{"name":"order_dir","in":"query","required":false,"schema":{"type":"string","description":"Order direction, either 'asc' or 'desc'.","default":"asc","title":"Order Dir"},"description":"Order direction, either 'asc' or 'desc'."},{"name":"updated_after","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Filter roms updated after this datetime (ISO 8601 format with timezone information).","title":"Updated After"},"description":"Filter roms updated after this datetime (ISO 8601 format with timezone information)."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":10000,"minimum":1,"description":"Page size limit","default":50,"title":"Limit"},"description":"Page size limit"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Page offset","default":0,"title":"Offset"},"description":"Page offset"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomLimitOffsetPage_SimpleRomSchema_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/roms/download":{"get":{"tags":["roms"],"summary":"Download Roms","description":"Download a list of roms as a zip file.","operationId":"download_roms_api_roms_download_get","security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"rom_ids","in":"query","required":true,"schema":{"type":"string","description":"Comma-separated list of ROM IDs to download as a zip file.","title":"Rom Ids"},"description":"Comma-separated list of ROM IDs to download as a zip file."},{"name":"filename","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Name for the zip file (optional).","title":"Filename"},"description":"Name for the zip file (optional)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/roms/by-metadata-provider":{"get":{"tags":["roms"],"summary":"Get Rom By Metadata Provider","description":"Retrieve a rom by metadata ID.","operationId":"get_rom_by_metadata_provider_api_roms_by_metadata_provider_get","security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"igdb_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"IGDB ID to search by","title":"Igdb Id"},"description":"IGDB ID to search by"},{"name":"moby_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"MobyGames ID to search by","title":"Moby Id"},"description":"MobyGames ID to search by"},{"name":"ss_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"ScreenScraper ID to search by","title":"Ss Id"},"description":"ScreenScraper ID to search by"},{"name":"ra_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"RetroAchievements ID to search by","title":"Ra Id"},"description":"RetroAchievements ID to search by"},{"name":"launchbox_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"LaunchBox ID to search by","title":"Launchbox Id"},"description":"LaunchBox ID to search by"},{"name":"hasheous_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Hasheous ID to search by","title":"Hasheous Id"},"description":"Hasheous ID to search by"},{"name":"tgdb_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"TGDB ID to search by","title":"Tgdb Id"},"description":"TGDB ID to search by"},{"name":"flashpoint_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Flashpoint ID to search by","title":"Flashpoint Id"},"description":"Flashpoint ID to search by"},{"name":"hltb_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"HLTB ID to search by","title":"Hltb Id"},"description":"HLTB ID to search by"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DetailedRomSchema"}}}},"404":{"description":"Not Found"},"400":{"description":"Bad Request"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/roms/by-hash":{"get":{"tags":["roms"],"summary":"Get Rom By Hash","operationId":"get_rom_by_hash_api_roms_by_hash_get","security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"crc_hash","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"CRC hash value","title":"Crc Hash"},"description":"CRC hash value"},{"name":"md5_hash","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"MD5 hash value","title":"Md5 Hash"},"description":"MD5 hash value"},{"name":"sha1_hash","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"SHA1 hash value","title":"Sha1 Hash"},"description":"SHA1 hash value"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DetailedRomSchema"}}}},"404":{"description":"Not Found"},"400":{"description":"Bad Request"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/roms/filters":{"get":{"tags":["roms"],"summary":"Get Rom Filters","operationId":"get_rom_filters_api_roms_filters_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RomFiltersDict"}}}}},"security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}]}},"/api/roms/{id}":{"get":{"tags":["roms"],"summary":"Get Rom","description":"Retrieve a rom by ID.","operationId":"get_rom_api_roms__id__get","security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Rom internal id.","title":"Id"},"description":"Rom internal id."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DetailedRomSchema"}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["roms"],"summary":"Update Rom","description":"Update a rom.","operationId":"update_rom_api_roms__id__put","security":[{"OAuth2PasswordBearer":["roms.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Rom internal id.","title":"Id"},"description":"Rom internal id."},{"name":"remove_cover","in":"query","required":false,"schema":{"type":"boolean","description":"Whether to remove the cover image for this rom.","default":false,"title":"Remove Cover"},"description":"Whether to remove the cover image for this rom."},{"name":"unmatch_metadata","in":"query","required":false,"schema":{"type":"boolean","description":"Whether to remove the metadata matches for this game.","default":false,"title":"Unmatch Metadata"},"description":"Whether to remove the metadata matches for this game."}],"requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_update_rom_api_roms__id__put"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DetailedRomSchema"}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/roms/{id}/content/{file_name}":{"head":{"tags":["roms"],"summary":"Head Rom Content","description":"Retrieve head information for a rom file download.","operationId":"head_rom_content_api_roms__id__content__file_name__head","security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Rom internal id.","title":"Id"},"description":"Rom internal id."},{"name":"file_name","in":"path","required":true,"schema":{"type":"string","description":"File name to download","title":"File Name"},"description":"File name to download"},{"name":"file_ids","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated list of file ids to download for multi-part roms.","title":"File Ids"},"description":"Comma-separated list of file ids to download for multi-part roms."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["roms"],"summary":"Get Rom Content","description":"Download a rom.\n\nThis endpoint serves the content of the requested rom, as:\n- A single file for single file roms.\n- A zipped file for multi-part roms, including a .m3u file if applicable.","operationId":"get_rom_content_api_roms__id__content__file_name__get","security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Rom internal id.","title":"Id"},"description":"Rom internal id."},{"name":"file_name","in":"path","required":true,"schema":{"type":"string","description":"Zip file output name","title":"File Name"},"description":"Zip file output name"},{"name":"file_ids","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated list of file ids to download for multi-part roms.","title":"File Ids"},"description":"Comma-separated list of file ids to download for multi-part roms."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/roms/{id}/manuals":{"post":{"tags":["roms"],"summary":"Add Rom Manuals","description":"Upload manuals for a rom.","operationId":"add_rom_manuals_api_roms__id__manuals_post","security":[{"OAuth2PasswordBearer":["roms.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Rom internal id.","title":"Id"},"description":"Rom internal id."},{"name":"x-upload-filename","in":"header","required":true,"schema":{"type":"string","description":"The name of the file being uploaded.","title":"X-Upload-Filename"},"description":"The name of the file being uploaded."}],"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["roms"],"summary":"Delete Rom Manuals","description":"Delete manuals for a rom.","operationId":"delete_rom_manuals_api_roms__id__manuals_delete","security":[{"OAuth2PasswordBearer":["roms.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Rom internal id.","title":"Id"},"description":"Rom internal id."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/roms/delete":{"post":{"tags":["roms"],"summary":"Delete Roms","description":"Delete roms.","operationId":"delete_roms_api_roms_delete_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_delete_roms_api_roms_delete_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkOperationResponse"}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":["roms.write"]},{"HTTPBasic":[]}]}},"/api/roms/{id}/props":{"put":{"tags":["roms"],"summary":"Update Rom User","description":"Update rom data associated to the current user.","operationId":"update_rom_user_api_roms__id__props_put","security":[{"OAuth2PasswordBearer":["roms.user.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Rom internal id.","title":"Id"},"description":"Rom internal id."}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_update_rom_user_api_roms__id__props_put"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RomUserSchema"}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/roms/files/{id}":{"get":{"tags":["roms"],"summary":"Get Romfile","description":"Retrieve a rom file by ID.","operationId":"get_romfile_api_roms_files__id__get","security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Rom file internal id.","title":"Id"},"description":"Rom file internal id."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RomFileSchema"}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/romsfiles/{id}/content/{file_name}":{"get":{"tags":["roms"],"summary":"Get Romfile Content","description":"Download a rom file.","operationId":"get_romfile_content_api_romsfiles__id__content__file_name__get","security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Rom file internal id.","title":"Id"},"description":"Rom file internal id."},{"name":"file_name","in":"path","required":true,"schema":{"type":"string","description":"File name to download","title":"File Name"},"description":"File name to download"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/roms/{id}/notes":{"get":{"tags":["roms"],"summary":"Get Rom Notes","description":"Get all notes for a ROM.","operationId":"get_rom_notes_api_roms__id__notes_get","security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Rom internal id.","title":"Id"},"description":"Rom internal id."},{"name":"public_only","in":"query","required":false,"schema":{"type":"boolean","description":"Only return public notes","default":false,"title":"Public Only"},"description":"Only return public notes"},{"name":"search","in":"query","required":false,"schema":{"type":"string","description":"Search notes by title or content","title":"Search"},"description":"Search notes by title or content"},{"name":"tags","in":"query","required":false,"schema":{"type":"array","items":{"type":"string"},"description":"Filter by tags","title":"Tags"},"description":"Filter by tags"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UserNoteSchema"},"title":"Response Get Rom Notes Api Roms Id Notes Get"}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["roms"],"summary":"Create Rom Note","description":"Create a new note for a ROM.","operationId":"create_rom_note_api_roms__id__notes_post","security":[{"OAuth2PasswordBearer":["roms.user.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Rom internal id.","title":"Id"},"description":"Rom internal id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Note Data"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserNoteSchema"}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/roms/{id}/notes/{note_id}":{"put":{"tags":["roms"],"summary":"Update Rom Note","description":"Update a ROM note.","operationId":"update_rom_note_api_roms__id__notes__note_id__put","security":[{"OAuth2PasswordBearer":["roms.user.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Rom internal id.","title":"Id"},"description":"Rom internal id."},{"name":"note_id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Note id.","title":"Note Id"},"description":"Note id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Note Data"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserNoteSchema"}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["roms"],"summary":"Delete Rom Note","description":"Delete a ROM note.","operationId":"delete_rom_note_api_roms__id__notes__note_id__delete","security":[{"OAuth2PasswordBearer":["roms.user.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Rom internal id.","title":"Id"},"description":"Rom internal id."},{"name":"note_id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Note id.","title":"Note Id"},"description":"Note id."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Rom Note Api Roms Id Notes Note Id Delete"}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/search/roms":{"get":{"tags":["search"],"summary":"Search Rom","description":"Search for rom in metadata providers\n\nArgs:\n request (Request): FastAPI request\n rom_id (int): Rom ID\n source (str): Source of the rom\n search_term (str, optional): Search term. Defaults to None.\n search_by (str, optional): Search by name or ID. Defaults to \"name\".\n search_extended (bool, optional): Search extended info. Defaults to False.\n\nReturns:\n list[SearchRomSchema]: List of matched roms","operationId":"search_rom_api_search_roms_get","security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"rom_id","in":"query","required":true,"schema":{"type":"integer","title":"Rom Id"}},{"name":"search_term","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Search Term"}},{"name":"search_by","in":"query","required":false,"schema":{"type":"string","default":"name","title":"Search By"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SearchRomSchema"},"title":"Response Search Rom Api Search Roms Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/search/cover":{"get":{"tags":["search"],"summary":"Search Cover","operationId":"search_cover_api_search_cover_get","security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"search_term","in":"query","required":false,"schema":{"type":"string","default":"","title":"Search Term"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SearchCoverSchema"},"title":"Response Search Cover Api Search Cover Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/saves":{"post":{"tags":["saves"],"summary":"Add Save","operationId":"add_save_api_saves_post","security":[{"OAuth2PasswordBearer":["assets.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"rom_id","in":"query","required":true,"schema":{"type":"integer","title":"Rom Id"}},{"name":"emulator","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Emulator"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SaveSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["saves"],"summary":"Get Saves","operationId":"get_saves_api_saves_get","security":[{"OAuth2PasswordBearer":["assets.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"rom_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rom Id"}},{"name":"platform_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Platform Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SaveSchema"},"title":"Response Get Saves Api Saves Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/saves/{id}":{"get":{"tags":["saves"],"summary":"Get Save","operationId":"get_save_api_saves__id__get","security":[{"OAuth2PasswordBearer":["assets.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","title":"Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SaveSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["saves"],"summary":"Update Save","operationId":"update_save_api_saves__id__put","security":[{"OAuth2PasswordBearer":["assets.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","title":"Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SaveSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/saves/delete":{"post":{"tags":["saves"],"summary":"Delete Saves","description":"Delete saves.","operationId":"delete_saves_api_saves_delete_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_delete_saves_api_saves_delete_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"type":"integer"},"type":"array","title":"Response Delete Saves Api Saves Delete Post"}}}},"400":{"description":"Bad Request"},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":["assets.write"]},{"HTTPBasic":[]}]}},"/api/states":{"post":{"tags":["states"],"summary":"Add State","operationId":"add_state_api_states_post","security":[{"OAuth2PasswordBearer":["assets.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"rom_id","in":"query","required":true,"schema":{"type":"integer","title":"Rom Id"}},{"name":"emulator","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Emulator"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StateSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["states"],"summary":"Get States","operationId":"get_states_api_states_get","security":[{"OAuth2PasswordBearer":["assets.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"rom_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rom Id"}},{"name":"platform_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Platform Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/StateSchema"},"title":"Response Get States Api States Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/states/{id}":{"get":{"tags":["states"],"summary":"Get State","operationId":"get_state_api_states__id__get","security":[{"OAuth2PasswordBearer":["assets.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","title":"Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StateSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["states"],"summary":"Update State","operationId":"update_state_api_states__id__put","security":[{"OAuth2PasswordBearer":["assets.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","title":"Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StateSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/states/delete":{"post":{"tags":["states"],"summary":"Delete States","description":"Delete states.","operationId":"delete_states_api_states_delete_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_delete_states_api_states_delete_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"type":"integer"},"type":"array","title":"Response Delete States Api States Delete Post"}}}},"400":{"description":"Bad Request"},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":["assets.write"]},{"HTTPBasic":[]}]}},"/api/tasks":{"get":{"tags":["tasks"],"summary":"List Tasks","description":"List all available tasks grouped by task type.\n\nArgs:\n request (Request): FastAPI Request object\nReturns:\n GroupedTasksDict: Dictionary with tasks grouped by their type (scheduled, manual, watcher)","operationId":"list_tasks_api_tasks_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"items":{"$ref":"#/components/schemas/TaskInfo"},"type":"array"},"type":"object","title":"Response List Tasks Api Tasks Get"}}}}},"security":[{"OAuth2PasswordBearer":["tasks.run"]},{"HTTPBasic":[]}]}},"/api/tasks/status":{"get":{"tags":["tasks"],"summary":"Get Tasks Status","description":"Get all active, queued, completed, and failed tasks.\n\nArgs:\n request (Request): FastAPI Request object\nReturns:\n list[TaskStatusResponse]: List of all tasks with their current status","operationId":"get_tasks_status_api_tasks_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"anyOf":[{"$ref":"#/components/schemas/ScanTaskStatusResponse"},{"$ref":"#/components/schemas/ConversionTaskStatusResponse"},{"$ref":"#/components/schemas/UpdateTaskStatusResponse"},{"$ref":"#/components/schemas/CleanupTaskStatusResponse"},{"$ref":"#/components/schemas/WatcherTaskStatusResponse"},{"$ref":"#/components/schemas/GenericTaskStatusResponse"}]},"type":"array","title":"Response Get Tasks Status Api Tasks Status Get"}}}}},"security":[{"OAuth2PasswordBearer":["tasks.run"]},{"HTTPBasic":[]}]}},"/api/tasks/{task_id}":{"get":{"tags":["tasks"],"summary":"Get Task By Id","description":"Get the status of a task by its job ID.\n\nArgs:\n request (Request): FastAPI Request object\n task_id (str): Job ID of the task to retrieve status for\nReturns:\n TaskStatusResponse: Task status information","operationId":"get_task_by_id_api_tasks__task_id__get","security":[{"OAuth2PasswordBearer":["tasks.run"]},{"HTTPBasic":[]}],"parameters":[{"name":"task_id","in":"path","required":true,"schema":{"type":"string","title":"Task Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/ScanTaskStatusResponse"},{"$ref":"#/components/schemas/ConversionTaskStatusResponse"},{"$ref":"#/components/schemas/UpdateTaskStatusResponse"},{"$ref":"#/components/schemas/CleanupTaskStatusResponse"},{"$ref":"#/components/schemas/WatcherTaskStatusResponse"},{"$ref":"#/components/schemas/GenericTaskStatusResponse"}],"title":"Response Get Task By Id Api Tasks Task Id Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/tasks/run":{"post":{"tags":["tasks"],"summary":"Run All Tasks","description":"Run all runnable tasks endpoint\n\nArgs:\n request (Request): FastAPI Request object\nReturns:\n TaskExecutionResponse: Task execution response with details","operationId":"run_all_tasks_api_tasks_run_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/TaskExecutionResponse"},"type":"array","title":"Response Run All Tasks Api Tasks Run Post"}}}}},"security":[{"OAuth2PasswordBearer":["tasks.run"]},{"HTTPBasic":[]}]}},"/api/tasks/run/{task_name}":{"post":{"tags":["tasks"],"summary":"Run Single Task","description":"Run a single task endpoint.\n\nArgs:\n request (Request): FastAPI Request object\n task_name (str): Name of the task to run\nReturns:\n TaskExecutionResponse: Task execution response with details","operationId":"run_single_task_api_tasks_run__task_name__post","security":[{"OAuth2PasswordBearer":["tasks.run"]},{"HTTPBasic":[]}],"parameters":[{"name":"task_name","in":"path","required":true,"schema":{"type":"string","title":"Task Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskExecutionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/feeds/webrcade":{"get":{"tags":["feeds"],"summary":"Platforms Webrcade Feed","description":"Get webrcade feed endpoint\nhttps://docs.webrcade.com/feeds/format/\n\nArgs:\n request (Request): Fastapi Request object\n\nReturns:\n WebrcadeFeedSchema: Webrcade feed object schema","operationId":"platforms_webrcade_feed_api_feeds_webrcade_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebrcadeFeedSchema"}}}}},"security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}]}},"/api/feeds/tinfoil":{"get":{"tags":["feeds"],"summary":"Tinfoil Index Feed","description":"Get tinfoil custom index feed endpoint\nhttps://blawar.github.io/tinfoil/custom_index/\n\nArgs:\n request (Request): Fastapi Request object\n slug (str, optional): Platform slug. Defaults to \"switch\".\n\nReturns:\n TinfoilFeedSchema: Tinfoil feed object schema","operationId":"tinfoil_index_feed_api_feeds_tinfoil_get","security":[{"OAuth2PasswordBearer":[]},{"HTTPBasic":[]}],"parameters":[{"name":"slug","in":"query","required":false,"schema":{"type":"string","default":"switch","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TinfoilFeedSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/feeds/pkgi/ps3/{content_type}":{"get":{"tags":["feeds"],"summary":"Pkgi Ps3 Feed","description":"Get PKGi PS3 feed endpoint\nhttps://github.com/bucanero/pkgi-ps3\n\nArgs:\n request (Request): Fastapi Request object\n content_type (str): Content type (game, dlc, demo, update, patch, mod, translation, prototype)\n\nReturns:\n Response: txt file with PKGi PS3 database format","operationId":"pkgi_ps3_feed_api_feeds_pkgi_ps3__content_type__get","security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"content_type","in":"path","required":true,"schema":{"type":"string","description":"Content type","title":"Content Type"},"description":"Content type"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/feeds/pkgi/psvita/{content_type}":{"get":{"tags":["feeds"],"summary":"Pkgi Psvita Feed","description":"Get PKGi PS Vita feed endpoint\nhttps://github.com/mmozeiko/pkgi\n\nArgs:\n request (Request): Fastapi Request object\n content_type (str): Content type (game, dlc, demo, update, patch, mod, translation, prototype)\n\nReturns:\n Response: txt file with PKGi PS Vita database format","operationId":"pkgi_psvita_feed_api_feeds_pkgi_psvita__content_type__get","security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"content_type","in":"path","required":true,"schema":{"type":"string","description":"Content type","title":"Content Type"},"description":"Content type"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/feeds/pkgi/psp/{content_type}":{"get":{"tags":["feeds"],"summary":"Pkgi Psp Feed","description":"Get PKGi PSP feed endpoint\nhttps://github.com/bucanero/pkgi-psp\n\nArgs:\n request (Request): Fastapi Request object\n content_type (str): Content type (game, dlc, demo, update, patch, mod, translation, prototype)\n\nReturns:\n Response: txt file with PKGi PSP database format","operationId":"pkgi_psp_feed_api_feeds_pkgi_psp__content_type__get","security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"content_type","in":"path","required":true,"schema":{"type":"string","description":"Content type","title":"Content Type"},"description":"Content type"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/feeds/fpkgi/{platform_slug}":{"get":{"tags":["feeds"],"summary":"Fpkgi Feed","description":"https://github.com/ItsJokerZz/FPKGi\n\nArgs:\n request (Request): Fastapi Request object\n platform_slug (str): Platform slug (ps4, ps5)\n\nReturns:\n Response: JSON file in FPKGi format","operationId":"fpkgi_feed_api_feeds_fpkgi__platform_slug__get","security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"platform_slug","in":"path","required":true,"schema":{"type":"string","title":"Platform Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/feeds/kekatsu/{platform_slug}":{"get":{"tags":["feeds"],"summary":"Kekatsu Ds Feed","description":"Get Kekatsu DS feed endpoint\nhttps://github.com/cavv-dev/Kekatsu-DS\n\nArgs:\n request (Request): Fastapi Request object\n platform_slug (str): Platform slug (nds, nintendo-ds, ds, gba, etc.)\n\nReturns:\n Response: Text file with Kekatsu DS database format","operationId":"kekatsu_ds_feed_api_feeds_kekatsu__platform_slug__get","security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"platform_slug","in":"path","required":true,"schema":{"type":"string","title":"Platform Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/config":{"get":{"tags":["config"],"summary":"Get Config","description":"Get config endpoint\n\nReturns:\n ConfigResponse: RomM's configuration","operationId":"get_config_api_config_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfigResponse"}}}}}}},"/api/config/system/platforms":{"post":{"tags":["config"],"summary":"Add Platform Binding","description":"Add platform binding to the configuration","operationId":"add_platform_binding_api_config_system_platforms_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":["platforms.write"]},{"HTTPBasic":[]}]}},"/api/config/system/platforms/{fs_slug}":{"delete":{"tags":["config"],"summary":"Delete Platform Binding","description":"Delete platform binding from the configuration","operationId":"delete_platform_binding_api_config_system_platforms__fs_slug__delete","security":[{"OAuth2PasswordBearer":["platforms.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"fs_slug","in":"path","required":true,"schema":{"type":"string","title":"Fs Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/config/system/versions":{"post":{"tags":["config"],"summary":"Add Platform Version","description":"Add platform version to the configuration","operationId":"add_platform_version_api_config_system_versions_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":["platforms.write"]},{"HTTPBasic":[]}]}},"/api/config/system/versions/{fs_slug}":{"delete":{"tags":["config"],"summary":"Delete Platform Version","description":"Delete platform version from the configuration","operationId":"delete_platform_version_api_config_system_versions__fs_slug__delete","security":[{"OAuth2PasswordBearer":["platforms.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"fs_slug","in":"path","required":true,"schema":{"type":"string","title":"Fs Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/config/exclude":{"post":{"tags":["config"],"summary":"Add Exclusion","description":"Add platform exclusion to the configuration","operationId":"add_exclusion_api_config_exclude_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":["platforms.write"]},{"HTTPBasic":[]}]}},"/api/config/exclude/{exclusion_type}/{exclusion_value}":{"delete":{"tags":["config"],"summary":"Delete Exclusion","description":"Delete platform binding from the configuration","operationId":"delete_exclusion_api_config_exclude__exclusion_type___exclusion_value__delete","security":[{"OAuth2PasswordBearer":["platforms.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"exclusion_type","in":"path","required":true,"schema":{"type":"string","title":"Exclusion Type"}},{"name":"exclusion_value","in":"path","required":true,"schema":{"type":"string","title":"Exclusion Value"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/stats":{"get":{"tags":["stats"],"summary":"Stats","description":"Endpoint to return the current RomM stats\n\nReturns:\n dict: Dictionary with all the stats","operationId":"stats_api_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatsReturn"}}}}}}},"/api/raw/assets/{path}":{"head":{"tags":["raw"],"summary":"Head Raw Asset","operationId":"head_raw_asset_api_raw_assets__path__head","security":[{"OAuth2PasswordBearer":["assets.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"path","in":"path","required":true,"schema":{"type":"string","title":"Path"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["raw"],"summary":"Get Raw Asset","description":"Download a single asset file\n\nArgs:\n request (Request): Fastapi Request object\n path (str): Relative path to the asset file\n\nReturns:\n FileResponse: Returns a single asset file\n\nRaises:\n HTTPException: 404 if asset not found or access denied","operationId":"get_raw_asset_api_raw_assets__path__get","security":[{"OAuth2PasswordBearer":["assets.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"path","in":"path","required":true,"schema":{"type":"string","title":"Path"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/screenshots":{"post":{"tags":["screenshots"],"summary":"Add Screenshot","operationId":"add_screenshot_api_screenshots_post","security":[{"OAuth2PasswordBearer":["assets.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"rom_id","in":"query","required":true,"schema":{"type":"integer","title":"Rom Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScreenshotSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/firmware":{"post":{"tags":["firmware"],"summary":"Add Firmware","description":"Upload firmware files endpoint\n\nArgs:\n request (Request): Fastapi Request object\n platform_slug (str): Slug of the platform where to upload the files\n files (list[UploadFile], optional): List of files to upload\n\nRaises:\n HTTPException\n\nReturns:\n AddFirmwareResponse: Standard message response","operationId":"add_firmware_api_firmware_post","security":[{"OAuth2PasswordBearer":["firmware.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"platform_id","in":"query","required":true,"schema":{"type":"integer","title":"Platform Id"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_add_firmware_api_firmware_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddFirmwareResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["firmware"],"summary":"Get Platform Firmware","description":"Get firmware endpoint\n\nArgs:\n request (Request): Fastapi Request object\n\nReturns:\n list[FirmwareSchema]: Firmware stored in the database","operationId":"get_platform_firmware_api_firmware_get","security":[{"OAuth2PasswordBearer":["firmware.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"platform_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Platform Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/FirmwareSchema"},"title":"Response Get Platform Firmware Api Firmware Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/firmware/{id}":{"get":{"tags":["firmware"],"summary":"Get Firmware","description":"Get firmware endpoint\n\nArgs:\n request (Request): Fastapi Request object\n id (int): Firmware internal id\n\nReturns:\n FirmwareSchema: Firmware stored in the database","operationId":"get_firmware_api_firmware__id__get","security":[{"OAuth2PasswordBearer":["firmware.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","title":"Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FirmwareSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/firmware/{id}/content/{file_name}":{"head":{"tags":["firmware"],"summary":"Head Firmware Content","description":"Head firmware content endpoint\n\nArgs:\n request (Request): Fastapi Request object\n id (int): Rom internal id\n file_name (str): Required due to a bug in emulatorjs\n\nReturns:\n FileResponse: Returns the response with headers","operationId":"head_firmware_content_api_firmware__id__content__file_name__head","security":[{"OAuth2PasswordBearer":["firmware.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","title":"Id"}},{"name":"file_name","in":"path","required":true,"schema":{"type":"string","title":"File Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["firmware"],"summary":"Get Firmware Content","description":"Download firmware endpoint\n\nArgs:\n request (Request): Fastapi Request object\n id (int): Rom internal id\n file_name (str): Required due to a bug in emulatorjs\n\nReturns:\n FileResponse: Returns the firmware file","operationId":"get_firmware_content_api_firmware__id__content__file_name__get","security":[{"OAuth2PasswordBearer":["firmware.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","title":"Id"}},{"name":"file_name","in":"path","required":true,"schema":{"type":"string","title":"File Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/firmware/delete":{"post":{"tags":["firmware"],"summary":"Delete Firmware","description":"Delete firmware.","operationId":"delete_firmware_api_firmware_delete_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_delete_firmware_api_firmware_delete_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkOperationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":["firmware.write"]},{"HTTPBasic":[]}]}},"/api/collections":{"post":{"tags":["collections"],"summary":"Add Collection","description":"Create collection endpoint\n\nArgs:\n request (Request): Fastapi Request object\n\nReturns:\n CollectionSchema: Just created collection","operationId":"add_collection_api_collections_post","security":[{"OAuth2PasswordBearer":["collections.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"is_public","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Public"}},{"name":"is_favorite","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Favorite"}}],"requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_add_collection_api_collections_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["collections"],"summary":"Get Collections","description":"Get collections endpoint\n\nArgs:\n request (Request): Fastapi Request object\n updated_after: Filter collections updated after this datetime\n\nReturns:\n list[CollectionSchema]: List of collections","operationId":"get_collections_api_collections_get","security":[{"OAuth2PasswordBearer":["collections.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"updated_after","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Filter collections updated after this datetime (ISO 8601 format with timezone information).","title":"Updated After"},"description":"Filter collections updated after this datetime (ISO 8601 format with timezone information)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CollectionSchema"},"title":"Response Get Collections Api Collections Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/collections/smart":{"post":{"tags":["collections"],"summary":"Add Smart Collection","description":"Create smart collection endpoint\n\nArgs:\n request (Request): Fastapi Request object\n\nReturns:\n SmartCollectionSchema: Just created smart collection","operationId":"add_smart_collection_api_collections_smart_post","security":[{"OAuth2PasswordBearer":["collections.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"is_public","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Public"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SmartCollectionSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["collections"],"summary":"Get Smart Collections","description":"Get smart collections endpoint\n\nArgs:\n request (Request): Fastapi Request object\n updated_after: Filter smart collections updated after this datetime\n\nReturns:\n list[SmartCollectionSchema]: List of smart collections","operationId":"get_smart_collections_api_collections_smart_get","security":[{"OAuth2PasswordBearer":["collections.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"updated_after","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Filter smart collections updated after this datetime (ISO 8601 format with timezone information).","title":"Updated After"},"description":"Filter smart collections updated after this datetime (ISO 8601 format with timezone information)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SmartCollectionSchema"},"title":"Response Get Smart Collections Api Collections Smart Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/collections/virtual":{"get":{"tags":["collections"],"summary":"Get Virtual Collections","description":"Get virtual collections endpoint\n\nArgs:\n request (Request): Fastapi Request object\n\nReturns:\n list[VirtualCollectionSchema]: List of virtual collections","operationId":"get_virtual_collections_api_collections_virtual_get","security":[{"OAuth2PasswordBearer":["collections.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"type","in":"query","required":true,"schema":{"type":"string","title":"Type"}},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/VirtualCollectionSchema"},"title":"Response Get Virtual Collections Api Collections Virtual Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/collections/{id}":{"get":{"tags":["collections"],"summary":"Get Collection","description":"Get collections endpoint\n\nArgs:\n request (Request): Fastapi Request object\n id (int, optional): Collection id. Defaults to None.\n\nReturns:\n CollectionSchema: Collection","operationId":"get_collection_api_collections__id__get","security":[{"OAuth2PasswordBearer":["collections.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","title":"Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["collections"],"summary":"Update Collection","description":"Update collection endpoint\n\nArgs:\n request (Request): Fastapi Request object\n\nReturns:\n CollectionSchema: Updated collection","operationId":"update_collection_api_collections__id__put","security":[{"OAuth2PasswordBearer":["collections.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","title":"Id"}},{"name":"remove_cover","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Remove Cover"}},{"name":"is_public","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Public"}}],"requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_update_collection_api_collections__id__put"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["collections"],"summary":"Delete Collection","description":"Delete a collection by ID.","operationId":"delete_collection_api_collections__id__delete","security":[{"OAuth2PasswordBearer":["collections.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Collection internal id.","title":"Id"},"description":"Collection internal id."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/collections/virtual/{id}":{"get":{"tags":["collections"],"summary":"Get Virtual Collection","description":"Get virtual collections endpoint\n\nArgs:\n request (Request): Fastapi Request object\n id (str): Virtual collection id\n\nReturns:\n VirtualCollectionSchema: Virtual collection","operationId":"get_virtual_collection_api_collections_virtual__id__get","security":[{"OAuth2PasswordBearer":["collections.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","title":"Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VirtualCollectionSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/collections/smart/{id}":{"get":{"tags":["collections"],"summary":"Get Smart Collection","description":"Get smart collection endpoint\n\nArgs:\n request (Request): Fastapi Request object\n id (int): Smart collection id\n\nReturns:\n SmartCollectionSchema: Smart collection","operationId":"get_smart_collection_api_collections_smart__id__get","security":[{"OAuth2PasswordBearer":["collections.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","title":"Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SmartCollectionSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["collections"],"summary":"Update Smart Collection","description":"Update smart collection endpoint\n\nArgs:\n request (Request): Fastapi Request object\n id (int): Smart collection id\n\nReturns:\n SmartCollectionSchema: Updated smart collection","operationId":"update_smart_collection_api_collections_smart__id__put","security":[{"OAuth2PasswordBearer":["collections.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","title":"Id"}},{"name":"is_public","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Public"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SmartCollectionSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["collections"],"summary":"Delete Smart Collection","description":"Delete a smart collection by ID.","operationId":"delete_smart_collection_api_collections_smart__id__delete","security":[{"OAuth2PasswordBearer":["collections.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Smart collection internal id.","title":"Id"},"description":"Smart collection internal id."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/gamelist/export":{"post":{"tags":["gamelist"],"summary":"Export Gamelist","description":"Export platforms/ROMs to gamelist.xml format and write to platform directories","operationId":"export_gamelist_api_gamelist_export_post","security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"platform_ids","in":"query","required":true,"schema":{"type":"array","items":{"type":"integer"},"description":"List of platform IDs to export","title":"Platform Ids"},"description":"List of platform IDs to export"},{"name":"local_export","in":"query","required":false,"schema":{"type":"boolean","description":"Use local paths instead of URLs","default":false,"title":"Local Export"},"description":"Use local paths instead of URLs"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/netplay/list":{"get":{"tags":["netplay"],"summary":"Get Rooms","operationId":"get_rooms_api_netplay_list_get","security":[{"OAuth2PasswordBearer":["assets.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"game_id","in":"query","required":true,"schema":{"type":"string","title":"Game Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/RoomsResponse"},"title":"Response Get Rooms Api Netplay List Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"AddFirmwareResponse":{"properties":{"uploaded":{"type":"integer","title":"Uploaded"},"firmware":{"items":{"$ref":"#/components/schemas/FirmwareSchema"},"type":"array","title":"Firmware"}},"type":"object","required":["uploaded","firmware"],"title":"AddFirmwareResponse"},"Body_add_collection_api_collections_post":{"properties":{"artwork":{"anyOf":[{"type":"string","format":"binary"},{"type":"null"}],"title":"Artwork"}},"type":"object","title":"Body_add_collection_api_collections_post"},"Body_add_firmware_api_firmware_post":{"properties":{"files":{"items":{"type":"string","format":"binary"},"type":"array","title":"Files"}},"type":"object","required":["files"],"title":"Body_add_firmware_api_firmware_post"},"Body_add_platform_api_platforms_post":{"properties":{"fs_slug":{"type":"string","title":"Fs Slug","description":"Platform slug."}},"type":"object","required":["fs_slug"],"title":"Body_add_platform_api_platforms_post"},"Body_add_user_api_users_post":{"properties":{"username":{"type":"string","title":"Username"},"email":{"type":"string","title":"Email"},"password":{"type":"string","title":"Password"},"role":{"type":"string","title":"Role"}},"type":"object","required":["username","email","password","role"],"title":"Body_add_user_api_users_post"},"Body_create_user_from_invite_api_users_register_post":{"properties":{"username":{"type":"string","title":"Username"},"email":{"type":"string","title":"Email"},"password":{"type":"string","title":"Password"},"token":{"type":"string","title":"Token"}},"type":"object","required":["username","email","password","token"],"title":"Body_create_user_from_invite_api_users_register_post"},"Body_delete_firmware_api_firmware_delete_post":{"properties":{"firmware":{"items":{"type":"integer"},"type":"array","title":"Firmware","description":"List of firmware ids to delete from database."},"delete_from_fs":{"items":{"type":"integer"},"type":"array","title":"Delete From Fs","description":"List of firmware ids to delete from filesystem."}},"type":"object","required":["firmware"],"title":"Body_delete_firmware_api_firmware_delete_post"},"Body_delete_roms_api_roms_delete_post":{"properties":{"roms":{"items":{"type":"integer"},"type":"array","title":"Roms","description":"List of rom ids to delete from database."},"delete_from_fs":{"items":{"type":"integer"},"type":"array","title":"Delete From Fs","description":"List of rom ids to delete from filesystem."}},"type":"object","required":["roms"],"title":"Body_delete_roms_api_roms_delete_post"},"Body_delete_saves_api_saves_delete_post":{"properties":{"saves":{"items":{"type":"integer"},"type":"array","title":"Saves","description":"List of save ids to delete from database."}},"type":"object","required":["saves"],"title":"Body_delete_saves_api_saves_delete_post"},"Body_delete_states_api_states_delete_post":{"properties":{"states":{"items":{"type":"integer"},"type":"array","title":"States","description":"List of states ids to delete from database."}},"type":"object","required":["states"],"title":"Body_delete_states_api_states_delete_post"},"Body_refresh_retro_achievements_api_users__id__ra_refresh_post":{"properties":{"incremental":{"type":"boolean","title":"Incremental","description":"Whether to only retrieve RetroAchievements progression incrementally.","default":false}},"type":"object","title":"Body_refresh_retro_achievements_api_users__id__ra_refresh_post"},"Body_request_password_reset_api_forgot_password_post":{"properties":{"username":{"type":"string","title":"Username"}},"type":"object","required":["username"],"title":"Body_request_password_reset_api_forgot_password_post"},"Body_reset_password_api_reset_password_post":{"properties":{"token":{"type":"string","title":"Token"},"new_password":{"type":"string","title":"New Password"}},"type":"object","required":["token","new_password"],"title":"Body_reset_password_api_reset_password_post"},"Body_token_api_token_post":{"properties":{"grant_type":{"type":"string","title":"Grant Type","default":"password"},"scope":{"type":"string","title":"Scope","default":""},"username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Username"},"password":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Password"},"client_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id"},"client_secret":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Secret"},"refresh_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Refresh Token"}},"type":"object","title":"Body_token_api_token_post"},"Body_update_collection_api_collections__id__put":{"properties":{"artwork":{"anyOf":[{"type":"string","format":"binary"},{"type":"null"}],"title":"Artwork"}},"type":"object","title":"Body_update_collection_api_collections__id__put"},"Body_update_platform_api_platforms__id__put":{"properties":{"aspect_ratio":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Aspect Ratio","description":"Cover aspect ratio."},"custom_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Custom Name","description":"Custom platform name."}},"type":"object","title":"Body_update_platform_api_platforms__id__put"},"Body_update_rom_api_roms__id__put":{"properties":{"artwork":{"anyOf":[{"type":"string","format":"binary"},{"type":"null"}],"title":"Artwork","description":"Custom artwork to set as cover."}},"type":"object","title":"Body_update_rom_api_roms__id__put"},"Body_update_rom_user_api_roms__id__props_put":{"properties":{"update_last_played":{"type":"boolean","title":"Update Last Played","description":"Whether to update the last played date.","default":false},"remove_last_played":{"type":"boolean","title":"Remove Last Played","description":"Whether to remove the last played date.","default":false}},"type":"object","title":"Body_update_rom_user_api_roms__id__props_put"},"BulkOperationResponse":{"properties":{"successful_items":{"type":"integer","title":"Successful Items"},"failed_items":{"type":"integer","title":"Failed Items"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"}},"type":"object","required":["successful_items","failed_items","errors"],"title":"BulkOperationResponse"},"CleanupStats":{"properties":{"platforms_in_db":{"type":"integer","title":"Platforms In Db"},"roms_in_db":{"type":"integer","title":"Roms In Db"},"platforms_in_fs":{"type":"integer","title":"Platforms In Fs"},"roms_in_fs":{"type":"integer","title":"Roms In Fs"},"removed_fs_platforms":{"type":"integer","title":"Removed Fs Platforms"},"removed_fs_roms":{"type":"integer","title":"Removed Fs Roms"}},"type":"object","required":["platforms_in_db","roms_in_db","platforms_in_fs","roms_in_fs","removed_fs_platforms","removed_fs_roms"],"title":"CleanupStats"},"CleanupTaskMeta":{"properties":{"cleanup_stats":{"anyOf":[{"$ref":"#/components/schemas/CleanupStats"},{"type":"null"}]}},"type":"object","required":["cleanup_stats"],"title":"CleanupTaskMeta"},"CleanupTaskStatusResponse":{"properties":{"task_name":{"type":"string","title":"Task Name"},"task_id":{"type":"string","title":"Task Id"},"status":{"$ref":"#/components/schemas/JobStatus"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"enqueued_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Enqueued At"},"started_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Started At"},"ended_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ended At"},"task_type":{"type":"string","const":"cleanup","title":"Task Type"},"meta":{"$ref":"#/components/schemas/CleanupTaskMeta"}},"type":"object","required":["task_name","task_id","status","created_at","enqueued_at","started_at","ended_at","task_type","meta"],"title":"CleanupTaskStatusResponse"},"CollectionSchema":{"properties":{"name":{"type":"string","title":"Name"},"description":{"type":"string","title":"Description"},"rom_ids":{"items":{"type":"integer"},"type":"array","uniqueItems":true,"title":"Rom Ids"},"rom_count":{"type":"integer","title":"Rom Count"},"path_cover_small":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path Cover Small"},"path_cover_large":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path Cover Large"},"path_covers_small":{"items":{"type":"string"},"type":"array","title":"Path Covers Small"},"path_covers_large":{"items":{"type":"string"},"type":"array","title":"Path Covers Large"},"is_public":{"type":"boolean","title":"Is Public","default":false},"is_favorite":{"type":"boolean","title":"Is Favorite","default":false},"is_virtual":{"type":"boolean","title":"Is Virtual","default":false},"is_smart":{"type":"boolean","title":"Is Smart","default":false},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"id":{"type":"integer","title":"Id"},"url_cover":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url Cover"},"user_id":{"type":"integer","title":"User Id"},"user__username":{"type":"string","title":"User Username"}},"type":"object","required":["name","description","rom_ids","rom_count","path_cover_small","path_cover_large","path_covers_small","path_covers_large","created_at","updated_at","id","url_cover","user_id","user__username"],"title":"CollectionSchema"},"ConfigResponse":{"properties":{"CONFIG_FILE_MOUNTED":{"type":"boolean","title":"Config File Mounted"},"CONFIG_FILE_WRITABLE":{"type":"boolean","title":"Config File Writable"},"EXCLUDED_PLATFORMS":{"items":{"type":"string"},"type":"array","title":"Excluded Platforms"},"EXCLUDED_SINGLE_EXT":{"items":{"type":"string"},"type":"array","title":"Excluded Single Ext"},"EXCLUDED_SINGLE_FILES":{"items":{"type":"string"},"type":"array","title":"Excluded Single Files"},"EXCLUDED_MULTI_FILES":{"items":{"type":"string"},"type":"array","title":"Excluded Multi Files"},"EXCLUDED_MULTI_PARTS_EXT":{"items":{"type":"string"},"type":"array","title":"Excluded Multi Parts Ext"},"EXCLUDED_MULTI_PARTS_FILES":{"items":{"type":"string"},"type":"array","title":"Excluded Multi Parts Files"},"PLATFORMS_BINDING":{"additionalProperties":{"type":"string"},"type":"object","title":"Platforms Binding"},"PLATFORMS_VERSIONS":{"additionalProperties":{"type":"string"},"type":"object","title":"Platforms Versions"},"SKIP_HASH_CALCULATION":{"type":"boolean","title":"Skip Hash Calculation"},"EJS_DEBUG":{"type":"boolean","title":"Ejs Debug"},"EJS_CACHE_LIMIT":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ejs Cache Limit"},"EJS_DISABLE_AUTO_UNLOAD":{"type":"boolean","title":"Ejs Disable Auto Unload"},"EJS_DISABLE_BATCH_BOOTUP":{"type":"boolean","title":"Ejs Disable Batch Bootup"},"EJS_NETPLAY_ENABLED":{"type":"boolean","title":"Ejs Netplay Enabled"},"EJS_NETPLAY_ICE_SERVERS":{"items":{"$ref":"#/components/schemas/NetplayICEServer"},"type":"array","title":"Ejs Netplay Ice Servers"},"EJS_SETTINGS":{"additionalProperties":{"additionalProperties":{"type":"string"},"type":"object"},"type":"object","title":"Ejs Settings"},"EJS_CONTROLS":{"additionalProperties":{"$ref":"#/components/schemas/EjsControls"},"type":"object","title":"Ejs Controls"},"SCAN_METADATA_PRIORITY":{"items":{"type":"string"},"type":"array","title":"Scan Metadata Priority"},"SCAN_ARTWORK_PRIORITY":{"items":{"type":"string"},"type":"array","title":"Scan Artwork Priority"},"SCAN_REGION_PRIORITY":{"items":{"type":"string"},"type":"array","title":"Scan Region Priority"},"SCAN_LANGUAGE_PRIORITY":{"items":{"type":"string"},"type":"array","title":"Scan Language Priority"},"SCAN_MEDIA":{"items":{"type":"string"},"type":"array","title":"Scan Media"}},"type":"object","required":["CONFIG_FILE_MOUNTED","CONFIG_FILE_WRITABLE","EXCLUDED_PLATFORMS","EXCLUDED_SINGLE_EXT","EXCLUDED_SINGLE_FILES","EXCLUDED_MULTI_FILES","EXCLUDED_MULTI_PARTS_EXT","EXCLUDED_MULTI_PARTS_FILES","PLATFORMS_BINDING","PLATFORMS_VERSIONS","SKIP_HASH_CALCULATION","EJS_DEBUG","EJS_CACHE_LIMIT","EJS_DISABLE_AUTO_UNLOAD","EJS_DISABLE_BATCH_BOOTUP","EJS_NETPLAY_ENABLED","EJS_NETPLAY_ICE_SERVERS","EJS_SETTINGS","EJS_CONTROLS","SCAN_METADATA_PRIORITY","SCAN_ARTWORK_PRIORITY","SCAN_REGION_PRIORITY","SCAN_LANGUAGE_PRIORITY","SCAN_MEDIA"],"title":"ConfigResponse"},"ConversionStats":{"properties":{"processed":{"type":"integer","title":"Processed"},"errors":{"type":"integer","title":"Errors"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["processed","errors","total"],"title":"ConversionStats"},"ConversionTaskMeta":{"properties":{"conversion_stats":{"anyOf":[{"$ref":"#/components/schemas/ConversionStats"},{"type":"null"}]}},"type":"object","required":["conversion_stats"],"title":"ConversionTaskMeta"},"ConversionTaskStatusResponse":{"properties":{"task_name":{"type":"string","title":"Task Name"},"task_id":{"type":"string","title":"Task Id"},"status":{"$ref":"#/components/schemas/JobStatus"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"enqueued_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Enqueued At"},"started_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Started At"},"ended_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ended At"},"task_type":{"type":"string","const":"conversion","title":"Task Type"},"meta":{"$ref":"#/components/schemas/ConversionTaskMeta"}},"type":"object","required":["task_name","task_id","status","created_at","enqueued_at","started_at","ended_at","task_type","meta"],"title":"ConversionTaskStatusResponse"},"CustomLimitOffsetPage_SimpleRomSchema_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/SimpleRomSchema"},"type":"array","title":"Items"},"total":{"type":"integer","minimum":0.0,"title":"Total"},"limit":{"type":"integer","minimum":1.0,"title":"Limit"},"offset":{"type":"integer","minimum":0.0,"title":"Offset"},"char_index":{"additionalProperties":{"type":"integer"},"type":"object","title":"Char Index"},"rom_id_index":{"items":{"type":"integer"},"type":"array","title":"Rom Id Index"},"filter_values":{"$ref":"#/components/schemas/RomFiltersDict"}},"type":"object","required":["items","total","limit","offset","char_index","rom_id_index","filter_values"],"title":"CustomLimitOffsetPage[SimpleRomSchema]"},"DetailedRomSchema":{"properties":{"id":{"type":"integer","title":"Id"},"igdb_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Igdb Id"},"sgdb_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sgdb Id"},"moby_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Moby Id"},"ss_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ss Id"},"ra_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ra Id"},"launchbox_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Launchbox Id"},"hasheous_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Hasheous Id"},"tgdb_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Tgdb Id"},"flashpoint_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Flashpoint Id"},"hltb_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Hltb Id"},"gamelist_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gamelist Id"},"platform_id":{"type":"integer","title":"Platform Id"},"platform_slug":{"type":"string","title":"Platform Slug"},"platform_fs_slug":{"type":"string","title":"Platform Fs Slug"},"platform_custom_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Platform Custom Name"},"platform_display_name":{"type":"string","title":"Platform Display Name"},"fs_name":{"type":"string","title":"Fs Name"},"fs_name_no_tags":{"type":"string","title":"Fs Name No Tags"},"fs_name_no_ext":{"type":"string","title":"Fs Name No Ext"},"fs_extension":{"type":"string","title":"Fs Extension"},"fs_path":{"type":"string","title":"Fs Path"},"fs_size_bytes":{"type":"integer","title":"Fs Size Bytes"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slug"},"summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Summary"},"alternative_names":{"items":{"type":"string"},"type":"array","title":"Alternative Names"},"youtube_video_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Youtube Video Id"},"metadatum":{"$ref":"#/components/schemas/RomMetadataSchema"},"igdb_metadata":{"anyOf":[{"$ref":"#/components/schemas/RomIGDBMetadata"},{"type":"null"}]},"moby_metadata":{"anyOf":[{"$ref":"#/components/schemas/RomMobyMetadata"},{"type":"null"}]},"ss_metadata":{"anyOf":[{"$ref":"#/components/schemas/RomSSMetadata"},{"type":"null"}]},"launchbox_metadata":{"anyOf":[{"$ref":"#/components/schemas/RomLaunchboxMetadata"},{"type":"null"}]},"hasheous_metadata":{"anyOf":[{"$ref":"#/components/schemas/RomHasheousMetadata"},{"type":"null"}]},"flashpoint_metadata":{"anyOf":[{"$ref":"#/components/schemas/RomFlashpointMetadata"},{"type":"null"}]},"hltb_metadata":{"anyOf":[{"$ref":"#/components/schemas/RomHLTBMetadata"},{"type":"null"}]},"gamelist_metadata":{"anyOf":[{"$ref":"#/components/schemas/RomGamelistMetadata"},{"type":"null"}]},"manual_metadata":{"anyOf":[{"$ref":"#/components/schemas/ManualMetadata"},{"type":"null"}]},"path_cover_small":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path Cover Small"},"path_cover_large":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path Cover Large"},"url_cover":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url Cover"},"has_manual":{"type":"boolean","title":"Has Manual"},"path_manual":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path Manual"},"url_manual":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url Manual"},"is_identifying":{"type":"boolean","title":"Is Identifying","default":false},"is_unidentified":{"type":"boolean","title":"Is Unidentified"},"is_identified":{"type":"boolean","title":"Is Identified"},"revision":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Revision"},"regions":{"items":{"type":"string"},"type":"array","title":"Regions"},"languages":{"items":{"type":"string"},"type":"array","title":"Languages"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags"},"crc_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Crc Hash"},"md5_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Md5 Hash"},"sha1_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sha1 Hash"},"has_simple_single_file":{"type":"boolean","title":"Has Simple Single File"},"has_nested_single_file":{"type":"boolean","title":"Has Nested Single File"},"has_multiple_files":{"type":"boolean","title":"Has Multiple Files"},"files":{"items":{"$ref":"#/components/schemas/RomFileSchema"},"type":"array","title":"Files"},"full_path":{"type":"string","title":"Full Path"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"missing_from_fs":{"type":"boolean","title":"Missing From Fs"},"has_notes":{"type":"boolean","title":"Has Notes"},"siblings":{"items":{"$ref":"#/components/schemas/SiblingRomSchema"},"type":"array","title":"Siblings"},"rom_user":{"$ref":"#/components/schemas/RomUserSchema"},"merged_screenshots":{"items":{"type":"string"},"type":"array","title":"Merged Screenshots"},"merged_ra_metadata":{"anyOf":[{"$ref":"#/components/schemas/RomRAMetadata"},{"type":"null"}]},"user_saves":{"items":{"$ref":"#/components/schemas/SaveSchema"},"type":"array","title":"User Saves"},"user_states":{"items":{"$ref":"#/components/schemas/StateSchema"},"type":"array","title":"User States"},"user_screenshots":{"items":{"$ref":"#/components/schemas/ScreenshotSchema"},"type":"array","title":"User Screenshots"},"user_collections":{"items":{"$ref":"#/components/schemas/UserCollectionSchema"},"type":"array","title":"User Collections"},"all_user_notes":{"items":{"$ref":"#/components/schemas/UserNoteSchema"},"type":"array","title":"All User Notes"}},"type":"object","required":["id","igdb_id","sgdb_id","moby_id","ss_id","ra_id","launchbox_id","hasheous_id","tgdb_id","flashpoint_id","hltb_id","gamelist_id","platform_id","platform_slug","platform_fs_slug","platform_custom_name","platform_display_name","fs_name","fs_name_no_tags","fs_name_no_ext","fs_extension","fs_path","fs_size_bytes","name","slug","summary","alternative_names","youtube_video_id","metadatum","igdb_metadata","moby_metadata","ss_metadata","launchbox_metadata","hasheous_metadata","flashpoint_metadata","hltb_metadata","gamelist_metadata","manual_metadata","path_cover_small","path_cover_large","url_cover","has_manual","path_manual","url_manual","is_unidentified","is_identified","revision","regions","languages","tags","crc_hash","md5_hash","sha1_hash","has_simple_single_file","has_nested_single_file","has_multiple_files","files","full_path","created_at","updated_at","missing_from_fs","has_notes","siblings","rom_user","merged_screenshots","merged_ra_metadata","user_saves","user_states","user_screenshots","user_collections","all_user_notes"],"title":"DetailedRomSchema"},"EarnedAchievement":{"properties":{"id":{"type":"string","title":"Id"},"date":{"type":"string","title":"Date"},"date_hardcore":{"type":"string","title":"Date Hardcore"}},"type":"object","required":["id","date"],"title":"EarnedAchievement"},"EjsControls":{"properties":{"_0":{"additionalProperties":{"$ref":"#/components/schemas/EjsControlsButton"},"type":"object","title":"0"},"_1":{"additionalProperties":{"$ref":"#/components/schemas/EjsControlsButton"},"type":"object","title":"1"},"_2":{"additionalProperties":{"$ref":"#/components/schemas/EjsControlsButton"},"type":"object","title":"2"},"_3":{"additionalProperties":{"$ref":"#/components/schemas/EjsControlsButton"},"type":"object","title":"3"}},"type":"object","required":["_0","_1","_2","_3"],"title":"EjsControls"},"EjsControlsButton":{"properties":{"value":{"type":"string","title":"Value"},"value2":{"type":"string","title":"Value2"}},"type":"object","title":"EjsControlsButton"},"EmulationDict":{"properties":{"DISABLE_EMULATOR_JS":{"type":"boolean","title":"Disable Emulator Js"},"DISABLE_RUFFLE_RS":{"type":"boolean","title":"Disable Ruffle Rs"}},"type":"object","required":["DISABLE_EMULATOR_JS","DISABLE_RUFFLE_RS"],"title":"EmulationDict"},"FilesystemDict":{"properties":{"FS_PLATFORMS":{"items":{"type":"string"},"type":"array","title":"Fs Platforms"}},"type":"object","required":["FS_PLATFORMS"],"title":"FilesystemDict"},"FirmwareSchema":{"properties":{"id":{"type":"integer","title":"Id"},"file_name":{"type":"string","title":"File Name"},"file_name_no_tags":{"type":"string","title":"File Name No Tags"},"file_name_no_ext":{"type":"string","title":"File Name No Ext"},"file_extension":{"type":"string","title":"File Extension"},"file_path":{"type":"string","title":"File Path"},"file_size_bytes":{"type":"integer","title":"File Size Bytes"},"full_path":{"type":"string","title":"Full Path"},"is_verified":{"type":"boolean","title":"Is Verified"},"crc_hash":{"type":"string","title":"Crc Hash"},"md5_hash":{"type":"string","title":"Md5 Hash"},"sha1_hash":{"type":"string","title":"Sha1 Hash"},"missing_from_fs":{"type":"boolean","title":"Missing From Fs"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","file_name","file_name_no_tags","file_name_no_ext","file_extension","file_path","file_size_bytes","full_path","is_verified","crc_hash","md5_hash","sha1_hash","missing_from_fs","created_at","updated_at"],"title":"FirmwareSchema"},"FrontendDict":{"properties":{"UPLOAD_TIMEOUT":{"type":"integer","title":"Upload Timeout"},"DISABLE_USERPASS_LOGIN":{"type":"boolean","title":"Disable Userpass Login"},"YOUTUBE_BASE_URL":{"type":"string","title":"Youtube Base Url"}},"type":"object","required":["UPLOAD_TIMEOUT","DISABLE_USERPASS_LOGIN","YOUTUBE_BASE_URL"],"title":"FrontendDict"},"GenericTaskMeta":{"properties":{},"type":"object","title":"GenericTaskMeta"},"GenericTaskStatusResponse":{"properties":{"task_name":{"type":"string","title":"Task Name"},"task_id":{"type":"string","title":"Task Id"},"status":{"$ref":"#/components/schemas/JobStatus"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"enqueued_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Enqueued At"},"started_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Started At"},"ended_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ended At"},"task_type":{"type":"string","const":"generic","title":"Task Type"},"meta":{"$ref":"#/components/schemas/GenericTaskMeta"}},"type":"object","required":["task_name","task_id","status","created_at","enqueued_at","started_at","ended_at","task_type","meta"],"title":"GenericTaskStatusResponse"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HeartbeatResponse":{"properties":{"SYSTEM":{"$ref":"#/components/schemas/SystemDict"},"METADATA_SOURCES":{"$ref":"#/components/schemas/MetadataSourcesDict"},"FILESYSTEM":{"$ref":"#/components/schemas/FilesystemDict"},"EMULATION":{"$ref":"#/components/schemas/EmulationDict"},"FRONTEND":{"$ref":"#/components/schemas/FrontendDict"},"OIDC":{"$ref":"#/components/schemas/OIDCDict"},"TASKS":{"$ref":"#/components/schemas/TasksDict"}},"type":"object","required":["SYSTEM","METADATA_SOURCES","FILESYSTEM","EMULATION","FRONTEND","OIDC","TASKS"],"title":"HeartbeatResponse"},"IGDBAgeRating":{"properties":{"rating":{"type":"string","title":"Rating"},"category":{"type":"string","title":"Category"},"rating_cover_url":{"type":"string","title":"Rating Cover Url"}},"type":"object","required":["rating","category","rating_cover_url"],"title":"IGDBAgeRating"},"IGDBMetadataMultiplayerMode":{"properties":{"campaigncoop":{"type":"boolean","title":"Campaigncoop"},"dropin":{"type":"boolean","title":"Dropin"},"lancoop":{"type":"boolean","title":"Lancoop"},"offlinecoop":{"type":"boolean","title":"Offlinecoop"},"offlinecoopmax":{"type":"integer","title":"Offlinecoopmax"},"offlinemax":{"type":"integer","title":"Offlinemax"},"onlinecoop":{"type":"integer","title":"Onlinecoop"},"onlinecoopmax":{"type":"integer","title":"Onlinecoopmax"},"onlinemax":{"type":"integer","title":"Onlinemax"},"splitscreen":{"type":"boolean","title":"Splitscreen"},"splitscreenonline":{"type":"boolean","title":"Splitscreenonline"},"platform":{"$ref":"#/components/schemas/IGDBMetadataPlatform"}},"type":"object","required":["campaigncoop","dropin","lancoop","offlinecoop","offlinecoopmax","offlinemax","onlinecoop","onlinecoopmax","onlinemax","splitscreen","splitscreenonline","platform"],"title":"IGDBMetadataMultiplayerMode"},"IGDBMetadataPlatform":{"properties":{"igdb_id":{"type":"integer","title":"Igdb Id"},"name":{"type":"string","title":"Name"}},"type":"object","required":["igdb_id","name"],"title":"IGDBMetadataPlatform"},"IGDBRelatedGame":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"slug":{"type":"string","title":"Slug"},"type":{"type":"string","title":"Type"},"cover_url":{"type":"string","title":"Cover Url"}},"type":"object","required":["id","name","slug","type","cover_url"],"title":"IGDBRelatedGame"},"InviteLinkSchema":{"properties":{"token":{"type":"string","title":"Token"}},"type":"object","required":["token"],"title":"InviteLinkSchema"},"JobStatus":{"type":"string","enum":["queued","finished","failed","started","deferred","scheduled","stopped","canceled"],"title":"JobStatus","description":"The Status of Job within its lifecycle at any given time."},"LaunchboxImage":{"properties":{"url":{"type":"string","title":"Url"},"type":{"type":"string","title":"Type"},"region":{"type":"string","title":"Region"}},"type":"object","required":["url"],"title":"LaunchboxImage"},"ManualMetadata":{"properties":{"genres":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Genres"},"franchises":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Franchises"},"companies":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Companies"},"game_modes":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Game Modes"},"age_ratings":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Age Ratings"},"first_release_date":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"First Release Date"},"youtube_video_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Youtube Video Id"}},"type":"object","title":"ManualMetadata"},"MetadataSourcesDict":{"properties":{"ANY_SOURCE_ENABLED":{"type":"boolean","title":"Any Source Enabled"},"IGDB_API_ENABLED":{"type":"boolean","title":"Igdb Api Enabled"},"SS_API_ENABLED":{"type":"boolean","title":"Ss Api Enabled"},"MOBY_API_ENABLED":{"type":"boolean","title":"Moby Api Enabled"},"STEAMGRIDDB_API_ENABLED":{"type":"boolean","title":"Steamgriddb Api Enabled"},"RA_API_ENABLED":{"type":"boolean","title":"Ra Api Enabled"},"LAUNCHBOX_API_ENABLED":{"type":"boolean","title":"Launchbox Api Enabled"},"HASHEOUS_API_ENABLED":{"type":"boolean","title":"Hasheous Api Enabled"},"PLAYMATCH_API_ENABLED":{"type":"boolean","title":"Playmatch Api Enabled"},"TGDB_API_ENABLED":{"type":"boolean","title":"Tgdb Api Enabled"},"FLASHPOINT_API_ENABLED":{"type":"boolean","title":"Flashpoint Api Enabled"},"HLTB_API_ENABLED":{"type":"boolean","title":"Hltb Api Enabled"}},"type":"object","required":["ANY_SOURCE_ENABLED","IGDB_API_ENABLED","SS_API_ENABLED","MOBY_API_ENABLED","STEAMGRIDDB_API_ENABLED","RA_API_ENABLED","LAUNCHBOX_API_ENABLED","HASHEOUS_API_ENABLED","PLAYMATCH_API_ENABLED","TGDB_API_ENABLED","FLASHPOINT_API_ENABLED","HLTB_API_ENABLED"],"title":"MetadataSourcesDict"},"MobyMetadataPlatform":{"properties":{"moby_id":{"type":"integer","title":"Moby Id"},"name":{"type":"string","title":"Name"}},"type":"object","required":["moby_id","name"],"title":"MobyMetadataPlatform"},"NetplayICEServer":{"properties":{"urls":{"type":"string","title":"Urls"},"username":{"type":"string","title":"Username"},"credential":{"type":"string","title":"Credential"}},"type":"object","required":["urls"],"title":"NetplayICEServer"},"OIDCDict":{"properties":{"ENABLED":{"type":"boolean","title":"Enabled"},"PROVIDER":{"type":"string","title":"Provider"}},"type":"object","required":["ENABLED","PROVIDER"],"title":"OIDCDict"},"PlatformSchema":{"properties":{"id":{"type":"integer","title":"Id"},"slug":{"type":"string","title":"Slug"},"fs_slug":{"type":"string","title":"Fs Slug"},"rom_count":{"type":"integer","title":"Rom Count"},"name":{"type":"string","title":"Name"},"igdb_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Igdb Slug"},"moby_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Moby Slug"},"hltb_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hltb Slug"},"custom_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Custom Name"},"igdb_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Igdb Id"},"sgdb_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sgdb Id"},"moby_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Moby Id"},"launchbox_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Launchbox Id"},"ss_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ss Id"},"ra_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ra Id"},"hasheous_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Hasheous Id"},"tgdb_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Tgdb Id"},"flashpoint_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Flashpoint Id"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"generation":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Generation"},"family_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Family Name"},"family_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Family Slug"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"},"url_logo":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url Logo"},"firmware":{"items":{"$ref":"#/components/schemas/FirmwareSchema"},"type":"array","title":"Firmware"},"aspect_ratio":{"type":"string","title":"Aspect Ratio","default":"2 / 3"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"fs_size_bytes":{"type":"integer","title":"Fs Size Bytes"},"is_unidentified":{"type":"boolean","title":"Is Unidentified"},"is_identified":{"type":"boolean","title":"Is Identified"},"missing_from_fs":{"type":"boolean","title":"Missing From Fs"},"display_name":{"type":"string","title":"Display Name","readOnly":true}},"type":"object","required":["id","slug","fs_slug","rom_count","name","igdb_slug","moby_slug","hltb_slug","created_at","updated_at","fs_size_bytes","is_unidentified","is_identified","missing_from_fs","display_name"],"title":"PlatformSchema"},"RAGameRomAchievement":{"properties":{"ra_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ra Id"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"points":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Points"},"num_awarded":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Num Awarded"},"num_awarded_hardcore":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Num Awarded Hardcore"},"badge_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Badge Id"},"badge_url_lock":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Badge Url Lock"},"badge_path_lock":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Badge Path Lock"},"badge_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Badge Url"},"badge_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Badge Path"},"display_order":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Display Order"},"type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"}},"type":"object","required":["ra_id","title","description","points","num_awarded","num_awarded_hardcore","badge_id","badge_url_lock","badge_path_lock","badge_url","badge_path","display_order","type"],"title":"RAGameRomAchievement"},"RAProgression":{"properties":{"total":{"type":"integer","title":"Total"},"results":{"items":{"$ref":"#/components/schemas/RAUserGameProgression"},"type":"array","title":"Results"}},"type":"object","title":"RAProgression"},"RAUserGameProgression":{"properties":{"rom_ra_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rom Ra Id"},"max_possible":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Possible"},"num_awarded":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Num Awarded"},"num_awarded_hardcore":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Num Awarded Hardcore"},"most_recent_awarded_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Most Recent Awarded Date"},"earned_achievements":{"items":{"$ref":"#/components/schemas/EarnedAchievement"},"type":"array","title":"Earned Achievements"}},"type":"object","required":["rom_ra_id","max_possible","num_awarded","num_awarded_hardcore","earned_achievements"],"title":"RAUserGameProgression"},"Role":{"type":"string","enum":["viewer","editor","admin"],"title":"Role"},"RomFileCategory":{"type":"string","enum":["game","dlc","hack","manual","patch","update","mod","demo","translation","prototype","cheat"],"title":"RomFileCategory"},"RomFileSchema":{"properties":{"id":{"type":"integer","title":"Id"},"rom_id":{"type":"integer","title":"Rom Id"},"file_name":{"type":"string","title":"File Name"},"file_path":{"type":"string","title":"File Path"},"file_size_bytes":{"type":"integer","title":"File Size Bytes"},"full_path":{"type":"string","title":"Full Path"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"last_modified":{"type":"string","format":"date-time","title":"Last Modified"},"crc_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Crc Hash"},"md5_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Md5 Hash"},"sha1_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sha1 Hash"},"category":{"anyOf":[{"$ref":"#/components/schemas/RomFileCategory"},{"type":"null"}]}},"type":"object","required":["id","rom_id","file_name","file_path","file_size_bytes","full_path","created_at","updated_at","last_modified","crc_hash","md5_hash","sha1_hash","category"],"title":"RomFileSchema"},"RomFiltersDict":{"properties":{"genres":{"items":{"type":"string"},"type":"array","title":"Genres"},"franchises":{"items":{"type":"string"},"type":"array","title":"Franchises"},"collections":{"items":{"type":"string"},"type":"array","title":"Collections"},"companies":{"items":{"type":"string"},"type":"array","title":"Companies"},"game_modes":{"items":{"type":"string"},"type":"array","title":"Game Modes"},"age_ratings":{"items":{"type":"string"},"type":"array","title":"Age Ratings"},"player_counts":{"items":{"type":"string"},"type":"array","title":"Player Counts"},"regions":{"items":{"type":"string"},"type":"array","title":"Regions"},"languages":{"items":{"type":"string"},"type":"array","title":"Languages"},"platforms":{"items":{"type":"integer"},"type":"array","title":"Platforms"}},"type":"object","required":["genres","franchises","collections","companies","game_modes","age_ratings","player_counts","regions","languages","platforms"],"title":"RomFiltersDict"},"RomFlashpointMetadata":{"properties":{"franchises":{"items":{"type":"string"},"type":"array","title":"Franchises"},"companies":{"items":{"type":"string"},"type":"array","title":"Companies"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"},"genres":{"items":{"type":"string"},"type":"array","title":"Genres"},"first_release_date":{"type":"string","title":"First Release Date"},"game_modes":{"items":{"type":"string"},"type":"array","title":"Game Modes"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Version"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"RomFlashpointMetadata"},"RomGamelistMetadata":{"properties":{"box2d_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Box2D Url"},"box2d_back_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Box2D Back Url"},"box3d_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Box3D Url"},"fanart_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fanart Url"},"image_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image Url"},"manual_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Manual Url"},"marquee_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Marquee Url"},"miximage_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Miximage Url"},"physical_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Physical Url"},"screenshot_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Screenshot Url"},"thumbnail_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Thumbnail Url"},"title_screen_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title Screen Url"},"video_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Video Url"},"rating":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rating"},"first_release_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Release Date"},"companies":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Companies"},"franchises":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Franchises"},"genres":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Genres"},"player_count":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Player Count"},"md5_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Md5 Hash"},"box3d_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Box3D Path"},"miximage_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Miximage Path"},"physical_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Physical Path"},"marquee_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Marquee Path"},"video_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Video Path"}},"type":"object","title":"RomGamelistMetadata"},"RomHLTBMetadata":{"properties":{"main_story":{"type":"integer","title":"Main Story"},"main_story_count":{"type":"integer","title":"Main Story Count"},"main_plus_extra":{"type":"integer","title":"Main Plus Extra"},"main_plus_extra_count":{"type":"integer","title":"Main Plus Extra Count"},"completionist":{"type":"integer","title":"Completionist"},"completionist_count":{"type":"integer","title":"Completionist Count"},"all_styles":{"type":"integer","title":"All Styles"},"all_styles_count":{"type":"integer","title":"All Styles Count"},"release_year":{"type":"integer","title":"Release Year"},"review_score":{"type":"integer","title":"Review Score"},"review_count":{"type":"integer","title":"Review Count"},"popularity":{"type":"integer","title":"Popularity"},"completions":{"type":"integer","title":"Completions"}},"type":"object","title":"RomHLTBMetadata"},"RomHasheousMetadata":{"properties":{"tosec_match":{"type":"boolean","title":"Tosec Match"},"mame_arcade_match":{"type":"boolean","title":"Mame Arcade Match"},"mame_mess_match":{"type":"boolean","title":"Mame Mess Match"},"nointro_match":{"type":"boolean","title":"Nointro Match"},"redump_match":{"type":"boolean","title":"Redump Match"},"whdload_match":{"type":"boolean","title":"Whdload Match"},"ra_match":{"type":"boolean","title":"Ra Match"},"fbneo_match":{"type":"boolean","title":"Fbneo Match"},"puredos_match":{"type":"boolean","title":"Puredos Match"}},"type":"object","title":"RomHasheousMetadata"},"RomIGDBMetadata":{"properties":{"total_rating":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Total Rating"},"aggregated_rating":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Aggregated Rating"},"first_release_date":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"First Release Date"},"youtube_video_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Youtube Video Id"},"genres":{"items":{"type":"string"},"type":"array","title":"Genres"},"franchises":{"items":{"type":"string"},"type":"array","title":"Franchises"},"alternative_names":{"items":{"type":"string"},"type":"array","title":"Alternative Names"},"collections":{"items":{"type":"string"},"type":"array","title":"Collections"},"companies":{"items":{"type":"string"},"type":"array","title":"Companies"},"game_modes":{"items":{"type":"string"},"type":"array","title":"Game Modes"},"age_ratings":{"items":{"$ref":"#/components/schemas/IGDBAgeRating"},"type":"array","title":"Age Ratings"},"platforms":{"items":{"$ref":"#/components/schemas/IGDBMetadataPlatform"},"type":"array","title":"Platforms"},"multiplayer_modes":{"items":{"$ref":"#/components/schemas/IGDBMetadataMultiplayerMode"},"type":"array","title":"Multiplayer Modes"},"player_count":{"type":"string","title":"Player Count"},"expansions":{"items":{"$ref":"#/components/schemas/IGDBRelatedGame"},"type":"array","title":"Expansions"},"dlcs":{"items":{"$ref":"#/components/schemas/IGDBRelatedGame"},"type":"array","title":"Dlcs"},"remasters":{"items":{"$ref":"#/components/schemas/IGDBRelatedGame"},"type":"array","title":"Remasters"},"remakes":{"items":{"$ref":"#/components/schemas/IGDBRelatedGame"},"type":"array","title":"Remakes"},"expanded_games":{"items":{"$ref":"#/components/schemas/IGDBRelatedGame"},"type":"array","title":"Expanded Games"},"ports":{"items":{"$ref":"#/components/schemas/IGDBRelatedGame"},"type":"array","title":"Ports"},"similar_games":{"items":{"$ref":"#/components/schemas/IGDBRelatedGame"},"type":"array","title":"Similar Games"}},"type":"object","title":"RomIGDBMetadata"},"RomLaunchboxMetadata":{"properties":{"first_release_date":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"First Release Date"},"max_players":{"type":"integer","title":"Max Players"},"release_type":{"type":"string","title":"Release Type"},"cooperative":{"type":"boolean","title":"Cooperative"},"youtube_video_id":{"type":"string","title":"Youtube Video Id"},"community_rating":{"type":"number","title":"Community Rating"},"community_rating_count":{"type":"integer","title":"Community Rating Count"},"wikipedia_url":{"type":"string","title":"Wikipedia Url"},"esrb":{"type":"string","title":"Esrb"},"genres":{"items":{"type":"string"},"type":"array","title":"Genres"},"companies":{"items":{"type":"string"},"type":"array","title":"Companies"},"images":{"items":{"$ref":"#/components/schemas/LaunchboxImage"},"type":"array","title":"Images"}},"type":"object","title":"RomLaunchboxMetadata"},"RomMetadataSchema":{"properties":{"rom_id":{"type":"integer","title":"Rom Id"},"genres":{"items":{"type":"string"},"type":"array","title":"Genres"},"franchises":{"items":{"type":"string"},"type":"array","title":"Franchises"},"collections":{"items":{"type":"string"},"type":"array","title":"Collections"},"companies":{"items":{"type":"string"},"type":"array","title":"Companies"},"game_modes":{"items":{"type":"string"},"type":"array","title":"Game Modes"},"age_ratings":{"items":{"type":"string"},"type":"array","title":"Age Ratings"},"player_count":{"type":"string","title":"Player Count"},"first_release_date":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"First Release Date"},"average_rating":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Average Rating"}},"type":"object","required":["rom_id","genres","franchises","collections","companies","game_modes","age_ratings","player_count","first_release_date","average_rating"],"title":"RomMetadataSchema"},"RomMobyMetadata":{"properties":{"moby_score":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Moby Score"},"genres":{"items":{"type":"string"},"type":"array","title":"Genres"},"alternate_titles":{"items":{"type":"string"},"type":"array","title":"Alternate Titles"},"platforms":{"items":{"$ref":"#/components/schemas/MobyMetadataPlatform"},"type":"array","title":"Platforms"}},"type":"object","title":"RomMobyMetadata"},"RomRAMetadata":{"properties":{"first_release_date":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"First Release Date"},"genres":{"items":{"type":"string"},"type":"array","title":"Genres"},"companies":{"items":{"type":"string"},"type":"array","title":"Companies"},"achievements":{"items":{"$ref":"#/components/schemas/RAGameRomAchievement"},"type":"array","title":"Achievements"}},"type":"object","title":"RomRAMetadata"},"RomSSMetadata":{"properties":{"bezel_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bezel Url"},"box2d_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Box2D Url"},"box2d_side_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Box2D Side Url"},"box2d_back_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Box2D Back Url"},"box3d_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Box3D Url"},"fanart_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fanart Url"},"fullbox_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fullbox Url"},"logo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Url"},"manual_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Manual Url"},"marquee_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Marquee Url"},"miximage_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Miximage Url"},"physical_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Physical Url"},"screenshot_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Screenshot Url"},"steamgrid_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Steamgrid Url"},"title_screen_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title Screen Url"},"video_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Video Url"},"video_normalized_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Video Normalized Url"},"bezel_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bezel Path"},"box2d_back_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Box2D Back Path"},"box3d_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Box3D Path"},"fanart_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fanart Path"},"miximage_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Miximage Path"},"physical_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Physical Path"},"marquee_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Marquee Path"},"logo_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Path"},"video_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Video Path"},"ss_score":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ss Score"},"first_release_date":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"First Release Date"},"alternative_names":{"items":{"type":"string"},"type":"array","title":"Alternative Names"},"companies":{"items":{"type":"string"},"type":"array","title":"Companies"},"franchises":{"items":{"type":"string"},"type":"array","title":"Franchises"},"game_modes":{"items":{"type":"string"},"type":"array","title":"Game Modes"},"genres":{"items":{"type":"string"},"type":"array","title":"Genres"},"player_count":{"type":"string","title":"Player Count"}},"type":"object","title":"RomSSMetadata"},"RomUserSchema":{"properties":{"id":{"type":"integer","title":"Id"},"user_id":{"type":"integer","title":"User Id"},"rom_id":{"type":"integer","title":"Rom Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"last_played":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Played"},"is_main_sibling":{"type":"boolean","title":"Is Main Sibling"},"backlogged":{"type":"boolean","title":"Backlogged"},"now_playing":{"type":"boolean","title":"Now Playing"},"hidden":{"type":"boolean","title":"Hidden"},"rating":{"type":"integer","title":"Rating"},"difficulty":{"type":"integer","title":"Difficulty"},"completion":{"type":"integer","title":"Completion"},"status":{"anyOf":[{"$ref":"#/components/schemas/RomUserStatus"},{"type":"null"}]},"user__username":{"type":"string","title":"User Username"}},"type":"object","required":["id","user_id","rom_id","created_at","updated_at","last_played","is_main_sibling","backlogged","now_playing","hidden","rating","difficulty","completion","status","user__username"],"title":"RomUserSchema"},"RomUserStatus":{"type":"string","enum":["incomplete","finished","completed_100","retired","never_playing"],"title":"RomUserStatus"},"RoomsResponse":{"properties":{"room_name":{"type":"string","title":"Room Name"},"current":{"type":"integer","title":"Current"},"max":{"type":"integer","title":"Max"},"player_name":{"type":"string","title":"Player Name"},"hasPassword":{"type":"boolean","title":"Haspassword"}},"type":"object","required":["room_name","current","max","player_name","hasPassword"],"title":"RoomsResponse"},"SGDBResource":{"properties":{"thumb":{"type":"string","title":"Thumb"},"url":{"type":"string","title":"Url"},"type":{"type":"string","title":"Type"}},"type":"object","required":["thumb","url","type"],"title":"SGDBResource"},"SaveSchema":{"properties":{"id":{"type":"integer","title":"Id"},"rom_id":{"type":"integer","title":"Rom Id"},"user_id":{"type":"integer","title":"User Id"},"file_name":{"type":"string","title":"File Name"},"file_name_no_tags":{"type":"string","title":"File Name No Tags"},"file_name_no_ext":{"type":"string","title":"File Name No Ext"},"file_extension":{"type":"string","title":"File Extension"},"file_path":{"type":"string","title":"File Path"},"file_size_bytes":{"type":"integer","title":"File Size Bytes"},"full_path":{"type":"string","title":"Full Path"},"download_path":{"type":"string","title":"Download Path"},"missing_from_fs":{"type":"boolean","title":"Missing From Fs"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"emulator":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Emulator"},"screenshot":{"anyOf":[{"$ref":"#/components/schemas/ScreenshotSchema"},{"type":"null"}]}},"type":"object","required":["id","rom_id","user_id","file_name","file_name_no_tags","file_name_no_ext","file_extension","file_path","file_size_bytes","full_path","download_path","missing_from_fs","created_at","updated_at","emulator","screenshot"],"title":"SaveSchema"},"ScanStats":{"properties":{"total_platforms":{"type":"integer","title":"Total Platforms"},"total_roms":{"type":"integer","title":"Total Roms"},"scanned_platforms":{"type":"integer","title":"Scanned Platforms"},"new_platforms":{"type":"integer","title":"New Platforms"},"identified_platforms":{"type":"integer","title":"Identified Platforms"},"scanned_roms":{"type":"integer","title":"Scanned Roms"},"new_roms":{"type":"integer","title":"New Roms"},"identified_roms":{"type":"integer","title":"Identified Roms"},"scanned_firmware":{"type":"integer","title":"Scanned Firmware"},"new_firmware":{"type":"integer","title":"New Firmware"}},"type":"object","required":["total_platforms","total_roms","scanned_platforms","new_platforms","identified_platforms","scanned_roms","new_roms","identified_roms","scanned_firmware","new_firmware"],"title":"ScanStats"},"ScanTaskMeta":{"properties":{"scan_stats":{"anyOf":[{"$ref":"#/components/schemas/ScanStats"},{"type":"null"}]}},"type":"object","required":["scan_stats"],"title":"ScanTaskMeta"},"ScanTaskStatusResponse":{"properties":{"task_name":{"type":"string","title":"Task Name"},"task_id":{"type":"string","title":"Task Id"},"status":{"$ref":"#/components/schemas/JobStatus"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"enqueued_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Enqueued At"},"started_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Started At"},"ended_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ended At"},"task_type":{"type":"string","const":"scan","title":"Task Type"},"meta":{"$ref":"#/components/schemas/ScanTaskMeta"}},"type":"object","required":["task_name","task_id","status","created_at","enqueued_at","started_at","ended_at","task_type","meta"],"title":"ScanTaskStatusResponse"},"ScreenshotSchema":{"properties":{"id":{"type":"integer","title":"Id"},"rom_id":{"type":"integer","title":"Rom Id"},"user_id":{"type":"integer","title":"User Id"},"file_name":{"type":"string","title":"File Name"},"file_name_no_tags":{"type":"string","title":"File Name No Tags"},"file_name_no_ext":{"type":"string","title":"File Name No Ext"},"file_extension":{"type":"string","title":"File Extension"},"file_path":{"type":"string","title":"File Path"},"file_size_bytes":{"type":"integer","title":"File Size Bytes"},"full_path":{"type":"string","title":"Full Path"},"download_path":{"type":"string","title":"Download Path"},"missing_from_fs":{"type":"boolean","title":"Missing From Fs"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","rom_id","user_id","file_name","file_name_no_tags","file_name_no_ext","file_extension","file_path","file_size_bytes","full_path","download_path","missing_from_fs","created_at","updated_at"],"title":"ScreenshotSchema"},"SearchCoverSchema":{"properties":{"name":{"type":"string","title":"Name"},"resources":{"items":{"$ref":"#/components/schemas/SGDBResource"},"type":"array","title":"Resources"}},"type":"object","required":["name","resources"],"title":"SearchCoverSchema"},"SearchRomSchema":{"properties":{"id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Id"},"igdb_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Igdb Id"},"moby_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Moby Id"},"ss_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ss Id"},"sgdb_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sgdb Id"},"flashpoint_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Flashpoint Id"},"launchbox_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Launchbox Id"},"platform_id":{"type":"integer","title":"Platform Id"},"name":{"type":"string","title":"Name"},"slug":{"type":"string","title":"Slug","default":""},"summary":{"type":"string","title":"Summary","default":""},"igdb_url_cover":{"type":"string","title":"Igdb Url Cover","default":""},"moby_url_cover":{"type":"string","title":"Moby Url Cover","default":""},"ss_url_cover":{"type":"string","title":"Ss Url Cover","default":""},"sgdb_url_cover":{"type":"string","title":"Sgdb Url Cover","default":""},"flashpoint_url_cover":{"type":"string","title":"Flashpoint Url Cover","default":""},"launchbox_url_cover":{"type":"string","title":"Launchbox Url Cover","default":""},"is_unidentified":{"type":"boolean","title":"Is Unidentified"},"is_identified":{"type":"boolean","title":"Is Identified"}},"type":"object","required":["platform_id","name","is_unidentified","is_identified"],"title":"SearchRomSchema"},"SiblingRomSchema":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"fs_name_no_tags":{"type":"string","title":"Fs Name No Tags"},"fs_name_no_ext":{"type":"string","title":"Fs Name No Ext"},"sort_comparator":{"type":"string","title":"Sort Comparator","readOnly":true}},"type":"object","required":["id","name","fs_name_no_tags","fs_name_no_ext","sort_comparator"],"title":"SiblingRomSchema"},"SimpleRomSchema":{"properties":{"id":{"type":"integer","title":"Id"},"igdb_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Igdb Id"},"sgdb_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sgdb Id"},"moby_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Moby Id"},"ss_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ss Id"},"ra_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ra Id"},"launchbox_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Launchbox Id"},"hasheous_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Hasheous Id"},"tgdb_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Tgdb Id"},"flashpoint_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Flashpoint Id"},"hltb_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Hltb Id"},"gamelist_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gamelist Id"},"platform_id":{"type":"integer","title":"Platform Id"},"platform_slug":{"type":"string","title":"Platform Slug"},"platform_fs_slug":{"type":"string","title":"Platform Fs Slug"},"platform_custom_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Platform Custom Name"},"platform_display_name":{"type":"string","title":"Platform Display Name"},"fs_name":{"type":"string","title":"Fs Name"},"fs_name_no_tags":{"type":"string","title":"Fs Name No Tags"},"fs_name_no_ext":{"type":"string","title":"Fs Name No Ext"},"fs_extension":{"type":"string","title":"Fs Extension"},"fs_path":{"type":"string","title":"Fs Path"},"fs_size_bytes":{"type":"integer","title":"Fs Size Bytes"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slug"},"summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Summary"},"alternative_names":{"items":{"type":"string"},"type":"array","title":"Alternative Names"},"youtube_video_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Youtube Video Id"},"metadatum":{"$ref":"#/components/schemas/RomMetadataSchema"},"igdb_metadata":{"anyOf":[{"$ref":"#/components/schemas/RomIGDBMetadata"},{"type":"null"}]},"moby_metadata":{"anyOf":[{"$ref":"#/components/schemas/RomMobyMetadata"},{"type":"null"}]},"ss_metadata":{"anyOf":[{"$ref":"#/components/schemas/RomSSMetadata"},{"type":"null"}]},"launchbox_metadata":{"anyOf":[{"$ref":"#/components/schemas/RomLaunchboxMetadata"},{"type":"null"}]},"hasheous_metadata":{"anyOf":[{"$ref":"#/components/schemas/RomHasheousMetadata"},{"type":"null"}]},"flashpoint_metadata":{"anyOf":[{"$ref":"#/components/schemas/RomFlashpointMetadata"},{"type":"null"}]},"hltb_metadata":{"anyOf":[{"$ref":"#/components/schemas/RomHLTBMetadata"},{"type":"null"}]},"gamelist_metadata":{"anyOf":[{"$ref":"#/components/schemas/RomGamelistMetadata"},{"type":"null"}]},"manual_metadata":{"anyOf":[{"$ref":"#/components/schemas/ManualMetadata"},{"type":"null"}]},"path_cover_small":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path Cover Small"},"path_cover_large":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path Cover Large"},"url_cover":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url Cover"},"has_manual":{"type":"boolean","title":"Has Manual"},"path_manual":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path Manual"},"url_manual":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url Manual"},"is_identifying":{"type":"boolean","title":"Is Identifying","default":false},"is_unidentified":{"type":"boolean","title":"Is Unidentified"},"is_identified":{"type":"boolean","title":"Is Identified"},"revision":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Revision"},"regions":{"items":{"type":"string"},"type":"array","title":"Regions"},"languages":{"items":{"type":"string"},"type":"array","title":"Languages"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags"},"crc_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Crc Hash"},"md5_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Md5 Hash"},"sha1_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sha1 Hash"},"has_simple_single_file":{"type":"boolean","title":"Has Simple Single File"},"has_nested_single_file":{"type":"boolean","title":"Has Nested Single File"},"has_multiple_files":{"type":"boolean","title":"Has Multiple Files"},"files":{"items":{"$ref":"#/components/schemas/RomFileSchema"},"type":"array","title":"Files"},"full_path":{"type":"string","title":"Full Path"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"missing_from_fs":{"type":"boolean","title":"Missing From Fs"},"has_notes":{"type":"boolean","title":"Has Notes"},"siblings":{"items":{"$ref":"#/components/schemas/SiblingRomSchema"},"type":"array","title":"Siblings"},"rom_user":{"$ref":"#/components/schemas/RomUserSchema"},"merged_screenshots":{"items":{"type":"string"},"type":"array","title":"Merged Screenshots"},"merged_ra_metadata":{"anyOf":[{"$ref":"#/components/schemas/RomRAMetadata"},{"type":"null"}]}},"type":"object","required":["id","igdb_id","sgdb_id","moby_id","ss_id","ra_id","launchbox_id","hasheous_id","tgdb_id","flashpoint_id","hltb_id","gamelist_id","platform_id","platform_slug","platform_fs_slug","platform_custom_name","platform_display_name","fs_name","fs_name_no_tags","fs_name_no_ext","fs_extension","fs_path","fs_size_bytes","name","slug","summary","alternative_names","youtube_video_id","metadatum","igdb_metadata","moby_metadata","ss_metadata","launchbox_metadata","hasheous_metadata","flashpoint_metadata","hltb_metadata","gamelist_metadata","manual_metadata","path_cover_small","path_cover_large","url_cover","has_manual","path_manual","url_manual","is_unidentified","is_identified","revision","regions","languages","tags","crc_hash","md5_hash","sha1_hash","has_simple_single_file","has_nested_single_file","has_multiple_files","files","full_path","created_at","updated_at","missing_from_fs","has_notes","siblings","rom_user","merged_screenshots","merged_ra_metadata"],"title":"SimpleRomSchema"},"SmartCollectionSchema":{"properties":{"name":{"type":"string","title":"Name"},"description":{"type":"string","title":"Description","default":""},"rom_ids":{"items":{"type":"integer"},"type":"array","uniqueItems":true,"title":"Rom Ids"},"rom_count":{"type":"integer","title":"Rom Count"},"path_cover_small":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path Cover Small"},"path_cover_large":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path Cover Large"},"path_covers_small":{"items":{"type":"string"},"type":"array","title":"Path Covers Small"},"path_covers_large":{"items":{"type":"string"},"type":"array","title":"Path Covers Large"},"is_public":{"type":"boolean","title":"Is Public","default":false},"is_favorite":{"type":"boolean","title":"Is Favorite","default":false},"is_virtual":{"type":"boolean","title":"Is Virtual","default":false},"is_smart":{"type":"boolean","title":"Is Smart","default":true},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"id":{"type":"integer","title":"Id"},"filter_criteria":{"additionalProperties":true,"type":"object","title":"Filter Criteria"},"filter_summary":{"type":"string","title":"Filter Summary"},"user_id":{"type":"integer","title":"User Id"},"user__username":{"type":"string","title":"User Username"}},"type":"object","required":["name","rom_ids","rom_count","path_cover_small","path_cover_large","path_covers_small","path_covers_large","created_at","updated_at","id","filter_criteria","filter_summary","user_id","user__username"],"title":"SmartCollectionSchema"},"StateSchema":{"properties":{"id":{"type":"integer","title":"Id"},"rom_id":{"type":"integer","title":"Rom Id"},"user_id":{"type":"integer","title":"User Id"},"file_name":{"type":"string","title":"File Name"},"file_name_no_tags":{"type":"string","title":"File Name No Tags"},"file_name_no_ext":{"type":"string","title":"File Name No Ext"},"file_extension":{"type":"string","title":"File Extension"},"file_path":{"type":"string","title":"File Path"},"file_size_bytes":{"type":"integer","title":"File Size Bytes"},"full_path":{"type":"string","title":"Full Path"},"download_path":{"type":"string","title":"Download Path"},"missing_from_fs":{"type":"boolean","title":"Missing From Fs"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"emulator":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Emulator"},"screenshot":{"anyOf":[{"$ref":"#/components/schemas/ScreenshotSchema"},{"type":"null"}]}},"type":"object","required":["id","rom_id","user_id","file_name","file_name_no_tags","file_name_no_ext","file_extension","file_path","file_size_bytes","full_path","download_path","missing_from_fs","created_at","updated_at","emulator","screenshot"],"title":"StateSchema"},"StatsReturn":{"properties":{"PLATFORMS":{"type":"integer","title":"Platforms"},"ROMS":{"type":"integer","title":"Roms"},"SAVES":{"type":"integer","title":"Saves"},"STATES":{"type":"integer","title":"States"},"SCREENSHOTS":{"type":"integer","title":"Screenshots"},"TOTAL_FILESIZE_BYTES":{"type":"integer","title":"Total Filesize Bytes"}},"type":"object","required":["PLATFORMS","ROMS","SAVES","STATES","SCREENSHOTS","TOTAL_FILESIZE_BYTES"],"title":"StatsReturn"},"SystemDict":{"properties":{"VERSION":{"type":"string","title":"Version"},"SHOW_SETUP_WIZARD":{"type":"boolean","title":"Show Setup Wizard"}},"type":"object","required":["VERSION","SHOW_SETUP_WIZARD"],"title":"SystemDict"},"TaskExecutionResponse":{"properties":{"task_name":{"type":"string","title":"Task Name"},"task_id":{"type":"string","title":"Task Id"},"status":{"$ref":"#/components/schemas/JobStatus"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"enqueued_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Enqueued At"}},"type":"object","required":["task_name","task_id","status","created_at","enqueued_at"],"title":"TaskExecutionResponse"},"TaskInfo":{"properties":{"name":{"type":"string","title":"Name"},"type":{"$ref":"#/components/schemas/TaskType"},"manual_run":{"type":"boolean","title":"Manual Run"},"title":{"type":"string","title":"Title"},"description":{"type":"string","title":"Description"},"enabled":{"type":"boolean","title":"Enabled"},"cron_string":{"type":"string","title":"Cron String"}},"type":"object","required":["name","type","manual_run","title","description","enabled","cron_string"],"title":"TaskInfo"},"TaskType":{"type":"string","enum":["scan","conversion","cleanup","update","watcher","generic"],"title":"TaskType","description":"Enumeration of task types for categorization and UI display."},"TasksDict":{"properties":{"ENABLE_SCHEDULED_RESCAN":{"type":"boolean","title":"Enable Scheduled Rescan"},"SCHEDULED_RESCAN_CRON":{"type":"string","title":"Scheduled Rescan Cron"},"ENABLE_SCHEDULED_UPDATE_SWITCH_TITLEDB":{"type":"boolean","title":"Enable Scheduled Update Switch Titledb"},"SCHEDULED_UPDATE_SWITCH_TITLEDB_CRON":{"type":"string","title":"Scheduled Update Switch Titledb Cron"},"ENABLE_SCHEDULED_UPDATE_LAUNCHBOX_METADATA":{"type":"boolean","title":"Enable Scheduled Update Launchbox Metadata"},"SCHEDULED_UPDATE_LAUNCHBOX_METADATA_CRON":{"type":"string","title":"Scheduled Update Launchbox Metadata Cron"},"ENABLE_SCHEDULED_CONVERT_IMAGES_TO_WEBP":{"type":"boolean","title":"Enable Scheduled Convert Images To Webp"},"SCHEDULED_CONVERT_IMAGES_TO_WEBP_CRON":{"type":"string","title":"Scheduled Convert Images To Webp Cron"}},"type":"object","required":["ENABLE_SCHEDULED_RESCAN","SCHEDULED_RESCAN_CRON","ENABLE_SCHEDULED_UPDATE_SWITCH_TITLEDB","SCHEDULED_UPDATE_SWITCH_TITLEDB_CRON","ENABLE_SCHEDULED_UPDATE_LAUNCHBOX_METADATA","SCHEDULED_UPDATE_LAUNCHBOX_METADATA_CRON","ENABLE_SCHEDULED_CONVERT_IMAGES_TO_WEBP","SCHEDULED_CONVERT_IMAGES_TO_WEBP_CRON"],"title":"TasksDict"},"TinfoilFeedFileSchema":{"properties":{"url":{"type":"string","title":"Url"},"size":{"type":"integer","title":"Size"}},"type":"object","required":["url","size"],"title":"TinfoilFeedFileSchema"},"TinfoilFeedSchema":{"properties":{"files":{"items":{"$ref":"#/components/schemas/TinfoilFeedFileSchema"},"type":"array","title":"Files"},"directories":{"items":{"type":"string"},"type":"array","title":"Directories"},"titledb":{"additionalProperties":{"additionalProperties":true,"type":"object"},"type":"object","title":"Titledb"},"success":{"type":"string","title":"Success"},"error":{"type":"string","title":"Error"}},"type":"object","required":["files","directories"],"title":"TinfoilFeedSchema"},"TokenResponse":{"properties":{"access_token":{"type":"string","title":"Access Token"},"refresh_token":{"type":"string","title":"Refresh Token"},"token_type":{"type":"string","title":"Token Type"},"expires":{"type":"integer","title":"Expires"}},"type":"object","required":["access_token","token_type","expires"],"title":"TokenResponse"},"UpdateStats":{"properties":{"processed":{"type":"integer","title":"Processed"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["processed","total"],"title":"UpdateStats"},"UpdateTaskMeta":{"properties":{"update_stats":{"anyOf":[{"$ref":"#/components/schemas/UpdateStats"},{"type":"null"}]}},"type":"object","required":["update_stats"],"title":"UpdateTaskMeta"},"UpdateTaskStatusResponse":{"properties":{"task_name":{"type":"string","title":"Task Name"},"task_id":{"type":"string","title":"Task Id"},"status":{"$ref":"#/components/schemas/JobStatus"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"enqueued_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Enqueued At"},"started_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Started At"},"ended_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ended At"},"task_type":{"type":"string","const":"update","title":"Task Type"},"meta":{"$ref":"#/components/schemas/UpdateTaskMeta"}},"type":"object","required":["task_name","task_id","status","created_at","enqueued_at","started_at","ended_at","task_type","meta"],"title":"UpdateTaskStatusResponse"},"UserCollectionSchema":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"}},"type":"object","required":["id","name"],"title":"UserCollectionSchema"},"UserForm":{"properties":{"username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Username"},"password":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Password"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role"},"enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Enabled"},"ra_username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ra Username"},"avatar":{"anyOf":[{"type":"string","format":"binary"},{"type":"null"}],"title":"Avatar"},"ui_settings":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ui Settings"}},"type":"object","title":"UserForm"},"UserNoteSchema":{"properties":{"id":{"type":"integer","title":"Id"},"title":{"type":"string","title":"Title"},"content":{"type":"string","title":"Content"},"is_public":{"type":"boolean","title":"Is Public"},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"user_id":{"type":"integer","title":"User Id"},"username":{"type":"string","title":"Username"}},"type":"object","required":["id","title","content","is_public","created_at","updated_at","user_id","username"],"title":"UserNoteSchema"},"UserSchema":{"properties":{"id":{"type":"integer","title":"Id"},"username":{"type":"string","title":"Username"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"enabled":{"type":"boolean","title":"Enabled"},"role":{"$ref":"#/components/schemas/Role"},"oauth_scopes":{"items":{"type":"string"},"type":"array","title":"Oauth Scopes"},"avatar_path":{"type":"string","title":"Avatar Path"},"last_login":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Login"},"last_active":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Active"},"ra_username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ra Username"},"ra_progression":{"anyOf":[{"$ref":"#/components/schemas/RAProgression"},{"type":"null"}]},"ui_settings":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Ui Settings"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","username","email","enabled","role","oauth_scopes","avatar_path","last_login","last_active","created_at","updated_at"],"title":"UserSchema"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VirtualCollectionSchema":{"properties":{"name":{"type":"string","title":"Name"},"description":{"type":"string","title":"Description"},"rom_ids":{"items":{"type":"integer"},"type":"array","uniqueItems":true,"title":"Rom Ids"},"rom_count":{"type":"integer","title":"Rom Count"},"path_cover_small":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path Cover Small"},"path_cover_large":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path Cover Large"},"path_covers_small":{"items":{"type":"string"},"type":"array","title":"Path Covers Small"},"path_covers_large":{"items":{"type":"string"},"type":"array","title":"Path Covers Large"},"is_public":{"type":"boolean","title":"Is Public","default":false},"is_favorite":{"type":"boolean","title":"Is Favorite","default":false},"is_virtual":{"type":"boolean","title":"Is Virtual","default":true},"is_smart":{"type":"boolean","title":"Is Smart","default":false},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"id":{"type":"string","title":"Id"},"type":{"type":"string","title":"Type"}},"type":"object","required":["name","description","rom_ids","rom_count","path_cover_small","path_cover_large","path_covers_small","path_covers_large","created_at","updated_at","id","type"],"title":"VirtualCollectionSchema"},"WatcherTaskMeta":{"properties":{},"type":"object","title":"WatcherTaskMeta"},"WatcherTaskStatusResponse":{"properties":{"task_name":{"type":"string","title":"Task Name"},"task_id":{"type":"string","title":"Task Id"},"status":{"$ref":"#/components/schemas/JobStatus"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"enqueued_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Enqueued At"},"started_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Started At"},"ended_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ended At"},"task_type":{"type":"string","const":"watcher","title":"Task Type"},"meta":{"$ref":"#/components/schemas/WatcherTaskMeta"}},"type":"object","required":["task_name","task_id","status","created_at","enqueued_at","started_at","ended_at","task_type","meta"],"title":"WatcherTaskStatusResponse"},"WebrcadeFeedCategorySchema":{"properties":{"title":{"type":"string","title":"Title"},"longTitle":{"type":"string","title":"Longtitle"},"background":{"type":"string","title":"Background"},"thumbnail":{"type":"string","title":"Thumbnail"},"description":{"type":"string","title":"Description"},"items":{"items":{"$ref":"#/components/schemas/WebrcadeFeedItemSchema"},"type":"array","title":"Items"}},"type":"object","required":["title","items"],"title":"WebrcadeFeedCategorySchema"},"WebrcadeFeedItemPropsSchema":{"properties":{"rom":{"type":"string","title":"Rom"}},"type":"object","required":["rom"],"title":"WebrcadeFeedItemPropsSchema"},"WebrcadeFeedItemSchema":{"properties":{"title":{"type":"string","title":"Title"},"longTitle":{"type":"string","title":"Longtitle"},"description":{"type":"string","title":"Description"},"type":{"type":"string","title":"Type"},"thumbnail":{"type":"string","title":"Thumbnail"},"background":{"type":"string","title":"Background"},"props":{"$ref":"#/components/schemas/WebrcadeFeedItemPropsSchema"}},"type":"object","required":["title","type","props"],"title":"WebrcadeFeedItemSchema"},"WebrcadeFeedSchema":{"properties":{"title":{"type":"string","title":"Title"},"longTitle":{"type":"string","title":"Longtitle"},"description":{"type":"string","title":"Description"},"thumbnail":{"type":"string","title":"Thumbnail"},"background":{"type":"string","title":"Background"},"categories":{"items":{"$ref":"#/components/schemas/WebrcadeFeedCategorySchema"},"type":"array","title":"Categories"}},"type":"object","required":["title","categories"],"title":"WebrcadeFeedSchema"}},"securitySchemes":{"OAuth2PasswordBearer":{"type":"oauth2","flows":{"password":{"scopes":{"me.read":"View your profile","roms.read":"View ROMs","platforms.read":"View platforms","assets.read":"View assets","firmware.read":"View firmware","roms.user.read":"View user-rom properties","collections.read":"View collections","me.write":"Modify your profile","assets.write":"Modify assets","roms.user.write":"Modify user-rom properties","collections.write":"Modify collections","roms.write":"Modify ROMs","platforms.write":"Modify platforms","firmware.write":"Modify firmware","users.read":"View users","users.write":"Modify users","tasks.run":"Run tasks"},"tokenUrl":"/token"}}},"HTTPBasic":{"type":"http","scheme":"basic"}}}} \ No newline at end of file +{"openapi":"3.1.0","info":{"title":"RomM API","version":"4.7.0"},"paths":{"/api/heartbeat":{"get":{"tags":["system"],"summary":"Heartbeat","description":"Endpoint to set the CSRF token in cache and return all the basic RomM config\n\nReturns:\n HeartbeatReturn: TypedDict structure with all the defined values in the HeartbeatReturn class.","operationId":"heartbeat_api_heartbeat_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HeartbeatResponse"}}}}}}},"/api/heartbeat/metadata/{source}":{"get":{"tags":["system"],"summary":"Metadata Heartbeat","description":"Endpoint to return the heartbeat of the metadata sources","operationId":"metadata_heartbeat_api_heartbeat_metadata__source__get","parameters":[{"name":"source","in":"path","required":true,"schema":{"type":"string","title":"Source"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"boolean","title":"Response Metadata Heartbeat Api Heartbeat Metadata Source Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/setup/library":{"get":{"tags":["system"],"summary":"Get Setup Library Info","description":"Get library structure information for setup wizard.\n\nOnly accessible during initial setup (no admin users) or with authentication.\n\nReturns:\n - detected_structure: \"struct_a\" (roms/{platform}), \"struct_b\" ({platform}/roms), or None\n - existing_platforms: list of objects with fs_slug and rom_count\n - supported_platforms: list of all supported platforms with metadata","operationId":"get_setup_library_info_api_setup_library_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]},{"HTTPBasic":[]}]}},"/api/setup/platforms":{"post":{"tags":["system"],"summary":"Create Setup Platforms","description":"Create platform folders during setup wizard.\n\nOnly accessible during initial setup (no admin users) or with authentication.\n\nArgs:\n platform_slugs: List of platform fs_slugs to create\n\nReturns:\n - success: bool\n - created_count: number of platforms created\n - message: success or error message","operationId":"create_setup_platforms_api_setup_platforms_post","requestBody":{"content":{"application/json":{"schema":{"items":{"type":"string"},"type":"array","title":"Platform Slugs"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]},{"HTTPBasic":[]}]}},"/api/login":{"post":{"tags":["auth"],"summary":"Login","description":"Session login endpoint\n\nArgs:\n request (Request): Fastapi Request object\n credentials: Defaults to Depends(HTTPBasic()).\n\nRaises:\n CredentialsException: Invalid credentials\n UserDisabledException: Auth is disabled","operationId":"login_api_login_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBasic":[]}]}},"/api/logout":{"post":{"tags":["auth"],"summary":"Logout","description":"Session logout endpoint\n\nArgs:\n request (Request): Fastapi Request object","operationId":"logout_api_logout_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/token":{"post":{"tags":["auth"],"summary":"Token","description":"OAuth2 token endpoint\n\nArgs:\n form_data (Annotated[OAuth2RequestForm, Depends): Form Data with OAuth2 info\n\nRaises:\n HTTPException: Missing refresh token\n HTTPException: Invalid refresh token\n HTTPException: Missing username or password\n HTTPException: Invalid username or password\n HTTPException: Client credentials are not yet supported\n HTTPException: Invalid or unsupported grant type\n HTTPException: Insufficient scope\n\nReturns:\n TokenResponse: TypedDict with the new generated token info","operationId":"token_api_token_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_token_api_token_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/login/openid":{"get":{"tags":["auth"],"summary":"Login Via Openid","description":"OIDC login endpoint\n\nArgs:\n request (Request): Fastapi Request object\n\nRaises:\n OIDCDisabledException: OAuth is disabled\n OIDCNotConfiguredException: OAuth not configured\n\nReturns:\n RedirectResponse: Redirect to OIDC provider","operationId":"login_via_openid_api_login_openid_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/oauth/openid":{"get":{"tags":["auth"],"summary":"Auth Openid","description":"OIDC callback endpoint\n\nArgs:\n request (Request): Fastapi Request object\n\nRaises:\n OIDCDisabledException: OAuth is disabled\n OIDCNotConfiguredException: OAuth not configured\n AuthCredentialsException: Invalid credentials\n UserDisabledException: Auth is disabled\n\nReturns:\n RedirectResponse: Redirect to home page","operationId":"auth_openid_api_oauth_openid_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/forgot-password":{"post":{"tags":["auth"],"summary":"Request Password Reset","description":"Request a password reset link for the user.\n\nArgs:\n username (str): Username of the user requesting the reset\nReturns:\n None: Returns 200 OK status","operationId":"request_password_reset_api_forgot_password_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_request_password_reset_api_forgot_password_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/reset-password":{"post":{"tags":["auth"],"summary":"Reset Password","description":"Reset password using the token.\n\nArgs:\n token (str): Reset token from the URL\n new_password (str): New user password\n\nReturns:\n None: Returns 200 OK status","operationId":"reset_password_api_reset_password_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_reset_password_api_reset_password_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/users":{"get":{"tags":["users"],"summary":"Get Users","description":"Get all users endpoint\n\nArgs:\n request (Request): Fastapi Request object\n\nReturns:\n list[UserSchema]: All users stored in the RomM's database","operationId":"get_users_api_users_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/UserSchema"},"type":"array","title":"Response Get Users Api Users Get"}}}}},"security":[{"OAuth2PasswordBearer":["users.read"]},{"HTTPBasic":[]}]},"post":{"tags":["users"],"summary":"Add User","description":"Create user endpoint\n\nArgs:\n request (Request): Fastapi Requests object\n username (str): User username\n password (str): User password\n email (str): User email\n role (str): RomM Role object represented as string\n\nReturns:\n UserSchema: Newly created user","operationId":"add_user_api_users_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_add_user_api_users_post"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]},{"HTTPBasic":[]}]}},"/api/users/invite-link":{"post":{"tags":["users"],"summary":"Create Invite Link","description":"Create an invite link for a user.\n\nArgs:\n request (Request): FastAPI Request object\n role (str): The role of the user\n\nReturns:\n InviteLinkSchema: Invite link","operationId":"create_invite_link_api_users_invite_link_post","security":[{"OAuth2PasswordBearer":[]},{"HTTPBasic":[]}],"parameters":[{"name":"role","in":"query","required":true,"schema":{"type":"string","title":"Role"}}],"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InviteLinkSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/users/register":{"post":{"tags":["users"],"summary":"Create User From Invite","description":"Create user endpoint with invite link\n\nArgs:\n username (str): User username\n email (str): User email\n password (str): User password\n token (str): Invite link token\n\nReturns:\n UserSchema: Newly created user","operationId":"create_user_from_invite_api_users_register_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_create_user_from_invite_api_users_register_post"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/users/identifiers":{"get":{"tags":["users"],"summary":"Get User Identifiers","description":"Get all user identifiers endpoint\n\nArgs:\n request (Request): Fastapi Request object\n\nReturns:\n list[int]: All user ids stored in the RomM's database","operationId":"get_user_identifiers_api_users_identifiers_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"type":"integer"},"type":"array","title":"Response Get User Identifiers Api Users Identifiers Get"}}}}},"security":[{"OAuth2PasswordBearer":["users.read"]},{"HTTPBasic":[]}]}},"/api/users/me":{"get":{"tags":["users"],"summary":"Get Current User","description":"Get current user endpoint\n\nArgs:\n request (Request): Fastapi Request object\n\nReturns:\n UserSchema | None: Current user","operationId":"get_current_user_api_users_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/UserSchema"},{"type":"null"}],"title":"Response Get Current User Api Users Me Get"}}}}},"security":[{"OAuth2PasswordBearer":["me.read"]},{"HTTPBasic":[]}]}},"/api/users/{id}":{"get":{"tags":["users"],"summary":"Get User","description":"Get user endpoint\n\nArgs:\n request (Request): Fastapi Request object\n\nReturns:\n UserSchem: User stored in the RomM's database","operationId":"get_user_api_users__id__get","security":[{"OAuth2PasswordBearer":["users.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","title":"Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["users"],"summary":"Update User","description":"Update user endpoint\n\nArgs:\n request (Request): Fastapi Requests object\n user_id (int): User internal id\n form_data (Annotated[UserUpdateForm, Depends): Form Data with user updated info\n\nRaises:\n HTTPException: User is not found in database\n HTTPException: Username already in use by another user\n\nReturns:\n UserSchema: Updated user info","operationId":"update_user_api_users__id__put","security":[{"OAuth2PasswordBearer":["me.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","title":"Id"}}],"requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/UserForm"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["users"],"summary":"Delete User","description":"Delete a user by ID.\n\nRaises:\n HTTPException: User is not found in database\n HTTPException: User deleting itself\n HTTPException: User is the last admin user","operationId":"delete_user_api_users__id__delete","security":[{"OAuth2PasswordBearer":["users.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"User internal id.","title":"Id"},"description":"User internal id."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"400":{"description":"Bad Request"},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/users/{id}/ra/refresh":{"post":{"tags":["users"],"summary":"Refresh RetroAchievements","description":"Refresh RetroAchievements progression data for a user.","operationId":"refresh_retro_achievements_api_users__id__ra_refresh_post","security":[{"OAuth2PasswordBearer":["me.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"User internal id.","title":"Id"},"description":"User internal id."}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_refresh_retro_achievements_api_users__id__ra_refresh_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/devices":{"get":{"tags":["devices"],"summary":"Get Devices","operationId":"get_devices_api_devices_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/DeviceSchema"},"type":"array","title":"Response Get Devices Api Devices Get"}}}}},"security":[{"OAuth2PasswordBearer":["devices.read"]},{"HTTPBasic":[]}]},"post":{"tags":["devices"],"summary":"Register Device","operationId":"register_device_api_devices_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceCreatePayload"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceCreateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":["devices.write"]},{"HTTPBasic":[]}]}},"/api/devices/{device_id}":{"get":{"tags":["devices"],"summary":"Get Device","operationId":"get_device_api_devices__device_id__get","security":[{"OAuth2PasswordBearer":["devices.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["devices"],"summary":"Update Device","operationId":"update_device_api_devices__device_id__put","security":[{"OAuth2PasswordBearer":["devices.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceUpdatePayload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["devices"],"summary":"Delete Device","operationId":"delete_device_api_devices__device_id__delete","security":[{"OAuth2PasswordBearer":["devices.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/platforms":{"post":{"tags":["platforms"],"summary":"Add Platform","description":"Create a platform.","operationId":"add_platform_api_platforms_post","security":[{"OAuth2PasswordBearer":["platforms.write"]},{"HTTPBasic":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_add_platform_api_platforms_post"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlatformSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["platforms"],"summary":"Get Platforms","description":"Retrieve platforms.","operationId":"get_platforms_api_platforms_get","security":[{"OAuth2PasswordBearer":["platforms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"updated_after","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Filter platforms updated after this datetime (ISO 8601 format with timezone information).","title":"Updated After"},"description":"Filter platforms updated after this datetime (ISO 8601 format with timezone information)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PlatformSchema"},"title":"Response Get Platforms Api Platforms Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/platforms/identifiers":{"get":{"tags":["platforms"],"summary":"Get Platform Identifiers","description":"Retrieve platform identifiers.","operationId":"get_platform_identifiers_api_platforms_identifiers_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"type":"integer"},"type":"array","title":"Response Get Platform Identifiers Api Platforms Identifiers Get"}}}}},"security":[{"OAuth2PasswordBearer":["platforms.read"]},{"HTTPBasic":[]}]}},"/api/platforms/supported":{"get":{"tags":["platforms"],"summary":"Get Supported Platforms Endpoint","description":"Retrieve the list of supported platforms.","operationId":"get_supported_platforms_endpoint_api_platforms_supported_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/PlatformSchema"},"type":"array","title":"Response Get Supported Platforms Endpoint Api Platforms Supported Get"}}}}},"security":[{"OAuth2PasswordBearer":["platforms.read"]},{"HTTPBasic":[]}]}},"/api/platforms/{id}":{"get":{"tags":["platforms"],"summary":"Get Platform","description":"Retrieve a platform by ID.","operationId":"get_platform_api_platforms__id__get","security":[{"OAuth2PasswordBearer":["platforms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Platform id.","title":"Id"},"description":"Platform id."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlatformSchema"}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["platforms"],"summary":"Update Platform","description":"Update a platform.","operationId":"update_platform_api_platforms__id__put","security":[{"OAuth2PasswordBearer":["platforms.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Platform id.","title":"Id"},"description":"Platform id."}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_update_platform_api_platforms__id__put"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlatformSchema"}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["platforms"],"summary":"Delete Platform","description":"Delete a platform by ID.","operationId":"delete_platform_api_platforms__id__delete","security":[{"OAuth2PasswordBearer":["platforms.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Platform id.","title":"Id"},"description":"Platform id."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/roms":{"post":{"tags":["roms"],"summary":"Add Rom","description":"Upload a single rom.","operationId":"add_rom_api_roms_post","security":[{"OAuth2PasswordBearer":["roms.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"x-upload-platform","in":"header","required":true,"schema":{"type":"integer","minimum":1,"description":"Platform internal id.","title":"X-Upload-Platform"},"description":"Platform internal id."},{"name":"x-upload-filename","in":"header","required":true,"schema":{"type":"string","description":"The name of the file being uploaded.","title":"X-Upload-Filename"},"description":"The name of the file being uploaded."}],"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"400":{"description":"Bad Request"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["roms"],"summary":"Get Roms","description":"Retrieve roms.","operationId":"get_roms_api_roms_get","security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"with_char_index","in":"query","required":false,"schema":{"type":"boolean","description":"Whether to get the char index.","default":true,"title":"With Char Index"},"description":"Whether to get the char index."},{"name":"with_filter_values","in":"query","required":false,"schema":{"type":"boolean","description":"Whether to return filter values.","default":true,"title":"With Filter Values"},"description":"Whether to return filter values."},{"name":"search_term","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search term to filter roms.","title":"Search Term"},"description":"Search term to filter roms."},{"name":"platform_ids","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"integer"}},{"type":"null"}],"description":"Platform internal ids. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned.","title":"Platform Ids"},"description":"Platform internal ids. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned."},{"name":"collection_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}],"description":"Collection internal id.","title":"Collection Id"},"description":"Collection internal id."},{"name":"virtual_collection_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Virtual collection internal id.","title":"Virtual Collection Id"},"description":"Virtual collection internal id."},{"name":"smart_collection_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}],"description":"Smart collection internal id.","title":"Smart Collection Id"},"description":"Smart collection internal id."},{"name":"matched","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Whether the rom matched at least one metadata source.","title":"Matched"},"description":"Whether the rom matched at least one metadata source."},{"name":"favorite","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Whether the rom is marked as favorite.","title":"Favorite"},"description":"Whether the rom is marked as favorite."},{"name":"duplicate","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Whether the rom is marked as duplicate.","title":"Duplicate"},"description":"Whether the rom is marked as duplicate."},{"name":"last_played","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Whether the rom has a last played value for the current user.","title":"Last Played"},"description":"Whether the rom has a last played value for the current user."},{"name":"playable","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Whether the rom is playable from the browser.","title":"Playable"},"description":"Whether the rom is playable from the browser."},{"name":"missing","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Whether the rom is missing from the filesystem.","title":"Missing"},"description":"Whether the rom is missing from the filesystem."},{"name":"has_ra","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Whether the rom has RetroAchievements data.","title":"Has Ra"},"description":"Whether the rom has RetroAchievements data."},{"name":"verified","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Whether the rom is verified by Hasheous.","title":"Verified"},"description":"Whether the rom is verified by Hasheous."},{"name":"group_by_meta_id","in":"query","required":false,"schema":{"type":"boolean","description":"Whether to group roms by metadata ID (IGDB / Moby / ScreenScraper / RetroAchievements / LaunchBox).","default":false,"title":"Group By Meta Id"},"description":"Whether to group roms by metadata ID (IGDB / Moby / ScreenScraper / RetroAchievements / LaunchBox)."},{"name":"genres","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Associated genre. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned.","title":"Genres"},"description":"Associated genre. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned."},{"name":"franchises","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Associated franchise. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned.","title":"Franchises"},"description":"Associated franchise. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned."},{"name":"collections","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Associated collection. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned.","title":"Collections"},"description":"Associated collection. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned."},{"name":"companies","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Associated company. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned.","title":"Companies"},"description":"Associated company. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned."},{"name":"age_ratings","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Associated age rating. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned.","title":"Age Ratings"},"description":"Associated age rating. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned."},{"name":"statuses","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Game status, set by the current user. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned.","title":"Statuses"},"description":"Game status, set by the current user. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned."},{"name":"regions","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Associated region tag. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned.","title":"Regions"},"description":"Associated region tag. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned."},{"name":"languages","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Associated language tag. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned.","title":"Languages"},"description":"Associated language tag. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned."},{"name":"player_counts","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Associated player count. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned.","title":"Player Counts"},"description":"Associated player count. Multiple values are allowed by repeating the parameter, and results that match any of the values will be returned."},{"name":"genres_logic","in":"query","required":false,"schema":{"type":"string","description":"Logic operator for genres filter: 'any' (OR), 'all' (AND) or 'none' (NOT).","default":"any","title":"Genres Logic"},"description":"Logic operator for genres filter: 'any' (OR), 'all' (AND) or 'none' (NOT)."},{"name":"franchises_logic","in":"query","required":false,"schema":{"type":"string","description":"Logic operator for franchises filter: 'any' (OR), 'all' (AND) or 'none' (NOT).","default":"any","title":"Franchises Logic"},"description":"Logic operator for franchises filter: 'any' (OR), 'all' (AND) or 'none' (NOT)."},{"name":"collections_logic","in":"query","required":false,"schema":{"type":"string","description":"Logic operator for collections filter: 'any' (OR), 'all' (AND) or 'none' (NOT).","default":"any","title":"Collections Logic"},"description":"Logic operator for collections filter: 'any' (OR), 'all' (AND) or 'none' (NOT)."},{"name":"companies_logic","in":"query","required":false,"schema":{"type":"string","description":"Logic operator for companies filter: 'any' (OR), 'all' (AND) or 'none' (NOT).","default":"any","title":"Companies Logic"},"description":"Logic operator for companies filter: 'any' (OR), 'all' (AND) or 'none' (NOT)."},{"name":"age_ratings_logic","in":"query","required":false,"schema":{"type":"string","description":"Logic operator for age ratings filter: 'any' (OR), 'all' (AND) or 'none' (NOT).","default":"any","title":"Age Ratings Logic"},"description":"Logic operator for age ratings filter: 'any' (OR), 'all' (AND) or 'none' (NOT)."},{"name":"regions_logic","in":"query","required":false,"schema":{"type":"string","description":"Logic operator for regions filter: 'any' (OR), 'all' (AND) or 'none' (NOT).","default":"any","title":"Regions Logic"},"description":"Logic operator for regions filter: 'any' (OR), 'all' (AND) or 'none' (NOT)."},{"name":"languages_logic","in":"query","required":false,"schema":{"type":"string","description":"Logic operator for languages filter: 'any' (OR), 'all' (AND) or 'none' (NOT).","default":"any","title":"Languages Logic"},"description":"Logic operator for languages filter: 'any' (OR), 'all' (AND) or 'none' (NOT)."},{"name":"statuses_logic","in":"query","required":false,"schema":{"type":"string","description":"Logic operator for statuses filter: 'any' (OR), 'all' (AND) or 'none' (NOT).","default":"any","title":"Statuses Logic"},"description":"Logic operator for statuses filter: 'any' (OR), 'all' (AND) or 'none' (NOT)."},{"name":"player_counts_logic","in":"query","required":false,"schema":{"type":"string","description":"Logic operator for player counts filter: 'any' (OR), 'all' (AND) or 'none' (NOT).","default":"any","title":"Player Counts Logic"},"description":"Logic operator for player counts filter: 'any' (OR), 'all' (AND) or 'none' (NOT)."},{"name":"order_by","in":"query","required":false,"schema":{"type":"string","description":"Field to order results by.","default":"name","title":"Order By"},"description":"Field to order results by."},{"name":"order_dir","in":"query","required":false,"schema":{"type":"string","description":"Order direction, either 'asc' or 'desc'.","default":"asc","title":"Order Dir"},"description":"Order direction, either 'asc' or 'desc'."},{"name":"updated_after","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Filter roms updated after this datetime (ISO 8601 format with timezone information).","title":"Updated After"},"description":"Filter roms updated after this datetime (ISO 8601 format with timezone information)."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":10000,"minimum":1,"description":"Page size limit","default":50,"title":"Limit"},"description":"Page size limit"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Page offset","default":0,"title":"Offset"},"description":"Page offset"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomLimitOffsetPage_SimpleRomSchema_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/roms/identifiers":{"get":{"tags":["roms"],"summary":"Get Rom Identifiers","description":"Retrieve rom identifiers.","operationId":"get_rom_identifiers_api_roms_identifiers_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"type":"integer"},"type":"array","title":"Response Get Rom Identifiers Api Roms Identifiers Get"}}}}},"security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}]}},"/api/roms/download":{"get":{"tags":["roms"],"summary":"Download Roms","description":"Download a list of roms as a zip file.","operationId":"download_roms_api_roms_download_get","security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"rom_ids","in":"query","required":true,"schema":{"type":"string","description":"Comma-separated list of ROM IDs to download as a zip file.","title":"Rom Ids"},"description":"Comma-separated list of ROM IDs to download as a zip file."},{"name":"filename","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Name for the zip file (optional).","title":"Filename"},"description":"Name for the zip file (optional)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/roms/by-metadata-provider":{"get":{"tags":["roms"],"summary":"Get Rom By Metadata Provider","description":"Retrieve a rom by metadata ID.","operationId":"get_rom_by_metadata_provider_api_roms_by_metadata_provider_get","security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"igdb_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"IGDB ID to search by","title":"Igdb Id"},"description":"IGDB ID to search by"},{"name":"moby_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"MobyGames ID to search by","title":"Moby Id"},"description":"MobyGames ID to search by"},{"name":"ss_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"ScreenScraper ID to search by","title":"Ss Id"},"description":"ScreenScraper ID to search by"},{"name":"ra_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"RetroAchievements ID to search by","title":"Ra Id"},"description":"RetroAchievements ID to search by"},{"name":"launchbox_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"LaunchBox ID to search by","title":"Launchbox Id"},"description":"LaunchBox ID to search by"},{"name":"hasheous_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Hasheous ID to search by","title":"Hasheous Id"},"description":"Hasheous ID to search by"},{"name":"tgdb_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"TGDB ID to search by","title":"Tgdb Id"},"description":"TGDB ID to search by"},{"name":"flashpoint_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Flashpoint ID to search by","title":"Flashpoint Id"},"description":"Flashpoint ID to search by"},{"name":"hltb_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"HLTB ID to search by","title":"Hltb Id"},"description":"HLTB ID to search by"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DetailedRomSchema"}}}},"404":{"description":"Not Found"},"400":{"description":"Bad Request"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/roms/by-hash":{"get":{"tags":["roms"],"summary":"Get Rom By Hash","operationId":"get_rom_by_hash_api_roms_by_hash_get","security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"crc_hash","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"CRC hash value","title":"Crc Hash"},"description":"CRC hash value"},{"name":"md5_hash","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"MD5 hash value","title":"Md5 Hash"},"description":"MD5 hash value"},{"name":"sha1_hash","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"SHA1 hash value","title":"Sha1 Hash"},"description":"SHA1 hash value"},{"name":"ra_hash","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"RetroAchievements hash value","title":"Ra Hash"},"description":"RetroAchievements hash value"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DetailedRomSchema"}}}},"404":{"description":"Not Found"},"400":{"description":"Bad Request"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/roms/filters":{"get":{"tags":["roms"],"summary":"Get Rom Filters","operationId":"get_rom_filters_api_roms_filters_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RomFiltersDict"}}}}},"security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}]}},"/api/roms/{id}":{"get":{"tags":["roms"],"summary":"Get Rom","description":"Retrieve a rom by ID.","operationId":"get_rom_api_roms__id__get","security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Rom internal id.","title":"Id"},"description":"Rom internal id."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DetailedRomSchema"}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["roms"],"summary":"Update Rom","description":"Update a rom.","operationId":"update_rom_api_roms__id__put","security":[{"OAuth2PasswordBearer":["roms.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Rom internal id.","title":"Id"},"description":"Rom internal id."},{"name":"remove_cover","in":"query","required":false,"schema":{"type":"boolean","description":"Whether to remove the cover image for this rom.","default":false,"title":"Remove Cover"},"description":"Whether to remove the cover image for this rom."},{"name":"unmatch_metadata","in":"query","required":false,"schema":{"type":"boolean","description":"Whether to remove the metadata matches for this game.","default":false,"title":"Unmatch Metadata"},"description":"Whether to remove the metadata matches for this game."}],"requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_update_rom_api_roms__id__put"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DetailedRomSchema"}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/roms/{id}/content/{file_name}":{"head":{"tags":["roms"],"summary":"Head Rom Content","description":"Retrieve head information for a rom file download.","operationId":"head_rom_content_api_roms__id__content__file_name__head","security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Rom internal id.","title":"Id"},"description":"Rom internal id."},{"name":"file_name","in":"path","required":true,"schema":{"type":"string","description":"File name to download","title":"File Name"},"description":"File name to download"},{"name":"file_ids","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated list of file ids to download for multi-part roms.","title":"File Ids"},"description":"Comma-separated list of file ids to download for multi-part roms."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["roms"],"summary":"Get Rom Content","description":"Download a rom.\n\nThis endpoint serves the content of the requested rom, as:\n- A single file for single file roms.\n- A zipped file for multi-part roms, including a .m3u file if applicable.","operationId":"get_rom_content_api_roms__id__content__file_name__get","security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Rom internal id.","title":"Id"},"description":"Rom internal id."},{"name":"file_name","in":"path","required":true,"schema":{"type":"string","description":"Zip file output name","title":"File Name"},"description":"Zip file output name"},{"name":"file_ids","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated list of file ids to download for multi-part roms.","title":"File Ids"},"description":"Comma-separated list of file ids to download for multi-part roms."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/roms/{id}/manuals":{"post":{"tags":["roms"],"summary":"Add Rom Manuals","description":"Upload manuals for a rom.","operationId":"add_rom_manuals_api_roms__id__manuals_post","security":[{"OAuth2PasswordBearer":["roms.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Rom internal id.","title":"Id"},"description":"Rom internal id."},{"name":"x-upload-filename","in":"header","required":true,"schema":{"type":"string","description":"The name of the file being uploaded.","title":"X-Upload-Filename"},"description":"The name of the file being uploaded."}],"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["roms"],"summary":"Delete Rom Manuals","description":"Delete manuals for a rom.","operationId":"delete_rom_manuals_api_roms__id__manuals_delete","security":[{"OAuth2PasswordBearer":["roms.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Rom internal id.","title":"Id"},"description":"Rom internal id."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/roms/delete":{"post":{"tags":["roms"],"summary":"Delete Roms","description":"Delete roms.","operationId":"delete_roms_api_roms_delete_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_delete_roms_api_roms_delete_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkOperationResponse"}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":["roms.write"]},{"HTTPBasic":[]}]}},"/api/roms/{id}/props":{"put":{"tags":["roms"],"summary":"Update Rom User","description":"Update rom data associated to the current user.","operationId":"update_rom_user_api_roms__id__props_put","security":[{"OAuth2PasswordBearer":["roms.user.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Rom internal id.","title":"Id"},"description":"Rom internal id."}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_update_rom_user_api_roms__id__props_put"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RomUserSchema"}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/roms/files/{id}":{"get":{"tags":["roms"],"summary":"Get Romfile","description":"Retrieve a rom file by ID.","operationId":"get_romfile_api_roms_files__id__get","security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Rom file internal id.","title":"Id"},"description":"Rom file internal id."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RomFileSchema"}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/romsfiles/{id}/content/{file_name}":{"get":{"tags":["roms"],"summary":"Get Romfile Content","description":"Download a rom file.","operationId":"get_romfile_content_api_romsfiles__id__content__file_name__get","security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Rom file internal id.","title":"Id"},"description":"Rom file internal id."},{"name":"file_name","in":"path","required":true,"schema":{"type":"string","description":"File name to download","title":"File Name"},"description":"File name to download"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/roms/{id}/notes":{"get":{"tags":["roms"],"summary":"Get Rom Notes","description":"Get all notes for a ROM.","operationId":"get_rom_notes_api_roms__id__notes_get","security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Rom internal id.","title":"Id"},"description":"Rom internal id."},{"name":"public_only","in":"query","required":false,"schema":{"type":"boolean","description":"Only return public notes","default":false,"title":"Public Only"},"description":"Only return public notes"},{"name":"search","in":"query","required":false,"schema":{"type":"string","description":"Search notes by title or content","title":"Search"},"description":"Search notes by title or content"},{"name":"tags","in":"query","required":false,"schema":{"type":"array","items":{"type":"string"},"description":"Filter by tags","title":"Tags"},"description":"Filter by tags"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UserNoteSchema"},"title":"Response Get Rom Notes Api Roms Id Notes Get"}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["roms"],"summary":"Create Rom Note","description":"Create a new note for a ROM.","operationId":"create_rom_note_api_roms__id__notes_post","security":[{"OAuth2PasswordBearer":["roms.user.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Rom internal id.","title":"Id"},"description":"Rom internal id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Note Data"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserNoteSchema"}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/roms/{id}/notes/identifiers":{"get":{"tags":["roms"],"summary":"Get Rom Note Identifiers","description":"Get all note identifiers for a ROM.","operationId":"get_rom_note_identifiers_api_roms__id__notes_identifiers_get","security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Rom internal id.","title":"Id"},"description":"Rom internal id."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"integer"},"title":"Response Get Rom Note Identifiers Api Roms Id Notes Identifiers Get"}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/roms/{id}/notes/{note_id}":{"put":{"tags":["roms"],"summary":"Update Rom Note","description":"Update a ROM note.","operationId":"update_rom_note_api_roms__id__notes__note_id__put","security":[{"OAuth2PasswordBearer":["roms.user.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Rom internal id.","title":"Id"},"description":"Rom internal id."},{"name":"note_id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Note id.","title":"Note Id"},"description":"Note id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Note Data"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserNoteSchema"}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["roms"],"summary":"Delete Rom Note","description":"Delete a ROM note.","operationId":"delete_rom_note_api_roms__id__notes__note_id__delete","security":[{"OAuth2PasswordBearer":["roms.user.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Rom internal id.","title":"Id"},"description":"Rom internal id."},{"name":"note_id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Note id.","title":"Note Id"},"description":"Note id."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Rom Note Api Roms Id Notes Note Id Delete"}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/search/roms":{"get":{"tags":["search"],"summary":"Search Rom","description":"Search for rom in metadata providers\n\nArgs:\n request (Request): FastAPI request\n rom_id (int): Rom ID\n source (str): Source of the rom\n search_term (str, optional): Search term. Defaults to None.\n search_by (str, optional): Search by name or ID. Defaults to \"name\".\n search_extended (bool, optional): Search extended info. Defaults to False.\n\nReturns:\n list[SearchRomSchema]: List of matched roms","operationId":"search_rom_api_search_roms_get","security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"rom_id","in":"query","required":true,"schema":{"type":"integer","title":"Rom Id"}},{"name":"search_term","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Search Term"}},{"name":"search_by","in":"query","required":false,"schema":{"type":"string","default":"name","title":"Search By"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SearchRomSchema"},"title":"Response Search Rom Api Search Roms Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/search/cover":{"get":{"tags":["search"],"summary":"Search Cover","operationId":"search_cover_api_search_cover_get","security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"search_term","in":"query","required":false,"schema":{"type":"string","default":"","title":"Search Term"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SearchCoverSchema"},"title":"Response Search Cover Api Search Cover Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/saves":{"post":{"tags":["saves"],"summary":"Add Save","description":"Upload a save file for a ROM.","operationId":"add_save_api_saves_post","security":[{"OAuth2PasswordBearer":["assets.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"rom_id","in":"query","required":true,"schema":{"type":"integer","title":"Rom Id"}},{"name":"emulator","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Emulator"}},{"name":"slot","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slot"}},{"name":"device_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Device Id"}},{"name":"overwrite","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Overwrite"}},{"name":"autocleanup","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Autocleanup"}},{"name":"autocleanup_limit","in":"query","required":false,"schema":{"type":"integer","default":10,"title":"Autocleanup Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SaveSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["saves"],"summary":"Get Saves","description":"Retrieve saves for the current user.","operationId":"get_saves_api_saves_get","security":[{"OAuth2PasswordBearer":["assets.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"rom_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rom Id"}},{"name":"platform_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Platform Id"}},{"name":"device_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Device Id"}},{"name":"slot","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slot"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SaveSchema"},"title":"Response Get Saves Api Saves Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/saves/identifiers":{"get":{"tags":["saves"],"summary":"Get Save Identifiers","description":"Retrieve save identifiers.","operationId":"get_save_identifiers_api_saves_identifiers_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"type":"integer"},"type":"array","title":"Response Get Save Identifiers Api Saves Identifiers Get"}}}}},"security":[{"OAuth2PasswordBearer":["assets.read"]},{"HTTPBasic":[]}]}},"/api/saves/summary":{"get":{"tags":["saves"],"summary":"Get Saves Summary","description":"Retrieve saves summary grouped by slot.","operationId":"get_saves_summary_api_saves_summary_get","security":[{"OAuth2PasswordBearer":["assets.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"rom_id","in":"query","required":true,"schema":{"type":"integer","title":"Rom Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SaveSummarySchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/saves/{id}":{"get":{"tags":["saves"],"summary":"Get Save","description":"Retrieve a save by ID.","operationId":"get_save_api_saves__id__get","security":[{"OAuth2PasswordBearer":["assets.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","title":"Id"}},{"name":"device_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Device Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SaveSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["saves"],"summary":"Update Save","description":"Update a save file.","operationId":"update_save_api_saves__id__put","security":[{"OAuth2PasswordBearer":["assets.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","title":"Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SaveSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/saves/{id}/content":{"get":{"tags":["saves"],"summary":"Download Save","description":"Download a save file.","operationId":"download_save_api_saves__id__content_get","security":[{"OAuth2PasswordBearer":["assets.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","title":"Id"}},{"name":"device_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Device Id"}},{"name":"optimistic","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Optimistic"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/saves/{id}/downloaded":{"post":{"tags":["saves"],"summary":"Confirm Download","description":"Confirm a save was downloaded successfully.","operationId":"confirm_download_api_saves__id__downloaded_post","security":[{"OAuth2PasswordBearer":["devices.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","title":"Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_confirm_download_api_saves__id__downloaded_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SaveSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/saves/delete":{"post":{"tags":["saves"],"summary":"Delete Saves","description":"Delete saves.","operationId":"delete_saves_api_saves_delete_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_delete_saves_api_saves_delete_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"type":"integer"},"type":"array","title":"Response Delete Saves Api Saves Delete Post"}}}},"400":{"description":"Bad Request"},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":["assets.write"]},{"HTTPBasic":[]}]}},"/api/saves/{id}/track":{"post":{"tags":["saves"],"summary":"Track Save","description":"Re-enable sync tracking for a save on a device.","operationId":"track_save_api_saves__id__track_post","security":[{"OAuth2PasswordBearer":["devices.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","title":"Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_track_save_api_saves__id__track_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SaveSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/saves/{id}/untrack":{"post":{"tags":["saves"],"summary":"Untrack Save","description":"Disable sync tracking for a save on a device.","operationId":"untrack_save_api_saves__id__untrack_post","security":[{"OAuth2PasswordBearer":["devices.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","title":"Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_untrack_save_api_saves__id__untrack_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SaveSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/states":{"post":{"tags":["states"],"summary":"Add State","operationId":"add_state_api_states_post","security":[{"OAuth2PasswordBearer":["assets.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"rom_id","in":"query","required":true,"schema":{"type":"integer","title":"Rom Id"}},{"name":"emulator","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Emulator"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StateSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["states"],"summary":"Get States","operationId":"get_states_api_states_get","security":[{"OAuth2PasswordBearer":["assets.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"rom_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rom Id"}},{"name":"platform_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Platform Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/StateSchema"},"title":"Response Get States Api States Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/states/identifiers":{"get":{"tags":["states"],"summary":"Get State Identifiers","description":"Get state identifiers endpoint\n\nArgs:\n request (Request): Fastapi Request object\n\nReturns:\n list[int]: List of state IDs","operationId":"get_state_identifiers_api_states_identifiers_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"type":"integer"},"type":"array","title":"Response Get State Identifiers Api States Identifiers Get"}}}}},"security":[{"OAuth2PasswordBearer":["assets.read"]},{"HTTPBasic":[]}]}},"/api/states/{id}":{"get":{"tags":["states"],"summary":"Get State","operationId":"get_state_api_states__id__get","security":[{"OAuth2PasswordBearer":["assets.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","title":"Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StateSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["states"],"summary":"Update State","operationId":"update_state_api_states__id__put","security":[{"OAuth2PasswordBearer":["assets.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","title":"Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StateSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/states/delete":{"post":{"tags":["states"],"summary":"Delete States","description":"Delete states.","operationId":"delete_states_api_states_delete_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_delete_states_api_states_delete_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"type":"integer"},"type":"array","title":"Response Delete States Api States Delete Post"}}}},"400":{"description":"Bad Request"},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":["assets.write"]},{"HTTPBasic":[]}]}},"/api/tasks":{"get":{"tags":["tasks"],"summary":"List Tasks","description":"List all available tasks grouped by task type.\n\nArgs:\n request (Request): FastAPI Request object\nReturns:\n GroupedTasksDict: Dictionary with tasks grouped by their type (scheduled, manual, watcher)","operationId":"list_tasks_api_tasks_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"items":{"$ref":"#/components/schemas/TaskInfo"},"type":"array"},"type":"object","title":"Response List Tasks Api Tasks Get"}}}}},"security":[{"OAuth2PasswordBearer":["tasks.run"]},{"HTTPBasic":[]}]}},"/api/tasks/status":{"get":{"tags":["tasks"],"summary":"Get Tasks Status","description":"Get all active, queued, completed, and failed tasks.\n\nArgs:\n request (Request): FastAPI Request object\nReturns:\n list[TaskStatusResponse]: List of all tasks with their current status","operationId":"get_tasks_status_api_tasks_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"anyOf":[{"$ref":"#/components/schemas/ScanTaskStatusResponse"},{"$ref":"#/components/schemas/ConversionTaskStatusResponse"},{"$ref":"#/components/schemas/UpdateTaskStatusResponse"},{"$ref":"#/components/schemas/CleanupTaskStatusResponse"},{"$ref":"#/components/schemas/WatcherTaskStatusResponse"},{"$ref":"#/components/schemas/GenericTaskStatusResponse"}]},"type":"array","title":"Response Get Tasks Status Api Tasks Status Get"}}}}},"security":[{"OAuth2PasswordBearer":["tasks.run"]},{"HTTPBasic":[]}]}},"/api/tasks/{task_id}":{"get":{"tags":["tasks"],"summary":"Get Task By Id","description":"Get the status of a task by its job ID.\n\nArgs:\n request (Request): FastAPI Request object\n task_id (str): Job ID of the task to retrieve status for\nReturns:\n TaskStatusResponse: Task status information","operationId":"get_task_by_id_api_tasks__task_id__get","security":[{"OAuth2PasswordBearer":["tasks.run"]},{"HTTPBasic":[]}],"parameters":[{"name":"task_id","in":"path","required":true,"schema":{"type":"string","title":"Task Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/ScanTaskStatusResponse"},{"$ref":"#/components/schemas/ConversionTaskStatusResponse"},{"$ref":"#/components/schemas/UpdateTaskStatusResponse"},{"$ref":"#/components/schemas/CleanupTaskStatusResponse"},{"$ref":"#/components/schemas/WatcherTaskStatusResponse"},{"$ref":"#/components/schemas/GenericTaskStatusResponse"}],"title":"Response Get Task By Id Api Tasks Task Id Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/tasks/run":{"post":{"tags":["tasks"],"summary":"Run All Tasks","description":"Run all runnable tasks endpoint\n\nArgs:\n request (Request): FastAPI Request object\nReturns:\n TaskExecutionResponse: Task execution response with details","operationId":"run_all_tasks_api_tasks_run_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/TaskExecutionResponse"},"type":"array","title":"Response Run All Tasks Api Tasks Run Post"}}}}},"security":[{"OAuth2PasswordBearer":["tasks.run"]},{"HTTPBasic":[]}]}},"/api/tasks/run/{task_name}":{"post":{"tags":["tasks"],"summary":"Run Single Task","description":"Run a single task endpoint.\n\nArgs:\n request (Request): FastAPI Request object\n task_name (str): Name of the task to run\nReturns:\n TaskExecutionResponse: Task execution response with details","operationId":"run_single_task_api_tasks_run__task_name__post","security":[{"OAuth2PasswordBearer":["tasks.run"]},{"HTTPBasic":[]}],"parameters":[{"name":"task_name","in":"path","required":true,"schema":{"type":"string","title":"Task Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskExecutionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/feeds/webrcade":{"get":{"tags":["feeds"],"summary":"Platforms Webrcade Feed","description":"Get webrcade feed endpoint\nhttps://docs.webrcade.com/feeds/format/\n\nArgs:\n request (Request): Fastapi Request object\n\nReturns:\n WebrcadeFeedSchema: Webrcade feed object schema","operationId":"platforms_webrcade_feed_api_feeds_webrcade_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebrcadeFeedSchema"}}}}},"security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}]}},"/api/feeds/tinfoil":{"get":{"tags":["feeds"],"summary":"Tinfoil Index Feed","description":"Get tinfoil custom index feed endpoint\nhttps://blawar.github.io/tinfoil/custom_index/\n\nArgs:\n request (Request): Fastapi Request object\n slug (str, optional): Platform slug. Defaults to \"switch\".\n\nReturns:\n TinfoilFeedSchema: Tinfoil feed object schema","operationId":"tinfoil_index_feed_api_feeds_tinfoil_get","security":[{"OAuth2PasswordBearer":[]},{"HTTPBasic":[]}],"parameters":[{"name":"slug","in":"query","required":false,"schema":{"type":"string","default":"switch","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TinfoilFeedSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/feeds/pkgi/ps3/{content_type}":{"get":{"tags":["feeds"],"summary":"Pkgi Ps3 Feed","description":"Get PKGi PS3 feed endpoint\nhttps://github.com/bucanero/pkgi-ps3\n\nArgs:\n request (Request): Fastapi Request object\n content_type (str): Content type (game, dlc, demo, update, patch, mod, translation, prototype)\n\nReturns:\n Response: txt file with PKGi PS3 database format","operationId":"pkgi_ps3_feed_api_feeds_pkgi_ps3__content_type__get","security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"content_type","in":"path","required":true,"schema":{"type":"string","description":"Content type","title":"Content Type"},"description":"Content type"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/feeds/pkgi/psvita/{content_type}":{"get":{"tags":["feeds"],"summary":"Pkgi Psvita Feed","description":"Get PKGi PS Vita feed endpoint\nhttps://github.com/mmozeiko/pkgi\n\nArgs:\n request (Request): Fastapi Request object\n content_type (str): Content type (game, dlc, demo, update, patch, mod, translation, prototype)\n\nReturns:\n Response: txt file with PKGi PS Vita database format","operationId":"pkgi_psvita_feed_api_feeds_pkgi_psvita__content_type__get","security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"content_type","in":"path","required":true,"schema":{"type":"string","description":"Content type","title":"Content Type"},"description":"Content type"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/feeds/pkgi/psp/{content_type}":{"get":{"tags":["feeds"],"summary":"Pkgi Psp Feed","description":"Get PKGi PSP feed endpoint\nhttps://github.com/bucanero/pkgi-psp\n\nArgs:\n request (Request): Fastapi Request object\n content_type (str): Content type (game, dlc, demo, update, patch, mod, translation, prototype)\n\nReturns:\n Response: txt file with PKGi PSP database format","operationId":"pkgi_psp_feed_api_feeds_pkgi_psp__content_type__get","security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"content_type","in":"path","required":true,"schema":{"type":"string","description":"Content type","title":"Content Type"},"description":"Content type"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/feeds/fpkgi/{platform_slug}":{"get":{"tags":["feeds"],"summary":"Fpkgi Feed","description":"https://github.com/ItsJokerZz/FPKGi\n\nArgs:\n request (Request): Fastapi Request object\n platform_slug (str): Platform slug (ps4, ps5)\n\nReturns:\n Response: JSON file in FPKGi format","operationId":"fpkgi_feed_api_feeds_fpkgi__platform_slug__get","security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"platform_slug","in":"path","required":true,"schema":{"type":"string","title":"Platform Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/feeds/kekatsu/{platform_slug}":{"get":{"tags":["feeds"],"summary":"Kekatsu Ds Feed","description":"Get Kekatsu DS feed endpoint\nhttps://github.com/cavv-dev/Kekatsu-DS\n\nArgs:\n request (Request): Fastapi Request object\n platform_slug (str): Platform slug (nds, nintendo-ds, ds, gba, etc.)\n\nReturns:\n Response: Text file with Kekatsu DS database format","operationId":"kekatsu_ds_feed_api_feeds_kekatsu__platform_slug__get","security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"platform_slug","in":"path","required":true,"schema":{"type":"string","title":"Platform Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/feeds/pkgj/psp/games":{"get":{"tags":["feeds"],"summary":"Pkgj Psp Games Feed","operationId":"pkgj_psp_games_feed_api_feeds_pkgj_psp_games_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}]}},"/api/feeds/pkgj/psp/dlc":{"get":{"tags":["feeds"],"summary":"Pkgj Psp Dlcs Feed","operationId":"pkgj_psp_dlcs_feed_api_feeds_pkgj_psp_dlc_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}]}},"/api/feeds/pkgj/psvita/games":{"get":{"tags":["feeds"],"summary":"Pkgj Psv Games Feed","operationId":"pkgj_psv_games_feed_api_feeds_pkgj_psvita_games_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}]}},"/api/feeds/pkgj/psvita/dlc":{"get":{"tags":["feeds"],"summary":"Pkgj Psv Dlcs Feed","operationId":"pkgj_psv_dlcs_feed_api_feeds_pkgj_psvita_dlc_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}]}},"/api/feeds/pkgj/psx/games":{"get":{"tags":["feeds"],"summary":"Pkgj Psx Games Feed","operationId":"pkgj_psx_games_feed_api_feeds_pkgj_psx_games_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}]}},"/api/config":{"get":{"tags":["config"],"summary":"Get Config","description":"Get config endpoint\n\nReturns:\n ConfigResponse: RomM's configuration","operationId":"get_config_api_config_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfigResponse"}}}}}}},"/api/config/system/platforms":{"post":{"tags":["config"],"summary":"Add Platform Binding","description":"Add platform binding to the configuration","operationId":"add_platform_binding_api_config_system_platforms_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":["platforms.write"]},{"HTTPBasic":[]}]}},"/api/config/system/platforms/{fs_slug}":{"delete":{"tags":["config"],"summary":"Delete Platform Binding","description":"Delete platform binding from the configuration","operationId":"delete_platform_binding_api_config_system_platforms__fs_slug__delete","security":[{"OAuth2PasswordBearer":["platforms.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"fs_slug","in":"path","required":true,"schema":{"type":"string","title":"Fs Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/config/system/versions":{"post":{"tags":["config"],"summary":"Add Platform Version","description":"Add platform version to the configuration","operationId":"add_platform_version_api_config_system_versions_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":["platforms.write"]},{"HTTPBasic":[]}]}},"/api/config/system/versions/{fs_slug}":{"delete":{"tags":["config"],"summary":"Delete Platform Version","description":"Delete platform version from the configuration","operationId":"delete_platform_version_api_config_system_versions__fs_slug__delete","security":[{"OAuth2PasswordBearer":["platforms.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"fs_slug","in":"path","required":true,"schema":{"type":"string","title":"Fs Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/config/exclude":{"post":{"tags":["config"],"summary":"Add Exclusion","description":"Add platform exclusion to the configuration","operationId":"add_exclusion_api_config_exclude_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":["platforms.write"]},{"HTTPBasic":[]}]}},"/api/config/exclude/{exclusion_type}/{exclusion_value}":{"delete":{"tags":["config"],"summary":"Delete Exclusion","description":"Delete platform binding from the configuration","operationId":"delete_exclusion_api_config_exclude__exclusion_type___exclusion_value__delete","security":[{"OAuth2PasswordBearer":["platforms.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"exclusion_type","in":"path","required":true,"schema":{"type":"string","title":"Exclusion Type"}},{"name":"exclusion_value","in":"path","required":true,"schema":{"type":"string","title":"Exclusion Value"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/stats":{"get":{"tags":["stats"],"summary":"Stats","description":"Endpoint to return the current RomM stats\n\nReturns:\n dict: Dictionary with all the stats","operationId":"stats_api_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatsReturn"}}}}}}},"/api/raw/assets/{path}":{"head":{"tags":["raw"],"summary":"Head Raw Asset","operationId":"head_raw_asset_api_raw_assets__path__head","security":[{"OAuth2PasswordBearer":["assets.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"path","in":"path","required":true,"schema":{"type":"string","title":"Path"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["raw"],"summary":"Get Raw Asset","description":"Download a single asset file\n\nArgs:\n request (Request): Fastapi Request object\n path (str): Relative path to the asset file\n\nReturns:\n FileResponse: Returns a single asset file\n\nRaises:\n HTTPException: 404 if asset not found or access denied","operationId":"get_raw_asset_api_raw_assets__path__get","security":[{"OAuth2PasswordBearer":["assets.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"path","in":"path","required":true,"schema":{"type":"string","title":"Path"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/screenshots":{"post":{"tags":["screenshots"],"summary":"Add Screenshot","operationId":"add_screenshot_api_screenshots_post","security":[{"OAuth2PasswordBearer":["assets.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"rom_id","in":"query","required":true,"schema":{"type":"integer","title":"Rom Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScreenshotSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/firmware":{"post":{"tags":["firmware"],"summary":"Add Firmware","description":"Upload firmware files endpoint\n\nArgs:\n request (Request): Fastapi Request object\n platform_slug (str): Slug of the platform where to upload the files\n files (list[UploadFile], optional): List of files to upload\n\nRaises:\n HTTPException\n\nReturns:\n AddFirmwareResponse: Standard message response","operationId":"add_firmware_api_firmware_post","security":[{"OAuth2PasswordBearer":["firmware.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"platform_id","in":"query","required":true,"schema":{"type":"integer","title":"Platform Id"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_add_firmware_api_firmware_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddFirmwareResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["firmware"],"summary":"Get Platform Firmware","description":"Get firmware endpoint\n\nArgs:\n request (Request): Fastapi Request object\n\nReturns:\n list[FirmwareSchema]: Firmware stored in the database","operationId":"get_platform_firmware_api_firmware_get","security":[{"OAuth2PasswordBearer":["firmware.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"platform_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Platform Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/FirmwareSchema"},"title":"Response Get Platform Firmware Api Firmware Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/firmware/identifiers":{"get":{"tags":["firmware"],"summary":"Get Firmware Identifiers","description":"Get firmware identifiers endpoint\n\nArgs:\n request (Request): Fastapi Request object\n\nReturns:\n list[int]: List of firmware IDs","operationId":"get_firmware_identifiers_api_firmware_identifiers_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"type":"integer"},"type":"array","title":"Response Get Firmware Identifiers Api Firmware Identifiers Get"}}}}},"security":[{"OAuth2PasswordBearer":["firmware.read"]},{"HTTPBasic":[]}]}},"/api/firmware/{id}":{"get":{"tags":["firmware"],"summary":"Get Firmware","description":"Get firmware endpoint\n\nArgs:\n request (Request): Fastapi Request object\n id (int): Firmware internal id\n\nReturns:\n FirmwareSchema: Firmware stored in the database","operationId":"get_firmware_api_firmware__id__get","security":[{"OAuth2PasswordBearer":["firmware.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","title":"Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FirmwareSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/firmware/{id}/content/{file_name}":{"head":{"tags":["firmware"],"summary":"Head Firmware Content","description":"Head firmware content endpoint\n\nArgs:\n request (Request): Fastapi Request object\n id (int): Rom internal id\n file_name (str): Required due to a bug in emulatorjs\n\nReturns:\n FileResponse: Returns the response with headers","operationId":"head_firmware_content_api_firmware__id__content__file_name__head","security":[{"OAuth2PasswordBearer":["firmware.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","title":"Id"}},{"name":"file_name","in":"path","required":true,"schema":{"type":"string","title":"File Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["firmware"],"summary":"Get Firmware Content","description":"Download firmware endpoint\n\nArgs:\n request (Request): Fastapi Request object\n id (int): Rom internal id\n file_name (str): Required due to a bug in emulatorjs\n\nReturns:\n FileResponse: Returns the firmware file","operationId":"get_firmware_content_api_firmware__id__content__file_name__get","security":[{"OAuth2PasswordBearer":["firmware.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","title":"Id"}},{"name":"file_name","in":"path","required":true,"schema":{"type":"string","title":"File Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/firmware/delete":{"post":{"tags":["firmware"],"summary":"Delete Firmware","description":"Delete firmware.","operationId":"delete_firmware_api_firmware_delete_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_delete_firmware_api_firmware_delete_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkOperationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":["firmware.write"]},{"HTTPBasic":[]}]}},"/api/collections":{"post":{"tags":["collections"],"summary":"Add Collection","description":"Create collection endpoint\n\nArgs:\n request (Request): Fastapi Request object\n\nReturns:\n CollectionSchema: Just created collection","operationId":"add_collection_api_collections_post","security":[{"OAuth2PasswordBearer":["collections.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"is_public","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Public"}},{"name":"is_favorite","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Favorite"}}],"requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_add_collection_api_collections_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["collections"],"summary":"Get Collections","description":"Get collections endpoint\n\nArgs:\n request (Request): Fastapi Request object\n updated_after: Filter collections updated after this datetime\n\nReturns:\n list[CollectionSchema]: List of collections","operationId":"get_collections_api_collections_get","security":[{"OAuth2PasswordBearer":["collections.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"updated_after","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Filter collections updated after this datetime (ISO 8601 format with timezone information).","title":"Updated After"},"description":"Filter collections updated after this datetime (ISO 8601 format with timezone information)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CollectionSchema"},"title":"Response Get Collections Api Collections Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/collections/smart":{"post":{"tags":["collections"],"summary":"Add Smart Collection","description":"Create smart collection endpoint\n\nArgs:\n request (Request): Fastapi Request object\n\nReturns:\n SmartCollectionSchema: Just created smart collection","operationId":"add_smart_collection_api_collections_smart_post","security":[{"OAuth2PasswordBearer":["collections.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"is_public","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Public"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SmartCollectionSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["collections"],"summary":"Get Smart Collections","description":"Get smart collections endpoint\n\nArgs:\n request (Request): Fastapi Request object\n updated_after: Filter smart collections updated after this datetime\n\nReturns:\n list[SmartCollectionSchema]: List of smart collections","operationId":"get_smart_collections_api_collections_smart_get","security":[{"OAuth2PasswordBearer":["collections.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"updated_after","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Filter smart collections updated after this datetime (ISO 8601 format with timezone information).","title":"Updated After"},"description":"Filter smart collections updated after this datetime (ISO 8601 format with timezone information)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SmartCollectionSchema"},"title":"Response Get Smart Collections Api Collections Smart Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/collections/identifiers":{"get":{"tags":["collections"],"summary":"Get Collection Identifiers","description":"Get collections identifiers endpoint\n\nArgs:\n request (Request): Fastapi Request object\n\nReturns:\n list[int]: List of collection IDs","operationId":"get_collection_identifiers_api_collections_identifiers_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"type":"integer"},"type":"array","title":"Response Get Collection Identifiers Api Collections Identifiers Get"}}}}},"security":[{"OAuth2PasswordBearer":["collections.read"]},{"HTTPBasic":[]}]}},"/api/collections/virtual":{"get":{"tags":["collections"],"summary":"Get Virtual Collections","description":"Get virtual collections endpoint\n\nArgs:\n request (Request): Fastapi Request object\n\nReturns:\n list[VirtualCollectionSchema]: List of virtual collections","operationId":"get_virtual_collections_api_collections_virtual_get","security":[{"OAuth2PasswordBearer":["collections.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"type","in":"query","required":true,"schema":{"type":"string","title":"Type"}},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/VirtualCollectionSchema"},"title":"Response Get Virtual Collections Api Collections Virtual Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/collections/virtual/identifiers":{"get":{"tags":["collections"],"summary":"Get Virtual Collection Identifiers","description":"Get virtual collections identifiers endpoint\n\nArgs:\n request (Request): Fastapi Request object\n\nReturns:\n list[str]: List of generated virtual collection IDs","operationId":"get_virtual_collection_identifiers_api_collections_virtual_identifiers_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"type":"string"},"type":"array","title":"Response Get Virtual Collection Identifiers Api Collections Virtual Identifiers Get"}}}}},"security":[{"OAuth2PasswordBearer":["collections.read"]},{"HTTPBasic":[]}]}},"/api/collections/smart/identifiers":{"get":{"tags":["collections"],"summary":"Get Smart Collection Identifiers","description":"Get smart collections identifiers endpoint\n\nArgs:\n request (Request): Fastapi Request object\n\nReturns:\n list[int]: List of smart collection IDs","operationId":"get_smart_collection_identifiers_api_collections_smart_identifiers_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"type":"integer"},"type":"array","title":"Response Get Smart Collection Identifiers Api Collections Smart Identifiers Get"}}}}},"security":[{"OAuth2PasswordBearer":["collections.read"]},{"HTTPBasic":[]}]}},"/api/collections/{id}":{"get":{"tags":["collections"],"summary":"Get Collection","description":"Get collections endpoint\n\nArgs:\n request (Request): Fastapi Request object\n id (int, optional): Collection id. Defaults to None.\n\nReturns:\n CollectionSchema: Collection","operationId":"get_collection_api_collections__id__get","security":[{"OAuth2PasswordBearer":["collections.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","title":"Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["collections"],"summary":"Update Collection","description":"Update collection endpoint\n\nArgs:\n request (Request): Fastapi Request object\n\nReturns:\n CollectionSchema: Updated collection","operationId":"update_collection_api_collections__id__put","security":[{"OAuth2PasswordBearer":["collections.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","title":"Id"}},{"name":"remove_cover","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Remove Cover"}},{"name":"is_public","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Public"}}],"requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_update_collection_api_collections__id__put"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["collections"],"summary":"Delete Collection","description":"Delete a collection by ID.","operationId":"delete_collection_api_collections__id__delete","security":[{"OAuth2PasswordBearer":["collections.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Collection internal id.","title":"Id"},"description":"Collection internal id."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/collections/virtual/{id}":{"get":{"tags":["collections"],"summary":"Get Virtual Collection","description":"Get virtual collections endpoint\n\nArgs:\n request (Request): Fastapi Request object\n id (str): Virtual collection id\n\nReturns:\n VirtualCollectionSchema: Virtual collection","operationId":"get_virtual_collection_api_collections_virtual__id__get","security":[{"OAuth2PasswordBearer":["collections.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","title":"Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VirtualCollectionSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/collections/smart/{id}":{"get":{"tags":["collections"],"summary":"Get Smart Collection","description":"Get smart collection endpoint\n\nArgs:\n request (Request): Fastapi Request object\n id (int): Smart collection id\n\nReturns:\n SmartCollectionSchema: Smart collection","operationId":"get_smart_collection_api_collections_smart__id__get","security":[{"OAuth2PasswordBearer":["collections.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","title":"Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SmartCollectionSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["collections"],"summary":"Update Smart Collection","description":"Update smart collection endpoint\n\nArgs:\n request (Request): Fastapi Request object\n id (int): Smart collection id\n\nReturns:\n SmartCollectionSchema: Updated smart collection","operationId":"update_smart_collection_api_collections_smart__id__put","security":[{"OAuth2PasswordBearer":["collections.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","title":"Id"}},{"name":"is_public","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Public"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SmartCollectionSchema"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["collections"],"summary":"Delete Smart Collection","description":"Delete a smart collection by ID.","operationId":"delete_smart_collection_api_collections_smart__id__delete","security":[{"OAuth2PasswordBearer":["collections.write"]},{"HTTPBasic":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"description":"Smart collection internal id.","title":"Id"},"description":"Smart collection internal id."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/gamelist/export":{"post":{"tags":["gamelist"],"summary":"Export Gamelist","description":"Export platforms/ROMs to gamelist.xml format and write to platform directories","operationId":"export_gamelist_api_gamelist_export_post","security":[{"OAuth2PasswordBearer":["roms.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"platform_ids","in":"query","required":true,"schema":{"type":"array","items":{"type":"integer"},"description":"List of platform IDs to export","title":"Platform Ids"},"description":"List of platform IDs to export"},{"name":"local_export","in":"query","required":false,"schema":{"type":"boolean","description":"Use local paths instead of URLs","default":false,"title":"Local Export"},"description":"Use local paths instead of URLs"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/netplay/list":{"get":{"tags":["netplay"],"summary":"Get Rooms","operationId":"get_rooms_api_netplay_list_get","security":[{"OAuth2PasswordBearer":["assets.read"]},{"HTTPBasic":[]}],"parameters":[{"name":"game_id","in":"query","required":true,"schema":{"type":"string","title":"Game Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/RoomsResponse"},"title":"Response Get Rooms Api Netplay List Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"AddFirmwareResponse":{"properties":{"uploaded":{"type":"integer","title":"Uploaded"},"firmware":{"items":{"$ref":"#/components/schemas/FirmwareSchema"},"type":"array","title":"Firmware"}},"type":"object","required":["uploaded","firmware"],"title":"AddFirmwareResponse"},"Body_add_collection_api_collections_post":{"properties":{"artwork":{"anyOf":[{"type":"string","format":"binary"},{"type":"null"}],"title":"Artwork"}},"type":"object","title":"Body_add_collection_api_collections_post"},"Body_add_firmware_api_firmware_post":{"properties":{"files":{"items":{"type":"string","format":"binary"},"type":"array","title":"Files"}},"type":"object","required":["files"],"title":"Body_add_firmware_api_firmware_post"},"Body_add_platform_api_platforms_post":{"properties":{"fs_slug":{"type":"string","title":"Fs Slug","description":"Platform slug."}},"type":"object","required":["fs_slug"],"title":"Body_add_platform_api_platforms_post"},"Body_add_user_api_users_post":{"properties":{"username":{"type":"string","title":"Username"},"email":{"type":"string","title":"Email"},"password":{"type":"string","title":"Password"},"role":{"type":"string","title":"Role"}},"type":"object","required":["username","email","password","role"],"title":"Body_add_user_api_users_post"},"Body_confirm_download_api_saves__id__downloaded_post":{"properties":{"device_id":{"type":"string","title":"Device Id"}},"type":"object","required":["device_id"],"title":"Body_confirm_download_api_saves__id__downloaded_post"},"Body_create_user_from_invite_api_users_register_post":{"properties":{"username":{"type":"string","title":"Username"},"email":{"type":"string","title":"Email"},"password":{"type":"string","title":"Password"},"token":{"type":"string","title":"Token"}},"type":"object","required":["username","email","password","token"],"title":"Body_create_user_from_invite_api_users_register_post"},"Body_delete_firmware_api_firmware_delete_post":{"properties":{"firmware":{"items":{"type":"integer"},"type":"array","title":"Firmware","description":"List of firmware ids to delete from database."},"delete_from_fs":{"items":{"type":"integer"},"type":"array","title":"Delete From Fs","description":"List of firmware ids to delete from filesystem."}},"type":"object","required":["firmware"],"title":"Body_delete_firmware_api_firmware_delete_post"},"Body_delete_roms_api_roms_delete_post":{"properties":{"roms":{"items":{"type":"integer"},"type":"array","title":"Roms","description":"List of rom ids to delete from database."},"delete_from_fs":{"items":{"type":"integer"},"type":"array","title":"Delete From Fs","description":"List of rom ids to delete from filesystem."}},"type":"object","required":["roms"],"title":"Body_delete_roms_api_roms_delete_post"},"Body_delete_saves_api_saves_delete_post":{"properties":{"saves":{"items":{"type":"integer"},"type":"array","title":"Saves","description":"List of save ids to delete from database."}},"type":"object","required":["saves"],"title":"Body_delete_saves_api_saves_delete_post"},"Body_delete_states_api_states_delete_post":{"properties":{"states":{"items":{"type":"integer"},"type":"array","title":"States","description":"List of states ids to delete from database."}},"type":"object","required":["states"],"title":"Body_delete_states_api_states_delete_post"},"Body_refresh_retro_achievements_api_users__id__ra_refresh_post":{"properties":{"incremental":{"type":"boolean","title":"Incremental","description":"Whether to only retrieve RetroAchievements progression incrementally.","default":false}},"type":"object","title":"Body_refresh_retro_achievements_api_users__id__ra_refresh_post"},"Body_request_password_reset_api_forgot_password_post":{"properties":{"username":{"type":"string","title":"Username"}},"type":"object","required":["username"],"title":"Body_request_password_reset_api_forgot_password_post"},"Body_reset_password_api_reset_password_post":{"properties":{"token":{"type":"string","title":"Token"},"new_password":{"type":"string","title":"New Password"}},"type":"object","required":["token","new_password"],"title":"Body_reset_password_api_reset_password_post"},"Body_token_api_token_post":{"properties":{"grant_type":{"type":"string","title":"Grant Type","default":"password"},"scope":{"type":"string","title":"Scope","default":""},"username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Username"},"password":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Password"},"client_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id"},"client_secret":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Secret"},"refresh_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Refresh Token"}},"type":"object","title":"Body_token_api_token_post"},"Body_track_save_api_saves__id__track_post":{"properties":{"device_id":{"type":"string","title":"Device Id"}},"type":"object","required":["device_id"],"title":"Body_track_save_api_saves__id__track_post"},"Body_untrack_save_api_saves__id__untrack_post":{"properties":{"device_id":{"type":"string","title":"Device Id"}},"type":"object","required":["device_id"],"title":"Body_untrack_save_api_saves__id__untrack_post"},"Body_update_collection_api_collections__id__put":{"properties":{"artwork":{"anyOf":[{"type":"string","format":"binary"},{"type":"null"}],"title":"Artwork"}},"type":"object","title":"Body_update_collection_api_collections__id__put"},"Body_update_platform_api_platforms__id__put":{"properties":{"aspect_ratio":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Aspect Ratio","description":"Cover aspect ratio."},"custom_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Custom Name","description":"Custom platform name."}},"type":"object","title":"Body_update_platform_api_platforms__id__put"},"Body_update_rom_api_roms__id__put":{"properties":{"artwork":{"anyOf":[{"type":"string","format":"binary"},{"type":"null"}],"title":"Artwork","description":"Custom artwork to set as cover."}},"type":"object","title":"Body_update_rom_api_roms__id__put"},"Body_update_rom_user_api_roms__id__props_put":{"properties":{"update_last_played":{"type":"boolean","title":"Update Last Played","description":"Whether to update the last played date.","default":false},"remove_last_played":{"type":"boolean","title":"Remove Last Played","description":"Whether to remove the last played date.","default":false}},"type":"object","title":"Body_update_rom_user_api_roms__id__props_put"},"BulkOperationResponse":{"properties":{"successful_items":{"type":"integer","title":"Successful Items"},"failed_items":{"type":"integer","title":"Failed Items"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"}},"type":"object","required":["successful_items","failed_items","errors"],"title":"BulkOperationResponse"},"CleanupStats":{"properties":{"platforms_in_db":{"type":"integer","title":"Platforms In Db"},"roms_in_db":{"type":"integer","title":"Roms In Db"},"platforms_in_fs":{"type":"integer","title":"Platforms In Fs"},"roms_in_fs":{"type":"integer","title":"Roms In Fs"},"removed_fs_platforms":{"type":"integer","title":"Removed Fs Platforms"},"removed_fs_roms":{"type":"integer","title":"Removed Fs Roms"}},"type":"object","required":["platforms_in_db","roms_in_db","platforms_in_fs","roms_in_fs","removed_fs_platforms","removed_fs_roms"],"title":"CleanupStats"},"CleanupTaskMeta":{"properties":{"cleanup_stats":{"anyOf":[{"$ref":"#/components/schemas/CleanupStats"},{"type":"null"}]}},"type":"object","required":["cleanup_stats"],"title":"CleanupTaskMeta"},"CleanupTaskStatusResponse":{"properties":{"task_name":{"type":"string","title":"Task Name"},"task_id":{"type":"string","title":"Task Id"},"status":{"$ref":"#/components/schemas/JobStatus"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"enqueued_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Enqueued At"},"started_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Started At"},"ended_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ended At"},"task_type":{"type":"string","const":"cleanup","title":"Task Type"},"meta":{"$ref":"#/components/schemas/CleanupTaskMeta"}},"type":"object","required":["task_name","task_id","status","created_at","enqueued_at","started_at","ended_at","task_type","meta"],"title":"CleanupTaskStatusResponse"},"CollectionSchema":{"properties":{"name":{"type":"string","title":"Name"},"description":{"type":"string","title":"Description"},"rom_ids":{"items":{"type":"integer"},"type":"array","uniqueItems":true,"title":"Rom Ids"},"rom_count":{"type":"integer","title":"Rom Count"},"path_cover_small":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path Cover Small"},"path_cover_large":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path Cover Large"},"path_covers_small":{"items":{"type":"string"},"type":"array","title":"Path Covers Small"},"path_covers_large":{"items":{"type":"string"},"type":"array","title":"Path Covers Large"},"is_public":{"type":"boolean","title":"Is Public","default":false},"is_favorite":{"type":"boolean","title":"Is Favorite","default":false},"is_virtual":{"type":"boolean","title":"Is Virtual","default":false},"is_smart":{"type":"boolean","title":"Is Smart","default":false},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"id":{"type":"integer","title":"Id"},"url_cover":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url Cover"},"user_id":{"type":"integer","title":"User Id"},"owner_username":{"type":"string","title":"Owner Username"}},"type":"object","required":["name","description","rom_ids","rom_count","path_cover_small","path_cover_large","path_covers_small","path_covers_large","created_at","updated_at","id","url_cover","user_id","owner_username"],"title":"CollectionSchema"},"ConfigResponse":{"properties":{"CONFIG_FILE_MOUNTED":{"type":"boolean","title":"Config File Mounted"},"CONFIG_FILE_WRITABLE":{"type":"boolean","title":"Config File Writable"},"EXCLUDED_PLATFORMS":{"items":{"type":"string"},"type":"array","title":"Excluded Platforms"},"EXCLUDED_SINGLE_EXT":{"items":{"type":"string"},"type":"array","title":"Excluded Single Ext"},"EXCLUDED_SINGLE_FILES":{"items":{"type":"string"},"type":"array","title":"Excluded Single Files"},"EXCLUDED_MULTI_FILES":{"items":{"type":"string"},"type":"array","title":"Excluded Multi Files"},"EXCLUDED_MULTI_PARTS_EXT":{"items":{"type":"string"},"type":"array","title":"Excluded Multi Parts Ext"},"EXCLUDED_MULTI_PARTS_FILES":{"items":{"type":"string"},"type":"array","title":"Excluded Multi Parts Files"},"PLATFORMS_BINDING":{"additionalProperties":{"type":"string"},"type":"object","title":"Platforms Binding"},"PLATFORMS_VERSIONS":{"additionalProperties":{"type":"string"},"type":"object","title":"Platforms Versions"},"SKIP_HASH_CALCULATION":{"type":"boolean","title":"Skip Hash Calculation"},"EJS_DEBUG":{"type":"boolean","title":"Ejs Debug"},"EJS_CACHE_LIMIT":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ejs Cache Limit"},"EJS_DISABLE_AUTO_UNLOAD":{"type":"boolean","title":"Ejs Disable Auto Unload"},"EJS_DISABLE_BATCH_BOOTUP":{"type":"boolean","title":"Ejs Disable Batch Bootup"},"EJS_NETPLAY_ENABLED":{"type":"boolean","title":"Ejs Netplay Enabled"},"EJS_NETPLAY_ICE_SERVERS":{"items":{"$ref":"#/components/schemas/NetplayICEServer"},"type":"array","title":"Ejs Netplay Ice Servers"},"EJS_SETTINGS":{"additionalProperties":{"additionalProperties":{"type":"string"},"type":"object"},"type":"object","title":"Ejs Settings"},"EJS_CONTROLS":{"additionalProperties":{"$ref":"#/components/schemas/EjsControls"},"type":"object","title":"Ejs Controls"},"SCAN_METADATA_PRIORITY":{"items":{"type":"string"},"type":"array","title":"Scan Metadata Priority"},"SCAN_ARTWORK_PRIORITY":{"items":{"type":"string"},"type":"array","title":"Scan Artwork Priority"},"SCAN_REGION_PRIORITY":{"items":{"type":"string"},"type":"array","title":"Scan Region Priority"},"SCAN_LANGUAGE_PRIORITY":{"items":{"type":"string"},"type":"array","title":"Scan Language Priority"},"SCAN_MEDIA":{"items":{"type":"string"},"type":"array","title":"Scan Media"}},"type":"object","required":["CONFIG_FILE_MOUNTED","CONFIG_FILE_WRITABLE","EXCLUDED_PLATFORMS","EXCLUDED_SINGLE_EXT","EXCLUDED_SINGLE_FILES","EXCLUDED_MULTI_FILES","EXCLUDED_MULTI_PARTS_EXT","EXCLUDED_MULTI_PARTS_FILES","PLATFORMS_BINDING","PLATFORMS_VERSIONS","SKIP_HASH_CALCULATION","EJS_DEBUG","EJS_CACHE_LIMIT","EJS_DISABLE_AUTO_UNLOAD","EJS_DISABLE_BATCH_BOOTUP","EJS_NETPLAY_ENABLED","EJS_NETPLAY_ICE_SERVERS","EJS_SETTINGS","EJS_CONTROLS","SCAN_METADATA_PRIORITY","SCAN_ARTWORK_PRIORITY","SCAN_REGION_PRIORITY","SCAN_LANGUAGE_PRIORITY","SCAN_MEDIA"],"title":"ConfigResponse"},"ConversionStats":{"properties":{"processed":{"type":"integer","title":"Processed"},"errors":{"type":"integer","title":"Errors"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["processed","errors","total"],"title":"ConversionStats"},"ConversionTaskMeta":{"properties":{"conversion_stats":{"anyOf":[{"$ref":"#/components/schemas/ConversionStats"},{"type":"null"}]}},"type":"object","required":["conversion_stats"],"title":"ConversionTaskMeta"},"ConversionTaskStatusResponse":{"properties":{"task_name":{"type":"string","title":"Task Name"},"task_id":{"type":"string","title":"Task Id"},"status":{"$ref":"#/components/schemas/JobStatus"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"enqueued_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Enqueued At"},"started_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Started At"},"ended_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ended At"},"task_type":{"type":"string","const":"conversion","title":"Task Type"},"meta":{"$ref":"#/components/schemas/ConversionTaskMeta"}},"type":"object","required":["task_name","task_id","status","created_at","enqueued_at","started_at","ended_at","task_type","meta"],"title":"ConversionTaskStatusResponse"},"CustomLimitOffsetPage_SimpleRomSchema_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/SimpleRomSchema"},"type":"array","title":"Items"},"total":{"type":"integer","minimum":0.0,"title":"Total"},"limit":{"type":"integer","minimum":1.0,"title":"Limit"},"offset":{"type":"integer","minimum":0.0,"title":"Offset"},"char_index":{"additionalProperties":{"type":"integer"},"type":"object","title":"Char Index"},"rom_id_index":{"items":{"type":"integer"},"type":"array","title":"Rom Id Index"},"filter_values":{"$ref":"#/components/schemas/RomFiltersDict"}},"type":"object","required":["items","total","limit","offset","char_index","rom_id_index","filter_values"],"title":"CustomLimitOffsetPage[SimpleRomSchema]"},"DetailedRomSchema":{"properties":{"id":{"type":"integer","title":"Id"},"igdb_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Igdb Id"},"sgdb_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sgdb Id"},"moby_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Moby Id"},"ss_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ss Id"},"ra_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ra Id"},"launchbox_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Launchbox Id"},"hasheous_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Hasheous Id"},"tgdb_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Tgdb Id"},"flashpoint_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Flashpoint Id"},"hltb_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Hltb Id"},"gamelist_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gamelist Id"},"platform_id":{"type":"integer","title":"Platform Id"},"platform_slug":{"type":"string","title":"Platform Slug"},"platform_fs_slug":{"type":"string","title":"Platform Fs Slug"},"platform_custom_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Platform Custom Name"},"platform_display_name":{"type":"string","title":"Platform Display Name"},"fs_name":{"type":"string","title":"Fs Name"},"fs_name_no_tags":{"type":"string","title":"Fs Name No Tags"},"fs_name_no_ext":{"type":"string","title":"Fs Name No Ext"},"fs_extension":{"type":"string","title":"Fs Extension"},"fs_path":{"type":"string","title":"Fs Path"},"fs_size_bytes":{"type":"integer","title":"Fs Size Bytes"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slug"},"summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Summary"},"alternative_names":{"items":{"type":"string"},"type":"array","title":"Alternative Names"},"youtube_video_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Youtube Video Id"},"metadatum":{"$ref":"#/components/schemas/RomMetadataSchema"},"igdb_metadata":{"anyOf":[{"$ref":"#/components/schemas/RomIGDBMetadata"},{"type":"null"}]},"moby_metadata":{"anyOf":[{"$ref":"#/components/schemas/RomMobyMetadata"},{"type":"null"}]},"ss_metadata":{"anyOf":[{"$ref":"#/components/schemas/RomSSMetadata"},{"type":"null"}]},"launchbox_metadata":{"anyOf":[{"$ref":"#/components/schemas/RomLaunchboxMetadata"},{"type":"null"}]},"hasheous_metadata":{"anyOf":[{"$ref":"#/components/schemas/RomHasheousMetadata"},{"type":"null"}]},"flashpoint_metadata":{"anyOf":[{"$ref":"#/components/schemas/RomFlashpointMetadata"},{"type":"null"}]},"hltb_metadata":{"anyOf":[{"$ref":"#/components/schemas/RomHLTBMetadata"},{"type":"null"}]},"gamelist_metadata":{"anyOf":[{"$ref":"#/components/schemas/RomGamelistMetadata"},{"type":"null"}]},"manual_metadata":{"anyOf":[{"$ref":"#/components/schemas/ManualMetadata"},{"type":"null"}]},"path_cover_small":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path Cover Small"},"path_cover_large":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path Cover Large"},"url_cover":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url Cover"},"has_manual":{"type":"boolean","title":"Has Manual"},"path_manual":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path Manual"},"url_manual":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url Manual"},"is_identifying":{"type":"boolean","title":"Is Identifying","default":false},"is_unidentified":{"type":"boolean","title":"Is Unidentified"},"is_identified":{"type":"boolean","title":"Is Identified"},"revision":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Revision"},"regions":{"items":{"type":"string"},"type":"array","title":"Regions"},"languages":{"items":{"type":"string"},"type":"array","title":"Languages"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags"},"crc_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Crc Hash"},"md5_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Md5 Hash"},"sha1_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sha1 Hash"},"ra_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ra Hash"},"has_simple_single_file":{"type":"boolean","title":"Has Simple Single File"},"has_nested_single_file":{"type":"boolean","title":"Has Nested Single File"},"has_multiple_files":{"type":"boolean","title":"Has Multiple Files"},"files":{"items":{"$ref":"#/components/schemas/RomFileSchema"},"type":"array","title":"Files"},"full_path":{"type":"string","title":"Full Path"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"missing_from_fs":{"type":"boolean","title":"Missing From Fs"},"has_notes":{"type":"boolean","title":"Has Notes"},"siblings":{"items":{"$ref":"#/components/schemas/SiblingRomSchema"},"type":"array","title":"Siblings"},"rom_user":{"$ref":"#/components/schemas/RomUserSchema"},"merged_screenshots":{"items":{"type":"string"},"type":"array","title":"Merged Screenshots"},"merged_ra_metadata":{"anyOf":[{"$ref":"#/components/schemas/RomRAMetadata"},{"type":"null"}]},"user_saves":{"items":{"$ref":"#/components/schemas/SaveSchema"},"type":"array","title":"User Saves"},"user_states":{"items":{"$ref":"#/components/schemas/StateSchema"},"type":"array","title":"User States"},"user_screenshots":{"items":{"$ref":"#/components/schemas/ScreenshotSchema"},"type":"array","title":"User Screenshots"},"user_collections":{"items":{"$ref":"#/components/schemas/UserCollectionSchema"},"type":"array","title":"User Collections"},"all_user_notes":{"items":{"$ref":"#/components/schemas/UserNoteSchema"},"type":"array","title":"All User Notes"}},"type":"object","required":["id","igdb_id","sgdb_id","moby_id","ss_id","ra_id","launchbox_id","hasheous_id","tgdb_id","flashpoint_id","hltb_id","gamelist_id","platform_id","platform_slug","platform_fs_slug","platform_custom_name","platform_display_name","fs_name","fs_name_no_tags","fs_name_no_ext","fs_extension","fs_path","fs_size_bytes","name","slug","summary","alternative_names","youtube_video_id","metadatum","igdb_metadata","moby_metadata","ss_metadata","launchbox_metadata","hasheous_metadata","flashpoint_metadata","hltb_metadata","gamelist_metadata","manual_metadata","path_cover_small","path_cover_large","url_cover","has_manual","path_manual","url_manual","is_unidentified","is_identified","revision","regions","languages","tags","crc_hash","md5_hash","sha1_hash","ra_hash","has_simple_single_file","has_nested_single_file","has_multiple_files","files","full_path","created_at","updated_at","missing_from_fs","has_notes","siblings","rom_user","merged_screenshots","merged_ra_metadata","user_saves","user_states","user_screenshots","user_collections","all_user_notes"],"title":"DetailedRomSchema"},"DeviceCreatePayload":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"platform":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Platform"},"client":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client"},"client_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Version"},"ip_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ip Address"},"mac_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mac Address"},"hostname":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hostname"},"allow_existing":{"type":"boolean","title":"Allow Existing","default":true},"allow_duplicate":{"type":"boolean","title":"Allow Duplicate","default":false},"reset_syncs":{"type":"boolean","title":"Reset Syncs","default":false}},"type":"object","title":"DeviceCreatePayload"},"DeviceCreateResponse":{"properties":{"device_id":{"type":"string","title":"Device Id"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["device_id","name","created_at"],"title":"DeviceCreateResponse"},"DeviceSchema":{"properties":{"id":{"type":"string","title":"Id"},"user_id":{"type":"integer","title":"User Id"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"platform":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Platform"},"client":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client"},"client_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Version"},"ip_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ip Address"},"mac_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mac Address"},"hostname":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hostname"},"sync_mode":{"$ref":"#/components/schemas/SyncMode"},"sync_enabled":{"type":"boolean","title":"Sync Enabled"},"last_seen":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Seen"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","user_id","name","platform","client","client_version","ip_address","mac_address","hostname","sync_mode","sync_enabled","last_seen","created_at","updated_at"],"title":"DeviceSchema"},"DeviceSyncSchema":{"properties":{"device_id":{"type":"string","title":"Device Id"},"device_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Device Name"},"last_synced_at":{"type":"string","format":"date-time","title":"Last Synced At"},"is_untracked":{"type":"boolean","title":"Is Untracked"},"is_current":{"type":"boolean","title":"Is Current"}},"type":"object","required":["device_id","device_name","last_synced_at","is_untracked","is_current"],"title":"DeviceSyncSchema"},"DeviceUpdatePayload":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"platform":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Platform"},"client":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client"},"client_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Version"},"ip_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ip Address"},"mac_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mac Address"},"hostname":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hostname"},"sync_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Sync Enabled"}},"type":"object","title":"DeviceUpdatePayload"},"EarnedAchievement":{"properties":{"id":{"type":"string","title":"Id"},"date":{"type":"string","title":"Date"},"date_hardcore":{"type":"string","title":"Date Hardcore"}},"type":"object","required":["id","date"],"title":"EarnedAchievement"},"EjsControls":{"properties":{"_0":{"additionalProperties":{"$ref":"#/components/schemas/EjsControlsButton"},"type":"object","title":"0"},"_1":{"additionalProperties":{"$ref":"#/components/schemas/EjsControlsButton"},"type":"object","title":"1"},"_2":{"additionalProperties":{"$ref":"#/components/schemas/EjsControlsButton"},"type":"object","title":"2"},"_3":{"additionalProperties":{"$ref":"#/components/schemas/EjsControlsButton"},"type":"object","title":"3"}},"type":"object","required":["_0","_1","_2","_3"],"title":"EjsControls"},"EjsControlsButton":{"properties":{"value":{"type":"string","title":"Value"},"value2":{"type":"string","title":"Value2"}},"type":"object","title":"EjsControlsButton"},"EmulationDict":{"properties":{"DISABLE_EMULATOR_JS":{"type":"boolean","title":"Disable Emulator Js"},"DISABLE_RUFFLE_RS":{"type":"boolean","title":"Disable Ruffle Rs"}},"type":"object","required":["DISABLE_EMULATOR_JS","DISABLE_RUFFLE_RS"],"title":"EmulationDict"},"FilesystemDict":{"properties":{"FS_PLATFORMS":{"items":{"type":"string"},"type":"array","title":"Fs Platforms"}},"type":"object","required":["FS_PLATFORMS"],"title":"FilesystemDict"},"FirmwareSchema":{"properties":{"id":{"type":"integer","title":"Id"},"file_name":{"type":"string","title":"File Name"},"file_name_no_tags":{"type":"string","title":"File Name No Tags"},"file_name_no_ext":{"type":"string","title":"File Name No Ext"},"file_extension":{"type":"string","title":"File Extension"},"file_path":{"type":"string","title":"File Path"},"file_size_bytes":{"type":"integer","title":"File Size Bytes"},"full_path":{"type":"string","title":"Full Path"},"is_verified":{"type":"boolean","title":"Is Verified"},"crc_hash":{"type":"string","title":"Crc Hash"},"md5_hash":{"type":"string","title":"Md5 Hash"},"sha1_hash":{"type":"string","title":"Sha1 Hash"},"missing_from_fs":{"type":"boolean","title":"Missing From Fs"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","file_name","file_name_no_tags","file_name_no_ext","file_extension","file_path","file_size_bytes","full_path","is_verified","crc_hash","md5_hash","sha1_hash","missing_from_fs","created_at","updated_at"],"title":"FirmwareSchema"},"FrontendDict":{"properties":{"UPLOAD_TIMEOUT":{"type":"integer","title":"Upload Timeout"},"DISABLE_USERPASS_LOGIN":{"type":"boolean","title":"Disable Userpass Login"},"YOUTUBE_BASE_URL":{"type":"string","title":"Youtube Base Url"}},"type":"object","required":["UPLOAD_TIMEOUT","DISABLE_USERPASS_LOGIN","YOUTUBE_BASE_URL"],"title":"FrontendDict"},"GenericTaskMeta":{"properties":{},"type":"object","title":"GenericTaskMeta"},"GenericTaskStatusResponse":{"properties":{"task_name":{"type":"string","title":"Task Name"},"task_id":{"type":"string","title":"Task Id"},"status":{"$ref":"#/components/schemas/JobStatus"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"enqueued_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Enqueued At"},"started_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Started At"},"ended_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ended At"},"task_type":{"type":"string","const":"generic","title":"Task Type"},"meta":{"$ref":"#/components/schemas/GenericTaskMeta"}},"type":"object","required":["task_name","task_id","status","created_at","enqueued_at","started_at","ended_at","task_type","meta"],"title":"GenericTaskStatusResponse"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HeartbeatResponse":{"properties":{"SYSTEM":{"$ref":"#/components/schemas/SystemDict"},"METADATA_SOURCES":{"$ref":"#/components/schemas/MetadataSourcesDict"},"FILESYSTEM":{"$ref":"#/components/schemas/FilesystemDict"},"EMULATION":{"$ref":"#/components/schemas/EmulationDict"},"FRONTEND":{"$ref":"#/components/schemas/FrontendDict"},"OIDC":{"$ref":"#/components/schemas/OIDCDict"},"TASKS":{"$ref":"#/components/schemas/TasksDict"}},"type":"object","required":["SYSTEM","METADATA_SOURCES","FILESYSTEM","EMULATION","FRONTEND","OIDC","TASKS"],"title":"HeartbeatResponse"},"IGDBAgeRating":{"properties":{"rating":{"type":"string","title":"Rating"},"category":{"type":"string","title":"Category"},"rating_cover_url":{"type":"string","title":"Rating Cover Url"}},"type":"object","required":["rating","category","rating_cover_url"],"title":"IGDBAgeRating"},"IGDBMetadataMultiplayerMode":{"properties":{"campaigncoop":{"type":"boolean","title":"Campaigncoop"},"dropin":{"type":"boolean","title":"Dropin"},"lancoop":{"type":"boolean","title":"Lancoop"},"offlinecoop":{"type":"boolean","title":"Offlinecoop"},"offlinecoopmax":{"type":"integer","title":"Offlinecoopmax"},"offlinemax":{"type":"integer","title":"Offlinemax"},"onlinecoop":{"type":"integer","title":"Onlinecoop"},"onlinecoopmax":{"type":"integer","title":"Onlinecoopmax"},"onlinemax":{"type":"integer","title":"Onlinemax"},"splitscreen":{"type":"boolean","title":"Splitscreen"},"splitscreenonline":{"type":"boolean","title":"Splitscreenonline"},"platform":{"$ref":"#/components/schemas/IGDBMetadataPlatform"}},"type":"object","required":["campaigncoop","dropin","lancoop","offlinecoop","offlinecoopmax","offlinemax","onlinecoop","onlinecoopmax","onlinemax","splitscreen","splitscreenonline","platform"],"title":"IGDBMetadataMultiplayerMode"},"IGDBMetadataPlatform":{"properties":{"igdb_id":{"type":"integer","title":"Igdb Id"},"name":{"type":"string","title":"Name"}},"type":"object","required":["igdb_id","name"],"title":"IGDBMetadataPlatform"},"IGDBRelatedGame":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"slug":{"type":"string","title":"Slug"},"type":{"type":"string","title":"Type"},"cover_url":{"type":"string","title":"Cover Url"}},"type":"object","required":["id","name","slug","type","cover_url"],"title":"IGDBRelatedGame"},"InviteLinkSchema":{"properties":{"token":{"type":"string","title":"Token"}},"type":"object","required":["token"],"title":"InviteLinkSchema"},"JobStatus":{"type":"string","enum":["queued","finished","failed","started","deferred","scheduled","stopped","canceled"],"title":"JobStatus","description":"The Status of Job within its lifecycle at any given time."},"LaunchboxImage":{"properties":{"url":{"type":"string","title":"Url"},"type":{"type":"string","title":"Type"},"region":{"type":"string","title":"Region"}},"type":"object","required":["url"],"title":"LaunchboxImage"},"ManualMetadata":{"properties":{"genres":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Genres"},"franchises":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Franchises"},"companies":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Companies"},"game_modes":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Game Modes"},"age_ratings":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Age Ratings"},"first_release_date":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"First Release Date"},"youtube_video_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Youtube Video Id"}},"type":"object","title":"ManualMetadata"},"MetadataSourcesDict":{"properties":{"ANY_SOURCE_ENABLED":{"type":"boolean","title":"Any Source Enabled"},"IGDB_API_ENABLED":{"type":"boolean","title":"Igdb Api Enabled"},"SS_API_ENABLED":{"type":"boolean","title":"Ss Api Enabled"},"MOBY_API_ENABLED":{"type":"boolean","title":"Moby Api Enabled"},"STEAMGRIDDB_API_ENABLED":{"type":"boolean","title":"Steamgriddb Api Enabled"},"RA_API_ENABLED":{"type":"boolean","title":"Ra Api Enabled"},"LAUNCHBOX_API_ENABLED":{"type":"boolean","title":"Launchbox Api Enabled"},"HASHEOUS_API_ENABLED":{"type":"boolean","title":"Hasheous Api Enabled"},"PLAYMATCH_API_ENABLED":{"type":"boolean","title":"Playmatch Api Enabled"},"TGDB_API_ENABLED":{"type":"boolean","title":"Tgdb Api Enabled"},"FLASHPOINT_API_ENABLED":{"type":"boolean","title":"Flashpoint Api Enabled"},"HLTB_API_ENABLED":{"type":"boolean","title":"Hltb Api Enabled"}},"type":"object","required":["ANY_SOURCE_ENABLED","IGDB_API_ENABLED","SS_API_ENABLED","MOBY_API_ENABLED","STEAMGRIDDB_API_ENABLED","RA_API_ENABLED","LAUNCHBOX_API_ENABLED","HASHEOUS_API_ENABLED","PLAYMATCH_API_ENABLED","TGDB_API_ENABLED","FLASHPOINT_API_ENABLED","HLTB_API_ENABLED"],"title":"MetadataSourcesDict"},"MobyMetadataPlatform":{"properties":{"moby_id":{"type":"integer","title":"Moby Id"},"name":{"type":"string","title":"Name"}},"type":"object","required":["moby_id","name"],"title":"MobyMetadataPlatform"},"NetplayICEServer":{"properties":{"urls":{"type":"string","title":"Urls"},"username":{"type":"string","title":"Username"},"credential":{"type":"string","title":"Credential"}},"type":"object","required":["urls"],"title":"NetplayICEServer"},"OIDCDict":{"properties":{"ENABLED":{"type":"boolean","title":"Enabled"},"AUTOLOGIN":{"type":"boolean","title":"Autologin"},"PROVIDER":{"type":"string","title":"Provider"}},"type":"object","required":["ENABLED","AUTOLOGIN","PROVIDER"],"title":"OIDCDict"},"PlatformSchema":{"properties":{"id":{"type":"integer","title":"Id"},"slug":{"type":"string","title":"Slug"},"fs_slug":{"type":"string","title":"Fs Slug"},"rom_count":{"type":"integer","title":"Rom Count"},"name":{"type":"string","title":"Name"},"igdb_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Igdb Slug"},"moby_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Moby Slug"},"hltb_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hltb Slug"},"custom_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Custom Name"},"igdb_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Igdb Id"},"sgdb_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sgdb Id"},"moby_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Moby Id"},"launchbox_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Launchbox Id"},"ss_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ss Id"},"ra_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ra Id"},"hasheous_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Hasheous Id"},"tgdb_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Tgdb Id"},"flashpoint_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Flashpoint Id"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"generation":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Generation"},"family_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Family Name"},"family_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Family Slug"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"},"url_logo":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url Logo"},"firmware":{"items":{"$ref":"#/components/schemas/FirmwareSchema"},"type":"array","title":"Firmware"},"aspect_ratio":{"type":"string","title":"Aspect Ratio","default":"2 / 3"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"fs_size_bytes":{"type":"integer","title":"Fs Size Bytes"},"is_unidentified":{"type":"boolean","title":"Is Unidentified"},"is_identified":{"type":"boolean","title":"Is Identified"},"missing_from_fs":{"type":"boolean","title":"Missing From Fs"},"display_name":{"type":"string","title":"Display Name","readOnly":true}},"type":"object","required":["id","slug","fs_slug","rom_count","name","igdb_slug","moby_slug","hltb_slug","created_at","updated_at","fs_size_bytes","is_unidentified","is_identified","missing_from_fs","display_name"],"title":"PlatformSchema"},"RAGameRomAchievement":{"properties":{"ra_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ra Id"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"points":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Points"},"num_awarded":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Num Awarded"},"num_awarded_hardcore":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Num Awarded Hardcore"},"badge_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Badge Id"},"badge_url_lock":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Badge Url Lock"},"badge_path_lock":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Badge Path Lock"},"badge_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Badge Url"},"badge_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Badge Path"},"display_order":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Display Order"},"type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"}},"type":"object","required":["ra_id","title","description","points","num_awarded","num_awarded_hardcore","badge_id","badge_url_lock","badge_path_lock","badge_url","badge_path","display_order","type"],"title":"RAGameRomAchievement"},"RAProgression":{"properties":{"total":{"type":"integer","title":"Total"},"results":{"items":{"$ref":"#/components/schemas/RAUserGameProgression"},"type":"array","title":"Results"}},"type":"object","title":"RAProgression"},"RAUserGameProgression":{"properties":{"rom_ra_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rom Ra Id"},"max_possible":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Possible"},"num_awarded":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Num Awarded"},"num_awarded_hardcore":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Num Awarded Hardcore"},"most_recent_awarded_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Most Recent Awarded Date"},"earned_achievements":{"items":{"$ref":"#/components/schemas/EarnedAchievement"},"type":"array","title":"Earned Achievements"}},"type":"object","required":["rom_ra_id","max_possible","num_awarded","num_awarded_hardcore","earned_achievements"],"title":"RAUserGameProgression"},"Role":{"type":"string","enum":["viewer","editor","admin"],"title":"Role"},"RomFileCategory":{"type":"string","enum":["game","dlc","hack","manual","patch","update","mod","demo","translation","prototype","cheat"],"title":"RomFileCategory"},"RomFileSchema":{"properties":{"id":{"type":"integer","title":"Id"},"rom_id":{"type":"integer","title":"Rom Id"},"file_name":{"type":"string","title":"File Name"},"file_path":{"type":"string","title":"File Path"},"file_size_bytes":{"type":"integer","title":"File Size Bytes"},"full_path":{"type":"string","title":"Full Path"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"last_modified":{"type":"string","format":"date-time","title":"Last Modified"},"crc_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Crc Hash"},"md5_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Md5 Hash"},"sha1_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sha1 Hash"},"ra_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ra Hash"},"category":{"anyOf":[{"$ref":"#/components/schemas/RomFileCategory"},{"type":"null"}]}},"type":"object","required":["id","rom_id","file_name","file_path","file_size_bytes","full_path","created_at","updated_at","last_modified","crc_hash","md5_hash","sha1_hash","ra_hash","category"],"title":"RomFileSchema"},"RomFiltersDict":{"properties":{"genres":{"items":{"type":"string"},"type":"array","title":"Genres"},"franchises":{"items":{"type":"string"},"type":"array","title":"Franchises"},"collections":{"items":{"type":"string"},"type":"array","title":"Collections"},"companies":{"items":{"type":"string"},"type":"array","title":"Companies"},"game_modes":{"items":{"type":"string"},"type":"array","title":"Game Modes"},"age_ratings":{"items":{"type":"string"},"type":"array","title":"Age Ratings"},"player_counts":{"items":{"type":"string"},"type":"array","title":"Player Counts"},"regions":{"items":{"type":"string"},"type":"array","title":"Regions"},"languages":{"items":{"type":"string"},"type":"array","title":"Languages"},"platforms":{"items":{"type":"integer"},"type":"array","title":"Platforms"}},"type":"object","required":["genres","franchises","collections","companies","game_modes","age_ratings","player_counts","regions","languages","platforms"],"title":"RomFiltersDict"},"RomFlashpointMetadata":{"properties":{"franchises":{"items":{"type":"string"},"type":"array","title":"Franchises"},"companies":{"items":{"type":"string"},"type":"array","title":"Companies"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"},"genres":{"items":{"type":"string"},"type":"array","title":"Genres"},"first_release_date":{"type":"string","title":"First Release Date"},"game_modes":{"items":{"type":"string"},"type":"array","title":"Game Modes"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Version"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"RomFlashpointMetadata"},"RomGamelistMetadata":{"properties":{"box2d_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Box2D Url"},"box2d_back_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Box2D Back Url"},"box3d_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Box3D Url"},"fanart_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fanart Url"},"image_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image Url"},"manual_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Manual Url"},"marquee_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Marquee Url"},"miximage_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Miximage Url"},"physical_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Physical Url"},"screenshot_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Screenshot Url"},"thumbnail_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Thumbnail Url"},"title_screen_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title Screen Url"},"video_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Video Url"},"rating":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rating"},"first_release_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Release Date"},"companies":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Companies"},"franchises":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Franchises"},"genres":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Genres"},"player_count":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Player Count"},"md5_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Md5 Hash"},"box3d_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Box3D Path"},"miximage_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Miximage Path"},"physical_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Physical Path"},"marquee_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Marquee Path"},"video_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Video Path"}},"type":"object","title":"RomGamelistMetadata"},"RomHLTBMetadata":{"properties":{"main_story":{"type":"integer","title":"Main Story"},"main_story_count":{"type":"integer","title":"Main Story Count"},"main_plus_extra":{"type":"integer","title":"Main Plus Extra"},"main_plus_extra_count":{"type":"integer","title":"Main Plus Extra Count"},"completionist":{"type":"integer","title":"Completionist"},"completionist_count":{"type":"integer","title":"Completionist Count"},"all_styles":{"type":"integer","title":"All Styles"},"all_styles_count":{"type":"integer","title":"All Styles Count"},"release_year":{"type":"integer","title":"Release Year"},"review_score":{"type":"integer","title":"Review Score"},"review_count":{"type":"integer","title":"Review Count"},"popularity":{"type":"integer","title":"Popularity"},"completions":{"type":"integer","title":"Completions"}},"type":"object","title":"RomHLTBMetadata"},"RomHasheousMetadata":{"properties":{"tosec_match":{"type":"boolean","title":"Tosec Match"},"mame_arcade_match":{"type":"boolean","title":"Mame Arcade Match"},"mame_mess_match":{"type":"boolean","title":"Mame Mess Match"},"nointro_match":{"type":"boolean","title":"Nointro Match"},"redump_match":{"type":"boolean","title":"Redump Match"},"whdload_match":{"type":"boolean","title":"Whdload Match"},"ra_match":{"type":"boolean","title":"Ra Match"},"fbneo_match":{"type":"boolean","title":"Fbneo Match"},"puredos_match":{"type":"boolean","title":"Puredos Match"}},"type":"object","title":"RomHasheousMetadata"},"RomIGDBMetadata":{"properties":{"total_rating":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Total Rating"},"aggregated_rating":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Aggregated Rating"},"first_release_date":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"First Release Date"},"youtube_video_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Youtube Video Id"},"genres":{"items":{"type":"string"},"type":"array","title":"Genres"},"franchises":{"items":{"type":"string"},"type":"array","title":"Franchises"},"alternative_names":{"items":{"type":"string"},"type":"array","title":"Alternative Names"},"collections":{"items":{"type":"string"},"type":"array","title":"Collections"},"companies":{"items":{"type":"string"},"type":"array","title":"Companies"},"game_modes":{"items":{"type":"string"},"type":"array","title":"Game Modes"},"age_ratings":{"items":{"$ref":"#/components/schemas/IGDBAgeRating"},"type":"array","title":"Age Ratings"},"platforms":{"items":{"$ref":"#/components/schemas/IGDBMetadataPlatform"},"type":"array","title":"Platforms"},"multiplayer_modes":{"items":{"$ref":"#/components/schemas/IGDBMetadataMultiplayerMode"},"type":"array","title":"Multiplayer Modes"},"player_count":{"type":"string","title":"Player Count"},"expansions":{"items":{"$ref":"#/components/schemas/IGDBRelatedGame"},"type":"array","title":"Expansions"},"dlcs":{"items":{"$ref":"#/components/schemas/IGDBRelatedGame"},"type":"array","title":"Dlcs"},"remasters":{"items":{"$ref":"#/components/schemas/IGDBRelatedGame"},"type":"array","title":"Remasters"},"remakes":{"items":{"$ref":"#/components/schemas/IGDBRelatedGame"},"type":"array","title":"Remakes"},"expanded_games":{"items":{"$ref":"#/components/schemas/IGDBRelatedGame"},"type":"array","title":"Expanded Games"},"ports":{"items":{"$ref":"#/components/schemas/IGDBRelatedGame"},"type":"array","title":"Ports"},"similar_games":{"items":{"$ref":"#/components/schemas/IGDBRelatedGame"},"type":"array","title":"Similar Games"}},"type":"object","title":"RomIGDBMetadata"},"RomLaunchboxMetadata":{"properties":{"first_release_date":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"First Release Date"},"max_players":{"type":"integer","title":"Max Players"},"release_type":{"type":"string","title":"Release Type"},"cooperative":{"type":"boolean","title":"Cooperative"},"youtube_video_id":{"type":"string","title":"Youtube Video Id"},"community_rating":{"type":"number","title":"Community Rating"},"community_rating_count":{"type":"integer","title":"Community Rating Count"},"wikipedia_url":{"type":"string","title":"Wikipedia Url"},"esrb":{"type":"string","title":"Esrb"},"genres":{"items":{"type":"string"},"type":"array","title":"Genres"},"companies":{"items":{"type":"string"},"type":"array","title":"Companies"},"images":{"items":{"$ref":"#/components/schemas/LaunchboxImage"},"type":"array","title":"Images"}},"type":"object","title":"RomLaunchboxMetadata"},"RomMetadataSchema":{"properties":{"rom_id":{"type":"integer","title":"Rom Id"},"genres":{"items":{"type":"string"},"type":"array","title":"Genres"},"franchises":{"items":{"type":"string"},"type":"array","title":"Franchises"},"collections":{"items":{"type":"string"},"type":"array","title":"Collections"},"companies":{"items":{"type":"string"},"type":"array","title":"Companies"},"game_modes":{"items":{"type":"string"},"type":"array","title":"Game Modes"},"age_ratings":{"items":{"type":"string"},"type":"array","title":"Age Ratings"},"player_count":{"type":"string","title":"Player Count"},"first_release_date":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"First Release Date"},"average_rating":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Average Rating"}},"type":"object","required":["rom_id","genres","franchises","collections","companies","game_modes","age_ratings","player_count","first_release_date","average_rating"],"title":"RomMetadataSchema"},"RomMobyMetadata":{"properties":{"moby_score":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Moby Score"},"genres":{"items":{"type":"string"},"type":"array","title":"Genres"},"alternate_titles":{"items":{"type":"string"},"type":"array","title":"Alternate Titles"},"platforms":{"items":{"$ref":"#/components/schemas/MobyMetadataPlatform"},"type":"array","title":"Platforms"}},"type":"object","title":"RomMobyMetadata"},"RomRAMetadata":{"properties":{"first_release_date":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"First Release Date"},"genres":{"items":{"type":"string"},"type":"array","title":"Genres"},"companies":{"items":{"type":"string"},"type":"array","title":"Companies"},"achievements":{"items":{"$ref":"#/components/schemas/RAGameRomAchievement"},"type":"array","title":"Achievements"}},"type":"object","title":"RomRAMetadata"},"RomSSMetadata":{"properties":{"bezel_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bezel Url"},"box2d_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Box2D Url"},"box2d_side_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Box2D Side Url"},"box2d_back_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Box2D Back Url"},"box3d_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Box3D Url"},"fanart_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fanart Url"},"fullbox_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fullbox Url"},"logo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Url"},"manual_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Manual Url"},"marquee_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Marquee Url"},"miximage_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Miximage Url"},"physical_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Physical Url"},"screenshot_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Screenshot Url"},"steamgrid_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Steamgrid Url"},"title_screen_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title Screen Url"},"video_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Video Url"},"video_normalized_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Video Normalized Url"},"bezel_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bezel Path"},"box2d_back_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Box2D Back Path"},"box3d_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Box3D Path"},"fanart_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fanart Path"},"miximage_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Miximage Path"},"physical_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Physical Path"},"marquee_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Marquee Path"},"logo_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Path"},"video_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Video Path"},"ss_score":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ss Score"},"first_release_date":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"First Release Date"},"alternative_names":{"items":{"type":"string"},"type":"array","title":"Alternative Names"},"companies":{"items":{"type":"string"},"type":"array","title":"Companies"},"franchises":{"items":{"type":"string"},"type":"array","title":"Franchises"},"game_modes":{"items":{"type":"string"},"type":"array","title":"Game Modes"},"genres":{"items":{"type":"string"},"type":"array","title":"Genres"},"player_count":{"type":"string","title":"Player Count"}},"type":"object","title":"RomSSMetadata"},"RomUserSchema":{"properties":{"id":{"type":"integer","title":"Id"},"user_id":{"type":"integer","title":"User Id"},"rom_id":{"type":"integer","title":"Rom Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"last_played":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Played"},"is_main_sibling":{"type":"boolean","title":"Is Main Sibling"},"backlogged":{"type":"boolean","title":"Backlogged"},"now_playing":{"type":"boolean","title":"Now Playing"},"hidden":{"type":"boolean","title":"Hidden"},"rating":{"type":"integer","title":"Rating"},"difficulty":{"type":"integer","title":"Difficulty"},"completion":{"type":"integer","title":"Completion"},"status":{"anyOf":[{"$ref":"#/components/schemas/RomUserStatus"},{"type":"null"}]}},"type":"object","required":["id","user_id","rom_id","created_at","updated_at","last_played","is_main_sibling","backlogged","now_playing","hidden","rating","difficulty","completion","status"],"title":"RomUserSchema"},"RomUserStatus":{"type":"string","enum":["incomplete","finished","completed_100","retired","never_playing"],"title":"RomUserStatus"},"RoomsResponse":{"properties":{"room_name":{"type":"string","title":"Room Name"},"current":{"type":"integer","title":"Current"},"max":{"type":"integer","title":"Max"},"player_name":{"type":"string","title":"Player Name"},"hasPassword":{"type":"boolean","title":"Haspassword"}},"type":"object","required":["room_name","current","max","player_name","hasPassword"],"title":"RoomsResponse"},"SGDBResource":{"properties":{"thumb":{"type":"string","title":"Thumb"},"url":{"type":"string","title":"Url"},"type":{"type":"string","title":"Type"}},"type":"object","required":["thumb","url","type"],"title":"SGDBResource"},"SaveSchema":{"properties":{"id":{"type":"integer","title":"Id"},"rom_id":{"type":"integer","title":"Rom Id"},"user_id":{"type":"integer","title":"User Id"},"file_name":{"type":"string","title":"File Name"},"file_name_no_tags":{"type":"string","title":"File Name No Tags"},"file_name_no_ext":{"type":"string","title":"File Name No Ext"},"file_extension":{"type":"string","title":"File Extension"},"file_path":{"type":"string","title":"File Path"},"file_size_bytes":{"type":"integer","title":"File Size Bytes"},"full_path":{"type":"string","title":"Full Path"},"download_path":{"type":"string","title":"Download Path"},"missing_from_fs":{"type":"boolean","title":"Missing From Fs"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"emulator":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Emulator"},"slot":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slot"},"content_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content Hash"},"screenshot":{"anyOf":[{"$ref":"#/components/schemas/ScreenshotSchema"},{"type":"null"}]},"device_syncs":{"items":{"$ref":"#/components/schemas/DeviceSyncSchema"},"type":"array","title":"Device Syncs","default":[]}},"type":"object","required":["id","rom_id","user_id","file_name","file_name_no_tags","file_name_no_ext","file_extension","file_path","file_size_bytes","full_path","download_path","missing_from_fs","created_at","updated_at","emulator","screenshot"],"title":"SaveSchema"},"SaveSummarySchema":{"properties":{"total_count":{"type":"integer","title":"Total Count"},"slots":{"items":{"$ref":"#/components/schemas/SlotSummarySchema"},"type":"array","title":"Slots"}},"type":"object","required":["total_count","slots"],"title":"SaveSummarySchema"},"ScanStats":{"properties":{"total_platforms":{"type":"integer","title":"Total Platforms"},"total_roms":{"type":"integer","title":"Total Roms"},"scanned_platforms":{"type":"integer","title":"Scanned Platforms"},"new_platforms":{"type":"integer","title":"New Platforms"},"identified_platforms":{"type":"integer","title":"Identified Platforms"},"scanned_roms":{"type":"integer","title":"Scanned Roms"},"new_roms":{"type":"integer","title":"New Roms"},"identified_roms":{"type":"integer","title":"Identified Roms"},"scanned_firmware":{"type":"integer","title":"Scanned Firmware"},"new_firmware":{"type":"integer","title":"New Firmware"}},"type":"object","required":["total_platforms","total_roms","scanned_platforms","new_platforms","identified_platforms","scanned_roms","new_roms","identified_roms","scanned_firmware","new_firmware"],"title":"ScanStats"},"ScanTaskMeta":{"properties":{"scan_stats":{"anyOf":[{"$ref":"#/components/schemas/ScanStats"},{"type":"null"}]}},"type":"object","required":["scan_stats"],"title":"ScanTaskMeta"},"ScanTaskStatusResponse":{"properties":{"task_name":{"type":"string","title":"Task Name"},"task_id":{"type":"string","title":"Task Id"},"status":{"$ref":"#/components/schemas/JobStatus"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"enqueued_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Enqueued At"},"started_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Started At"},"ended_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ended At"},"task_type":{"type":"string","const":"scan","title":"Task Type"},"meta":{"$ref":"#/components/schemas/ScanTaskMeta"}},"type":"object","required":["task_name","task_id","status","created_at","enqueued_at","started_at","ended_at","task_type","meta"],"title":"ScanTaskStatusResponse"},"ScreenshotSchema":{"properties":{"id":{"type":"integer","title":"Id"},"rom_id":{"type":"integer","title":"Rom Id"},"user_id":{"type":"integer","title":"User Id"},"file_name":{"type":"string","title":"File Name"},"file_name_no_tags":{"type":"string","title":"File Name No Tags"},"file_name_no_ext":{"type":"string","title":"File Name No Ext"},"file_extension":{"type":"string","title":"File Extension"},"file_path":{"type":"string","title":"File Path"},"file_size_bytes":{"type":"integer","title":"File Size Bytes"},"full_path":{"type":"string","title":"Full Path"},"download_path":{"type":"string","title":"Download Path"},"missing_from_fs":{"type":"boolean","title":"Missing From Fs"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","rom_id","user_id","file_name","file_name_no_tags","file_name_no_ext","file_extension","file_path","file_size_bytes","full_path","download_path","missing_from_fs","created_at","updated_at"],"title":"ScreenshotSchema"},"SearchCoverSchema":{"properties":{"name":{"type":"string","title":"Name"},"resources":{"items":{"$ref":"#/components/schemas/SGDBResource"},"type":"array","title":"Resources"}},"type":"object","required":["name","resources"],"title":"SearchCoverSchema"},"SearchRomSchema":{"properties":{"id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Id"},"igdb_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Igdb Id"},"moby_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Moby Id"},"ss_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ss Id"},"sgdb_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sgdb Id"},"flashpoint_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Flashpoint Id"},"launchbox_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Launchbox Id"},"platform_id":{"type":"integer","title":"Platform Id"},"name":{"type":"string","title":"Name"},"slug":{"type":"string","title":"Slug","default":""},"summary":{"type":"string","title":"Summary","default":""},"igdb_url_cover":{"type":"string","title":"Igdb Url Cover","default":""},"moby_url_cover":{"type":"string","title":"Moby Url Cover","default":""},"ss_url_cover":{"type":"string","title":"Ss Url Cover","default":""},"sgdb_url_cover":{"type":"string","title":"Sgdb Url Cover","default":""},"flashpoint_url_cover":{"type":"string","title":"Flashpoint Url Cover","default":""},"launchbox_url_cover":{"type":"string","title":"Launchbox Url Cover","default":""},"is_unidentified":{"type":"boolean","title":"Is Unidentified"},"is_identified":{"type":"boolean","title":"Is Identified"}},"type":"object","required":["platform_id","name","is_unidentified","is_identified"],"title":"SearchRomSchema"},"SiblingRomSchema":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"fs_name_no_tags":{"type":"string","title":"Fs Name No Tags"},"fs_name_no_ext":{"type":"string","title":"Fs Name No Ext"},"sort_comparator":{"type":"string","title":"Sort Comparator","readOnly":true}},"type":"object","required":["id","name","fs_name_no_tags","fs_name_no_ext","sort_comparator"],"title":"SiblingRomSchema"},"SimpleRomSchema":{"properties":{"id":{"type":"integer","title":"Id"},"igdb_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Igdb Id"},"sgdb_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sgdb Id"},"moby_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Moby Id"},"ss_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ss Id"},"ra_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ra Id"},"launchbox_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Launchbox Id"},"hasheous_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Hasheous Id"},"tgdb_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Tgdb Id"},"flashpoint_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Flashpoint Id"},"hltb_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Hltb Id"},"gamelist_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gamelist Id"},"platform_id":{"type":"integer","title":"Platform Id"},"platform_slug":{"type":"string","title":"Platform Slug"},"platform_fs_slug":{"type":"string","title":"Platform Fs Slug"},"platform_custom_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Platform Custom Name"},"platform_display_name":{"type":"string","title":"Platform Display Name"},"fs_name":{"type":"string","title":"Fs Name"},"fs_name_no_tags":{"type":"string","title":"Fs Name No Tags"},"fs_name_no_ext":{"type":"string","title":"Fs Name No Ext"},"fs_extension":{"type":"string","title":"Fs Extension"},"fs_path":{"type":"string","title":"Fs Path"},"fs_size_bytes":{"type":"integer","title":"Fs Size Bytes"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slug"},"summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Summary"},"alternative_names":{"items":{"type":"string"},"type":"array","title":"Alternative Names"},"youtube_video_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Youtube Video Id"},"metadatum":{"$ref":"#/components/schemas/RomMetadataSchema"},"igdb_metadata":{"anyOf":[{"$ref":"#/components/schemas/RomIGDBMetadata"},{"type":"null"}]},"moby_metadata":{"anyOf":[{"$ref":"#/components/schemas/RomMobyMetadata"},{"type":"null"}]},"ss_metadata":{"anyOf":[{"$ref":"#/components/schemas/RomSSMetadata"},{"type":"null"}]},"launchbox_metadata":{"anyOf":[{"$ref":"#/components/schemas/RomLaunchboxMetadata"},{"type":"null"}]},"hasheous_metadata":{"anyOf":[{"$ref":"#/components/schemas/RomHasheousMetadata"},{"type":"null"}]},"flashpoint_metadata":{"anyOf":[{"$ref":"#/components/schemas/RomFlashpointMetadata"},{"type":"null"}]},"hltb_metadata":{"anyOf":[{"$ref":"#/components/schemas/RomHLTBMetadata"},{"type":"null"}]},"gamelist_metadata":{"anyOf":[{"$ref":"#/components/schemas/RomGamelistMetadata"},{"type":"null"}]},"manual_metadata":{"anyOf":[{"$ref":"#/components/schemas/ManualMetadata"},{"type":"null"}]},"path_cover_small":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path Cover Small"},"path_cover_large":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path Cover Large"},"url_cover":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url Cover"},"has_manual":{"type":"boolean","title":"Has Manual"},"path_manual":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path Manual"},"url_manual":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url Manual"},"is_identifying":{"type":"boolean","title":"Is Identifying","default":false},"is_unidentified":{"type":"boolean","title":"Is Unidentified"},"is_identified":{"type":"boolean","title":"Is Identified"},"revision":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Revision"},"regions":{"items":{"type":"string"},"type":"array","title":"Regions"},"languages":{"items":{"type":"string"},"type":"array","title":"Languages"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags"},"crc_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Crc Hash"},"md5_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Md5 Hash"},"sha1_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sha1 Hash"},"ra_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ra Hash"},"has_simple_single_file":{"type":"boolean","title":"Has Simple Single File"},"has_nested_single_file":{"type":"boolean","title":"Has Nested Single File"},"has_multiple_files":{"type":"boolean","title":"Has Multiple Files"},"files":{"items":{"$ref":"#/components/schemas/RomFileSchema"},"type":"array","title":"Files"},"full_path":{"type":"string","title":"Full Path"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"missing_from_fs":{"type":"boolean","title":"Missing From Fs"},"has_notes":{"type":"boolean","title":"Has Notes"},"siblings":{"items":{"$ref":"#/components/schemas/SiblingRomSchema"},"type":"array","title":"Siblings"},"rom_user":{"$ref":"#/components/schemas/RomUserSchema"},"merged_screenshots":{"items":{"type":"string"},"type":"array","title":"Merged Screenshots"},"merged_ra_metadata":{"anyOf":[{"$ref":"#/components/schemas/RomRAMetadata"},{"type":"null"}]}},"type":"object","required":["id","igdb_id","sgdb_id","moby_id","ss_id","ra_id","launchbox_id","hasheous_id","tgdb_id","flashpoint_id","hltb_id","gamelist_id","platform_id","platform_slug","platform_fs_slug","platform_custom_name","platform_display_name","fs_name","fs_name_no_tags","fs_name_no_ext","fs_extension","fs_path","fs_size_bytes","name","slug","summary","alternative_names","youtube_video_id","metadatum","igdb_metadata","moby_metadata","ss_metadata","launchbox_metadata","hasheous_metadata","flashpoint_metadata","hltb_metadata","gamelist_metadata","manual_metadata","path_cover_small","path_cover_large","url_cover","has_manual","path_manual","url_manual","is_unidentified","is_identified","revision","regions","languages","tags","crc_hash","md5_hash","sha1_hash","ra_hash","has_simple_single_file","has_nested_single_file","has_multiple_files","files","full_path","created_at","updated_at","missing_from_fs","has_notes","siblings","rom_user","merged_screenshots","merged_ra_metadata"],"title":"SimpleRomSchema"},"SlotSummarySchema":{"properties":{"slot":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slot"},"count":{"type":"integer","title":"Count"},"latest":{"$ref":"#/components/schemas/SaveSchema"}},"type":"object","required":["slot","count","latest"],"title":"SlotSummarySchema"},"SmartCollectionSchema":{"properties":{"name":{"type":"string","title":"Name"},"description":{"type":"string","title":"Description","default":""},"rom_ids":{"items":{"type":"integer"},"type":"array","uniqueItems":true,"title":"Rom Ids"},"rom_count":{"type":"integer","title":"Rom Count"},"path_cover_small":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path Cover Small"},"path_cover_large":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path Cover Large"},"path_covers_small":{"items":{"type":"string"},"type":"array","title":"Path Covers Small"},"path_covers_large":{"items":{"type":"string"},"type":"array","title":"Path Covers Large"},"is_public":{"type":"boolean","title":"Is Public","default":false},"is_favorite":{"type":"boolean","title":"Is Favorite","default":false},"is_virtual":{"type":"boolean","title":"Is Virtual","default":false},"is_smart":{"type":"boolean","title":"Is Smart","default":true},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"id":{"type":"integer","title":"Id"},"filter_criteria":{"additionalProperties":true,"type":"object","title":"Filter Criteria"},"filter_summary":{"type":"string","title":"Filter Summary"},"user_id":{"type":"integer","title":"User Id"},"owner_username":{"type":"string","title":"Owner Username"}},"type":"object","required":["name","rom_ids","rom_count","path_cover_small","path_cover_large","path_covers_small","path_covers_large","created_at","updated_at","id","filter_criteria","filter_summary","user_id","owner_username"],"title":"SmartCollectionSchema"},"StateSchema":{"properties":{"id":{"type":"integer","title":"Id"},"rom_id":{"type":"integer","title":"Rom Id"},"user_id":{"type":"integer","title":"User Id"},"file_name":{"type":"string","title":"File Name"},"file_name_no_tags":{"type":"string","title":"File Name No Tags"},"file_name_no_ext":{"type":"string","title":"File Name No Ext"},"file_extension":{"type":"string","title":"File Extension"},"file_path":{"type":"string","title":"File Path"},"file_size_bytes":{"type":"integer","title":"File Size Bytes"},"full_path":{"type":"string","title":"Full Path"},"download_path":{"type":"string","title":"Download Path"},"missing_from_fs":{"type":"boolean","title":"Missing From Fs"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"emulator":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Emulator"},"screenshot":{"anyOf":[{"$ref":"#/components/schemas/ScreenshotSchema"},{"type":"null"}]}},"type":"object","required":["id","rom_id","user_id","file_name","file_name_no_tags","file_name_no_ext","file_extension","file_path","file_size_bytes","full_path","download_path","missing_from_fs","created_at","updated_at","emulator","screenshot"],"title":"StateSchema"},"StatsReturn":{"properties":{"PLATFORMS":{"type":"integer","title":"Platforms"},"ROMS":{"type":"integer","title":"Roms"},"SAVES":{"type":"integer","title":"Saves"},"STATES":{"type":"integer","title":"States"},"SCREENSHOTS":{"type":"integer","title":"Screenshots"},"TOTAL_FILESIZE_BYTES":{"type":"integer","title":"Total Filesize Bytes"}},"type":"object","required":["PLATFORMS","ROMS","SAVES","STATES","SCREENSHOTS","TOTAL_FILESIZE_BYTES"],"title":"StatsReturn"},"SyncMode":{"type":"string","enum":["api","file_transfer","push_pull"],"title":"SyncMode"},"SystemDict":{"properties":{"VERSION":{"type":"string","title":"Version"},"SHOW_SETUP_WIZARD":{"type":"boolean","title":"Show Setup Wizard"}},"type":"object","required":["VERSION","SHOW_SETUP_WIZARD"],"title":"SystemDict"},"TaskExecutionResponse":{"properties":{"task_name":{"type":"string","title":"Task Name"},"task_id":{"type":"string","title":"Task Id"},"status":{"$ref":"#/components/schemas/JobStatus"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"enqueued_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Enqueued At"}},"type":"object","required":["task_name","task_id","status","created_at","enqueued_at"],"title":"TaskExecutionResponse"},"TaskInfo":{"properties":{"name":{"type":"string","title":"Name"},"type":{"$ref":"#/components/schemas/TaskType"},"manual_run":{"type":"boolean","title":"Manual Run"},"title":{"type":"string","title":"Title"},"description":{"type":"string","title":"Description"},"enabled":{"type":"boolean","title":"Enabled"},"cron_string":{"type":"string","title":"Cron String"}},"type":"object","required":["name","type","manual_run","title","description","enabled","cron_string"],"title":"TaskInfo"},"TaskType":{"type":"string","enum":["scan","conversion","cleanup","update","watcher","generic"],"title":"TaskType","description":"Enumeration of task types for categorization and UI display."},"TasksDict":{"properties":{"ENABLE_SCHEDULED_RESCAN":{"type":"boolean","title":"Enable Scheduled Rescan"},"SCHEDULED_RESCAN_CRON":{"type":"string","title":"Scheduled Rescan Cron"},"ENABLE_SCHEDULED_UPDATE_SWITCH_TITLEDB":{"type":"boolean","title":"Enable Scheduled Update Switch Titledb"},"SCHEDULED_UPDATE_SWITCH_TITLEDB_CRON":{"type":"string","title":"Scheduled Update Switch Titledb Cron"},"ENABLE_SCHEDULED_UPDATE_LAUNCHBOX_METADATA":{"type":"boolean","title":"Enable Scheduled Update Launchbox Metadata"},"SCHEDULED_UPDATE_LAUNCHBOX_METADATA_CRON":{"type":"string","title":"Scheduled Update Launchbox Metadata Cron"},"ENABLE_SCHEDULED_CONVERT_IMAGES_TO_WEBP":{"type":"boolean","title":"Enable Scheduled Convert Images To Webp"},"SCHEDULED_CONVERT_IMAGES_TO_WEBP_CRON":{"type":"string","title":"Scheduled Convert Images To Webp Cron"}},"type":"object","required":["ENABLE_SCHEDULED_RESCAN","SCHEDULED_RESCAN_CRON","ENABLE_SCHEDULED_UPDATE_SWITCH_TITLEDB","SCHEDULED_UPDATE_SWITCH_TITLEDB_CRON","ENABLE_SCHEDULED_UPDATE_LAUNCHBOX_METADATA","SCHEDULED_UPDATE_LAUNCHBOX_METADATA_CRON","ENABLE_SCHEDULED_CONVERT_IMAGES_TO_WEBP","SCHEDULED_CONVERT_IMAGES_TO_WEBP_CRON"],"title":"TasksDict"},"TinfoilFeedFileSchema":{"properties":{"url":{"type":"string","title":"Url"},"size":{"type":"integer","title":"Size"}},"type":"object","required":["url","size"],"title":"TinfoilFeedFileSchema"},"TinfoilFeedSchema":{"properties":{"files":{"items":{"$ref":"#/components/schemas/TinfoilFeedFileSchema"},"type":"array","title":"Files"},"directories":{"items":{"type":"string"},"type":"array","title":"Directories"},"titledb":{"additionalProperties":{"additionalProperties":true,"type":"object"},"type":"object","title":"Titledb"},"success":{"type":"string","title":"Success"},"error":{"type":"string","title":"Error"}},"type":"object","required":["files","directories"],"title":"TinfoilFeedSchema"},"TokenResponse":{"properties":{"access_token":{"type":"string","title":"Access Token"},"refresh_token":{"type":"string","title":"Refresh Token"},"token_type":{"type":"string","title":"Token Type"},"expires":{"type":"integer","title":"Expires"}},"type":"object","required":["access_token","token_type","expires"],"title":"TokenResponse"},"UpdateStats":{"properties":{"processed":{"type":"integer","title":"Processed"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["processed","total"],"title":"UpdateStats"},"UpdateTaskMeta":{"properties":{"update_stats":{"anyOf":[{"$ref":"#/components/schemas/UpdateStats"},{"type":"null"}]}},"type":"object","required":["update_stats"],"title":"UpdateTaskMeta"},"UpdateTaskStatusResponse":{"properties":{"task_name":{"type":"string","title":"Task Name"},"task_id":{"type":"string","title":"Task Id"},"status":{"$ref":"#/components/schemas/JobStatus"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"enqueued_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Enqueued At"},"started_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Started At"},"ended_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ended At"},"task_type":{"type":"string","const":"update","title":"Task Type"},"meta":{"$ref":"#/components/schemas/UpdateTaskMeta"}},"type":"object","required":["task_name","task_id","status","created_at","enqueued_at","started_at","ended_at","task_type","meta"],"title":"UpdateTaskStatusResponse"},"UserCollectionSchema":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"}},"type":"object","required":["id","name"],"title":"UserCollectionSchema"},"UserForm":{"properties":{"username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Username"},"password":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Password"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role"},"enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Enabled"},"ra_username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ra Username"},"avatar":{"anyOf":[{"type":"string","format":"binary"},{"type":"null"}],"title":"Avatar"},"ui_settings":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ui Settings"}},"type":"object","title":"UserForm"},"UserNoteSchema":{"properties":{"id":{"type":"integer","title":"Id"},"title":{"type":"string","title":"Title"},"content":{"type":"string","title":"Content"},"is_public":{"type":"boolean","title":"Is Public"},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"user_id":{"type":"integer","title":"User Id"},"username":{"type":"string","title":"Username"}},"type":"object","required":["id","title","content","is_public","created_at","updated_at","user_id","username"],"title":"UserNoteSchema"},"UserSchema":{"properties":{"id":{"type":"integer","title":"Id"},"username":{"type":"string","title":"Username"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"enabled":{"type":"boolean","title":"Enabled"},"role":{"$ref":"#/components/schemas/Role"},"oauth_scopes":{"items":{"type":"string"},"type":"array","title":"Oauth Scopes"},"avatar_path":{"type":"string","title":"Avatar Path"},"last_login":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Login"},"last_active":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Active"},"ra_username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ra Username"},"ra_progression":{"anyOf":[{"$ref":"#/components/schemas/RAProgression"},{"type":"null"}]},"ui_settings":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Ui Settings"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","username","email","enabled","role","oauth_scopes","avatar_path","last_login","last_active","created_at","updated_at"],"title":"UserSchema"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VirtualCollectionSchema":{"properties":{"name":{"type":"string","title":"Name"},"description":{"type":"string","title":"Description"},"rom_ids":{"items":{"type":"integer"},"type":"array","uniqueItems":true,"title":"Rom Ids"},"rom_count":{"type":"integer","title":"Rom Count"},"path_cover_small":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path Cover Small"},"path_cover_large":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path Cover Large"},"path_covers_small":{"items":{"type":"string"},"type":"array","title":"Path Covers Small"},"path_covers_large":{"items":{"type":"string"},"type":"array","title":"Path Covers Large"},"is_public":{"type":"boolean","title":"Is Public","default":false},"is_favorite":{"type":"boolean","title":"Is Favorite","default":false},"is_virtual":{"type":"boolean","title":"Is Virtual","default":true},"is_smart":{"type":"boolean","title":"Is Smart","default":false},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"id":{"type":"string","title":"Id"},"type":{"type":"string","title":"Type"}},"type":"object","required":["name","description","rom_ids","rom_count","path_cover_small","path_cover_large","path_covers_small","path_covers_large","created_at","updated_at","id","type"],"title":"VirtualCollectionSchema"},"WatcherTaskMeta":{"properties":{},"type":"object","title":"WatcherTaskMeta"},"WatcherTaskStatusResponse":{"properties":{"task_name":{"type":"string","title":"Task Name"},"task_id":{"type":"string","title":"Task Id"},"status":{"$ref":"#/components/schemas/JobStatus"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"enqueued_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Enqueued At"},"started_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Started At"},"ended_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ended At"},"task_type":{"type":"string","const":"watcher","title":"Task Type"},"meta":{"$ref":"#/components/schemas/WatcherTaskMeta"}},"type":"object","required":["task_name","task_id","status","created_at","enqueued_at","started_at","ended_at","task_type","meta"],"title":"WatcherTaskStatusResponse"},"WebrcadeFeedCategorySchema":{"properties":{"title":{"type":"string","title":"Title"},"longTitle":{"type":"string","title":"Longtitle"},"background":{"type":"string","title":"Background"},"thumbnail":{"type":"string","title":"Thumbnail"},"description":{"type":"string","title":"Description"},"items":{"items":{"$ref":"#/components/schemas/WebrcadeFeedItemSchema"},"type":"array","title":"Items"}},"type":"object","required":["title","items"],"title":"WebrcadeFeedCategorySchema"},"WebrcadeFeedItemPropsSchema":{"properties":{"rom":{"type":"string","title":"Rom"}},"type":"object","required":["rom"],"title":"WebrcadeFeedItemPropsSchema"},"WebrcadeFeedItemSchema":{"properties":{"title":{"type":"string","title":"Title"},"longTitle":{"type":"string","title":"Longtitle"},"description":{"type":"string","title":"Description"},"type":{"type":"string","title":"Type"},"thumbnail":{"type":"string","title":"Thumbnail"},"background":{"type":"string","title":"Background"},"props":{"$ref":"#/components/schemas/WebrcadeFeedItemPropsSchema"}},"type":"object","required":["title","type","props"],"title":"WebrcadeFeedItemSchema"},"WebrcadeFeedSchema":{"properties":{"title":{"type":"string","title":"Title"},"longTitle":{"type":"string","title":"Longtitle"},"description":{"type":"string","title":"Description"},"thumbnail":{"type":"string","title":"Thumbnail"},"background":{"type":"string","title":"Background"},"categories":{"items":{"$ref":"#/components/schemas/WebrcadeFeedCategorySchema"},"type":"array","title":"Categories"}},"type":"object","required":["title","categories"],"title":"WebrcadeFeedSchema"}},"securitySchemes":{"OAuth2PasswordBearer":{"type":"oauth2","flows":{"password":{"scopes":{"me.read":"View your profile","roms.read":"View ROMs","platforms.read":"View platforms","assets.read":"View assets","devices.read":"View devices","firmware.read":"View firmware","roms.user.read":"View user-rom properties","collections.read":"View collections","me.write":"Modify your profile","assets.write":"Modify assets","devices.write":"Modify devices","roms.user.write":"Modify user-rom properties","collections.write":"Modify collections","roms.write":"Modify ROMs","platforms.write":"Modify platforms","firmware.write":"Modify firmware","users.read":"View users","users.write":"Modify users","tasks.run":"Run tasks"},"tokenUrl":"/token"}}},"HTTPBasic":{"type":"http","scheme":"basic"}}}} \ No newline at end of file diff --git a/src/bun/api/app.ts b/src/bun/api/app.ts index ac4e780..b7b4050 100644 --- a/src/bun/api/app.ts +++ b/src/bun/api/app.ts @@ -22,6 +22,7 @@ import { appPath, getErrorMessage } from "../utils"; import { DrizzleSqliteDODatabase } from "drizzle-orm/durable-sqlite"; import { ensureDir } from "fs-extra"; import UpdateStoreJob from "./jobs/update-store"; +import { getStoreFolder } from "./store/services/gamesService"; export const config = new Conf({ projectName: projectPackage.name, @@ -47,6 +48,8 @@ export const customEmulators = new Conf>({ console.log("Config Path Located At: ", config.path); console.log("Custom Emulator Paths Located At: ", customEmulators.path); console.log("App Directory is ", process.env.APPDIR); +console.log("Store Directory is ", getStoreFolder()); + const fileCookieStore = new FileCookieStore(path.join(path.dirname(config.path), 'cookies.json')); console.log("Cookie Jar Path Located At: ", fileCookieStore.filePath); export const jar = new CookieJar(fileCookieStore); diff --git a/src/bun/api/cache.ts b/src/bun/api/cache.ts index c8c4a8f..941ba7a 100644 --- a/src/bun/api/cache.ts +++ b/src/bun/api/cache.ts @@ -1,6 +1,7 @@ import { eq } from "drizzle-orm"; import { cache } from "./app"; import cacheSchema from "@schema/cache"; +import { GithubReleaseSchema } from "@/shared/constants"; export const CACHE_KEYS = { ROM_PLATFORMS: 'rom-platforms', @@ -31,4 +32,14 @@ export async function getOrCached (key: string, getter: () => Promise, opt .run(); return data; +} + +export async function getOrCachedGithubRelease (path: string) +{ + return getOrCached(`github-release-${path}`, async () => + { + const response = await fetch(`https://api.github.com/repos/${path}/releases/latest`, { method: "GET" }); + if (!response.ok) throw new Error(response.statusText); + return GithubReleaseSchema.parseAsync(await response.json()); + }); } \ No newline at end of file diff --git a/src/bun/api/games/games.ts b/src/bun/api/games/games.ts index 152207b..7f67457 100644 --- a/src/bun/api/games/games.ts +++ b/src/bun/api/games/games.ts @@ -1,22 +1,27 @@ import Elysia, { status } from "elysia"; -import { activeGame, config, db, events, taskQueue } from "../app"; -import { and, eq, getTableColumns, sql } from "drizzle-orm"; -import z from "zod"; +import { activeGame, config, db, emulatorsDb, events, taskQueue } from "../app"; +import { and, eq, getTableColumns, inArray, not, or, sql } from "drizzle-orm"; +import z, { number } from "zod"; import * as schema from "@schema/app"; import fs from "node:fs/promises"; -import { FrontEndGameType, FrontEndGameTypeDetailed, GameListFilterSchema } from "@shared/constants"; -import { getRomApiRomsIdGet, getRomsApiRomsGet } from "@clients/romm"; +import { FrontEndEmulator, FrontEndGameType, FrontEndGameTypeDetailed, FrontEndGameTypeDetailedEmulator, GameListFilterSchema, SERVER_URL } from "@shared/constants"; +import { getCurrentUserApiUsersMeGet, getPlatformsApiPlatformsGet, getRomApiRomsIdGet, getRomsApiRomsGet } from "@clients/romm"; import { InstallJob } from "../jobs/install-job"; import path from "node:path"; -import { calculateSize, checkInstalled, convertLocalToFrontend, convertRomToFrontend, convertRomToFrontendDetailed, convertStoreToFrontend, convertStoreToFrontendDetailed, getLocalGameMatch } from "./services/utils"; +import { calculateSize, checkInstalled, convertLocalToFrontend, convertRomToFrontend, convertRomToFrontendDetailed, convertStoreToFrontend, convertStoreToFrontendDetailed, getLocalGameDetailed, getLocalGameMatch, getSourceGameDetailed } from "./services/utils"; import buildStatusResponse, { getValidLaunchCommandsForGame } from "./services/statusService"; import { errorToResponse } from "elysia/adapter/bun/handler"; -import { launchCommand } from "./services/launchGameService"; -import { getErrorMessage } from "@/bun/utils"; +import { getEmulatorsForSystem, launchCommand } from "./services/launchGameService"; +import { getErrorMessage, SeededRandom, shuffleInPlace } from "@/bun/utils"; import { defaultFormats, defaultPlugins } from 'jimp'; import { createJimp } from "@jimp/core"; import webp from "@jimp/wasm-webp"; -import { extractStoreGameSourceId, getStoreGame, getStoreGameFromPath, getStoreGameManifest } from "../store/services/gamesService"; +import * as emulatorSchema from '@schema/emulators'; +import { buildStoreFrontendEmulatorSystems, extractStoreGameSourceId, getShuffledStoreGames, getStoreEmulatorPackage, getStoreGame, getStoreGameFromPath, getStoreGameManifest } from "../store/services/gamesService"; +import { convertStoreEmulatorToFrontend } from "../store/services/emulatorsService"; +import { use } from "react"; +import { CACHE_KEYS, getOrCached } from "../cache"; +import { host } from "@/bun/utils/host"; // A custom jimp that supports webp const Jimp = createJimp({ @@ -123,22 +128,52 @@ export default new Elysia() }) .get('/games', async ({ query, set }) => { - const where: any[] = []; - if (query.platform_slug) - { - where.push(eq(schema.platforms.slug, query.platform_slug)); - } - - if (query.source) - { - where.push(eq(schema.games.source, query.source)); - } - const games: FrontEndGameType[] = []; - let localGamesSet: Set | undefined; - if (!query.collection_id) + if (query.source === 'store') { + const shuffledGames = await getShuffledStoreGames(); + set.headers['x-max-items'] = shuffledGames.length; + const storeGames = await Promise.all(shuffledGames + .slice(query.offset ?? 0, Math.min((query.offset ?? 0) + (query.limit ?? 50), shuffledGames.length)) + .map(async (e) => + { + const system = path.dirname(e.path); + const id = path.basename(e.path, path.extname(e.path)); + + const localGame = await db.select({ + ...getTableColumns(schema.games), + platform: schema.platforms, + screenshotIds: sql`coalesce(json_group_array(${schema.screenshots.id}),json('[]'))`.mapWith(d => JSON.parse(d) as number[]), + }) + .from(schema.games) + .leftJoin(schema.platforms, eq(schema.platforms.id, schema.games.platform_id)) + .leftJoin(schema.screenshots, eq(schema.screenshots.game_id, schema.games.id)) + .groupBy(schema.games.id) + .where(and(eq(schema.games.source, 'store'), eq(schema.games.source_id, `${system}@${id}`))); + + if (localGame.length > 0) return convertLocalToFrontend(localGame[0]); + + const storeGame = await getStoreGameFromPath(e.path); + + return convertStoreToFrontend(system, id, storeGame); + })); + games.push(...storeGames.filter(g => g !== undefined)); + } else + { + const where: any[] = []; + let localGamesSet: Set | undefined; + + if (query.platform_slug) + { + where.push(eq(schema.platforms.slug, query.platform_slug)); + } + + if (query.source) + { + where.push(eq(schema.games.source, query.source)); + } + const localGames = await db.select({ ...getTableColumns(schema.games), platform: schema.platforms, @@ -153,52 +188,30 @@ export default new Elysia() .where(and(...where)); localGamesSet = new Set(localGames.filter(g => !!g.source_id && !!g.source).map(g => `${g.source}@${g.source_id}`)); - games.push(...localGames.map(g => + + if (!query.collection_id) { - return convertLocalToFrontend(g); - })); - } - - if (((!query.platform_source || query.platform_source === 'romm') || !!query.collection_id) && (!query.source || query.source === 'romm')) - { - 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 - }, throwOnError: true - }); - games.push(...rommGames.data.items.filter(g => !localGamesSet?.has(`romm@${g.id}`)).map(g => - { - return convertRomToFrontend(g); - })); - } - - if (query.source === 'store') - { - const gamesManifest = await getStoreGameManifest(); - set.headers['x-max-items'] = gamesManifest.filter(g => g.type === 'blob').length; - - const storeGames = await Promise.all(gamesManifest - .slice(query.offset ?? 0, Math.min((query.offset ?? 0) + (query.limit ?? 50), gamesManifest.length)) - .map(async (e) => + games.push(...localGames.map(g => { - const system = path.dirname(e.path); - const id = path.basename(e.path, path.extname(e.path)); - - const localGame = await db.query.games.findFirst({ columns: { id: true }, where: and(eq(schema.games.source, 'store'), eq(schema.games.source_id, `${system}@${id}`)) }); - - if (localGame) - { - return undefined; - } - - const storeGame = await getStoreGameFromPath(e.path); - - return convertStoreToFrontend(system, id, storeGame); + return convertLocalToFrontend(g); })); - games.push(...storeGames.filter(g => g !== undefined)); + } + + if (((!query.platform_source || query.platform_source === 'romm') || !!query.collection_id) && (!query.source || query.source === 'romm')) + { + 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 + }, throwOnError: true + }); + games.push(...rommGames.data.items.filter(g => !localGamesSet?.has(`romm@${g.id}`)).map(g => + { + return convertRomToFrontend(g); + })); + } } return { games }; @@ -231,92 +244,59 @@ export default new Elysia() }) .get('/game/:source/:id', async ({ params: { source, id } }) => { - async function getLocalGameDetailed (match: any) + const sourceData = await getSourceGameDetailed(source, id); + + if (sourceData) { - const localGame = await db.query.games.findFirst({ - where: match, - with: { - screenshots: { columns: { id: true } }, - platform: { columns: { name: true, slug: true } } - } - }); - if (localGame) + if (sourceData.platform_slug) { - 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 undefined; - } - - if (source === 'local') - { - const localGame = await getLocalGameDetailed(eq(schema.games.id, Number(id))); - if (localGame) return localGame; - return status('Not Found'); - } - else - { - const localGame = await getLocalGameDetailed(getLocalGameMatch(id, source)); - if (localGame) return localGame; - - if (source === 'romm') - { - const rom = await getRomApiRomsIdGet({ path: { id: Number(id) } }); - if (rom.data) + const systemMapping = await emulatorsDb.query.systemMappings.findFirst({ where: and(eq(emulatorSchema.systemMappings.sourceSlug, sourceData.platform_slug), eq(emulatorSchema.systemMappings.source, 'romm')) }); + if (systemMapping) { - const romGame = convertRomToFrontendDetailed(rom.data); - return romGame; + const emulatorNames = await getEmulatorsForSystem(systemMapping.system); + const emulators = await Promise.all(emulatorNames.map(n => getStoreEmulatorPackage(n).then(e => ({ name: n, data: e })))); + + sourceData.emulators = await Promise.all(emulators.map(async ({ name, data }) => + { + if (data) + { + const systems = await buildStoreFrontendEmulatorSystems(data); + return { ...await convertStoreEmulatorToFrontend(data, 0, systems), store_exists: true }; + } + else if (name === 'EMULATORJS') + { + return { + name: 'EMULATORJS', + validSource: { binPath: SERVER_URL(host), type: 'js', exists: true }, + logo: `/api/romm/image?url=${encodeURIComponent('https://emulatorjs.org/logo/EmulatorJS.png')}`, + systems: [], + gameCount: 0 + } satisfies FrontEndGameTypeDetailedEmulator; + } + else + { + return { + name: name, + logo: "", + systems: [], + gameCount: 0 + } satisfies FrontEndGameTypeDetailedEmulator; + } + + })); } - - return status("Not Found", rom.response); - } - else if (source === 'store') - { - const gameId = extractStoreGameSourceId(id); - const storeGame = await getStoreGame(gameId.system, gameId.id); - if (!storeGame) return status("Not Found"); - return convertStoreToFrontendDetailed(gameId.system, gameId.id, storeGame); } + return sourceData; + } else + { return status("Not Found"); } }, { params: z.object({ source: z.string(), id: z.string() }) }) - .get('/status/:source/:id', async ({ params: { source, id }, set }) => - { - set.headers["content-type"] = 'text/event-stream'; - set.headers["cache-control"] = 'no-cache'; - set.headers['connection'] = 'keep-alive'; - return buildStatusResponse(source, id); - }, { - response: z.any(), - params: z.object({ id: z.string(), source: z.string() }), - query: z.object({ isLocal: z.boolean().optional() }) - }) + .use(buildStatusResponse()) .delete('/game/:source/:id', async ({ params: { source, id } }) => { const deleted = await db.delete(schema.games).where(getLocalGameMatch(id, source)).returning({ path_fs: schema.games.path_fs }); @@ -332,11 +312,11 @@ export default new Elysia() }) .post('/game/:source/:id/install', async ({ params: { id, source } }) => { - if (!taskQueue.hasActive()) + if (!taskQueue.findJob(`install-rom-${source}-${id}`, InstallJob)) { if (source === 'romm' || source === 'store') { - taskQueue.enqueue(`install-rom-${source}-${id}`, new InstallJob(id, source, id)); + taskQueue.enqueue(`install-rom-${source}-${id}`, new InstallJob(id, source, id, { dryRun: true })); return status(200); } @@ -349,7 +329,20 @@ export default new Elysia() params: z.object({ id: z.string(), source: z.string() }), response: z.any() }) - .post('/game/:source/:id/play', async ({ params: { id, source }, query, set }) => + .delete('/game/:source/:id/install', async ({ params: { id, source } }) => + { + const job = taskQueue.findJob(`install-rom-${source}-${id}`, InstallJob); + if (job) + { + job.abort('cancel'); + return status('OK'); + } + return status('Not Found'); + }, { + params: z.object({ id: z.string(), source: z.string() }), + response: z.any() + }) + .post('/game/:source/:id/play', async ({ params: { id, source }, body, set }) => { const validCommands = await getValidLaunchCommandsForGame(source, id); if (validCommands) @@ -362,11 +355,11 @@ export default new Elysia() { try { - const validCommand = query.command_id ? validCommands.commands.find(c => c.id === query.command_id) : validCommands.commands[0]; + const validCommand = body.command_id ? validCommands.commands.find(c => c.id === body.command_id) : validCommands.commands[0]; if (validCommand) { // launch command waits for the game to exit, we don't want that. - launchCommand(validCommand.command, source, id, validCommands.gameId); + launchCommand(validCommand, source, id, validCommands.gameId); return { type: 'application', command: null }; } else { @@ -382,7 +375,7 @@ export default new Elysia() } }, { params: z.object({ id: z.string(), source: z.string() }), - query: z.object({ command_id: z.number().or(z.string()).optional() }), + body: z.object({ command_id: z.number().or(z.string()).optional() }), response: z.object({ type: z.enum(['emulatorjs', 'application']), command: z.string().nullable() }) }) .post("/stop", async ({ }) => @@ -404,4 +397,190 @@ export default new Elysia() .get('/emulatorjs/data/*', async () => { return status("Not Found"); + }) + .get('/recommended/games/emulator/:id', async ({ params: { id } }) => + { + const emulator = await getStoreEmulatorPackage(id); + if (!emulator) return status("Not Found"); + const systems = await buildStoreFrontendEmulatorSystems(emulator); + const systemsIdSet = new Set(systems.map(s => s.id)); + const systemsRommSlugSet = new Set(systems.filter(s => s.romm_slug).map(s => s.romm_slug!)); + + const games: FrontEndGameType[] = []; + + let localGamesSet: Set | undefined; + + const localGames = await db.select({ + ...getTableColumns(schema.games), + platform: schema.platforms, + screenshotIds: sql`coalesce(json_group_array(${schema.screenshots.id}),json('[]'))`.mapWith(d => JSON.parse(d) as number[]), + }) + .from(schema.games) + .leftJoin(schema.platforms, eq(schema.platforms.id, schema.games.platform_id)) + .leftJoin(schema.screenshots, eq(schema.screenshots.game_id, schema.games.id)) + .groupBy(schema.games.id) + .where(inArray(schema.platforms.slug, systems.map(s => s.id))); + + localGamesSet = new Set(localGames.filter(g => !!g.source_id && !!g.source).map(g => `${g.source}@${g.source_id}`)); + games.push(...localGames.map(g => + { + return convertLocalToFrontend(g); + }).slice(0, 3)); + + const rommPlatforms = await getOrCached(CACHE_KEYS.ROM_PLATFORMS, () => getPlatformsApiPlatformsGet({ throwOnError: true }), { expireMs: 60 * 60 * 1000 }).then(d => d.data).catch(e => console.error(e)); + + if (rommPlatforms) + { + const platformIds = rommPlatforms.filter(p => systemsRommSlugSet.has(p.slug)).map(s => s.id); + if (platformIds.length > 0) + { + const rommGames = await getRomsApiRomsGet({ + query: { + platform_ids: platformIds + } + }); + + let gamesPerSystem = Math.round(3 / systemsRommSlugSet.size); + + for (const slug of systemsRommSlugSet) + { + const systemRommGames = rommGames.data?.items.filter(g => !localGamesSet?.has(`romm@${g.id}`) && slug === g.platform_slug).map(g => + { + return convertRomToFrontend(g); + }).slice(0, gamesPerSystem) ?? []; + games.push(...systemRommGames); + } + } + } + + const gamesManifest = await getStoreGameManifest(); + const storeGames = await Promise.all(gamesManifest + .filter(g => systemsIdSet.has(path.dirname(g.path))) + .map(async (e) => + { + const system = path.dirname(e.path); + const id = path.basename(e.path, path.extname(e.path)); + + const localGame = await db.query.games.findFirst({ columns: { id: true }, where: and(eq(schema.games.source, 'store'), eq(schema.games.source_id, `${system}@${id}`)) }); + + if (localGame) + { + return undefined; + } + + const storeGame = await getStoreGameFromPath(e.path); + + return convertStoreToFrontend(system, id, storeGame); + })); + + games.push(...storeGames.filter(g => g !== undefined).slice(0, 3)); + + return games; + }) + .get('/recommended/games/game/:source/:id', async ({ params: { source, id } }) => + { + 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 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; + + const games: (FrontEndGameType & { metadata?: any; })[] = []; + + const localGames = await db.select({ ...getTableColumns(schema.games), platform: schema.platforms }) + .from(schema.games) + .leftJoin(schema.platforms, eq(schema.platforms.id, schema.games.platform_id)) + .groupBy(schema.games.id); + + 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 }))); + + const rommPlatforms = await getOrCached(CACHE_KEYS.ROM_PLATFORMS, () => getPlatformsApiPlatformsGet({ throwOnError: true }), { expireMs: 60 * 60 * 1000 }).then(d => d.data).catch(e => console.error(e)); + if (rommPlatforms) + { + const rommPlatform = rommPlatforms.find(p => p.slug === sourceData.platform_slug); + if (rommPlatform) + { + const rommGames = await getRomsApiRomsGet({ query: { genres: sourceData.genres, genres_logic: 'any' } }); + if (rommGames.data) + { + games.push(...rommGames.data.items.filter(g => !localGamesSourceSet.has(`romm@${g.id}`)).map(g => ({ ...convertRomToFrontend(g), metadata: g.metadatum }))); + } + } + } + + const shuffledGames = await getShuffledStoreGames(); + const storeGames = await Promise.all(shuffledGames + .filter(g => + { + const system = path.dirname(g.path); + const id = path.basename(g.path, path.extname(g.path)); + + if (localGamesSourceSet.has(`${system}@${id}`)) + return false; + + if (esSystem) + { + if (path.dirname(g.path) === esSystem.system) return true; + } + + return false; + }) + .map(async (e) => + { + const system = path.dirname(e.path); + const id = path.basename(e.path, path.extname(e.path)); + const storeGame = await getStoreGameFromPath(e.path); + return convertStoreToFrontend(system, id, storeGame); + })); + + if (storeGames) + { + games.push(...storeGames.slice(0, 3)); + } + + const random = new SeededRandom(Math.round(new Date().getTime() / 1000 / 60 / 60)); + + const rankedGames = games.filter(g => + { + if (sourceData.source && g.id.id === sourceData.source_id && g.id.source === sourceData.source) + { + return false; + } + + if (g.id.id === sourceData.id.id && g.id.source === sourceData.id.source) + { + return false; + } + + return true; + }).map(g => + { + let rank = random.next(); + + if (g.platform_slug === sourceData.platform_slug) + rank += 1; + + if (g.metadata) + { + if (g.metadata.companies instanceof Array && g.metadata.companies.some((c: string) => sourceCompaniesSet.has(c))) + { + rank += 1; + } + + if (g.metadata.genres instanceof Array && g.metadata.genres.some((g: string) => sourceGenresSet.has(g))) + { + rank += 1; + } + } + + return { rank: rank, game: g }; + }); + + rankedGames.sort((lhs, rhs) => rhs.rank - lhs.rank); + + return rankedGames.map(g => g.game).slice(0, 10); }); \ No newline at end of file diff --git a/src/bun/api/games/platforms.ts b/src/bun/api/games/platforms.ts index 73e0347..ee92a3f 100644 --- a/src/bun/api/games/platforms.ts +++ b/src/bun/api/games/platforms.ts @@ -1,7 +1,7 @@ import Elysia, { status } from "elysia"; import { getPlatformApiPlatformsIdGet, getPlatformsApiPlatformsGet, getRomsApiRomsGet } from "@clients/romm"; import z from "zod"; -import { count, eq, getTableColumns } from "drizzle-orm"; +import { and, count, eq, getTableColumns, not } from "drizzle-orm"; import { db } from "../app"; import { FrontEndPlatformType } from "@shared/constants"; import * as schema from "@schema/app"; @@ -25,17 +25,35 @@ export default new Elysia() { const frontEndPlatforms = await Promise.all(rommPlatforms.map(async p => { - const game = await getRomsApiRomsGet({ query: { platform_ids: [p.id] } }); + const screenshots: string[] = []; + const rommGames = await getRomsApiRomsGet({ query: { platform_ids: [p.id], limit: 3 } }).then(d => d.data); + if (rommGames) + { + const rommScreenshots = rommGames.items.find(i => i.merged_screenshots.length > 0)?.merged_screenshots.map(s => `/api/romm/image/romm/${s}`); + if (rommScreenshots) + screenshots.push(...rommScreenshots); + } + + if (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) + 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); + const platform: FrontEndPlatformType = { slug: p.slug, name: p.display_name, family_name: p.family_name, path_cover: `/api/romm/image/romm/assets/platforms/${p.slug}.svg`, - game_count: p.rom_count, + game_count: p.rom_count + localGames.length, updated_at: new Date(p.updated_at), id: { source: 'romm', id: String(p.id) }, hasLocal: localPlatformSet.has(p.slug), - paths_screenshots: game.data?.items[0]?.merged_screenshots.map(s => `/api/romm/image/romm/${s}`) ?? [] + paths_screenshots: screenshots }; return platform; diff --git a/src/bun/api/games/services/launchGameService.ts b/src/bun/api/games/services/launchGameService.ts index d554dc3..2b7276d 100644 --- a/src/bun/api/games/services/launchGameService.ts +++ b/src/bun/api/games/services/launchGameService.ts @@ -5,16 +5,18 @@ import { existsSync, readFileSync } from 'node:fs'; import * as schema from '@schema/emulators'; import * as appSchema from "@schema/app"; import { eq } from 'drizzle-orm'; -import { activeGame, config, db, emulatorsDb, events, setActiveGame } from '../../app'; +import { activeGame, config, customEmulators, db, emulatorsDb, events, setActiveGame } from '../../app'; import os from 'node:os'; import { $ } from 'bun'; import { spawn } from 'node:child_process'; import { updateRomUserApiRomsIdPropsPut } from '@/clients/romm'; -import { CommandEntry } from '@/shared/constants'; +import { CommandEntry, EmulatorSourceType } from '@/shared/constants'; +import { cores } from '../../emulatorjs/emulatorjs'; export const varRegex = /%([^%]+)%/g; +export const assignRegex = /(%\w+%)=(\S+) /g; -export async function launchCommand (validCommand: string, source: string, sourceId: string, id: number) +export async function launchCommand (validCommand: { command: string, startDir?: string; }, source: string, sourceId: string, id: number) { if (activeGame && activeGame.process?.killed === false) { @@ -31,8 +33,9 @@ export async function launchCommand (validCommand: string, source: string, sourc await new Promise((resolve, reject) => { - const game = spawn(validCommand, { - shell: true + const game = spawn(validCommand.command, { + shell: true, + cwd: validCommand.startDir }); game.stdout.on('data', data => console.log(data)); game.on('close', (code) => @@ -99,6 +102,54 @@ export async function launchCommand (validCommand: string, source: string, sourc }*/ } +/** + * Get the emulators related to the given system + * @param systemSlug the ES-DE slug for the system + */ +export async function getEmulatorsForSystem (systemSlug: string) +{ + const system = await emulatorsDb.query.systems.findFirst({ + with: { commands: true }, + where: eq(schema.systems.name, systemSlug) + }); + + if (!system) + { + throw new Error(`Could not find system '${systemSlug}'`); + } + + const emulators = new Set(); + await Promise.all(system.commands.map(async (command, index) => + { + let cmd = command.command; + + const matches = Array.from(cmd.matchAll(varRegex)); + matches.forEach(([value]) => + { + if (value.startsWith("%EMULATOR_")) + { + const emulatorName = value.substring("%EMULATOR_".length, value.length - 1); + emulators.add(emulatorName); + return; + } + }); + })); + + + + if (cores[systemSlug]) + { + emulators.add('EMULATORJS'); + } + + return Array.from(emulators); +} + +/** + * + * @param data Uses es-de system slug + * @returns + */ export async function getValidLaunchCommands (data: { systemSlug: string; gamePath: string; @@ -160,101 +211,151 @@ export async function getValidLaunchCommands (data: { } } - const formattedCommands = await Promise.all(system.commands.map(async (command, index) => + function escapeWindowsArg (arg: string): string { - const label = command.label; - let cmd = command.command; + return `"${arg + .replace(/(\\*)"/g, '$1$1\\"') // escape quotes + .replace(/(\\*)$/, '$1$1') // escape trailing backslashes + }"`; + } - let emulator: string | undefined = undefined; - let rom = validFiles[0]; - - if (cmd.includes('%ESCAPESPECIALS%')) - rom = rom.replace(/[&()^=;,]/g, ''); - - const staticVars: Record = { - '%ROM%': $.escape(rom), - '%ROMRAW%': validFiles[0], - '%ROMRAWWIN%': $.escape(validFiles[0].replace('/', '\\')), - '%ESPATH%': $.escape(path.dirname(Bun.main)), - '%ROMPATH%': $.escape(gamePath), - '%BASENAME%': $.escape(path.basename(validFiles[0], path.extname(validFiles[0]))), - '%FILENAME%': $.escape(path.basename(validFiles[0])) - }; - - cmd = cmd.replace(/\%INJECT\%=(?[\w\%.\/\\]+)/g, (_, injectFile: string) => + const formattedCommands = await Promise.all(system.commands + .filter(c => !c.command.includes(`%ENABLESHORTCUTS%`)) + .map(async (command, index) => { - try + const label = command.label; + let cmd = command.command; + + let emulator: string | undefined = undefined; + let rom = validFiles[0]; + + if (cmd.includes('%ESCAPESPECIALS%')) + rom = rom.replace(/[&()^=;,]/g, ''); + + + + const staticVars: Record = { + '%ROM%': escapeWindowsArg(rom), + '%ROMRAW%': validFiles[0], + '%ROMRAWWIN%': escapeWindowsArg(validFiles[0].replaceAll('/', '\\')), + '%ESPATH%': escapeWindowsArg(path.dirname(Bun.main)), + '%ROMPATH%': escapeWindowsArg(gamePath), + '%BASENAME%': escapeWindowsArg(path.basename(validFiles[0], path.extname(validFiles[0]))), + '%FILENAME%': escapeWindowsArg(path.basename(validFiles[0])), + '%ESCAPESPECIALS%': "", + '%HIDEWINDOW%': "" + }; + + cmd = cmd.replace(/\%INJECT\%=(?[\w\%.\/\\]+)/g, (_, injectFile: string) => { - const resolvedInjectFile = injectFile.replace(varRegex, (a) => + try { - return staticVars[a] ?? a; + const resolvedInjectFile = injectFile.replace(varRegex, (a) => + { + return staticVars[a] ?? a; + }); + if (existsSync(resolvedInjectFile)) + { + const rawContents = readFileSync(resolvedInjectFile, { encoding: 'utf-8' }); + return rawContents.split('\n').map(v => v.replace('\r', '')).join(' '); + } + + return ''; + } catch (error) + { + return ''; + } + }); + + const matches = Array.from(cmd.matchAll(varRegex)); + const varList = await Promise.all(matches.map(async ([value]) => + { + if (value.startsWith("%EMULATOR_")) + { + const emulatorName = value.substring("%EMULATOR_".length, value.length - 1); + let execs = await findExecsByName(emulatorName); + let validExec = execs.find(e => e.exists); + + emulator = emulatorName; + return [[value, validExec ? validExec.path : undefined], ['%EMUDIR%', validExec ? escapeWindowsArg(path.dirname(validExec.path)) : undefined]]; + } + + const key = value[0].substring(1, value.length - 1); + return [[value, process.env[key]]]; + })); + + const vars = { ...Object.fromEntries(varList.flatMap(l => l)), ...staticVars }; + let startDir: string | undefined = undefined; + + if ('%STARTDIR%' in vars) + { + delete vars['%STARTDIR%']; + + cmd = cmd.replace(assignRegex, (match, p1, p2) => + { + if (p1 === '%STARTDIR%') + { + startDir = varRegex.test(p2) ? staticVars[p2] : p2; + } + return ""; }); - if (existsSync(resolvedInjectFile)) - { - const rawContents = readFileSync(resolvedInjectFile, { encoding: 'utf-8' }); - return rawContents.split('\n').map(v => v.replace('\r', '')).join(' '); - } - - return ''; - } catch (error) - { - return ''; - } - }); - - const matches = Array.from(cmd.matchAll(varRegex)); - const varList = await Promise.all(matches.map(async ([value]) => - { - if (value.startsWith("%EMULATOR_")) - { - const emulatorName = value.substring("%EMULATOR_".length, value.length - 1); - let exec = await findExecByName(emulatorName); - if (data.customEmulatorConfig.has(emulatorName)) - { - exec = { path: data.customEmulatorConfig.get(emulatorName)!, type: 'custom' }; - } - - emulator = emulatorName; - return [[value, exec ? exec.path : undefined], ['%EMUDIR%', exec ? $.escape(path.dirname(exec.path)) : undefined]]; } - const key = value[0].substring(1, value.length - 1); - return [[value, process.env[key]]]; + // missing variable + const invalid = Object.entries(vars).find(c => c[1] === undefined); + + const formattedCommand = cmd.replace(varRegex, (s) => vars[s] ?? '').trim(); + + return { + id: index, + label: label ?? undefined, + command: formattedCommand, + startDir, + valid: !invalid, emulator + } satisfies CommandEntry; })); - const vars = { ...Object.fromEntries(varList.flatMap(l => l)), ...staticVars }; - vars['%ESCAPESPECIALS%'] = ""; - vars['%HIDEWINDOW%'] = ''; - - // missing variable - const invalid = Object.entries(vars).find(c => c[1] === undefined); - - const formattedCommand = cmd.replace(varRegex, (s) => vars[s] ?? '').trim(); - - return { - id: index, - label: label ?? undefined, - command: formattedCommand, - valid: !invalid, emulator - } satisfies CommandEntry; - })); - return formattedCommands.filter(c => !!c); } -export async function findExecByName (emulatorName: string) +export async function findExecsByName (emulatorName: string) { const emulator = await emulatorsDb.query.emulators.findFirst({ where: eq(schema.emulators.name, emulatorName) }); if (!emulator) { throw new Error(`Could not find emulator ${emulatorName}`); } - return findExec(emulator); + return findExecs(emulatorName, emulator); } -export async function findExec (emulator: { winregistrypath: string[], systempath: string[], staticpath: string[]; }) +export function findStoreEmulatorExec (id: string, emulator?: { systempath: string[]; }): EmulatorSourceType | undefined { - if (os.platform() === 'win32') + const storeEmulatorFolder = path.join(config.get('downloadPath'), 'emulators', id); + const storeExecName = emulator?.systempath.find(name => existsSync(path.join(storeEmulatorFolder, name))); + if (storeExecName) + { + return { binPath: path.join(storeEmulatorFolder, storeExecName), rootPath: storeEmulatorFolder, exists: true, type: "store" }; + } + + return undefined; +} + +export async function findExecs (id: string, emulator?: { winregistrypath: string[], systempath: string[], staticpath: string[]; }) +{ + const execs: EmulatorSourceType[] = []; + + if (customEmulators.has(id)) + { + execs.push({ binPath: customEmulators.get(id), type: 'custom', exists: await fs.exists(customEmulators.get(id)) }); + } + + if (emulator && emulator.systempath.length > 0) + { + const storePath = findStoreEmulatorExec(id, emulator); + if (storePath) execs.push(storePath); + } + + if (emulator && os.platform() === 'win32') { const regValues = emulator.winregistrypath; if (regValues.length > 0) @@ -264,32 +365,32 @@ export async function findExec (emulator: { winregistrypath: string[], systempat const registryValue = await readRegistryValue(node); if (registryValue) { - return { path: registryValue, type: 'registry' }; + execs.push({ binPath: registryValue, type: 'registry', exists: true }); } } } } - const systempaths = emulator.systempath; - if (systempaths.length > 0) + if (emulator && emulator.systempath.length > 0) { - const systemPath = await resolveSystemPath(systempaths); + const systemPath = await resolveSystemPath(emulator.systempath); if (systemPath) { - return { path: systemPath, type: 'system' }; + execs.push({ binPath: systemPath, type: 'system', exists: true }); } } - const staticPaths = emulator.staticpath; - if (staticPaths.length > 0) + if (emulator && emulator.staticpath.length > 0) { - const staticPath = await resolveStaticPath(staticPaths); + const staticPath = await resolveStaticPath(emulator.staticpath); if (staticPath) { - return { path: staticPath, type: 'static' }; + execs.push({ binPath: staticPath, type: 'static', exists: true }); } } + + return execs; } async function readRegistryValue (text: string) diff --git a/src/bun/api/games/services/statusService.ts b/src/bun/api/games/services/statusService.ts index 2acde4c..8b56b51 100644 --- a/src/bun/api/games/services/statusService.ts +++ b/src/bun/api/games/services/statusService.ts @@ -11,6 +11,10 @@ import { ErrorLike } from "elysia/universal"; import { getStoreGameFromId } from "../../store/services/gamesService"; import { cores } from "../../emulatorjs/emulatorjs"; import { host } from "@/bun/utils/host"; +import Elysia from "elysia"; +import z from "zod"; +import data from "@emulators"; +import { InstallJob, InstallJobStates } from "../../jobs/install-job"; class CommandSearchError extends Error { @@ -54,8 +58,11 @@ export async function getValidLaunchCommandsForGame (source: string, id: string) { const gameUrl = `${RPC_URL(host)}/api/romm/rom/${source}/${id}`; commands.push({ - id: 'emulatorjs', - label: "Emulator JS", command: `core=${cores[localGame.platform_slug]}&gameUrl=${encodeURIComponent(gameUrl)}`, valid: true, emulator: 'emulatorjs' + id: 'EMULATORJS', + label: "Emulator JS", + command: `core=${cores[localGame.platform_slug]}&gameUrl=${encodeURIComponent(gameUrl)}`, + valid: true, + emulator: 'EMULATORJS' }); } @@ -89,97 +96,93 @@ export async function getValidLaunchCommandsForGame (source: string, id: string) return undefined; } -export default async function buildStatusResponse (source: string, id: string) +export default function buildStatusResponse () { - let cleanup: (() => void) | undefined; - let closed = false; - return new Response(new ReadableStream({ - async start (controller) + return new Elysia().ws('/status/:source/:id', { + response: z.discriminatedUnion('status', [ + z.object({ status: z.literal('error'), error: z.unknown() }), + z.object({ status: z.literal('installed'), commands: z.array(z.any()), details: z.string().optional() }), + z.object({ status: z.literal(['refresh', 'queued']) }), + z.object({ status: z.literal('playing'), details: z.string() }), + z.object({ status: z.literal('install'), details: z.string() }), + z.object({ status: z.literal(['download', 'extract']), progress: z.number() }), + ]), + message (ws, data) { - const encoder = new TextEncoder(); - - function enqueue (data: GameInstallProgress, event?: 'error' | 'refresh' | 'ping') + if (data === 'cancel') { - if (closed) return; - const evntString = event ? `event: ${event}\n` : ''; - controller.enqueue(encoder.encode(`${evntString}data: ${JSON.stringify(data)}\n\n`)); + const activeTask = taskQueue.findJob(`install-rom-${ws.data.params.source}-${ws.data.params.id}`, InstallJob); + activeTask?.abort('cancel'); } - - await sendLatests(); - - // seems to help with issue of buffers not flushing, keeping the connection open forcefully - const keepAlive = setInterval(() => - { - if (closed) return clearInterval(keepAlive); - try - { - enqueue({}, 'ping'); - } catch - { - closed = true; - clearInterval(keepAlive); - } - }, 15000); - - const sourceId = `${source}-${id}`; + }, + async open (ws) + { + sendLatests(); async function sendLatests () { - if (closed) return; - const localGame = await db.query.games.findFirst({ where: getLocalGameMatch(id, source), columns: { id: true } }); - const activeTask = taskQueue.findJob(`install-rom-${source}-${id}`); + if (ws.readyState > 1) return; + const localGame = await db.query.games.findFirst({ where: getLocalGameMatch(ws.data.params.id, ws.data.params.source), columns: { id: true } }); + const activeTask = taskQueue.findJob(`install-rom-${ws.data.params.source}-${ws.data.params.id}`, InstallJob); if (activeTask) { - enqueue({ - progress: activeTask.progress, - status: activeTask.state as any - }); + if (activeTask.status === 'queued') + { + ws.send({ status: 'queued' }); + } else + { + ws.send({ status: activeTask.state as InstallJobStates, progress: activeTask.progress }); + } } else if (activeGame && activeGame.gameId === localGame?.id) { - enqueue({ status: 'playing' as GameStatusType, details: 'Playing' }); + ws.send({ status: 'playing', details: 'Playing' }); } else { - const validCommand = await getValidLaunchCommandsForGame(source, id); + const validCommand = await getValidLaunchCommandsForGame(ws.data.params.source, ws.data.params.id); if (validCommand) { if (validCommand instanceof Error) { - enqueue({ status: validCommand.name as GameStatusType, error: validCommand.message }); + ws.send({ status: 'error', error: validCommand.message }); } else { - enqueue({ status: 'installed', details: validCommand.commands[0].label, commands: validCommand.commands }); + ws.send({ + status: 'installed', + details: validCommand.commands[0].label, + commands: validCommand.commands + }); } } - else if (source === 'romm') + else if (ws.data.params.source === 'romm') { // TODO: Add Caching - const remoteGame = await getRomApiRomsIdGet({ path: { id: Number(id) } }); + const remoteGame = await getRomApiRomsIdGet({ path: { id: Number(ws.data.params.id) } }); const stats = await fs.statfs(config.get('downloadPath')); if (remoteGame.data?.fs_size_bytes && remoteGame.data?.fs_size_bytes > stats.bsize * stats.bavail) { - enqueue({ status: 'error', error: "Not Enough Free Space" }); + ws.send({ status: 'error', error: "Not Enough Free Space" }); } else { - enqueue({ status: 'install', details: 'Install' }); + ws.send({ status: 'install', details: 'Install' }); } - } else if (source === 'store') + } else if (ws.data.params.source === 'store') { - const storeGame = await getStoreGameFromId(id); + const storeGame = await getStoreGameFromId(ws.data.params.id); const fileResponse = await fetch(storeGame.file, { method: 'HEAD' }); const size = Number(fileResponse.headers.get('content-length')); const stats = await fs.statfs(config.get('downloadPath')); if (size > stats.bsize * stats.bavail) { - enqueue({ status: 'error', error: "Not Enough Free Space" }); + ws.send({ status: 'error', error: "Not Enough Free Space" }); } else { - enqueue({ status: 'install', details: 'Install' }); + ws.send({ status: 'install', details: 'Install' }); } } } @@ -190,50 +193,56 @@ export default async function buildStatusResponse (source: string, id: string) { if (data.error) { - enqueue({ + ws.send({ status: 'error', error: data.error - }, 'error'); + }); } await sendLatests(); }; events.on('activegameexit', handleActiveExit); dispose.push(() => events.off('activegameexit', handleActiveExit)); - dispose.push(taskQueue.on('progress', ({ id, progress, state }) => + dispose.push(taskQueue.on('progress', (data) => { - if (id.endsWith(sourceId)) + if (data.id === `install-rom-${ws.data.params.source}-${ws.data.params.id}`) { - enqueue({ progress, status: state as any }); + + ws.send({ status: data.job.state as InstallJobStates, progress: data.progress }); } })); - dispose.push(taskQueue.on('completed', ({ id }) => + dispose.push(taskQueue.on('queued', (data) => { - if (id.endsWith(sourceId)) + if (data.id === `install-rom-${ws.data.params.source}-${ws.data.params.id}`) { - enqueue({}, 'refresh'); + ws.send({ status: 'queued' }); } })); - dispose.push(taskQueue.on('error', ({ id, error }) => + dispose.push(taskQueue.on('completed', (data) => { - if (id.endsWith(sourceId)) + if (data.id === `install-rom-${ws.data.params.source}-${ws.data.params.id}`) { - enqueue({ + ws.send({ status: 'refresh' }); + } + })); + dispose.push(taskQueue.on('error', (data) => + { + if (data.id === `install-rom-${ws.data.params.source}-${ws.data.params.id}`) + { + ws.send({ status: 'error', - error: getErrorMessage(error) - }, 'error'); + error: getErrorMessage(data.error) + }); } })); - cleanup = () => + (ws.data as any).cleanup = () => { - closed = true; dispose.forEach(f => f()); }; }, - cancel () + close (ws, code, reason) { - cleanup?.(); - cleanup = undefined; + (ws.data as any).cleanup?.(); }, - })); + }); } \ No newline at end of file diff --git a/src/bun/api/games/services/utils.ts b/src/bun/api/games/services/utils.ts index c2a1e8b..5cc3a96 100644 --- a/src/bun/api/games/services/utils.ts +++ b/src/bun/api/games/services/utils.ts @@ -1,12 +1,14 @@ import getFolderSize from "get-folder-size"; import fs from "node:fs/promises"; import path from "node:path"; -import { config, emulatorsDb } from "../../app"; +import { config, db, emulatorsDb } from "../../app"; import { and, eq } from "drizzle-orm"; import * as schema from "@schema/app"; -import { FrontEndGameType, FrontEndGameTypeDetailed, StoreGameType } from "@shared/constants"; -import { DetailedRomSchema, SimpleRomSchema } from "@clients/romm"; +import { FrontEndGameType, FrontEndGameTypeDetailed, FrontEndGameTypeDetailedAchievement, StoreGameType } from "@shared/constants"; +import { DetailedRomSchema, getCurrentUserApiUsersMeGet, getRomApiRomsIdGet, SimpleRomSchema } from "@clients/romm"; import * as emulatorSchema from "@schema/emulators"; +import romm from "@/mainview/scripts/queries/romm"; +import { extractStoreGameSourceId, getStoreGame } from "../../store/services/gamesService"; export async function calculateSize (installPath: string | null) { @@ -127,7 +129,7 @@ export async function convertStoreToFrontend (system: string, id: string, storeG slug: null, name: storeGame.title, platform_id: null, - platform_slug: system, + platform_slug: rommSystem?.sourceSlug ?? system, paths_screenshots: storeGame.pictures.screenshots?.map((s: string) => `/api/romm/image?url=${encodeURIComponent(s)}`) ?? [] }; @@ -157,21 +159,138 @@ export async function convertStoreToFrontendDetailed (system: string, id: string return detailed; } -export function convertRomToFrontendDetailed (rom: DetailedRomSchema) +export async function convertRomToFrontendDetailed (rom: DetailedRomSchema) { const detailed: FrontEndGameTypeDetailed = { ...convertRomToFrontend(rom), summary: rom.summary, fs_size_bytes: rom.fs_size_bytes, local: false, - missing: rom.missing_from_fs + 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 }; + + const userData = await getCurrentUserApiUsersMeGet(); + const gameAchievements = userData.data?.ra_progression?.results?.find(p => p.rom_ra_id == rom.ra_id); + if (rom.merged_ra_metadata?.achievements) { + const earnedMap = new Map(gameAchievements?.earned_achievements.map(a => [a.id, { date: new Date(a.date), date_hardcore: a.date_hardcore ? new Date(a.date_hardcore) : undefined }])); detailed.achievements = { - unlocked: rom.merged_ra_metadata.achievements?.map(a => a.num_awarded).length, + unlocked: gameAchievements?.num_awarded ?? 0, + entires: rom.merged_ra_metadata.achievements.map(a => + { + const earned = a.badge_id ? earnedMap.get(a.badge_id) : undefined; + const ach: FrontEndGameTypeDetailedAchievement = { + id: a.badge_id ?? String(a.ra_id) ?? 'unknown', + title: a.title ?? "Unknown", + badge_url: (earned ? a.badge_url : a.badge_url_lock) ?? undefined, + date: earned?.date, + date_hardcode: earned?.date_hardcode, + description: a.description ?? undefined, + display_order: a.display_order ?? 0, + type: a.type ?? undefined + }; + + return ach; + }).sort((a, b) => a.display_order - b.display_order), total: rom.merged_ra_metadata.achievements.length }; } return detailed; +} + +export async function getLocalGameDetailed (match: any) +{ + const localGame = await db.query.games.findFirst({ + where: match, + with: { + screenshots: { columns: { id: true } }, + platform: { columns: { name: true, slug: true } } + } + }); + + 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 undefined; +} + +export async function getSourceGameDetailed (source: string, id: string) +{ + if (source === 'local') + { + const localGame = await getLocalGameDetailed(eq(schema.games.id, Number(id))); + if (localGame) return localGame; + return undefined; + } + else + { + const localGame = await getLocalGameDetailed(getLocalGameMatch(id, source)); + if (source === 'romm') + { + const rom = await getRomApiRomsIdGet({ path: { id: Number(id) } }); + if (rom.data) + { + const romGame = await convertRomToFrontendDetailed(rom.data); + if (localGame) + { + return { + ...romGame, + ...localGame, + }; + } + return romGame; + } + else if (localGame) + { + return localGame; + } + + return undefined; + } + else if (source === 'store') + { + const gameId = extractStoreGameSourceId(id); + const storeGame = await getStoreGame(gameId.system, gameId.id); + if (!storeGame) return undefined; + const storeFrontendGame = await convertStoreToFrontendDetailed(gameId.system, gameId.id, storeGame); + if (localGame) + { + return { ...storeFrontendGame, ...localGame }; + } + return storeFrontendGame; + } else if (localGame) + { + return localGame; + } + + return undefined; + } } \ No newline at end of file diff --git a/src/bun/api/jobs/emulator-download-job.ts b/src/bun/api/jobs/emulator-download-job.ts new file mode 100644 index 0000000..1e4a673 --- /dev/null +++ b/src/bun/api/jobs/emulator-download-job.ts @@ -0,0 +1,105 @@ +import { EmulatorPackageType } from "@/shared/constants"; +import { getStoreEmulatorPackage } from "../store/services/gamesService"; +import { IJob, JobContext } from "../task-queue"; +import z from "zod"; +import { Glob } from "bun"; +import { config } from "../app"; +import path from 'node:path'; +import { getOrCachedGithubRelease } from "../cache"; +import _7z from '7zip-min'; +import fs from "node:fs/promises"; +import { Downloader } from "@/bun/utils/downloader"; +import { move } from "fs-extra"; + +type EmulatorDownloadStates = "download" | "extract"; + +export class EmulatorDownloadJob implements IJob, EmulatorDownloadStates> +{ + static id = "download-emulator" as const; + static dataSchema = z.object({ emulator: z.string() }); + emulator: string; + downloadSource: string; + emulatorPackage?: EmulatorPackageType; + + constructor(emulator: string, downloadSource: string) + { + this.emulator = emulator; + this.downloadSource = downloadSource; + } + + async start (context: JobContext, EmulatorDownloadStates>) + { + this.emulatorPackage = await getStoreEmulatorPackage(this.emulator); + if (!this.emulatorPackage) throw new Error("Emulator not found"); + if (!this.emulatorPackage.downloads) throw new Error("Emulator has no downloads"); + + const validDownloads = this.emulatorPackage.downloads[`${process.platform}:${process.arch}`]; + if (!validDownloads) throw new Error(`Now downloads in ${this.emulatorPackage.name} for platform ${process.platform}:${process.arch}`); + + const validDownload = validDownloads.find(d => d.type === this.downloadSource); + if (!validDownload || !validDownload.path) throw new Error(`Download type ${this.downloadSource} not found`); + + console.log("Trying To Download from ", `https://api.github.com/repos/${validDownload.path}/releases/latest`); + const latestRelease = await getOrCachedGithubRelease(validDownload.path); + const glob = new Glob(validDownload.pattern); + const validAsset = latestRelease.assets.find(a => glob.match(a.name)); + if (!validAsset) throw new Error("Could Not Find Valid Asset"); + const downloadUrl = validAsset.browser_download_url; + const emulatorsFolder = path.join(config.get('downloadPath'), "emulators", this.emulator); + + const isArchive = validAsset.content_type === 'application/x-7z-compressed' || validAsset.name.endsWith('.7z') || validAsset.content_type === 'application/zip' || validAsset.name.endsWith('.zip'); + + const isAppImage = validAsset.name.endsWith(".AppImage"); + + if (!isArchive && !isAppImage) + { + throw new Error("Invalid Download Type"); + } + + const tmpFolder = path.join(config.get("downloadPath"), ".tmp"); + const downloader = new Downloader(this.emulator, + [{ url: new URL(downloadUrl), file_name: path.basename(downloadUrl), file_path: this.emulator }], + tmpFolder, + { + onProgress (stats) + { + context.setProgress(stats.progress, 'download'); + }, + }); + + const destinationPaths = await downloader.start(); + if (destinationPaths) + { + if (isArchive) + { + if (await downloader.start() && destinationPaths[0]) + { + let destinationPath = destinationPaths[0]; + await _7z.unpack(destinationPath, emulatorsFolder); + await fs.rm(destinationPath, { recursive: true }); + + // check if 1 root folder we need to get rid of + const contents = await fs.readdir(emulatorsFolder); + if (contents.length === 1) + { + const stat = await fs.stat(path.join(emulatorsFolder, contents[0])); + if (stat.isDirectory()) + { + console.log("Found 1 root folder, using that instead"); + const tmpEmulatorsFolder = `${emulatorsFolder} (1)`; + await move(path.join(emulatorsFolder, contents[0]), tmpEmulatorsFolder, { overwrite: true }); + await move(tmpEmulatorsFolder, emulatorsFolder, { overwrite: true }); + } + } + } + } + } + } + + exposeData () + { + return { emulator: this.emulator }; + } + +} + diff --git a/src/bun/api/jobs/install-job.ts b/src/bun/api/jobs/install-job.ts index 710d0e4..b095e8f 100644 --- a/src/bun/api/jobs/install-job.ts +++ b/src/bun/api/jobs/install-job.ts @@ -6,12 +6,14 @@ import * as schema from "@schema/app"; import * as emulatorSchema from "@schema/emulators"; import path from 'node:path'; import { getPlatformApiPlatformsIdGet, getRomApiRomsIdGet, PlatformSchema } from "@clients/romm"; -import { config, db, emulatorsDb, jar } from "../app"; -import unzip from 'unzip-stream'; -import { Readable, Transform } from "node:stream"; +import { config, db, emulatorsDb, events, jar } from "../app"; import { extractStoreGameSourceId, getStoreGameFromId } from "../store/services/gamesService"; import * as igdb from 'ts-igdb-client'; import secrets from "../secrets"; +import { hashFile } from "@/bun/utils"; +import { Downloader } from "@/bun/utils/downloader"; +import { sleep } from "bun"; +import _7z from '7zip-min'; interface JobConfig { @@ -19,13 +21,16 @@ interface JobConfig dryDownload?: boolean; } -export class InstallJob implements IJob +export type InstallJobStates = 'download' | 'extract'; + +export class InstallJob implements IJob { public gameId: string; public source: string; public sourceId: string; public config?: JobConfig; static id = "install-job" as const; + public group = InstallJob.id; constructor(id: string, source: string, sourceId: string, config?: JobConfig) { @@ -35,162 +40,124 @@ export class InstallJob implements IJob this.source = source; } - public async start (cx: JobContext) + public async start (cx: JobContext) { cx.setProgress(0, 'download'); fs.mkdir(config.get('downloadPath'), { recursive: true }); + const downloadPath = config.get('downloadPath'); + + let files: { + url: URL, + file_path: string; + file_name: string; + size?: number; + }[] = []; + let cookie: string = ''; + let screenshotUrls: string[]; + let coverUrl: string; + let rommPlatform: PlatformSchema | undefined; + let slug: string | null; + let path_fs: string | undefined; + let summary: string | null; + let name: string | null; + let last_played: Date | null; + let igdb_id: number | null; + let ra_id: number | null; + let source_id: string; + let system_slug: string; + let extract_path: string; + let metadata: any | undefined; + + switch (this.source) + { + case 'romm': + + const rom = (await getRomApiRomsIdGet({ path: { id: Number(this.gameId) }, throwOnError: true })).data; + rommPlatform = (await getPlatformApiPlatformsIdGet({ path: { id: rom.platform_id }, throwOnError: true })).data; + + const rommAddress = config.get('rommAddress'); + coverUrl = `${rommAddress}${rom.path_cover_large}`; + screenshotUrls = rom.merged_screenshots.map(s => `${config.get('rommAddress')}${s}`); + last_played = rom.rom_user.last_played ? new Date(rom.rom_user.last_played) : null; + igdb_id = rom.igdb_id; + ra_id = rom.ra_id; + summary = rom.summary; + name = rom.name; + path_fs = path.join(rom.fs_path, rom.fs_name); + source_id = String(rom.id); + slug = rom.slug; + system_slug = rommPlatform.slug; + extract_path = ''; + metadata = rom.metadatum; + + const rommFiles = await Promise.all(rom.files.map(async f => + { + const localPath = path.join(config.get('downloadPath'), f.full_path); + if (f.md5_hash && await fs.exists(localPath)) + { + const existingHash = await hashFile(localPath, 'sha1'); + if (existingHash === f.md5_hash) + { + console.log("File Already Present: ", f.full_path); + return undefined; + } + + console.warn("File ", f.full_path, 'with hash', existingHash, 'has different hash than', f.sha1_hash); + } + + return { + url: new URL(`${config.get('rommAddress')}/api/romsfiles/${f.id}/content/${f.file_name}`), + file_name: f.file_name, + file_path: path.join(config.get('downloadPath'), f.file_path), + size: f.file_size_bytes + }; + })); + + files.push(...rommFiles.filter(f => f !== undefined)); + cookie = await jar.getCookieString(config.get('rommAddress') ?? ''); + break; + case 'store': + const game = await getStoreGameFromId(this.gameId); + const gameId = extractStoreGameSourceId(this.gameId); + coverUrl = game.pictures.titlescreens[0]; + screenshotUrls = game.pictures.screenshots; + files.push({ url: new URL(game.file), file_path: `roms/${game.system}`, file_name: path.basename(decodeURI(game.file)) }); + slug = this.gameId; + source_id = this.gameId; + name = game.title; + summary = game.description; + system_slug = gameId.system; + extract_path = path.join('roms', gameId.system); + + break; + default: + throw new Error("Unsupported source"); + } + if (this.config?.dryRun !== true) { - const downloadPath = config.get('downloadPath'); - - let downloadUrl: URL; - let cookie: string = ''; - let screenshotUrls: string[]; - let coverUrl: string; - let rommPlatform: PlatformSchema | undefined; - let slug: string | null; - let path_fs: string | undefined; - let summary: string | null; - let name: string | null; - let last_played: Date | null; - let igdb_id: number | null; - let ra_id: number | null; - let source_id: string; - let system_slug: string; - let extract_path: string; - - switch (this.source) - { - case 'romm': - - const rom = (await getRomApiRomsIdGet({ path: { id: Number(this.gameId) }, throwOnError: true })).data; - rommPlatform = (await getPlatformApiPlatformsIdGet({ path: { id: rom.platform_id }, throwOnError: true })).data; - - const rommAddress = config.get('rommAddress'); - coverUrl = `${rommAddress}${rom.path_cover_large}`; - screenshotUrls = rom.merged_screenshots.map(s => `${config.get('rommAddress')}${s}`); - last_played = rom.rom_user.last_played ? new Date(rom.rom_user.last_played) : null; - igdb_id = rom.igdb_id; - ra_id = rom.ra_id; - summary = rom.summary; - name = rom.name; - path_fs = path.join(rom.fs_path, rom.fs_name); - source_id = String(rom.id); - slug = rom.slug; - system_slug = rommPlatform.slug; - extract_path = ''; - - downloadUrl = new URL(`${config.get('rommAddress')}/api/roms/download`); - downloadUrl.searchParams.set('rom_ids', String(this.gameId)); - cookie = await jar.getCookieString(config.get('rommAddress') ?? ''); - break; - case 'store': - const game = await getStoreGameFromId(this.gameId); - const gameId = extractStoreGameSourceId(this.gameId); - coverUrl = game.pictures.titlescreens[0]; - screenshotUrls = game.pictures.screenshots; - downloadUrl = new URL(game.file); - slug = this.gameId; - source_id = this.gameId; - name = game.title; - summary = game.description; - system_slug = gameId.system; - extract_path = 'roms', gameId.system; - - break; - default: - throw new Error("Unsupported source"); - } - if (this.config?.dryDownload !== true) { - /* - // download files for rom - const downloadUrl = new URL(`${config.get('rommAddress')}/api/roms/download`); - downloadUrl.searchParams.set('rom_ids', String(this.id)); - const downloader = new DownloaderHelper(downloadUrl.href, downloadPath, { - headers: { - cookie: await jar.getCookieString(config.get('rommAddress') ?? '') - }, - fileName: `${this.id}.zip`, - // Romm doesn't support resume download - override: true - }); - - cx.abortSignal.addEventListener('abort', downloader.stop); - - downloader.on('progress.throttled', e => - { - cx.setProgress(e.progress, 'download'); - }); - - downloader.on('error', (e) => - { - cx.abort(e); - }); - const finishPromise = new Promise(resolve => - { - downloader.on("end", ({ filePath }) => resolve(filePath)); - }); - - await downloader.start().catch(err => console.error(err)); - const zipFilePath = await finishPromise; - - cx.setProgress(0, 'extract'); - - const zip = new StreamZip.async({ file: zipFilePath }); - const totalCount = await zip.entriesCount; - let extractCount = 0; - zip.on('extract', async (entry, file) => - { - console.log(`Extracted ${entry.name} to ${file}`); - cx.setProgress(extractCount / totalCount * 100, 'extract'); - extractCount++; - }); - await zip.extract(null, downloadPath); - await zip.close(); - - await fs.rm(zipFilePath);*/ - - cx.setProgress(0, 'download'); - - const res = await fetch(downloadUrl, { - headers: { - cookie: cookie - }, - }); - - const totalBytes = Number(res.headers.get("content-length")) || 0; - let bytesReceived = 0; - - const progressStream = new Transform({ - transform (chunk, _, callback) + const downloader = new Downloader(`game-${this.source}-${this.gameId}`, + files, + config.get('downloadPath'), { - bytesReceived += chunk.length; - if (totalBytes > 0) + signal: cx.abortSignal, + onProgress (stats) { - const percent = (bytesReceived / totalBytes) * 100; - cx.setProgress(percent, 'download'); - } - this.push(chunk); - callback(); - } - }); - - await new Promise((resolve, reject) => - { - const extract = unzip.Extract({ path: path.join(downloadPath, extract_path), }); - (extract as any).unzipStream.on('entry', (entry: any) => - { - if (!path_fs) - path_fs = path.join(extract_path, entry.path); + cx.setProgress(stats.progress, 'download'); + }, }); - Readable.fromWeb(res.body as any).pipe(progressStream) - .pipe(extract) - .on('close', resolve) - .on('error', reject); - }); + + const downloadedFiles = await downloader.start(); + if (extract_path && downloadedFiles) + { + for (const path of downloadedFiles) + { + await _7z.unpack(path, extract_path); + } + } } if (this.config?.dryDownload === true) @@ -198,8 +165,6 @@ export class InstallJob implements IJob await mkdir(path.join(downloadPath, extract_path), { recursive: true }); } - - const coverResponse = await fetch(coverUrl); const cover = Buffer.from(await coverResponse.arrayBuffer()); @@ -291,7 +256,8 @@ export class InstallJob implements IJob summary: summary, name, cover, - cover_type: coverResponse.headers.get('content-type') + cover_type: coverResponse.headers.get('content-type'), + metadata }; const [{ id }] = await tx.insert(schema.games).values(game).returning({ id: schema.games.id }); @@ -327,7 +293,17 @@ export class InstallJob implements IJob } }); + } else + { + for (let i = 0; i < 10; i++) + { + cx.setProgress(i * 10, "download"); + if (cx.abortSignal.aborted) return; + await sleep(1000); + } } + + events.emit('notification', { message: `${name}: Installed`, type: 'success', duration: 8000 }); } } \ No newline at end of file diff --git a/src/bun/api/jobs/jobs.ts b/src/bun/api/jobs/jobs.ts index 317211b..2c4e3c2 100644 --- a/src/bun/api/jobs/jobs.ts +++ b/src/bun/api/jobs/jobs.ts @@ -1,13 +1,21 @@ import Elysia from "elysia"; -import z, { } from "zod"; +import z, { _ZodType, ZodAny, ZodObject, ZodTypeAny } from "zod"; import { taskQueue } from "../app"; import { LoginJob } from "./login-job"; import TwitchLoginJob from "./twitch-login-job"; import UpdateStoreJob from "./update-store"; +import { EmulatorDownloadJob } from "./emulator-download-job"; +import { getErrorMessage } from "@/bun/utils"; +import { IJob } from "../task-queue"; -function registerJob (_job: T, path: Path, dataSchema: TS) +function registerJob< + const Path extends string, + const Schema extends ZodTypeAny, + const States extends string, + T extends IJob, States> +> (_job: { id: Path; dataSchema: Schema; } & (new (...args: any[]) => T)) { - return new Elysia().ws(path, { + return new Elysia().ws(_job.id, { body: z.discriminatedUnion('type', [ z.object({ type: z.literal('cancel') }) ]), @@ -16,14 +24,14 @@ function registerJob { - if (id === path) + if (id === _job.id) { ws.send({ type: 'started', status: job.status, progress: job.progress, data: job.job.exposeData?.() }); } }), taskQueue.on('progress', ({ id, job }) => { - if (id === path) + if (id === _job.id) { ws.send({ type: 'started', status: job.status, progress: job.progress, data: job.job.exposeData?.() }); } }), - taskQueue.on('completed', ({ id }) => + taskQueue.on('completed', ({ id, job }) => { - if (id === path) + if (id === _job.id) { - ws.send({ type: 'completed' }); + ws.send({ type: 'completed', data: job.job.exposeData?.() }); + } + }), + taskQueue.on('ended', ({ id, job }) => + { + if (id === _job.id) + { + ws.send({ type: 'ended', data: job.job.exposeData?.() }); } }), taskQueue.on('error', ({ id, error }) => { - if (id === path) + if (id === _job.id) { - ws.send({ type: 'error', error: error }); + ws.send({ type: 'error', error: getErrorMessage(error) }); } }) ]; @@ -68,13 +83,14 @@ function registerJob, "base"> { endsAt: Date; startedAt: Date; @@ -25,7 +25,7 @@ export class LoginJob implements IJob exposeData = (): z.infer => ({ endsAt: this.endsAt, startedAt: this.startedAt, url: this.url }); - async start (context: JobContext): Promise + async start (context: JobContext, "base">): Promise { const loginServer = new Elysia({ serve: { hostname: localIp, port: LOGIN_PORT } }) .use(cors()) diff --git a/src/bun/api/jobs/twitch-login-job.ts b/src/bun/api/jobs/twitch-login-job.ts index 3d2a0c0..59b5fdb 100644 --- a/src/bun/api/jobs/twitch-login-job.ts +++ b/src/bun/api/jobs/twitch-login-job.ts @@ -16,7 +16,9 @@ interface TwitchDevice verification_uri: string; } -export default class TwitchLoginJob implements IJob +type States = "Retrieving Device" | "Waiting For Authentication"; + +export default class TwitchLoginJob implements IJob, States> { twitchScopes = "analytics:read:extensions analytics:read:games user:read:email"; device?: TwitchDevice; @@ -38,7 +40,7 @@ export default class TwitchLoginJob implements IJob user_code: this.device.user_code }) : undefined; - async start (context: JobContext): Promise + async start (context: JobContext, States>): Promise { context.setProgress(0, "Retrieving Device"); let res = await fetch("https://id.twitch.tv/oauth2/device", { diff --git a/src/bun/api/jobs/update-store.ts b/src/bun/api/jobs/update-store.ts index ce97c27..a842b1d 100644 --- a/src/bun/api/jobs/update-store.ts +++ b/src/bun/api/jobs/update-store.ts @@ -1,12 +1,14 @@ import { ensureDir } from "fs-extra"; import { IJob, JobContext } from "../task-queue"; -import { getStoreFolder } from "../store/store"; +import { getStoreFolder } from "../store/services/gamesService"; +import z from "zod"; -export default class UpdateStoreJob implements IJob +export default class UpdateStoreJob implements IJob { static id = "update-store" as const; static origin = "https://github.com/simeonradivoev/gameflow-store.git"; static branch = "master"; + static dataSchema = z.never(); async gitCommand (commands: string[], dir: string) { @@ -40,8 +42,10 @@ export default class UpdateStoreJob implements IJob return (await this.gitCommand(["status", "--porcelain"], dir)).length > 0; } - async start (context: JobContext) + async start (context: JobContext) { + if (process.env.CUSTOM_STORE_PATH) return; + const storeFolder = getStoreFolder(); await ensureDir(storeFolder); context.setProgress(10); diff --git a/src/bun/api/schema/emulators.ts b/src/bun/api/schema/emulators.ts index 0af0ff0..c70679d 100644 --- a/src/bun/api/schema/emulators.ts +++ b/src/bun/api/schema/emulators.ts @@ -3,6 +3,7 @@ import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; export const emulators = sqliteTable('emulators', { name: text().primaryKey().unique(), + fullname: text(), systempath: text({ mode: 'json' }).notNull().$type().default(sql`(json_array())`), staticpath: text({ mode: 'json' }).notNull().$type().default(sql`(json_array())`), corepath: text({ mode: 'json' }).notNull().$type().default(sql`(json_array())`), diff --git a/src/bun/api/settings/services.ts b/src/bun/api/settings/services.ts index 1f7b2d1..04efda2 100644 --- a/src/bun/api/settings/services.ts +++ b/src/bun/api/settings/services.ts @@ -1,12 +1,13 @@ import * as appSchema from '@schema/app'; -import { findExecByName } from "../games/services/launchGameService"; import * as emulatorSchema from "@schema/emulators"; import { eq, inArray } from 'drizzle-orm'; import { customEmulators, db, emulatorsDb } from '../app'; import fs from 'node:fs/promises'; import { cores } from '../emulatorjs/emulatorjs'; -import { FrontEndEmulator } from '@/shared/constants'; +import { FrontEndEmulator, SERVER_URL } from '@/shared/constants'; +import { findExecsByName } from '../games/services/launchGameService'; +import { host } from '@/bun/utils/host'; /** * Get emulators based on local games. Only the ones we probably need. @@ -53,14 +54,8 @@ export async function getRelevantEmulators () const groupedEmulators = Map.groupBy(emulators, ({ emulator }) => emulator); const finalEmulators = await Promise.all(Array.from(groupedEmulators.entries()).map(async ([emulator, system_slug]) => { - let execPath: { path: string; type: string, } | undefined; - if (customEmulators.has(emulator)) - { - execPath = { path: customEmulators.get(emulator), type: 'custom' }; - } else - { - execPath = await findExecByName(emulator); - } + const execPaths = await findExecsByName(emulator); + const validExecPath = execPaths.find(e => e.exists); let platform: number | null | undefined = null; const validSystemSlug = system_slug.find(s => s.system); @@ -68,45 +63,31 @@ export async function getRelevantEmulators () { platform = platformLookup.get(validSystemSlug.system)?.platform_id; } - - // check if automatic or custom path found existing binary. - // This might not be the actual emulator but I don't care. - const exists = !!execPath && await fs.exists(execPath.path); const systems = Array.from(new Set(system_slug.filter(s => s.system).map(s => s.system!))); - if (exists) + if (validExecPath) { systems.forEach(s => platformViability.set(s, true)); } - const em: FrontEndEmulator & { isCritical: boolean; path?: { path: string, type: string; }; } = { + const em: FrontEndEmulator & { isCritical: boolean; } = { name: emulator, - exists: exists, logo: platform ? `/api/romm/platform/local/${platform}/cover` : '', systems: systems.map(s => platformLookup.get(s)).filter(s => !!s).map(e => ({ icon: `/api/romm/image/romm/assets/platforms/${e.es_slug}.svg`, name: e.platform_name ?? 'Unknown', id: e.es_slug ?? '' })), gameCount: 0, - description: '', - homepage: '', - type: 'emulator', - os: [process.platform as any], isCritical: false, - path: execPath, + validSource: validExecPath }; return em; })); finalEmulators.push({ - name: 'emulatorjs', - exists: true, - path: { path: 'localhost', type: 'js' }, + name: 'EMULATORJS', + validSource: { binPath: `${SERVER_URL(host)}`, type: 'js', exists: true }, logo: `/api/romm/image?url=${encodeURIComponent('https://emulatorjs.org/logo/EmulatorJS.png')}`, systems: [], gameCount: 0, - type: 'emulator', - description: '', - homepage: '', - os: [process.platform as any], - isCritical: false + isCritical: false, }); return finalEmulators.map(e => diff --git a/src/bun/api/store/services/emulatorsService.ts b/src/bun/api/store/services/emulatorsService.ts new file mode 100644 index 0000000..d7595bf --- /dev/null +++ b/src/bun/api/store/services/emulatorsService.ts @@ -0,0 +1,31 @@ +import { EmulatorPackageType, EmulatorSourceType, FrontEndEmulator } from "@/shared/constants"; +import { emulatorsDb } from "../../app"; +import * as emulatorSchema from '@schema/emulators'; +import { findExecs } from "../../games/services/launchGameService"; +import { eq } from "drizzle-orm"; + +export async function convertStoreEmulatorToFrontend (emulator: EmulatorPackageType, gameCount: number, systems: { + id: string; + name: string; + icon: string; +}[]) +{ + let execPath: EmulatorSourceType | undefined; + const esEmulator = await emulatorsDb.query.emulators.findFirst({ where: eq(emulatorSchema.emulators.name, emulator.name) }); + + if (esEmulator) + { + const allExecs = await findExecs(emulator.name, esEmulator); + if (allExecs.length > 0) execPath = allExecs[0]; + } + + const em: FrontEndEmulator = { + name: emulator.name, + logo: emulator.logo, + systems, + gameCount, + validSource: execPath + }; + + return em; +} \ No newline at end of file diff --git a/src/bun/api/store/services/gamesService.ts b/src/bun/api/store/services/gamesService.ts index 4221e5a..3ebb355 100644 --- a/src/bun/api/store/services/gamesService.ts +++ b/src/bun/api/store/services/gamesService.ts @@ -1,5 +1,22 @@ -import { GithubManifestSchema, StoreGameSchema } from "@/shared/constants"; +import { EmulatorPackageSchema, EmulatorPackageType, GithubManifestSchema, StoreGameSchema } from "@/shared/constants"; import { CACHE_KEYS, getOrCached } from "../../cache"; +import { and, eq } from "drizzle-orm"; +import { config, emulatorsDb } from '../../app'; +import path from "node:path"; +import fs from 'node:fs/promises'; +import * as emulatorSchema from '@schema/emulators'; +import { shuffleInPlace } from "@/bun/utils"; + +export async function getShuffledStoreGames () +{ + return getOrCached('shuffled-store-games', async () => + { + const gamesManifest = await getStoreGameManifest(); + const allStoreGames = gamesManifest.filter(g => g.type === 'blob'); + shuffleInPlace(allStoreGames, Math.round(new Date().getTime() / 1000 / 60 / 60)); + return allStoreGames; + }, { expireMs: 1000 / 60 / 60 }); +} export async function getStoreGameManifest () { @@ -56,4 +73,55 @@ export async function getStoreGameFromPath (path: string) .then(e => e.json()) .then(g => StoreGameSchema.parseAsync(g))); return game; +} + +export function getStoreFolder () +{ + if (process.env.CUSTOM_STORE_PATH) return process.env.CUSTOM_STORE_PATH; + const downlodDir = config.get('downloadPath'); + return path.join(downlodDir, "store"); +} + +export async function getStoreEmulatorPackage (id: string) +{ + const emulatorPath = path.join(getStoreFolder(), "buckets", "emulators", `${id}.json`); + if (await fs.exists(emulatorPath)) + return EmulatorPackageSchema.parseAsync(JSON.parse(await fs.readFile(emulatorPath, 'utf-8'))); + return undefined; +} + +export async function getAllStoreEmulatorPackages () +{ + const emulatorsBucket = path.join(getStoreFolder(), "buckets", "emulators"); + const emulators = await fs.readdir(emulatorsBucket); + const emulatorsRawData = await Promise.all(emulators.map(e => fs.readFile(path.join(emulatorsBucket, e), 'utf-8'))); + + const emulatesParsed = emulatorsRawData.map(d => EmulatorPackageSchema.safeParse(JSON.parse(d))).filter(e => + { + if (e.error) + { + console.error(e.error); + } + return e.data; + }).map(e => e.data!); + + return emulatesParsed; +} + +export async function buildStoreFrontendEmulatorSystems (emulator: EmulatorPackageType) +{ + const systems = await Promise.all(emulator.systems.map(async system => + { + const rommSystem = await emulatorsDb.query.systemMappings.findFirst({ + where: and(eq(emulatorSchema.systemMappings.source, 'romm'), eq(emulatorSchema.systemMappings.system, system)) + }); + + const esSystem = await emulatorsDb.query.systems.findFirst({ where: eq(emulatorSchema.emulators.name, system), columns: { fullname: true } }); + + let icon: string = `/api/romm/image/romm/assets/platforms/${rommSystem?.sourceSlug ?? system}.svg`; + + return { id: system, romm_slug: rommSystem?.sourceSlug, name: esSystem?.fullname ?? system, icon: icon }; + })); + + return systems; } \ No newline at end of file diff --git a/src/bun/api/store/store.ts b/src/bun/api/store/store.ts index 24dfba4..f7e96bc 100644 --- a/src/bun/api/store/store.ts +++ b/src/bun/api/store/store.ts @@ -1,61 +1,19 @@ -import Elysia from "elysia"; -import { config, customEmulators, db } from "../app"; +import Elysia, { status } from "elysia"; +import { config, db, taskQueue } from "../app"; import path from "node:path"; import fs from 'node:fs/promises'; -import { EmulatorPackageSchema, EmulatorPackageType, FrontEndEmulator, FrontEndEmulatorDetailed, StoreGameSchema } from "@/shared/constants"; -import { findExec } from "../games/services/launchGameService"; -import { emulatorsDb } from '../app'; -import { and, eq } from "drizzle-orm"; -import * as emulatorSchema from '@schema/emulators'; +import { FrontEndEmulatorDetailed, FrontEndEmulatorDetailedDownload, StoreGameSchema } from "@/shared/constants"; +import { findExecsByName } from "../games/services/launchGameService"; import * as appSchema from '@schema/app'; import z from "zod"; import { convertLocalToFrontendDetailed, convertStoreToFrontendDetailed, getLocalGameMatch } from "../games/services/utils"; import { getPlatformsApiPlatformsGet } from "@/clients/romm"; -import { CACHE_KEYS, getOrCached } from "../cache"; - -export function getStoreFolder () -{ - const downlodDir = config.get('downloadPath'); - return path.join(downlodDir, "store"); -} - -async function getAllStoreEmulatorPackages () -{ - const downlodDir = config.get('downloadPath'); - const emulatorsBucket = path.join(downlodDir, "store", "buckets", "emulators"); - const emulators = await fs.readdir(emulatorsBucket); - const emulatorsRawData = await Promise.all(emulators.map(e => fs.readFile(path.join(emulatorsBucket, e), 'utf-8'))); - - const emulatesParsed = emulatorsRawData.map(d => EmulatorPackageSchema.safeParse(JSON.parse(d))).filter(e => - { - if (e.error) - { - console.error(e.error); - } - return e.data; - }).map(e => e.data!); - - return emulatesParsed; -} - -async function buildSystems (emulator: EmulatorPackageType) -{ - const systems = await Promise.all(emulator.systems.map(async system => - { - const rommSystem = await emulatorsDb.query.systemMappings.findFirst({ - where: and(eq(emulatorSchema.systemMappings.source, 'romm'), eq(emulatorSchema.systemMappings.system, system)) - }); - - const esSystem = await emulatorsDb.query.systems.findFirst({ where: eq(emulatorSchema.emulators.name, system), columns: { fullname: true } }); - - let icon: string = `/api/romm/image/romm/assets/platforms/${rommSystem?.sourceSlug ?? system}.svg`; - - return { id: system, name: esSystem?.fullname ?? system, icon: icon }; - })); - - return systems; -} +import { CACHE_KEYS, getOrCached, getOrCachedGithubRelease } from "../cache"; +import { buildStoreFrontendEmulatorSystems, getAllStoreEmulatorPackages, getStoreEmulatorPackage } from "./services/gamesService"; +import { EmulatorDownloadJob } from "../jobs/emulator-download-job"; +import { Glob } from "bun"; +import { convertStoreEmulatorToFrontend } from "./services/emulatorsService"; export const store = new Elysia({ prefix: '/api/store' }) .get('/emulators', async ({ query }) => @@ -70,27 +28,10 @@ export const store = new Elysia({ prefix: '/api/store' }) .filter(e => e.os.includes(process.platform as any)) .map(async (emulator) => { - let execPath: { path: string; type: string; } | undefined; - const esEmulator = await emulatorsDb.query.emulators.findFirst({ where: eq(emulatorSchema.emulators.name, emulator.name) }); - - if (esEmulator) - { - if (customEmulators.has(emulator?.name)) - { - execPath = { path: customEmulators.get(emulator.name), type: 'custom' }; - } else - { - execPath = await findExec(esEmulator); - } - } - - const exists = !!execPath && await fs.exists(execPath.path); - const systems = await buildSystems(emulator); - + const systems = await buildStoreFrontendEmulatorSystems(emulator); const gameCounts = await Promise.all(systems.map(async (s) => { - const rommMapping = await emulatorsDb.query.systemMappings.findFirst({ where: and(eq(emulatorSchema.systemMappings.source, 'romm'), eq(emulatorSchema.systemMappings.system, s.id)) }); - const romPlatform = rommPlatforms?.find(p => p.slug === (rommMapping?.sourceSlug ?? s.id)); + const romPlatform = rommPlatforms?.find(p => p.slug === (s.romm_slug ?? s.id)); if (romPlatform) { return romPlatform.rom_count; @@ -101,13 +42,12 @@ export const store = new Elysia({ prefix: '/api/store' }) })); const gameCount = gameCounts.reduce((a, c) => a + c); - - return { ...emulator, exists, systems, gameCount } satisfies FrontEndEmulator; + return convertStoreEmulatorToFrontend(emulator, gameCount, systems); })); if (query.missing) { - frontEndEmulators = frontEndEmulators.filter(e => !e.exists); + frontEndEmulators = frontEndEmulators.filter(e => !e.validSource); } if (query.orderBy === 'importance') @@ -161,42 +101,65 @@ export const store = new Elysia({ prefix: '/api/store' }) return Bun.file(path.join(downlodDir, "store", "media", "screenshots", id, name)); }, { params: z.object({ id: z.string(), name: z.string() }) }) - .get('/details/emulator/:id', async ({ params: { id } }) => + .get('/emulator/:id', async ({ params: { id } }) => { const downlodDir = config.get('downloadPath'); - const emulatorPath = path.join(downlodDir, "store", "buckets", "emulators", `${id}.json`); + const emulatorPackage = await getStoreEmulatorPackage(id); + if (!emulatorPackage) return status("Not Found"); + + const systems = await buildStoreFrontendEmulatorSystems(emulatorPackage); + + const execPaths = await findExecsByName(emulatorPackage.name); + const emulatorScreenshotsPath = path.join(downlodDir, "store", "media", "screenshots", id); - const emulatorPackage = await EmulatorPackageSchema.parseAsync(JSON.parse(await fs.readFile(emulatorPath, 'utf-8'))); - - const systems = await buildSystems(emulatorPackage); - let execPath: { path: string; type: string; } | undefined; - const esEmulator = await emulatorsDb.query.emulators.findFirst({ where: eq(emulatorSchema.emulators.name, emulatorPackage.name) }); - - if (esEmulator) - { - if (customEmulators.has(emulatorPackage?.name)) - { - execPath = { path: customEmulators.get(emulatorPackage.name), type: 'custom' }; - } else - { - execPath = await findExec(esEmulator); - } - } - - const screenshots = await fs.exists(emulatorScreenshotsPath) ? await fs.readdir(emulatorScreenshotsPath) : []; - const exists = !!execPath && await fs.exists(execPath.path); + const validExec = execPaths.find(p => p.exists); const emulator: FrontEndEmulatorDetailed = { - ...emulatorPackage, + name: emulatorPackage.name, + description: emulatorPackage.description, systems, - exists, - status: { - source: execPath?.type, - location: execPath?.path - }, + validSource: validExec, screenshots: screenshots.map(s => `/api/store/screenshot/emulator/${id}/${s}`), - gameCount: 0 + gameCount: 0, + homepage: emulatorPackage.homepage, + downloads: await Promise.all(emulatorPackage.downloads?.[`${process.platform}:${process.arch}`].map(async d => + { + if (d.type === 'github' && d.path) + { + const release = await getOrCachedGithubRelease(d.path); + const glob = new Glob(d.pattern); + const download: FrontEndEmulatorDetailedDownload = { + name: d.type, + type: release.assets.find(a => glob.match(a.name))?.content_type + }; + return download; + }; + + return { name: d.type, type: "Unknown" }; + }) ?? []), + logo: emulatorPackage.logo, + sources: execPaths }; return emulator; - }, { params: z.object({ id: z.string() }) }); \ No newline at end of file + }, { params: z.object({ id: z.string() }) }) + .post('/install/emulator/:id/:source', async ({ params: { source, id } }) => + { + if (taskQueue.hasActiveOfType(EmulatorDownloadJob)) + { + return status("Conflict", "Installation already running"); + } + const job = new EmulatorDownloadJob(id, source); + return taskQueue.enqueue(EmulatorDownloadJob.id, job); + }) + .delete('/emulator/:id', async ({ params: { id } }) => + { + + const storeEmulatorFolder = path.join(config.get('downloadPath'), 'emulators', id); + if (await fs.exists(storeEmulatorFolder)) + { + fs.rm(storeEmulatorFolder, { recursive: true }); + return status("OK"); + } + return status("Not Found"); + }); \ No newline at end of file diff --git a/src/bun/api/system.ts b/src/bun/api/system.ts index 31741a4..5c592b1 100644 --- a/src/bun/api/system.ts +++ b/src/bun/api/system.ts @@ -11,7 +11,7 @@ import { DirSchema, DownloadsDrive } from "@/shared/constants"; import { getDevices, getDevicesCurated } from "./drives"; import getFolderSize from "get-folder-size"; import si from 'systeminformation'; -import { getStoreFolder } from "./store/store"; +import { getStoreFolder } from "./store/services/gamesService"; export const system = new Elysia({ prefix: '/api/system' }) .post('/show_keyboard', async ({ body: { XPosition, YPosition, Width, Height } }) => diff --git a/src/bun/api/task-queue.ts b/src/bun/api/task-queue.ts index 002f326..51f4fd2 100644 --- a/src/bun/api/task-queue.ts +++ b/src/bun/api/task-queue.ts @@ -1,40 +1,44 @@ +import { JobStatus } from '@/shared/constants'; import EventEmitter from 'node:events'; +import z, { ZodTypeAny } from 'zod'; export class TaskQueue { - private activeQueue: { context: JobContext, promise?: Promise; }[] = []; - private queue?: { context: JobContext, promise?: Promise; }[] = []; + private activeQueue: { context: JobContext, promise?: Promise; }[] = []; + private queue?: { context: JobContext, promise?: Promise; }[] = []; private events?: EventEmitter = new EventEmitter(); - public enqueue (id: string, job: IJob): Promise + public enqueue> (id: string, job: T) { this.disposeSafeguard(); if (!this.queue || !this.events) throw new Error("Queue disposed"); const context = new JobContext(id, this.events, job); this.queue.push({ context }); + this.events?.emit('queued', { id: context.id, job: context }); return this.processQueue(); } - private processQueue (): Promise + private processQueue () { if (!this.queue) return Promise.resolve(); - const top = this.queue.pop(); - if (top) + + const next = this.queue.filter(j => !j.context.job.group || !this.activeQueue.some(a => a.context.job.group === j.context.job.group)).map((job, i) => ({ i, job })); + + next.reverse().forEach(({ i }) => this.queue!.splice(i, 1)); + + next.forEach(job => { - const promise = top.context.start(); - top.promise = promise; - const index = this.queue.length; - this.activeQueue.push(top); + const promise = job.job.context.start(); + job.job.promise = promise; + this.activeQueue.push(job.job); promise.finally(() => { + const index = this.activeQueue.indexOf(job.job); this.activeQueue.splice(index, 1); - setTimeout(this.processQueue); + setTimeout(() => this.processQueue(), 0); }); - return promise; - - } - return Promise.resolve(); + }); } private disposeSafeguard () @@ -65,10 +69,15 @@ export class TaskQueue return job?.promise ?? Promise.resolve(); } - public findJob (id: string): IPublicJob | undefined + + public findJob> (id: string, type: new (...args: any[]) => T): IPublicJob | undefined { const job = this.queue?.find(j => j.context.id === id) ?? this.activeQueue?.find(j => j.context.id === id); - return job?.context; + if (job?.context.job instanceof type) + { + return job?.context; + } + return undefined; } public on (event: E, listener: E extends keyof EventsList ? EventsList[E] extends unknown[] ? (...args: EventsList[E]) => void : never : never): () => void @@ -99,12 +108,13 @@ export interface EventsList completed: [e: CompletedEvent]; error: [e: ErrorEvent]; ended: [e: BaseEvent]; + queued: [e: BaseEvent]; } interface BaseEvent { id: string; - job: IPublicJob; + job: IPublicJob; } interface ErrorEvent extends BaseEvent @@ -128,37 +138,50 @@ interface CompletedEvent extends BaseEvent } -export interface IJob +export interface IJob { - start (context: JobContext): Promise; - exposeData?(): any; + group?: string; + start (context: JobContext, TData, TState>): Promise; + exposeData?(): TData; } -export type JobStatus = 'completed' | 'error' | 'running' | 'waiting' | 'aborted'; - -export interface IPublicJob +export interface IPublicJob> { progress: number; state?: string; status: JobStatus; - job: IJob; + job: T; abort: (reason?: any) => void; } -export class JobContext implements IPublicJob +type JobClass = new (...args: any[]) => IJob; +type JobClassWithStatics = JobClass & { + id: string; + dataSchema?: any; +}; +export type JobContextFromClass = + JobContext< + InstanceType, + C extends { dataSchema: ZodTypeAny; } + ? z.infer + : never, + C['id'] + >; + +export class JobContext, TData, TState extends string> implements IPublicJob { private m_id: string; private m_progress: number = 0; - private m_state?: string; + private m_state?: TState; private running: boolean = false; private aborted: boolean = false; private completed: boolean = false; private error?: any; private events: EventEmitter; private abortController: AbortController; - private readonly m_job: IJob; + private readonly m_job: T; - constructor(id: string, events: EventEmitter, job: IJob) + constructor(id: string, events: EventEmitter, job: T) { this.m_id = id; this.m_job = job; @@ -202,7 +225,7 @@ export class JobContext implements IPublicJob if (this.error) return 'error'; if (this.aborted) return 'aborted'; if (this.running) return 'running'; - return 'waiting'; + return 'queued'; } public get id () { return this.m_id; } @@ -215,7 +238,11 @@ export class JobContext implements IPublicJob public get state () { return this.m_state; } - public setProgress (progress: number, state?: string) + /** + * @param progress The 0 to 100 progress + * @param state what type of progress is this. Is it really progress. I humanity even advancing. + */ + public setProgress (progress: number, state?: TState) { this.m_progress = progress; if (state) diff --git a/src/bun/index.ts b/src/bun/index.ts index 9ea71be..96b6f76 100644 --- a/src/bun/index.ts +++ b/src/bun/index.ts @@ -8,9 +8,9 @@ import { createInterface } from 'readline'; const api = RunAPIServer(); let bunServer: { stop: () => void; } | undefined; -if (!Bun.env.PUBLIC_ACCESS) +if (!process.env.PUBLIC_ACCESS) { - bunServer = RunBunServer(); + bunServer = await RunBunServer(); } async function cleanup () @@ -24,7 +24,7 @@ async function cleanup () process.exit(0); } -if (Bun.env.HEADLESS) +if (process.env.HEADLESS) { const rl = createInterface({ input: process.stdin }); diff --git a/src/bun/server.ts b/src/bun/server.ts index 86e5fac..d9ae6b0 100644 --- a/src/bun/server.ts +++ b/src/bun/server.ts @@ -8,7 +8,7 @@ import staticPlugin from "@elysiajs/static"; export function RunBunServer () { console.log("Launching Server on port ", SERVER_PORT); - return new Elysia() + const server = new Elysia() .use(cors()) .headers({ 'cross-origin-embedder-policy': 'credentialless', @@ -28,33 +28,11 @@ export function RunBunServer () assets: appPath("./dist"), prefix: "/", alwaysStatic: true - })).listen({ port: SERVER_PORT, hostname: host, development: true }, console.log); - /*return Bun.serve({ - port: SERVER_PORT, - hostname: host, - routes: { - "/": Bun.file(appPath("./dist/index.html")), - // Serve a file by lazily loading it into memory - "/favicon.ico": Bun.file(appPath("./dist/favicon.ico")), - "/emulatorjs/": Bun.file(appPath("./dist/emulatorjs/index.html")), - "/.well-known/appspecific/com.chrome.devtools.json": new Response( - JSON.stringify({ - name: appInfo.name, - version: appInfo.version, - debuggable: true, - }), - { - headers: { - "Content-Type": "application/json", - "Cache-Control": "no-cache", - }, - } - ) - }, - fetch: async (req) => - { - const url = new URL(req.url); - return new Response(Bun.file(appPath(`./${path.join('dist', url.pathname)}`))); - }, - });*/ + })); + + return new Promise((resolve) => + { + server.onStart(() => resolve(server)) + .listen({ port: SERVER_PORT, hostname: host, development: true }, console.log); + }); } \ No newline at end of file diff --git a/src/bun/types/types.d.ts b/src/bun/types/types.d.ts index dd95180..4ba73c2 100644 --- a/src/bun/types/types.d.ts +++ b/src/bun/types/types.d.ts @@ -6,7 +6,7 @@ export type ActiveGame = { process?: ChildProcess; gameId: number; name: string; - command: string; + command: { command: string, startDir?: string; }; }; interface ObjectConstructor diff --git a/src/bun/utils.ts b/src/bun/utils.ts index 33a58cf..487719a 100644 --- a/src/bun/utils.ts +++ b/src/bun/utils.ts @@ -1,5 +1,7 @@ import { $ } from 'bun'; import path from 'node:path'; +import { createHash } from "node:crypto"; +import { createReadStream } from "node:fs"; export function checkRunning (pid: number) { @@ -68,4 +70,44 @@ export async function openExternal (target: string) { return $`open ${target}`.throws(true); } -} \ No newline at end of file +} + +export function hashFile (path: string, algorithm: "sha1" | "md5"): Promise +{ + return new Promise((resolve, reject) => + { + const hash = createHash(algorithm); + const stream = createReadStream(path); + + stream.on("data", (data) => hash.update(data)); + stream.on("end", () => resolve(hash.digest("hex"))); + stream.on("error", reject); + }); +} + +export class SeededRandom +{ + seed: number; + + constructor(seed?: number) + { + this.seed = seed ?? new Date().getTime(); + } + + next () + { + var x = Math.sin(this.seed++) * 10000; + return x - Math.floor(x); + } +} + +export function shuffleInPlace (array: any[], startSeed?: number) +{ + const random = new SeededRandom(startSeed); + + for (let i = array.length - 1; i > 0; i--) + { + const j = Math.floor(random.next() * (i + 1)); + [array[i], array[j]] = [array[j], array[i]]; + } +} diff --git a/src/bun/utils/downloader.ts b/src/bun/utils/downloader.ts new file mode 100644 index 0000000..92a4893 --- /dev/null +++ b/src/bun/utils/downloader.ts @@ -0,0 +1,222 @@ +import { ensureDir, move } from "fs-extra"; +import path from 'node:path'; +import fs from 'node:fs/promises'; + +import { createWriteStream } from "node:fs"; +import { config, jar } from "../api/app"; +import { file } from "bun"; + +export interface FileEntry +{ + url: URL; + file_path: string; + file_name: string; + size?: number; +} + +export interface ProgressStats +{ + progress: number; +} + +interface TmpDownloadMetadata +{ + files: FileEntry[]; +} + +export class Downloader +{ + files: FileEntry[]; + headers?: Record; + onProgress?: (stats: ProgressStats) => void; + signal?: AbortSignal; + activeFile?: FileEntry; + downloadPath: string; + id: string; + tmpPath: string; + tmpPathMeta: string; + + constructor( + id: string, + files: FileEntry[], + downloadPath: string, init?: { + headers?: Record, + onProgress?: (stats: ProgressStats) => void; + signal?: AbortSignal; + }) + { + this.files = files; + this.headers = init?.headers; + this.onProgress = init?.onProgress; + this.signal = init?.signal; + this.downloadPath = downloadPath; + this.id = id; + this.tmpPath = path.join(config.get('downloadPath'), 'downloads', this.id); + this.tmpPathMeta = path.join(config.get('downloadPath'), 'downloads', `${this.id}.json`); + } + + async updateTmpDownload () + { + const meta: TmpDownloadMetadata = { + files: this.files + }; + + await ensureDir(path.join(config.get('downloadPath'), 'downloads')); + await fs.writeFile(this.tmpPathMeta, JSON.stringify(meta)); + } + + async start () + { + const totalSize = this.files.reduce((accum, current) => accum += current.size ?? 0, 0); + let bytesReceived = 0; + + if (this.files.some(f => path.isAbsolute(f.file_path))) + { + throw new Error("Only Relative Paths Supported"); + } + + await this.updateTmpDownload(); + + for (let i = 0; i < this.files.length; i++) + { + const file = this.files[i]; + this.activeFile = file; + const cookie = await jar.getCookieString(file.url.href); + + await ensureDir(path.join(this.tmpPath, file.file_path)); + + const filePath = path.join(this.tmpPath, file.file_path, file.file_name); + let start = 0; + + // 1. Check existing file + if (await fs.exists(filePath)) + { + start = ((await fs.stat(filePath)).size); + } + + // 2. Request remaining bytes + let res = await fetch(file.url, { + headers: { + ...this.headers, + ...(start > 0 + ? { Range: `bytes=${start}-` } + : undefined), + cookie + } + }); + + const resSize = Number(res.headers.get("content-length") ?? 0); + + if (start > 0) + { + if (res.status === 206) + { + console.log("Resume supported, continuing download"); + } else if (res.status === 200) + { + console.log("Server ignored Range, restarting download from beginning"); + start = 0; + + // Must make a new request from the beginning + res = await fetch(file.url, { headers: { ...this.headers, cookie } }); + + if (!res.ok) + { + throw new Error(`HTTP error: ${res.status} ${res.statusText}`); + } + } else if (res.status === 416) + { + const localSize = (await fs.stat(filePath)).size; + if (resSize && localSize === resSize) + { + console.log("File already fully downloaded, skipping"); + break; + } else + { + console.log("Partial file corrupt or changed, redownloading"); + start = 0; + res = await fetch(file.url, { headers: { ...this.headers, cookie } }); // full download + + if (!res.ok) + { + throw new Error(`HTTP error: ${res.status} ${res.statusText}`); + } + } + } + else + { + throw new Error(`HTTP error: ${res.status} ${res.statusText}`); + } + } else + { + if (!res.ok) throw new Error(`HTTP error: ${res.status} ${res.statusText}`); + } + + // 3. Append or overwrite + const stream = createWriteStream(filePath, { + flags: start > 0 ? "a" : "w", + highWaterMark: 64 * 1024 + }); + + const totalBytes = totalSize || Number(res.headers.get("content-length")) || 0; + if (totalSize <= 0) + bytesReceived = 0; + else + bytesReceived += start; + + const reader = res.body!.getReader(); + + let lastUpdate = 0; + + while (true) + { + const { done, value } = await reader.read(); + if (done) break; + + bytesReceived += value.length; + if (totalBytes > 0 && this.onProgress) + { + const percent = (bytesReceived / totalBytes) * 100; + + if (Date.now() - lastUpdate > 100) + { + this.onProgress({ progress: percent }); + lastUpdate = Date.now(); + } + } + + if (this.signal?.aborted) + { + if (this.signal.reason === 'cancel') + { + console.log("Canceling Download and cleaning up files"); + await fs.rm(this.tmpPath, { recursive: true }); + await fs.rm(this.tmpPathMeta); + return; + } + + console.log("Aborting Download: ", this.signal.reason); + break; + } + + if (!stream.write(value)) + { + await new Promise((resolve) => stream.once("drain", () => resolve(true))); + } + } + + await new Promise((resolve, reject) => + { + stream.end(() => resolve(undefined)); + stream.on("error", reject); + }); + } + + await move(this.tmpPath, this.downloadPath, { overwrite: true }); + if (await fs.exists(this.tmpPath)) + await fs.rm(this.tmpPath, { recursive: true }); + await fs.rm(this.tmpPathMeta); + + return this.files.map(f => path.join(this.downloadPath, f.file_path, f.file_name)); + } +} \ No newline at end of file diff --git a/src/clients/romm/@tanstack/react-query.gen.ts b/src/clients/romm/@tanstack/react-query.gen.ts index ae5aaab..3fa5c1e 100644 --- a/src/clients/romm/@tanstack/react-query.gen.ts +++ b/src/clients/romm/@tanstack/react-query.gen.ts @@ -3,8 +3,8 @@ import { type DefaultError, type InfiniteData, infiniteQueryOptions, queryOptions, type UseMutationOptions } from '@tanstack/react-query'; import { client } from '../client.gen'; -import { addCollectionApiCollectionsPost, addExclusionApiConfigExcludePost, addFirmwareApiFirmwarePost, addPlatformApiPlatformsPost, addPlatformBindingApiConfigSystemPlatformsPost, addPlatformVersionApiConfigSystemVersionsPost, addRomApiRomsPost, addRomManualsApiRomsIdManualsPost, addSaveApiSavesPost, addScreenshotApiScreenshotsPost, addSmartCollectionApiCollectionsSmartPost, addStateApiStatesPost, addUserApiUsersPost, authOpenidApiOauthOpenidGet, createInviteLinkApiUsersInviteLinkPost, createRomNoteApiRomsIdNotesPost, createSetupPlatformsApiSetupPlatformsPost, createUserFromInviteApiUsersRegisterPost, deleteCollectionApiCollectionsIdDelete, deleteExclusionApiConfigExcludeExclusionTypeExclusionValueDelete, deleteFirmwareApiFirmwareDeletePost, deletePlatformApiPlatformsIdDelete, deletePlatformBindingApiConfigSystemPlatformsFsSlugDelete, deletePlatformVersionApiConfigSystemVersionsFsSlugDelete, deleteRomManualsApiRomsIdManualsDelete, deleteRomNoteApiRomsIdNotesNoteIdDelete, deleteRomsApiRomsDeletePost, deleteSavesApiSavesDeletePost, deleteSmartCollectionApiCollectionsSmartIdDelete, deleteStatesApiStatesDeletePost, deleteUserApiUsersIdDelete, downloadRomsApiRomsDownloadGet, exportGamelistApiGamelistExportPost, fpkgiFeedApiFeedsFpkgiPlatformSlugGet, getCollectionApiCollectionsIdGet, getCollectionsApiCollectionsGet, getConfigApiConfigGet, getCurrentUserApiUsersMeGet, getFirmwareApiFirmwareIdGet, getFirmwareContentApiFirmwareIdContentFileNameGet, getPlatformApiPlatformsIdGet, getPlatformFirmwareApiFirmwareGet, getPlatformsApiPlatformsGet, getRawAssetApiRawAssetsPathGet, getRomApiRomsIdGet, getRomByHashApiRomsByHashGet, getRomByMetadataProviderApiRomsByMetadataProviderGet, getRomContentApiRomsIdContentFileNameGet, getRomfileApiRomsFilesIdGet, getRomfileContentApiRomsfilesIdContentFileNameGet, getRomFiltersApiRomsFiltersGet, getRomNotesApiRomsIdNotesGet, getRomsApiRomsGet, getRoomsApiNetplayListGet, getSaveApiSavesIdGet, getSavesApiSavesGet, getSetupLibraryInfoApiSetupLibraryGet, getSmartCollectionApiCollectionsSmartIdGet, getSmartCollectionsApiCollectionsSmartGet, getStateApiStatesIdGet, getStatesApiStatesGet, getSupportedPlatformsEndpointApiPlatformsSupportedGet, getTaskByIdApiTasksTaskIdGet, getTasksStatusApiTasksStatusGet, getUserApiUsersIdGet, getUsersApiUsersGet, getVirtualCollectionApiCollectionsVirtualIdGet, getVirtualCollectionsApiCollectionsVirtualGet, heartbeatApiHeartbeatGet, kekatsuDsFeedApiFeedsKekatsuPlatformSlugGet, listTasksApiTasksGet, loginApiLoginPost, loginViaOpenidApiLoginOpenidGet, logoutApiLogoutPost, metadataHeartbeatApiHeartbeatMetadataSourceGet, type Options, pkgiPs3FeedApiFeedsPkgiPs3ContentTypeGet, pkgiPspFeedApiFeedsPkgiPspContentTypeGet, pkgiPsvitaFeedApiFeedsPkgiPsvitaContentTypeGet, platformsWebrcadeFeedApiFeedsWebrcadeGet, refreshRetroAchievementsApiUsersIdRaRefreshPost, requestPasswordResetApiForgotPasswordPost, resetPasswordApiResetPasswordPost, runAllTasksApiTasksRunPost, runSingleTaskApiTasksRunTaskNamePost, searchCoverApiSearchCoverGet, searchRomApiSearchRomsGet, statsApiStatsGet, tinfoilIndexFeedApiFeedsTinfoilGet, tokenApiTokenPost, updateCollectionApiCollectionsIdPut, updatePlatformApiPlatformsIdPut, updateRomApiRomsIdPut, updateRomNoteApiRomsIdNotesNoteIdPut, updateRomUserApiRomsIdPropsPut, updateSaveApiSavesIdPut, updateSmartCollectionApiCollectionsSmartIdPut, updateStateApiStatesIdPut, updateUserApiUsersIdPut } from '../sdk.gen'; -import type { AddCollectionApiCollectionsPostData, AddCollectionApiCollectionsPostError, AddCollectionApiCollectionsPostResponse, AddExclusionApiConfigExcludePostData, AddFirmwareApiFirmwarePostData, AddFirmwareApiFirmwarePostError, AddFirmwareApiFirmwarePostResponse, AddPlatformApiPlatformsPostData, AddPlatformApiPlatformsPostError, AddPlatformApiPlatformsPostResponse, AddPlatformBindingApiConfigSystemPlatformsPostData, AddPlatformVersionApiConfigSystemVersionsPostData, AddRomApiRomsPostData, AddRomApiRomsPostError, AddRomManualsApiRomsIdManualsPostData, AddRomManualsApiRomsIdManualsPostError, AddSaveApiSavesPostData, AddSaveApiSavesPostError, AddSaveApiSavesPostResponse, AddScreenshotApiScreenshotsPostData, AddScreenshotApiScreenshotsPostError, AddScreenshotApiScreenshotsPostResponse, AddSmartCollectionApiCollectionsSmartPostData, AddSmartCollectionApiCollectionsSmartPostError, AddSmartCollectionApiCollectionsSmartPostResponse, AddStateApiStatesPostData, AddStateApiStatesPostError, AddStateApiStatesPostResponse, AddUserApiUsersPostData, AddUserApiUsersPostError, AddUserApiUsersPostResponse, AuthOpenidApiOauthOpenidGetData, CreateInviteLinkApiUsersInviteLinkPostData, CreateInviteLinkApiUsersInviteLinkPostError, CreateInviteLinkApiUsersInviteLinkPostResponse, CreateRomNoteApiRomsIdNotesPostData, CreateRomNoteApiRomsIdNotesPostError, CreateRomNoteApiRomsIdNotesPostResponse, CreateSetupPlatformsApiSetupPlatformsPostData, CreateSetupPlatformsApiSetupPlatformsPostError, CreateUserFromInviteApiUsersRegisterPostData, CreateUserFromInviteApiUsersRegisterPostError, CreateUserFromInviteApiUsersRegisterPostResponse, DeleteCollectionApiCollectionsIdDeleteData, DeleteCollectionApiCollectionsIdDeleteError, DeleteExclusionApiConfigExcludeExclusionTypeExclusionValueDeleteData, DeleteExclusionApiConfigExcludeExclusionTypeExclusionValueDeleteError, DeleteFirmwareApiFirmwareDeletePostData, DeleteFirmwareApiFirmwareDeletePostError, DeleteFirmwareApiFirmwareDeletePostResponse, DeletePlatformApiPlatformsIdDeleteData, DeletePlatformApiPlatformsIdDeleteError, DeletePlatformBindingApiConfigSystemPlatformsFsSlugDeleteData, DeletePlatformBindingApiConfigSystemPlatformsFsSlugDeleteError, DeletePlatformVersionApiConfigSystemVersionsFsSlugDeleteData, DeletePlatformVersionApiConfigSystemVersionsFsSlugDeleteError, DeleteRomManualsApiRomsIdManualsDeleteData, DeleteRomManualsApiRomsIdManualsDeleteError, DeleteRomNoteApiRomsIdNotesNoteIdDeleteData, DeleteRomNoteApiRomsIdNotesNoteIdDeleteError, DeleteRomNoteApiRomsIdNotesNoteIdDeleteResponse, DeleteRomsApiRomsDeletePostData, DeleteRomsApiRomsDeletePostError, DeleteRomsApiRomsDeletePostResponse, DeleteSavesApiSavesDeletePostData, DeleteSavesApiSavesDeletePostError, DeleteSavesApiSavesDeletePostResponse, DeleteSmartCollectionApiCollectionsSmartIdDeleteData, DeleteSmartCollectionApiCollectionsSmartIdDeleteError, DeleteStatesApiStatesDeletePostData, DeleteStatesApiStatesDeletePostError, DeleteStatesApiStatesDeletePostResponse, DeleteUserApiUsersIdDeleteData, DeleteUserApiUsersIdDeleteError, DownloadRomsApiRomsDownloadGetData, DownloadRomsApiRomsDownloadGetError, ExportGamelistApiGamelistExportPostData, ExportGamelistApiGamelistExportPostError, FpkgiFeedApiFeedsFpkgiPlatformSlugGetData, FpkgiFeedApiFeedsFpkgiPlatformSlugGetError, GetCollectionApiCollectionsIdGetData, GetCollectionApiCollectionsIdGetError, GetCollectionApiCollectionsIdGetResponse, GetCollectionsApiCollectionsGetData, GetCollectionsApiCollectionsGetError, GetCollectionsApiCollectionsGetResponse, GetConfigApiConfigGetData, GetConfigApiConfigGetResponse, GetCurrentUserApiUsersMeGetData, GetCurrentUserApiUsersMeGetResponse, GetFirmwareApiFirmwareIdGetData, GetFirmwareApiFirmwareIdGetError, GetFirmwareApiFirmwareIdGetResponse, GetFirmwareContentApiFirmwareIdContentFileNameGetData, GetFirmwareContentApiFirmwareIdContentFileNameGetError, GetPlatformApiPlatformsIdGetData, GetPlatformApiPlatformsIdGetError, GetPlatformApiPlatformsIdGetResponse, GetPlatformFirmwareApiFirmwareGetData, GetPlatformFirmwareApiFirmwareGetError, GetPlatformFirmwareApiFirmwareGetResponse, GetPlatformsApiPlatformsGetData, GetPlatformsApiPlatformsGetError, GetPlatformsApiPlatformsGetResponse, GetRawAssetApiRawAssetsPathGetData, GetRawAssetApiRawAssetsPathGetError, GetRomApiRomsIdGetData, GetRomApiRomsIdGetError, GetRomApiRomsIdGetResponse, GetRomByHashApiRomsByHashGetData, GetRomByHashApiRomsByHashGetError, GetRomByHashApiRomsByHashGetResponse, GetRomByMetadataProviderApiRomsByMetadataProviderGetData, GetRomByMetadataProviderApiRomsByMetadataProviderGetError, GetRomByMetadataProviderApiRomsByMetadataProviderGetResponse, GetRomContentApiRomsIdContentFileNameGetData, GetRomContentApiRomsIdContentFileNameGetError, GetRomfileApiRomsFilesIdGetData, GetRomfileApiRomsFilesIdGetError, GetRomfileApiRomsFilesIdGetResponse, GetRomfileContentApiRomsfilesIdContentFileNameGetData, GetRomfileContentApiRomsfilesIdContentFileNameGetError, GetRomFiltersApiRomsFiltersGetData, GetRomFiltersApiRomsFiltersGetResponse, GetRomNotesApiRomsIdNotesGetData, GetRomNotesApiRomsIdNotesGetError, GetRomNotesApiRomsIdNotesGetResponse, GetRomsApiRomsGetData, GetRomsApiRomsGetError, GetRomsApiRomsGetResponse, GetRoomsApiNetplayListGetData, GetRoomsApiNetplayListGetError, GetRoomsApiNetplayListGetResponse, GetSaveApiSavesIdGetData, GetSaveApiSavesIdGetError, GetSaveApiSavesIdGetResponse, GetSavesApiSavesGetData, GetSavesApiSavesGetError, GetSavesApiSavesGetResponse, GetSetupLibraryInfoApiSetupLibraryGetData, GetSmartCollectionApiCollectionsSmartIdGetData, GetSmartCollectionApiCollectionsSmartIdGetError, GetSmartCollectionApiCollectionsSmartIdGetResponse, GetSmartCollectionsApiCollectionsSmartGetData, GetSmartCollectionsApiCollectionsSmartGetError, GetSmartCollectionsApiCollectionsSmartGetResponse, GetStateApiStatesIdGetData, GetStateApiStatesIdGetError, GetStateApiStatesIdGetResponse, GetStatesApiStatesGetData, GetStatesApiStatesGetError, GetStatesApiStatesGetResponse, GetSupportedPlatformsEndpointApiPlatformsSupportedGetData, GetSupportedPlatformsEndpointApiPlatformsSupportedGetResponse, GetTaskByIdApiTasksTaskIdGetData, GetTaskByIdApiTasksTaskIdGetError, GetTaskByIdApiTasksTaskIdGetResponse, GetTasksStatusApiTasksStatusGetData, GetTasksStatusApiTasksStatusGetResponse, GetUserApiUsersIdGetData, GetUserApiUsersIdGetError, GetUserApiUsersIdGetResponse, GetUsersApiUsersGetData, GetUsersApiUsersGetResponse, GetVirtualCollectionApiCollectionsVirtualIdGetData, GetVirtualCollectionApiCollectionsVirtualIdGetError, GetVirtualCollectionApiCollectionsVirtualIdGetResponse, GetVirtualCollectionsApiCollectionsVirtualGetData, GetVirtualCollectionsApiCollectionsVirtualGetError, GetVirtualCollectionsApiCollectionsVirtualGetResponse, HeartbeatApiHeartbeatGetData, HeartbeatApiHeartbeatGetResponse, KekatsuDsFeedApiFeedsKekatsuPlatformSlugGetData, KekatsuDsFeedApiFeedsKekatsuPlatformSlugGetError, ListTasksApiTasksGetData, ListTasksApiTasksGetResponse, LoginApiLoginPostData, LoginViaOpenidApiLoginOpenidGetData, LogoutApiLogoutPostData, MetadataHeartbeatApiHeartbeatMetadataSourceGetData, MetadataHeartbeatApiHeartbeatMetadataSourceGetError, MetadataHeartbeatApiHeartbeatMetadataSourceGetResponse, PkgiPs3FeedApiFeedsPkgiPs3ContentTypeGetData, PkgiPs3FeedApiFeedsPkgiPs3ContentTypeGetError, PkgiPspFeedApiFeedsPkgiPspContentTypeGetData, PkgiPspFeedApiFeedsPkgiPspContentTypeGetError, PkgiPsvitaFeedApiFeedsPkgiPsvitaContentTypeGetData, PkgiPsvitaFeedApiFeedsPkgiPsvitaContentTypeGetError, PlatformsWebrcadeFeedApiFeedsWebrcadeGetData, PlatformsWebrcadeFeedApiFeedsWebrcadeGetResponse, RefreshRetroAchievementsApiUsersIdRaRefreshPostData, RefreshRetroAchievementsApiUsersIdRaRefreshPostError, RequestPasswordResetApiForgotPasswordPostData, RequestPasswordResetApiForgotPasswordPostError, ResetPasswordApiResetPasswordPostData, ResetPasswordApiResetPasswordPostError, RunAllTasksApiTasksRunPostData, RunAllTasksApiTasksRunPostResponse, RunSingleTaskApiTasksRunTaskNamePostData, RunSingleTaskApiTasksRunTaskNamePostError, RunSingleTaskApiTasksRunTaskNamePostResponse, SearchCoverApiSearchCoverGetData, SearchCoverApiSearchCoverGetError, SearchCoverApiSearchCoverGetResponse, SearchRomApiSearchRomsGetData, SearchRomApiSearchRomsGetError, SearchRomApiSearchRomsGetResponse, StatsApiStatsGetData, StatsApiStatsGetResponse, TinfoilIndexFeedApiFeedsTinfoilGetData, TinfoilIndexFeedApiFeedsTinfoilGetError, TinfoilIndexFeedApiFeedsTinfoilGetResponse, TokenApiTokenPostData, TokenApiTokenPostError, TokenApiTokenPostResponse, UpdateCollectionApiCollectionsIdPutData, UpdateCollectionApiCollectionsIdPutError, UpdateCollectionApiCollectionsIdPutResponse, UpdatePlatformApiPlatformsIdPutData, UpdatePlatformApiPlatformsIdPutError, UpdatePlatformApiPlatformsIdPutResponse, UpdateRomApiRomsIdPutData, UpdateRomApiRomsIdPutError, UpdateRomApiRomsIdPutResponse, UpdateRomNoteApiRomsIdNotesNoteIdPutData, UpdateRomNoteApiRomsIdNotesNoteIdPutError, UpdateRomNoteApiRomsIdNotesNoteIdPutResponse, UpdateRomUserApiRomsIdPropsPutData, UpdateRomUserApiRomsIdPropsPutError, UpdateRomUserApiRomsIdPropsPutResponse, UpdateSaveApiSavesIdPutData, UpdateSaveApiSavesIdPutError, UpdateSaveApiSavesIdPutResponse, UpdateSmartCollectionApiCollectionsSmartIdPutData, UpdateSmartCollectionApiCollectionsSmartIdPutError, UpdateSmartCollectionApiCollectionsSmartIdPutResponse, UpdateStateApiStatesIdPutData, UpdateStateApiStatesIdPutError, UpdateStateApiStatesIdPutResponse, UpdateUserApiUsersIdPutData, UpdateUserApiUsersIdPutError, UpdateUserApiUsersIdPutResponse } from '../types.gen'; +import { addCollectionApiCollectionsPost, addExclusionApiConfigExcludePost, addFirmwareApiFirmwarePost, addPlatformApiPlatformsPost, addPlatformBindingApiConfigSystemPlatformsPost, addPlatformVersionApiConfigSystemVersionsPost, addRomApiRomsPost, addRomManualsApiRomsIdManualsPost, addSaveApiSavesPost, addScreenshotApiScreenshotsPost, addSmartCollectionApiCollectionsSmartPost, addStateApiStatesPost, addUserApiUsersPost, authOpenidApiOauthOpenidGet, confirmDownloadApiSavesIdDownloadedPost, createInviteLinkApiUsersInviteLinkPost, createRomNoteApiRomsIdNotesPost, createSetupPlatformsApiSetupPlatformsPost, createUserFromInviteApiUsersRegisterPost, deleteCollectionApiCollectionsIdDelete, deleteDeviceApiDevicesDeviceIdDelete, deleteExclusionApiConfigExcludeExclusionTypeExclusionValueDelete, deleteFirmwareApiFirmwareDeletePost, deletePlatformApiPlatformsIdDelete, deletePlatformBindingApiConfigSystemPlatformsFsSlugDelete, deletePlatformVersionApiConfigSystemVersionsFsSlugDelete, deleteRomManualsApiRomsIdManualsDelete, deleteRomNoteApiRomsIdNotesNoteIdDelete, deleteRomsApiRomsDeletePost, deleteSavesApiSavesDeletePost, deleteSmartCollectionApiCollectionsSmartIdDelete, deleteStatesApiStatesDeletePost, deleteUserApiUsersIdDelete, downloadRomsApiRomsDownloadGet, downloadSaveApiSavesIdContentGet, exportGamelistApiGamelistExportPost, fpkgiFeedApiFeedsFpkgiPlatformSlugGet, getCollectionApiCollectionsIdGet, getCollectionIdentifiersApiCollectionsIdentifiersGet, getCollectionsApiCollectionsGet, getConfigApiConfigGet, getCurrentUserApiUsersMeGet, getDeviceApiDevicesDeviceIdGet, getDevicesApiDevicesGet, getFirmwareApiFirmwareIdGet, getFirmwareContentApiFirmwareIdContentFileNameGet, getFirmwareIdentifiersApiFirmwareIdentifiersGet, getPlatformApiPlatformsIdGet, getPlatformFirmwareApiFirmwareGet, getPlatformIdentifiersApiPlatformsIdentifiersGet, getPlatformsApiPlatformsGet, getRawAssetApiRawAssetsPathGet, getRomApiRomsIdGet, getRomByHashApiRomsByHashGet, getRomByMetadataProviderApiRomsByMetadataProviderGet, getRomContentApiRomsIdContentFileNameGet, getRomfileApiRomsFilesIdGet, getRomfileContentApiRomsfilesIdContentFileNameGet, getRomFiltersApiRomsFiltersGet, getRomIdentifiersApiRomsIdentifiersGet, getRomNoteIdentifiersApiRomsIdNotesIdentifiersGet, getRomNotesApiRomsIdNotesGet, getRomsApiRomsGet, getRoomsApiNetplayListGet, getSaveApiSavesIdGet, getSaveIdentifiersApiSavesIdentifiersGet, getSavesApiSavesGet, getSavesSummaryApiSavesSummaryGet, getSetupLibraryInfoApiSetupLibraryGet, getSmartCollectionApiCollectionsSmartIdGet, getSmartCollectionIdentifiersApiCollectionsSmartIdentifiersGet, getSmartCollectionsApiCollectionsSmartGet, getStateApiStatesIdGet, getStateIdentifiersApiStatesIdentifiersGet, getStatesApiStatesGet, getSupportedPlatformsEndpointApiPlatformsSupportedGet, getTaskByIdApiTasksTaskIdGet, getTasksStatusApiTasksStatusGet, getUserApiUsersIdGet, getUserIdentifiersApiUsersIdentifiersGet, getUsersApiUsersGet, getVirtualCollectionApiCollectionsVirtualIdGet, getVirtualCollectionIdentifiersApiCollectionsVirtualIdentifiersGet, getVirtualCollectionsApiCollectionsVirtualGet, heartbeatApiHeartbeatGet, kekatsuDsFeedApiFeedsKekatsuPlatformSlugGet, listTasksApiTasksGet, loginApiLoginPost, loginViaOpenidApiLoginOpenidGet, logoutApiLogoutPost, metadataHeartbeatApiHeartbeatMetadataSourceGet, type Options, pkgiPs3FeedApiFeedsPkgiPs3ContentTypeGet, pkgiPspFeedApiFeedsPkgiPspContentTypeGet, pkgiPsvitaFeedApiFeedsPkgiPsvitaContentTypeGet, pkgjPspDlcsFeedApiFeedsPkgjPspDlcGet, pkgjPspGamesFeedApiFeedsPkgjPspGamesGet, pkgjPsvDlcsFeedApiFeedsPkgjPsvitaDlcGet, pkgjPsvGamesFeedApiFeedsPkgjPsvitaGamesGet, pkgjPsxGamesFeedApiFeedsPkgjPsxGamesGet, platformsWebrcadeFeedApiFeedsWebrcadeGet, refreshRetroAchievementsApiUsersIdRaRefreshPost, registerDeviceApiDevicesPost, requestPasswordResetApiForgotPasswordPost, resetPasswordApiResetPasswordPost, runAllTasksApiTasksRunPost, runSingleTaskApiTasksRunTaskNamePost, searchCoverApiSearchCoverGet, searchRomApiSearchRomsGet, statsApiStatsGet, tinfoilIndexFeedApiFeedsTinfoilGet, tokenApiTokenPost, trackSaveApiSavesIdTrackPost, untrackSaveApiSavesIdUntrackPost, updateCollectionApiCollectionsIdPut, updateDeviceApiDevicesDeviceIdPut, updatePlatformApiPlatformsIdPut, updateRomApiRomsIdPut, updateRomNoteApiRomsIdNotesNoteIdPut, updateRomUserApiRomsIdPropsPut, updateSaveApiSavesIdPut, updateSmartCollectionApiCollectionsSmartIdPut, updateStateApiStatesIdPut, updateUserApiUsersIdPut } from '../sdk.gen'; +import type { AddCollectionApiCollectionsPostData, AddCollectionApiCollectionsPostError, AddCollectionApiCollectionsPostResponse, AddExclusionApiConfigExcludePostData, AddFirmwareApiFirmwarePostData, AddFirmwareApiFirmwarePostError, AddFirmwareApiFirmwarePostResponse, AddPlatformApiPlatformsPostData, AddPlatformApiPlatformsPostError, AddPlatformApiPlatformsPostResponse, AddPlatformBindingApiConfigSystemPlatformsPostData, AddPlatformVersionApiConfigSystemVersionsPostData, AddRomApiRomsPostData, AddRomApiRomsPostError, AddRomManualsApiRomsIdManualsPostData, AddRomManualsApiRomsIdManualsPostError, AddSaveApiSavesPostData, AddSaveApiSavesPostError, AddSaveApiSavesPostResponse, AddScreenshotApiScreenshotsPostData, AddScreenshotApiScreenshotsPostError, AddScreenshotApiScreenshotsPostResponse, AddSmartCollectionApiCollectionsSmartPostData, AddSmartCollectionApiCollectionsSmartPostError, AddSmartCollectionApiCollectionsSmartPostResponse, AddStateApiStatesPostData, AddStateApiStatesPostError, AddStateApiStatesPostResponse, AddUserApiUsersPostData, AddUserApiUsersPostError, AddUserApiUsersPostResponse, AuthOpenidApiOauthOpenidGetData, ConfirmDownloadApiSavesIdDownloadedPostData, ConfirmDownloadApiSavesIdDownloadedPostError, ConfirmDownloadApiSavesIdDownloadedPostResponse, CreateInviteLinkApiUsersInviteLinkPostData, CreateInviteLinkApiUsersInviteLinkPostError, CreateInviteLinkApiUsersInviteLinkPostResponse, CreateRomNoteApiRomsIdNotesPostData, CreateRomNoteApiRomsIdNotesPostError, CreateRomNoteApiRomsIdNotesPostResponse, CreateSetupPlatformsApiSetupPlatformsPostData, CreateSetupPlatformsApiSetupPlatformsPostError, CreateUserFromInviteApiUsersRegisterPostData, CreateUserFromInviteApiUsersRegisterPostError, CreateUserFromInviteApiUsersRegisterPostResponse, DeleteCollectionApiCollectionsIdDeleteData, DeleteCollectionApiCollectionsIdDeleteError, DeleteDeviceApiDevicesDeviceIdDeleteData, DeleteDeviceApiDevicesDeviceIdDeleteError, DeleteDeviceApiDevicesDeviceIdDeleteResponse, DeleteExclusionApiConfigExcludeExclusionTypeExclusionValueDeleteData, DeleteExclusionApiConfigExcludeExclusionTypeExclusionValueDeleteError, DeleteFirmwareApiFirmwareDeletePostData, DeleteFirmwareApiFirmwareDeletePostError, DeleteFirmwareApiFirmwareDeletePostResponse, DeletePlatformApiPlatformsIdDeleteData, DeletePlatformApiPlatformsIdDeleteError, DeletePlatformBindingApiConfigSystemPlatformsFsSlugDeleteData, DeletePlatformBindingApiConfigSystemPlatformsFsSlugDeleteError, DeletePlatformVersionApiConfigSystemVersionsFsSlugDeleteData, DeletePlatformVersionApiConfigSystemVersionsFsSlugDeleteError, DeleteRomManualsApiRomsIdManualsDeleteData, DeleteRomManualsApiRomsIdManualsDeleteError, DeleteRomNoteApiRomsIdNotesNoteIdDeleteData, DeleteRomNoteApiRomsIdNotesNoteIdDeleteError, DeleteRomNoteApiRomsIdNotesNoteIdDeleteResponse, DeleteRomsApiRomsDeletePostData, DeleteRomsApiRomsDeletePostError, DeleteRomsApiRomsDeletePostResponse, DeleteSavesApiSavesDeletePostData, DeleteSavesApiSavesDeletePostError, DeleteSavesApiSavesDeletePostResponse, DeleteSmartCollectionApiCollectionsSmartIdDeleteData, DeleteSmartCollectionApiCollectionsSmartIdDeleteError, DeleteStatesApiStatesDeletePostData, DeleteStatesApiStatesDeletePostError, DeleteStatesApiStatesDeletePostResponse, DeleteUserApiUsersIdDeleteData, DeleteUserApiUsersIdDeleteError, DownloadRomsApiRomsDownloadGetData, DownloadRomsApiRomsDownloadGetError, DownloadSaveApiSavesIdContentGetData, DownloadSaveApiSavesIdContentGetError, ExportGamelistApiGamelistExportPostData, ExportGamelistApiGamelistExportPostError, FpkgiFeedApiFeedsFpkgiPlatformSlugGetData, FpkgiFeedApiFeedsFpkgiPlatformSlugGetError, GetCollectionApiCollectionsIdGetData, GetCollectionApiCollectionsIdGetError, GetCollectionApiCollectionsIdGetResponse, GetCollectionIdentifiersApiCollectionsIdentifiersGetData, GetCollectionIdentifiersApiCollectionsIdentifiersGetResponse, GetCollectionsApiCollectionsGetData, GetCollectionsApiCollectionsGetError, GetCollectionsApiCollectionsGetResponse, GetConfigApiConfigGetData, GetConfigApiConfigGetResponse, GetCurrentUserApiUsersMeGetData, GetCurrentUserApiUsersMeGetResponse, GetDeviceApiDevicesDeviceIdGetData, GetDeviceApiDevicesDeviceIdGetError, GetDeviceApiDevicesDeviceIdGetResponse, GetDevicesApiDevicesGetData, GetDevicesApiDevicesGetResponse, GetFirmwareApiFirmwareIdGetData, GetFirmwareApiFirmwareIdGetError, GetFirmwareApiFirmwareIdGetResponse, GetFirmwareContentApiFirmwareIdContentFileNameGetData, GetFirmwareContentApiFirmwareIdContentFileNameGetError, GetFirmwareIdentifiersApiFirmwareIdentifiersGetData, GetFirmwareIdentifiersApiFirmwareIdentifiersGetResponse, GetPlatformApiPlatformsIdGetData, GetPlatformApiPlatformsIdGetError, GetPlatformApiPlatformsIdGetResponse, GetPlatformFirmwareApiFirmwareGetData, GetPlatformFirmwareApiFirmwareGetError, GetPlatformFirmwareApiFirmwareGetResponse, GetPlatformIdentifiersApiPlatformsIdentifiersGetData, GetPlatformIdentifiersApiPlatformsIdentifiersGetResponse, GetPlatformsApiPlatformsGetData, GetPlatformsApiPlatformsGetError, GetPlatformsApiPlatformsGetResponse, GetRawAssetApiRawAssetsPathGetData, GetRawAssetApiRawAssetsPathGetError, GetRomApiRomsIdGetData, GetRomApiRomsIdGetError, GetRomApiRomsIdGetResponse, GetRomByHashApiRomsByHashGetData, GetRomByHashApiRomsByHashGetError, GetRomByHashApiRomsByHashGetResponse, GetRomByMetadataProviderApiRomsByMetadataProviderGetData, GetRomByMetadataProviderApiRomsByMetadataProviderGetError, GetRomByMetadataProviderApiRomsByMetadataProviderGetResponse, GetRomContentApiRomsIdContentFileNameGetData, GetRomContentApiRomsIdContentFileNameGetError, GetRomfileApiRomsFilesIdGetData, GetRomfileApiRomsFilesIdGetError, GetRomfileApiRomsFilesIdGetResponse, GetRomfileContentApiRomsfilesIdContentFileNameGetData, GetRomfileContentApiRomsfilesIdContentFileNameGetError, GetRomFiltersApiRomsFiltersGetData, GetRomFiltersApiRomsFiltersGetResponse, GetRomIdentifiersApiRomsIdentifiersGetData, GetRomIdentifiersApiRomsIdentifiersGetResponse, GetRomNoteIdentifiersApiRomsIdNotesIdentifiersGetData, GetRomNoteIdentifiersApiRomsIdNotesIdentifiersGetError, GetRomNoteIdentifiersApiRomsIdNotesIdentifiersGetResponse, GetRomNotesApiRomsIdNotesGetData, GetRomNotesApiRomsIdNotesGetError, GetRomNotesApiRomsIdNotesGetResponse, GetRomsApiRomsGetData, GetRomsApiRomsGetError, GetRomsApiRomsGetResponse, GetRoomsApiNetplayListGetData, GetRoomsApiNetplayListGetError, GetRoomsApiNetplayListGetResponse, GetSaveApiSavesIdGetData, GetSaveApiSavesIdGetError, GetSaveApiSavesIdGetResponse, GetSaveIdentifiersApiSavesIdentifiersGetData, GetSaveIdentifiersApiSavesIdentifiersGetResponse, GetSavesApiSavesGetData, GetSavesApiSavesGetError, GetSavesApiSavesGetResponse, GetSavesSummaryApiSavesSummaryGetData, GetSavesSummaryApiSavesSummaryGetError, GetSavesSummaryApiSavesSummaryGetResponse, GetSetupLibraryInfoApiSetupLibraryGetData, GetSmartCollectionApiCollectionsSmartIdGetData, GetSmartCollectionApiCollectionsSmartIdGetError, GetSmartCollectionApiCollectionsSmartIdGetResponse, GetSmartCollectionIdentifiersApiCollectionsSmartIdentifiersGetData, GetSmartCollectionIdentifiersApiCollectionsSmartIdentifiersGetResponse, GetSmartCollectionsApiCollectionsSmartGetData, GetSmartCollectionsApiCollectionsSmartGetError, GetSmartCollectionsApiCollectionsSmartGetResponse, GetStateApiStatesIdGetData, GetStateApiStatesIdGetError, GetStateApiStatesIdGetResponse, GetStateIdentifiersApiStatesIdentifiersGetData, GetStateIdentifiersApiStatesIdentifiersGetResponse, GetStatesApiStatesGetData, GetStatesApiStatesGetError, GetStatesApiStatesGetResponse, GetSupportedPlatformsEndpointApiPlatformsSupportedGetData, GetSupportedPlatformsEndpointApiPlatformsSupportedGetResponse, GetTaskByIdApiTasksTaskIdGetData, GetTaskByIdApiTasksTaskIdGetError, GetTaskByIdApiTasksTaskIdGetResponse, GetTasksStatusApiTasksStatusGetData, GetTasksStatusApiTasksStatusGetResponse, GetUserApiUsersIdGetData, GetUserApiUsersIdGetError, GetUserApiUsersIdGetResponse, GetUserIdentifiersApiUsersIdentifiersGetData, GetUserIdentifiersApiUsersIdentifiersGetResponse, GetUsersApiUsersGetData, GetUsersApiUsersGetResponse, GetVirtualCollectionApiCollectionsVirtualIdGetData, GetVirtualCollectionApiCollectionsVirtualIdGetError, GetVirtualCollectionApiCollectionsVirtualIdGetResponse, GetVirtualCollectionIdentifiersApiCollectionsVirtualIdentifiersGetData, GetVirtualCollectionIdentifiersApiCollectionsVirtualIdentifiersGetResponse, GetVirtualCollectionsApiCollectionsVirtualGetData, GetVirtualCollectionsApiCollectionsVirtualGetError, GetVirtualCollectionsApiCollectionsVirtualGetResponse, HeartbeatApiHeartbeatGetData, HeartbeatApiHeartbeatGetResponse, KekatsuDsFeedApiFeedsKekatsuPlatformSlugGetData, KekatsuDsFeedApiFeedsKekatsuPlatformSlugGetError, ListTasksApiTasksGetData, ListTasksApiTasksGetResponse, LoginApiLoginPostData, LoginViaOpenidApiLoginOpenidGetData, LogoutApiLogoutPostData, MetadataHeartbeatApiHeartbeatMetadataSourceGetData, MetadataHeartbeatApiHeartbeatMetadataSourceGetError, MetadataHeartbeatApiHeartbeatMetadataSourceGetResponse, PkgiPs3FeedApiFeedsPkgiPs3ContentTypeGetData, PkgiPs3FeedApiFeedsPkgiPs3ContentTypeGetError, PkgiPspFeedApiFeedsPkgiPspContentTypeGetData, PkgiPspFeedApiFeedsPkgiPspContentTypeGetError, PkgiPsvitaFeedApiFeedsPkgiPsvitaContentTypeGetData, PkgiPsvitaFeedApiFeedsPkgiPsvitaContentTypeGetError, PkgjPspDlcsFeedApiFeedsPkgjPspDlcGetData, PkgjPspGamesFeedApiFeedsPkgjPspGamesGetData, PkgjPsvDlcsFeedApiFeedsPkgjPsvitaDlcGetData, PkgjPsvGamesFeedApiFeedsPkgjPsvitaGamesGetData, PkgjPsxGamesFeedApiFeedsPkgjPsxGamesGetData, PlatformsWebrcadeFeedApiFeedsWebrcadeGetData, PlatformsWebrcadeFeedApiFeedsWebrcadeGetResponse, RefreshRetroAchievementsApiUsersIdRaRefreshPostData, RefreshRetroAchievementsApiUsersIdRaRefreshPostError, RegisterDeviceApiDevicesPostData, RegisterDeviceApiDevicesPostError, RegisterDeviceApiDevicesPostResponse, RequestPasswordResetApiForgotPasswordPostData, RequestPasswordResetApiForgotPasswordPostError, ResetPasswordApiResetPasswordPostData, ResetPasswordApiResetPasswordPostError, RunAllTasksApiTasksRunPostData, RunAllTasksApiTasksRunPostResponse, RunSingleTaskApiTasksRunTaskNamePostData, RunSingleTaskApiTasksRunTaskNamePostError, RunSingleTaskApiTasksRunTaskNamePostResponse, SearchCoverApiSearchCoverGetData, SearchCoverApiSearchCoverGetError, SearchCoverApiSearchCoverGetResponse, SearchRomApiSearchRomsGetData, SearchRomApiSearchRomsGetError, SearchRomApiSearchRomsGetResponse, StatsApiStatsGetData, StatsApiStatsGetResponse, TinfoilIndexFeedApiFeedsTinfoilGetData, TinfoilIndexFeedApiFeedsTinfoilGetError, TinfoilIndexFeedApiFeedsTinfoilGetResponse, TokenApiTokenPostData, TokenApiTokenPostError, TokenApiTokenPostResponse, TrackSaveApiSavesIdTrackPostData, TrackSaveApiSavesIdTrackPostError, TrackSaveApiSavesIdTrackPostResponse, UntrackSaveApiSavesIdUntrackPostData, UntrackSaveApiSavesIdUntrackPostError, UntrackSaveApiSavesIdUntrackPostResponse, UpdateCollectionApiCollectionsIdPutData, UpdateCollectionApiCollectionsIdPutError, UpdateCollectionApiCollectionsIdPutResponse, UpdateDeviceApiDevicesDeviceIdPutData, UpdateDeviceApiDevicesDeviceIdPutError, UpdateDeviceApiDevicesDeviceIdPutResponse, UpdatePlatformApiPlatformsIdPutData, UpdatePlatformApiPlatformsIdPutError, UpdatePlatformApiPlatformsIdPutResponse, UpdateRomApiRomsIdPutData, UpdateRomApiRomsIdPutError, UpdateRomApiRomsIdPutResponse, UpdateRomNoteApiRomsIdNotesNoteIdPutData, UpdateRomNoteApiRomsIdNotesNoteIdPutError, UpdateRomNoteApiRomsIdNotesNoteIdPutResponse, UpdateRomUserApiRomsIdPropsPutData, UpdateRomUserApiRomsIdPropsPutError, UpdateRomUserApiRomsIdPropsPutResponse, UpdateSaveApiSavesIdPutData, UpdateSaveApiSavesIdPutError, UpdateSaveApiSavesIdPutResponse, UpdateSmartCollectionApiCollectionsSmartIdPutData, UpdateSmartCollectionApiCollectionsSmartIdPutError, UpdateSmartCollectionApiCollectionsSmartIdPutResponse, UpdateStateApiStatesIdPutData, UpdateStateApiStatesIdPutError, UpdateStateApiStatesIdPutResponse, UpdateUserApiUsersIdPutData, UpdateUserApiUsersIdPutError, UpdateUserApiUsersIdPutResponse } from '../types.gen'; export type QueryKey = [ Pick & { @@ -442,6 +442,32 @@ export const createUserFromInviteApiUsersRegisterPostMutation = (options?: Parti return mutationOptions; }; +export const getUserIdentifiersApiUsersIdentifiersGetQueryKey = (options?: Options) => createQueryKey('getUserIdentifiersApiUsersIdentifiersGet', options); + +/** + * Get User Identifiers + * + * Get all user identifiers endpoint + * + * Args: + * request (Request): Fastapi Request object + * + * Returns: + * list[int]: All user ids stored in the RomM's database + */ +export const getUserIdentifiersApiUsersIdentifiersGetOptions = (options?: Options) => queryOptions>({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getUserIdentifiersApiUsersIdentifiersGet({ + ...options, + ...queryKey[0], + signal, + throwOnError: true + }); + return data; + }, + queryKey: getUserIdentifiersApiUsersIdentifiersGetQueryKey(options) +}); + export const getCurrentUserApiUsersMeGetQueryKey = (options?: Options) => createQueryKey('getCurrentUserApiUsersMeGet', options); /** @@ -568,6 +594,93 @@ export const refreshRetroAchievementsApiUsersIdRaRefreshPostMutation = (options? return mutationOptions; }; +export const getDevicesApiDevicesGetQueryKey = (options?: Options) => createQueryKey('getDevicesApiDevicesGet', options); + +/** + * Get Devices + */ +export const getDevicesApiDevicesGetOptions = (options?: Options) => queryOptions>({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getDevicesApiDevicesGet({ + ...options, + ...queryKey[0], + signal, + throwOnError: true + }); + return data; + }, + queryKey: getDevicesApiDevicesGetQueryKey(options) +}); + +/** + * Register Device + */ +export const registerDeviceApiDevicesPostMutation = (options?: Partial>): UseMutationOptions> => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (fnOptions) => { + const { data } = await registerDeviceApiDevicesPost({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + +/** + * Delete Device + */ +export const deleteDeviceApiDevicesDeviceIdDeleteMutation = (options?: Partial>): UseMutationOptions> => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (fnOptions) => { + const { data } = await deleteDeviceApiDevicesDeviceIdDelete({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + +export const getDeviceApiDevicesDeviceIdGetQueryKey = (options: Options) => createQueryKey('getDeviceApiDevicesDeviceIdGet', options); + +/** + * Get Device + */ +export const getDeviceApiDevicesDeviceIdGetOptions = (options: Options) => queryOptions>({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getDeviceApiDevicesDeviceIdGet({ + ...options, + ...queryKey[0], + signal, + throwOnError: true + }); + return data; + }, + queryKey: getDeviceApiDevicesDeviceIdGetQueryKey(options) +}); + +/** + * Update Device + */ +export const updateDeviceApiDevicesDeviceIdPutMutation = (options?: Partial>): UseMutationOptions> => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (fnOptions) => { + const { data } = await updateDeviceApiDevicesDeviceIdPut({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + export const getPlatformsApiPlatformsGetQueryKey = (options?: Options) => createQueryKey('getPlatformsApiPlatformsGet', options); /** @@ -607,6 +720,26 @@ export const addPlatformApiPlatformsPostMutation = (options?: Partial) => createQueryKey('getPlatformIdentifiersApiPlatformsIdentifiersGet', options); + +/** + * Get Platform Identifiers + * + * Retrieve platform identifiers. + */ +export const getPlatformIdentifiersApiPlatformsIdentifiersGetOptions = (options?: Options) => queryOptions>({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getPlatformIdentifiersApiPlatformsIdentifiersGet({ + ...options, + ...queryKey[0], + signal, + throwOnError: true + }); + return data; + }, + queryKey: getPlatformIdentifiersApiPlatformsIdentifiersGetQueryKey(options) +}); + export const getSupportedPlatformsEndpointApiPlatformsSupportedGetQueryKey = (options?: Options) => createQueryKey('getSupportedPlatformsEndpointApiPlatformsSupportedGet', options); /** @@ -782,6 +915,26 @@ export const addRomApiRomsPostMutation = (options?: Partial) => createQueryKey('getRomIdentifiersApiRomsIdentifiersGet', options); + +/** + * Get Rom Identifiers + * + * Retrieve rom identifiers. + */ +export const getRomIdentifiersApiRomsIdentifiersGetOptions = (options?: Options) => queryOptions>({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getRomIdentifiersApiRomsIdentifiersGet({ + ...options, + ...queryKey[0], + signal, + throwOnError: true + }); + return data; + }, + queryKey: getRomIdentifiersApiRomsIdentifiersGetQueryKey(options) +}); + export const downloadRomsApiRomsDownloadGetQueryKey = (options: Options) => createQueryKey('downloadRomsApiRomsDownloadGet', options); /** @@ -1076,6 +1229,26 @@ export const createRomNoteApiRomsIdNotesPostMutation = (options?: Partial