diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 3b11859ce..ec8acd79c 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -2,7 +2,7 @@
**Description**
-Explain the changes or enhancements you are proposing with this pull request.
+Explain the changes or enhancements you are proposing with this pull request.
**Checklist**
Please check all that apply.
@@ -12,4 +12,4 @@
- [ ] I've assigned reviewers for this PR
- [ ] I've added unit tests that cover the changes
-#### Screenshots
+#### Screenshots (if applicable)
diff --git a/backend/endpoints/rom.py b/backend/endpoints/rom.py
index 23190caf9..84dda0c8d 100644
--- a/backend/endpoints/rom.py
+++ b/backend/endpoints/rom.py
@@ -448,6 +448,78 @@ async def download_roms(
)
+@protected_route(
+ router.get,
+ "/by-metadata-provider",
+ [] if DISABLE_DOWNLOAD_ENDPOINT_AUTH else [Scope.ROMS_READ],
+ responses={status.HTTP_404_NOT_FOUND: {}},
+)
+def get_rom_by_metadata(
+ request: Request,
+ igdb: Annotated[int | None, Query(description="IGDB ID to search by")] = None,
+ moby: Annotated[int | None, Query(description="MobyGames ID to search by")] = None,
+ ss: Annotated[
+ int | None, Query(description="ScreenScraper ID to search by")
+ ] = None,
+ ra: Annotated[
+ int | None, Query(description="RetroAchievements ID to search by")
+ ] = None,
+ launchbox: Annotated[
+ int | None, Query(description="LaunchBox ID to search by")
+ ] = None,
+ hasheous: Annotated[
+ int | None, Query(description="Hasheous ID to search by")
+ ] = None,
+ tgdb: Annotated[int | None, Query(description="TGDB ID to search by")] = None,
+ flashpoint: Annotated[
+ str | None, Query(description="Flashpoint ID to search by")
+ ] = None,
+ hltb: Annotated[int | None, Query(description="HLTB ID to search by")] = None,
+) -> DetailedRomSchema:
+ """Retrieve a rom by metadata ID."""
+
+ rom = db_rom_handler.get_rom_by_metadata_id(
+ igdb=igdb,
+ moby=moby,
+ ss=ss,
+ ra=ra,
+ launchbox=launchbox,
+ hasheous=hasheous,
+ tgdb=tgdb,
+ flashpoint=flashpoint,
+ hltb=hltb,
+ )
+
+ if not rom:
+ provided_ids = {
+ "igdb_id": igdb,
+ "moby_id": moby,
+ "ss_id": ss,
+ "ra_id": ra,
+ "launchbox_id": launchbox,
+ "hasheous_id": hasheous,
+ "tgdb_id": tgdb,
+ "flashpoint_id": flashpoint,
+ "hltb_id": hltb,
+ }
+ metadata_info = [
+ f"{key}={value}" for key, value in provided_ids.items() if value is not None
+ ]
+
+ if not metadata_info:
+ raise HTTPException(
+ status_code=status.HTTP_400_BAD_REQUEST,
+ detail="At least one metadata ID must be provided",
+ )
+
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND,
+ detail=f"ROM not found with metadata: {', '.join(metadata_info)}",
+ )
+
+ return DetailedRomSchema.from_orm_with_request(rom, request)
+
+
@protected_route(
router.get,
"/{id}",
@@ -938,7 +1010,7 @@ async def update_rom(
path_manual = await fs_resource_handler.get_manual(
rom=rom,
overwrite=True,
- url_manual=url_manual,
+ url_manual=str(url_manual) if url_manual else None,
)
cleaned_data.update(
{
diff --git a/backend/handler/database/roms_handler.py b/backend/handler/database/roms_handler.py
index 32a79e8a7..c972e04cf 100644
--- a/backend/handler/database/roms_handler.py
+++ b/backend/handler/database/roms_handler.py
@@ -977,3 +977,44 @@ class DBRomsHandler(DBBaseHandler):
)
)
return result.rowcount > 0
+
+ @begin_session
+ @with_details
+ def get_rom_by_metadata_id(
+ self,
+ igdb: int | None = None,
+ moby: int | None = None,
+ ss: int | None = None,
+ ra: int | None = None,
+ launchbox: int | None = None,
+ hasheous: int | None = None,
+ tgdb: int | None = None,
+ flashpoint: str | None = None,
+ hltb: int | None = None,
+ *,
+ query: Query = None,
+ session: Session = None,
+ ) -> Rom | None:
+ """Get a ROM by any metadata ID."""
+ filters = []
+ param_map = [
+ (igdb, Rom.igdb_id),
+ (moby, Rom.moby_id),
+ (ss, Rom.ss_id),
+ (ra, Rom.ra_id),
+ (launchbox, Rom.launchbox_id),
+ (hasheous, Rom.hasheous_id),
+ (tgdb, Rom.tgdb_id),
+ (flashpoint, Rom.flashpoint_id),
+ (hltb, Rom.hltb_id),
+ ]
+
+ for value, column in param_map:
+ if value is not None:
+ filters.append(column == value)
+
+ if not filters:
+ return None
+
+ # Use OR to find ROM matching any of the provided metadata IDs
+ return session.scalar(query.filter(or_(*filters)).limit(1))
diff --git a/frontend/src/components/common/Game/Card/Related.vue b/frontend/src/components/common/Game/Card/Related.vue
index e52ff91b1..cc189edb5 100644
--- a/frontend/src/components/common/Game/Card/Related.vue
+++ b/frontend/src/components/common/Game/Card/Related.vue
@@ -1,6 +1,7 @@
-
+
{
+ const params = { [provider]: id };
+ return api.get(`/roms/by-metadata-provider/`, {
+ params,
+ });
+}
+
async function searchRom({
romId,
searchTerm,
@@ -483,6 +496,7 @@ export default {
getRecentRoms,
getRecentPlayedRoms,
getRom,
+ getRomByMetadataProvider,
downloadRom,
bulkDownloadRoms,
searchRom,