diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..182b9ef27 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "python.testing.cwd": "${workspaceFolder}/backend", + "python.testing.pytestArgs": ["."], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true +} diff --git a/backend/endpoints/responses/rom.py b/backend/endpoints/responses/rom.py index a76e5b05e..e80ac99c0 100644 --- a/backend/endpoints/responses/rom.py +++ b/backend/endpoints/responses/rom.py @@ -122,6 +122,7 @@ class RomSchema(BaseModel): collections: list[str] companies: list[str] game_modes: list[str] + age_ratings: list[str] igdb_metadata: RomIGDBMetadata | None moby_metadata: RomMobyMetadata | None diff --git a/backend/endpoints/tests/test_rom.py b/backend/endpoints/tests/test_rom.py index 35d20b500..b37d763f8 100644 --- a/backend/endpoints/tests/test_rom.py +++ b/backend/endpoints/tests/test_rom.py @@ -64,6 +64,7 @@ def test_update_rom(rename_file_mock, get_rom_by_id_mock, client, access_token, "expanded_games": "[]", "ports": "[]", "similar_games": "[]", + "age_ratings": "[1, 2]", }, ) assert response.status_code == 200 diff --git a/backend/handler/metadata/igdb_handler.py b/backend/handler/metadata/igdb_handler.py index 8b8dd2246..7cd5db801 100644 --- a/backend/handler/metadata/igdb_handler.py +++ b/backend/handler/metadata/igdb_handler.py @@ -39,6 +39,12 @@ class IGDBPlatform(TypedDict): name: NotRequired[str] +class IGDBAgeRating(TypedDict): + rating: str + category: str + rating_cover_url: str + + class IGDBMetadataPlatform(TypedDict): igdb_id: int name: str @@ -63,6 +69,7 @@ class IGDBMetadata(TypedDict): collections: list[str] companies: list[str] game_modes: list[str] + age_ratings: list[IGDBAgeRating] platforms: list[IGDBMetadataPlatform] expansions: list[IGDBRelatedGame] dlcs: list[IGDBRelatedGame] @@ -105,6 +112,11 @@ def extract_metadata_from_igdb_rom( IGDBMetadataPlatform(igdb_id=p.get("id", ""), name=p.get("name", "")) for p in rom.get("platforms", []) ], + "age_ratings": [ + IGDB_AGE_RATINGS[r["rating"]] + for r in rom.get("age_ratings", []) + if r["rating"] in IGDB_AGE_RATINGS + ], "expansions": [ IGDBRelatedGame( id=e["id"], @@ -556,8 +568,8 @@ async def get_matched_roms_by_name( ] return [ - IGDBRom( # type: ignore[misc] - { + IGDBRom( + { # type: ignore[misc] k: v for k, v in { "igdb_id": rom["id"], @@ -565,12 +577,12 @@ async def get_matched_roms_by_name( "name": rom["name"], "summary": rom.get("summary", ""), "url_cover": self._normalize_cover_url( - rom.get("cover", {}) - .get("url", "") - .replace("t_thumb", "t_cover_big") + pydash.get(rom, "cover.url", "").replace( + "t_thumb", "t_cover_big" + ) ), "url_screenshots": [ - self._normalize_cover_url(s.get("url", "")) + self._normalize_cover_url(s.get("url", "")) # type: ignore[arg-type] for s in rom.get("screenshots", []) ], "igdb_metadata": extract_metadata_from_igdb_rom(rom), @@ -693,6 +705,7 @@ async def get_oauth_token(self) -> str: "similar_games.slug", "similar_games.name", "similar_games.cover.url", + "age_ratings.rating", ] SEARCH_FIELDS = ["game.id", "name"] @@ -921,3 +934,186 @@ async def get_oauth_token(self) -> str: {"slug": "vc", "name": "Virtual Console"}, {"slug": "airconsole", "name": "AirConsole"}, ] + +IGDB_AGE_RATINGS: dict[int, IGDBAgeRating] = { + 1: { + "rating": "Three", + "category": "PEGI", + "rating_cover_url": "https://www.igdb.com/icons/rating_icons/pegi/pegi_3.png", + }, + 2: { + "rating": "Seven", + "category": "PEGI", + "rating_cover_url": "https://www.igdb.com/icons/rating_icons/pegi/pegi_7.png", + }, + 3: { + "rating": "Twelve", + "category": "PEGI", + "rating_cover_url": "https://www.igdb.com/icons/rating_icons/pegi/pegi_12.png", + }, + 4: { + "rating": "Sixteen", + "category": "PEGI", + "rating_cover_url": "https://www.igdb.com/icons/rating_icons/pegi/pegi_16.png", + }, + 5: { + "rating": "Eighteen", + "category": "PEGI", + "rating_cover_url": "https://www.igdb.com/icons/rating_icons/pegi/pegi_18.png", + }, + 6: { + "rating": "RP", + "category": "ESRB", + "rating_cover_url": "https://www.igdb.com/icons/rating_icons/esrb/esrb_rp.png", + }, + 7: { + "rating": "EC", + "category": "ESRB", + "rating_cover_url": "https://www.igdb.com/icons/rating_icons/esrb/esrb_ec.png", + }, + 8: { + "rating": "E", + "category": "ESRB", + "rating_cover_url": "https://www.igdb.com/icons/rating_icons/esrb/esrb_e.png", + }, + 9: { + "rating": "E10", + "category": "ESRB", + "rating_cover_url": "https://www.igdb.com/icons/rating_icons/esrb/esrb_e10.png", + }, + 10: { + "rating": "T", + "category": "ESRB", + "rating_cover_url": "https://www.igdb.com/icons/rating_icons/esrb/esrb_t.png", + }, + 11: { + "rating": "M", + "category": "ESRB", + "rating_cover_url": "https://www.igdb.com/icons/rating_icons/esrb/esrb_m.png", + }, + 12: { + "rating": "AO", + "category": "ESRB", + "rating_cover_url": "https://www.igdb.com/icons/rating_icons/esrb/esrb_ao.png", + }, + 13: { + "rating": "CERO_A", + "category": "CERO", + "rating_cover_url": "https://www.igdb.com/icons/rating_icons/cero/cero_a.png", + }, + 14: { + "rating": "CERO_B", + "category": "CERO", + "rating_cover_url": "https://www.igdb.com/icons/rating_icons/cero/cero_b.png", + }, + 15: { + "rating": "CERO_C", + "category": "CERO", + "rating_cover_url": "https://www.igdb.com/icons/rating_icons/cero/cero_c.png", + }, + 16: { + "rating": "CERO_D", + "category": "CERO", + "rating_cover_url": "https://www.igdb.com/icons/rating_icons/cero/cero_d.png", + }, + 17: { + "rating": "CERO_Z", + "category": "CERO", + "rating_cover_url": "https://www.igdb.com/icons/rating_icons/cero/cero_z.png", + }, + 18: { + "rating": "USK_0", + "category": "USK", + "rating_cover_url": "https://www.igdb.com/icons/rating_icons/usk/usk_0.png", + }, + 19: { + "rating": "USK_6", + "category": "USK", + "rating_cover_url": "https://www.igdb.com/icons/rating_icons/usk/usk_6.png", + }, + 20: { + "rating": "USK_12", + "category": "USK", + "rating_cover_url": "https://www.igdb.com/icons/rating_icons/usk/usk_12.png", + }, + 21: { + "rating": "USK_16", + "category": "USK", + "rating_cover_url": "https://www.igdb.com/icons/rating_icons/usk/usk_16.png", + }, + 22: { + "rating": "USK_18", + "category": "USK", + "rating_cover_url": "https://www.igdb.com/icons/rating_icons/usk/usk_18.png", + }, + 23: { + "rating": "GRAC_ALL", + "category": "GRAC", + "rating_cover_url": "https://www.igdb.com/icons/rating_icons/grac/grac_all.png", + }, + 24: { + "rating": "GRAC_Twelve", + "category": "GRAC", + "rating_cover_url": "https://www.igdb.com/icons/rating_icons/grac/grac_twelve.png", + }, + 25: { + "rating": "GRAC_Fifteen", + "category": "GRAC", + "rating_cover_url": "https://www.igdb.com/icons/rating_icons/grac/grac_fifteen.png", + }, + 26: { + "rating": "GRAC_Eighteen", + "category": "GRAC", + "rating_cover_url": "https://www.igdb.com/icons/rating_icons/grac/grac_eighteen.png", + }, + 27: { + "rating": "GRAC_TESTING", + "category": "GRAC", + "rating_cover_url": "https://www.igdb.com/icons/rating_icons/grac/grac_testing.png", + }, + 28: { + "rating": "CLASS_IND_L", + "category": "CLASS_IND", + "rating_cover_url": "https://www.igdb.com/icons/rating_icons/classind/classind_l.png", + }, + 29: { + "rating": "CLASS_IND_Ten", + "category": "CLASS_IND", + "rating_cover_url": "https://www.igdb.com/icons/rating_icons/classind/classind_ten.png", + }, + 30: { + "rating": "CLASS_IND_Twelve", + "category": "CLASS_IND", + "rating_cover_url": "https://www.igdb.com/icons/rating_icons/classind/classind_twelve.png", + }, + 31: { + "rating": "ACB_G", + "category": "ACB", + "rating_cover_url": "https://www.igdb.com/icons/rating_icons/acb/acb_g.png", + }, + 32: { + "rating": "ACB_PG", + "category": "ACB", + "rating_cover_url": "https://www.igdb.com/icons/rating_icons/acb/acb_pg.png", + }, + 33: { + "rating": "ACB_M", + "category": "ACB", + "rating_cover_url": "https://www.igdb.com/icons/rating_icons/acb/acb_m.png", + }, + 34: { + "rating": "ACB_MA15", + "category": "ACB", + "rating_cover_url": "https://www.igdb.com/icons/rating_icons/acb/acb_ma15.png", + }, + 35: { + "rating": "ACB_R18", + "category": "ACB", + "rating_cover_url": "https://www.igdb.com/icons/rating_icons/acb/acb_r18.png", + }, + 36: { + "rating": "ACB_RC", + "category": "ACB", + "rating_cover_url": "https://www.igdb.com/icons/rating_icons/acb/acb_rc.png", + }, +} diff --git a/backend/models/rom.py b/backend/models/rom.py index 98cda9785..e198fcb15 100644 --- a/backend/models/rom.py +++ b/backend/models/rom.py @@ -170,6 +170,10 @@ def companies(self) -> list[str]: def game_modes(self) -> list[str]: return self.igdb_metadata.get("game_modes", []) + @property + def age_ratings(self) -> list[str]: + return [r["rating"] for r in self.igdb_metadata.get("age_ratings", [])] + @property def fs_resources_path(self) -> str: return f"roms/{str(self.platform_id)}/{str(self.id)}" diff --git a/backend/models/tests/rom_response_example.json b/backend/models/tests/rom_response_example.json index 8323c9815..d52ed2f51 100644 --- a/backend/models/tests/rom_response_example.json +++ b/backend/models/tests/rom_response_example.json @@ -124,6 +124,7 @@ } } ], + "age_ratings": [{ "rating": 1 }, { "rating": 2 }], "expansions": [ { "id": 239930, diff --git a/frontend/src/__generated__/index.ts b/frontend/src/__generated__/index.ts index c6ae557a6..b2cc3b96d 100644 --- a/frontend/src/__generated__/index.ts +++ b/frontend/src/__generated__/index.ts @@ -20,6 +20,7 @@ export type { EmulationDict } from './models/EmulationDict'; export type { FirmwareSchema } from './models/FirmwareSchema'; export type { HeartbeatResponse } from './models/HeartbeatResponse'; export type { HTTPValidationError } from './models/HTTPValidationError'; +export type { IGDBAgeRating } from './models/IGDBAgeRating'; export type { IGDBMetadataPlatform } from './models/IGDBMetadataPlatform'; export type { IGDBRelatedGame } from './models/IGDBRelatedGame'; export type { MessageResponse } from './models/MessageResponse'; diff --git a/frontend/src/__generated__/models/DetailedRomSchema.ts b/frontend/src/__generated__/models/DetailedRomSchema.ts index 3adeafbd4..c7517e44f 100644 --- a/frontend/src/__generated__/models/DetailedRomSchema.ts +++ b/frontend/src/__generated__/models/DetailedRomSchema.ts @@ -39,6 +39,7 @@ export type DetailedRomSchema = { collections: Array; companies: Array; game_modes: Array; + age_ratings: Array; igdb_metadata: (RomIGDBMetadata | null); moby_metadata: (RomMobyMetadata | null); path_cover_s: (string | null); diff --git a/frontend/src/__generated__/models/IGDBAgeRating.ts b/frontend/src/__generated__/models/IGDBAgeRating.ts new file mode 100644 index 000000000..3e5b6f228 --- /dev/null +++ b/frontend/src/__generated__/models/IGDBAgeRating.ts @@ -0,0 +1,11 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type IGDBAgeRating = { + rating: string; + category: string; + rating_cover_url: string; +}; + diff --git a/frontend/src/__generated__/models/RomIGDBMetadata.ts b/frontend/src/__generated__/models/RomIGDBMetadata.ts index d1c753bb3..c6f3eaa21 100644 --- a/frontend/src/__generated__/models/RomIGDBMetadata.ts +++ b/frontend/src/__generated__/models/RomIGDBMetadata.ts @@ -3,6 +3,7 @@ /* tslint:disable */ /* eslint-disable */ +import type { IGDBAgeRating } from './IGDBAgeRating'; import type { IGDBMetadataPlatform } from './IGDBMetadataPlatform'; import type { IGDBRelatedGame } from './IGDBRelatedGame'; @@ -17,6 +18,7 @@ export type RomIGDBMetadata = { collections?: Array; companies?: Array; game_modes?: Array; + age_ratings?: Array; platforms?: Array; expansions?: Array; dlcs?: Array; diff --git a/frontend/src/__generated__/models/RomSchema.ts b/frontend/src/__generated__/models/RomSchema.ts index 5b5a76f3b..a29ed3217 100644 --- a/frontend/src/__generated__/models/RomSchema.ts +++ b/frontend/src/__generated__/models/RomSchema.ts @@ -32,6 +32,7 @@ export type RomSchema = { collections: Array; companies: Array; game_modes: Array; + age_ratings: Array; igdb_metadata: (RomIGDBMetadata | null); moby_metadata: (RomMobyMetadata | null); path_cover_s: (string | null); diff --git a/frontend/src/__generated__/models/SimpleRomSchema.ts b/frontend/src/__generated__/models/SimpleRomSchema.ts index a414c6985..43ecf715b 100644 --- a/frontend/src/__generated__/models/SimpleRomSchema.ts +++ b/frontend/src/__generated__/models/SimpleRomSchema.ts @@ -34,6 +34,7 @@ export type SimpleRomSchema = { collections: Array; companies: Array; game_modes: Array; + age_ratings: Array; igdb_metadata: (RomIGDBMetadata | null); moby_metadata: (RomMobyMetadata | null); path_cover_s: (string | null); diff --git a/frontend/src/components/Administration/Users/Dialog/CreateUser.vue b/frontend/src/components/Administration/Users/Dialog/CreateUser.vue index 3379e6d08..d882f8c97 100644 --- a/frontend/src/components/Administration/Users/Dialog/CreateUser.vue +++ b/frontend/src/components/Administration/Users/Dialog/CreateUser.vue @@ -22,7 +22,6 @@ emitter?.on("showCreateUserDialog", () => { show.value = true; }); -// Functions async function createUser() { await userApi .createUser(user.value) diff --git a/frontend/src/components/Administration/Users/Dialog/EditUser.vue b/frontend/src/components/Administration/Users/Dialog/EditUser.vue index 218674bc9..d1198dbc1 100644 --- a/frontend/src/components/Administration/Users/Dialog/EditUser.vue +++ b/frontend/src/components/Administration/Users/Dialog/EditUser.vue @@ -22,7 +22,6 @@ emitter?.on("showEditUserDialog", (userToEdit) => { show.value = true; }); -// Functions function triggerFileInput() { const fileInput = document.getElementById("file-input"); fileInput?.click(); diff --git a/frontend/src/components/Administration/Users/Table.vue b/frontend/src/components/Administration/Users/Table.vue index 6318c420a..25697193c 100644 --- a/frontend/src/components/Administration/Users/Table.vue +++ b/frontend/src/components/Administration/Users/Table.vue @@ -58,7 +58,6 @@ const usersPerPage = ref(isNaN(storedUsersPerPage) ? 25 : storedUsersPerPage); const pageCount = ref(0); emitter?.on("updateDataTablePages", updateDataTablePages); -// Functions function updateDataTablePages() { pageCount.value = Math.ceil(usersStore.allUsers.length / usersPerPage.value); } diff --git a/frontend/src/components/Details/Info/GameInfo.vue b/frontend/src/components/Details/Info/GameInfo.vue index 79333bd68..42ebaff42 100644 --- a/frontend/src/components/Details/Info/GameInfo.vue +++ b/frontend/src/components/Details/Info/GameInfo.vue @@ -7,7 +7,6 @@ import { useRouter } from "vue-router"; const props = defineProps<{ rom: DetailedRom }>(); const { xs } = useDisplay(); -const galleryFilterStore = storeGalleryFilter(); const show = ref(false); const carousel = ref(0); const router = useRouter(); @@ -49,6 +48,30 @@ function onFilterClick(filter: FilterType, value: string) { + +