Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
417 changes: 417 additions & 0 deletions backend/alembic/versions/0112_publisher_developer_split.py

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions backend/endpoints/responses/rom.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ class UserNoteSchema(BaseModel):
"genres": list[str] | None,
"franchises": list[str] | None,
"companies": list[str] | None,
"publishers": list[str] | None,
"developers": list[str] | None,
"game_modes": list[str] | None,
"age_ratings": list[str] | None,
"first_release_date": int | None,
Expand Down Expand Up @@ -247,6 +249,8 @@ class RomMetadataSchema(BaseModel):
franchises: list[str]
collections: list[str]
companies: list[str]
publishers: list[str]
developers: list[str]
game_modes: list[str]
age_ratings: list[str]
player_count: str
Expand All @@ -269,6 +273,14 @@ def sort_collections(cls, v: list[str]) -> list[str]:
def sort_companies(cls, v: list[str]) -> list[str]:
return sorted(v)

@field_validator("publishers")
def sort_publishers(cls, v: list[str]) -> list[str]:
return sorted(v)

@field_validator("developers")
def sort_developers(cls, v: list[str]) -> list[str]:
return sorted(v)

@field_validator("game_modes")
def sort_game_modes(cls, v: list[str]) -> list[str]:
return sorted(v)
Expand Down Expand Up @@ -712,6 +724,8 @@ class RomFiltersDict(TypedDict):
franchises: list[str]
collections: list[str]
companies: list[str]
publishers: list[str]
developers: list[str]
game_modes: list[str]
age_ratings: list[str]
player_counts: list[str]
Expand Down
38 changes: 38 additions & 0 deletions backend/endpoints/roms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,24 @@ def get_roms(
),
),
] = None,
publishers: Annotated[
list[str] | None,
Query(
description=(
"Associated publisher. Multiple values are allowed by repeating"
" the parameter, and results that match any of the values will be returned."
),
),
] = None,
developers: Annotated[
list[str] | None,
Query(
description=(
"Associated developer. Multiple values are allowed by repeating"
" the parameter, and results that match any of the values will be returned."
),
),
] = None,
age_ratings: Annotated[
list[str] | None,
Query(
Expand Down Expand Up @@ -594,6 +612,18 @@ def get_roms(
description="Logic operator for companies filter: 'any' (OR), 'all' (AND) or 'none' (NOT).",
),
] = "any",
publishers_logic: Annotated[
str,
Query(
description="Logic operator for publishers filter: 'any' (OR), 'all' (AND) or 'none' (NOT).",
),
] = "any",
developers_logic: Annotated[
str,
Query(
description="Logic operator for developers filter: 'any' (OR), 'all' (AND) or 'none' (NOT).",
),
] = "any",
age_ratings_logic: Annotated[
str,
Query(
Expand Down Expand Up @@ -698,6 +728,8 @@ def get_roms(
franchises=franchises,
collections=collections,
companies=companies,
publishers=publishers,
developers=developers,
age_ratings=age_ratings,
statuses=statuses,
regions=regions,
Expand All @@ -710,6 +742,8 @@ def get_roms(
franchises_logic=franchises_logic,
collections_logic=collections_logic,
companies_logic=companies_logic,
publishers_logic=publishers_logic,
developers_logic=developers_logic,
age_ratings_logic=age_ratings_logic,
regions_logic=regions_logic,
languages_logic=languages_logic,
Expand Down Expand Up @@ -751,6 +785,8 @@ def get_roms(
or franchises
or collections
or companies
or publishers
or developers
or age_ratings
or statuses
or regions
Expand Down Expand Up @@ -794,6 +830,8 @@ def get_roms(
franchises=[],
collections=[],
companies=[],
publishers=[],
developers=[],
game_modes=[],
age_ratings=[],
player_counts=[],
Expand Down
13 changes: 10 additions & 3 deletions backend/handler/database/collections_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -480,9 +480,12 @@ def get_smart_collection_criteria(
"""
criteria = smart_collection.filter_criteria

# Early versions stored single values under `selected_*` keys.
def as_list(new_key: str, old_key: str) -> list[str] | None:
value = criteria.get(new_key) or criteria.get(old_key)
# Early versions stored single values under `selected_*` keys, for the
# filters that already existed then.
def as_list(new_key: str, old_key: str | None = None) -> list[str] | None:
value = criteria.get(new_key) or (
criteria.get(old_key) if old_key else None
)
if not value:
return None
return value if isinstance(value, list) else [value]
Expand Down Expand Up @@ -511,6 +514,8 @@ def as_list(new_key: str, old_key: str) -> list[str] | None:
"franchises": as_list("franchises", "selected_franchise"),
"collections": as_list("collections", "selected_collection"),
"companies": as_list("companies", "selected_company"),
"publishers": as_list("publishers"),
"developers": as_list("developers"),
"age_ratings": as_list("age_ratings", "selected_age_rating"),
"regions": as_list("regions", "selected_region"),
"languages": as_list("languages", "selected_language"),
Expand All @@ -522,6 +527,8 @@ def as_list(new_key: str, old_key: str) -> list[str] | None:
"franchises_logic": criteria.get("franchises_logic", "any"),
"collections_logic": criteria.get("collections_logic", "any"),
"companies_logic": criteria.get("companies_logic", "any"),
"publishers_logic": criteria.get("publishers_logic", "any"),
"developers_logic": criteria.get("developers_logic", "any"),
"age_ratings_logic": criteria.get("age_ratings_logic", "any"),
"regions_logic": criteria.get("regions_logic", "any"),
"languages_logic": criteria.get("languages_logic", "any"),
Expand Down
59 changes: 57 additions & 2 deletions backend/handler/database/roms_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@
RomFacets.franchises,
RomFacets.collections,
RomFacets.companies,
RomFacets.publishers,
RomFacets.developers,
RomFacets.game_modes,
RomFacets.age_ratings,
RomFacets.player_count,
Expand Down Expand Up @@ -1034,6 +1036,32 @@ def _filter_by_companies(
condition = op(RomMetadata.companies, values, session=session)
return query.filter(~condition) if match_none else query.filter(condition)

def _filter_by_publishers(
self,
query: Query,
*,
session: Session,
values: Sequence[str],
match_all: bool = False,
match_none: bool = False,
) -> Query:
op = json_array_contains_all if match_all else json_array_contains_any
condition = op(RomMetadata.publishers, values, session=session)
return query.filter(~condition) if match_none else query.filter(condition)

def _filter_by_developers(
self,
query: Query,
*,
session: Session,
values: Sequence[str],
match_all: bool = False,
match_none: bool = False,
) -> Query:
op = json_array_contains_all if match_all else json_array_contains_any
condition = op(RomMetadata.developers, values, session=session)
return query.filter(~condition) if match_none else query.filter(condition)

def _filter_by_age_ratings(
self,
query: Query,
Expand Down Expand Up @@ -1194,6 +1222,8 @@ def filter_roms(
franchises: Sequence[str] | None = None,
collections: Sequence[str] | None = None,
companies: Sequence[str] | None = None,
publishers: Sequence[str] | None = None,
developers: Sequence[str] | None = None,
age_ratings: Sequence[str] | None = None,
statuses: Sequence[str] | None = None,
regions: Sequence[str] | None = None,
Expand All @@ -1206,6 +1236,8 @@ def filter_roms(
franchises_logic: str = "any",
collections_logic: str = "any",
companies_logic: str = "any",
publishers_logic: str = "any",
developers_logic: str = "any",
age_ratings_logic: str = "any",
regions_logic: str = "any",
languages_logic: str = "any",
Expand Down Expand Up @@ -1352,6 +1384,8 @@ def filter_roms(
(franchises, franchises_logic, self._filter_by_franchises),
(collections, collections_logic, self._filter_by_collections),
(companies, companies_logic, self._filter_by_companies),
(publishers, publishers_logic, self._filter_by_publishers),
(developers, developers_logic, self._filter_by_developers),
(age_ratings, age_ratings_logic, self._filter_by_age_ratings),
(regions, regions_logic, self._filter_by_regions),
(languages, languages_logic, self._filter_by_languages),
Expand Down Expand Up @@ -1495,7 +1529,16 @@ def filter_roms(

# Optimize JOINs - only join tables when needed
needs_metadata_join = any(
[genres, franchises, collections, companies, age_ratings, player_counts]
[
genres,
franchises,
collections,
companies,
publishers,
developers,
age_ratings,
player_counts,
]
)

if needs_metadata_join:
Expand Down Expand Up @@ -1624,6 +1667,8 @@ def get_roms_scalar(
franchises=kwargs.get("franchises", None),
collections=kwargs.get("collections", None),
companies=kwargs.get("companies", None),
publishers=kwargs.get("publishers", None),
developers=kwargs.get("developers", None),
age_ratings=kwargs.get("age_ratings", None),
statuses=kwargs.get("statuses", None),
regions=kwargs.get("regions", None),
Expand All @@ -1636,6 +1681,8 @@ def get_roms_scalar(
franchises_logic=kwargs.get("franchises_logic", "any"),
collections_logic=kwargs.get("collections_logic", "any"),
companies_logic=kwargs.get("companies_logic", "any"),
publishers_logic=kwargs.get("publishers_logic", "any"),
developers_logic=kwargs.get("developers_logic", "any"),
age_ratings_logic=kwargs.get("age_ratings_logic", "any"),
regions_logic=kwargs.get("regions_logic", "any"),
languages_logic=kwargs.get("languages_logic", "any"),
Expand Down Expand Up @@ -2946,6 +2993,8 @@ def _collect_filter_values(
franchises = set()
collections = set()
companies = set()
publishers = set()
developers = set()
game_modes = set()
age_ratings = set()
player_counts = set()
Expand All @@ -2955,7 +3004,7 @@ def _collect_filter_values(
platforms = set()

for row in session.execute(statement):
g, f, cl, co, gm, ar, pc, rg, lg, tg, pid = row
g, f, cl, co, pub, dev, gm, ar, pc, rg, lg, tg, pid = row
if g:
genres.update(g)
if f:
Expand All @@ -2964,6 +3013,10 @@ def _collect_filter_values(
collections.update(cl)
if co:
companies.update(co)
if pub:
publishers.update(pub)
if dev:
developers.update(dev)
if gm:
game_modes.update(gm)
if ar:
Expand All @@ -2983,6 +3036,8 @@ def _collect_filter_values(
"franchises": sorted(franchises),
"collections": sorted(collections),
"companies": sorted(companies),
"publishers": sorted(publishers),
"developers": sorted(developers),
"game_modes": sorted(game_modes),
"age_ratings": sorted(age_ratings),
"player_counts": sorted(player_counts),
Expand Down
9 changes: 8 additions & 1 deletion backend/handler/metadata/flashpoint_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ class FlashpointGame(TypedDict):
class FlashpointMetadata(TypedDict):
franchises: list[str]
companies: list[str]
publishers: list[str]
developers: list[str]
source: str | None
genres: list[str]
first_release_date: str
Expand Down Expand Up @@ -75,9 +77,14 @@ def extract_flashpoint_metadata(game: FlashpointGame) -> FlashpointMetadata:
except (ValueError, TypeError):
first_release_date = ""

publishers = pydash.compact([game["publisher"]])
developers = pydash.compact([game["developer"]])

return FlashpointMetadata(
franchises=pydash.compact([game["series"]]),
companies=pydash.uniq(pydash.compact([game["developer"], game["publisher"]])),
companies=pydash.uniq([*developers, *publishers]),
publishers=publishers,
developers=developers,
source=game["source"],
genres=game["tags"],
first_release_date=first_release_date,
Expand Down
18 changes: 8 additions & 10 deletions backend/handler/metadata/gamelist_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ class GamelistMetadata(GamelistMetadataMedia):
first_release_date: str | None
sort_name: str | None
companies: list[str] | None
publishers: list[str] | None
developers: list[str] | None
franchises: list[str] | None
genres: list[str] | None
player_count: str | None
Expand Down Expand Up @@ -230,20 +232,16 @@ def extract_metadata_from_gamelist_rom(
)
md5 = md5_elem.text if md5_elem is not None and md5_elem.text else None

publishers = _split_comma_separated_values(publisher)
developers = _split_comma_separated_values(developer)

return GamelistMetadata(
rating=rating,
first_release_date=first_release_date,
sort_name=sort_name,
companies=list(
dict.fromkeys(
pydash.compact(
[
*_split_comma_separated_values(developer),
*_split_comma_separated_values(publisher),
]
)
)
),
companies=list(dict.fromkeys([*developers, *publishers])),
publishers=publishers,
developers=developers,
franchises=_split_comma_separated_values(family),
genres=_split_comma_separated_values(genre),
player_count=players,
Expand Down
14 changes: 14 additions & 0 deletions backend/handler/metadata/hasheous_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,18 @@ class HasheousRom(BaseRom):
ACCEPTABLE_FILE_EXTENSIONS_BY_PLATFORM_SLUG = {UPS.DC: ["bin", "chd", "cue"]}


def _involved_company_names(rom: dict[str, Any], role: str) -> list[str]:
"""Company names for an IGDB involvement role.

The proxy keys its expanded lists by id, so involvements arrive as a dict
rather than the list IGDB itself returns.
"""
involved = pydash.values(rom.get("involved_companies", {}))
return pydash.compact(
pydash.map_([c for c in involved if c.get(role)], "company.name")
)


def extract_metadata_from_igdb_rom(rom: dict[str, Any]) -> IGDBMetadata:
return IGDBMetadata(
{
Expand Down Expand Up @@ -88,6 +100,8 @@ def extract_metadata_from_igdb_rom(rom: dict[str, Any]) -> IGDBMetadata:
"companies": pydash.compact(
pydash.map_(rom.get("involved_companies", {}), "company.name")
),
"publishers": _involved_company_names(rom, "publisher"),
"developers": _involved_company_names(rom, "developer"),
"platforms": [
IGDBMetadataPlatform(igdb_id=p.get("id", ""), name=p.get("name", ""))
for p in pydash.map_(rom.get("platforms", {}))
Expand Down
Loading
Loading