diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 1b01f49fe..b76ad3d43 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -2,12 +2,12 @@ # To learn more about the format of this file, see https://docs.trunk.io/reference/trunk-yaml version: 0.1 cli: - version: 1.22.4 + version: 1.22.8 # Trunk provides extensibility via plugins. (https://docs.trunk.io/plugins) plugins: sources: - id: trunk - ref: v1.6.2 + ref: v1.6.5 uri: https://github.com/trunk-io/plugins # Many linters and tools depend on runtimes - configure them here. (https://docs.trunk.io/runtimes) runtimes: @@ -18,25 +18,25 @@ runtimes: # This is the section where you manage your linters. (https://docs.trunk.io/check/configuration) lint: enabled: - - markdownlint@0.41.0 - - eslint@9.9.1 - - actionlint@1.7.1 - - bandit@1.7.9 - - black@24.8.0 - - checkov@3.2.239 + - markdownlint@0.42.0 + - eslint@9.14.0 + - actionlint@1.7.4 + - bandit@1.7.10 + - black@24.10.0 + - checkov@3.2.296 - git-diff-check - isort@5.13.2 - mypy@1.13.0 - - osv-scanner@1.8.4 + - osv-scanner@1.9.1 - oxipng@9.1.2 - prettier@3.3.3 - - ruff@0.6.3 + - ruff@0.7.3 - shellcheck@0.10.0 - shfmt@3.6.0 - svgo@3.3.2 - taplo@0.9.3 - - trivy@0.54.1 - - trufflehog@3.81.10 + - trivy@0.56.2 + - trufflehog@3.83.6 - yamllint@1.35.1 ignore: - linters: [ALL] diff --git a/DEVELOPER_SETUP.md b/DEVELOPER_SETUP.md index 28d3f3fcd..fec76bc6d 100644 --- a/DEVELOPER_SETUP.md +++ b/DEVELOPER_SETUP.md @@ -130,7 +130,7 @@ docker exec -i romm-mariadb-dev mariadb -uroot -p < backend/romm_ ### - Run tests -*\_\_*Migrations will be run automatically when running the tests.\_\_\* +*\_*Migrations will be run automatically when running the tests.\_\_\_ ```sh cd backend diff --git a/backend/alembic/versions/0027_platforms_data.py b/backend/alembic/versions/0027_platforms_data.py new file mode 100644 index 000000000..2254d6cf5 --- /dev/null +++ b/backend/alembic/versions/0027_platforms_data.py @@ -0,0 +1,56 @@ +"""platforms_data + +Revision ID: 0027_platforms_data +Revises: 0026_romuser_status_fields +Create Date: 2024-11-17 23:05:31.038917 + +""" + +import sqlalchemy as sa +from alembic import op +from models.platform import DEFAULT_COVER_ASPECT_RATIO + +# revision identifiers, used by Alembic. +revision = "0027_platforms_data" +down_revision = "0026_romuser_status_fields" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("platforms", schema=None) as batch_op: + batch_op.add_column(sa.Column("category", sa.String(length=50), nullable=True)) + batch_op.add_column(sa.Column("generation", sa.Integer(), nullable=True)) + batch_op.add_column( + sa.Column("family_name", sa.String(length=1000), nullable=True) + ) + batch_op.add_column( + sa.Column("family_slug", sa.String(length=1000), nullable=True) + ) + batch_op.add_column(sa.Column("url", sa.String(length=1000), nullable=True)) + batch_op.add_column( + sa.Column("url_logo", sa.String(length=1000), nullable=True) + ) + batch_op.add_column( + sa.Column( + "aspect_ratio", + sa.String(length=10), + nullable=False, + server_default=DEFAULT_COVER_ASPECT_RATIO, + ) + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("platforms", schema=None) as batch_op: + batch_op.drop_column("url_logo") + batch_op.drop_column("url") + batch_op.drop_column("family_name") + batch_op.drop_column("family_slug") + batch_op.drop_column("generation") + batch_op.drop_column("category") + batch_op.drop_column("aspect_ratio") + # ### end Alembic commands ### diff --git a/backend/endpoints/config.py b/backend/endpoints/config.py index 1198fa6c5..2a06ab886 100644 --- a/backend/endpoints/config.py +++ b/backend/endpoints/config.py @@ -33,9 +33,6 @@ def get_config() -> ConfigResponse: EXCLUDED_MULTI_PARTS_FILES=cfg.EXCLUDED_MULTI_PARTS_FILES, PLATFORMS_BINDING=cfg.PLATFORMS_BINDING, PLATFORMS_VERSIONS=cfg.PLATFORMS_VERSIONS, - ROMS_FOLDER_NAME=cfg.ROMS_FOLDER_NAME, - FIRMWARE_FOLDER_NAME=cfg.FIRMWARE_FOLDER_NAME, - HIGH_PRIO_STRUCTURE_PATH=cfg.HIGH_PRIO_STRUCTURE_PATH, ) except ConfigNotReadableException as exc: log.critical(exc.message) diff --git a/backend/endpoints/platform.py b/backend/endpoints/platform.py index 54d4388f3..b7b7daf3a 100644 --- a/backend/endpoints/platform.py +++ b/backend/endpoints/platform.py @@ -112,17 +112,26 @@ def get_platform(request: Request, id: int) -> PlatformSchema: @protected_route(router.put, "/platforms/{id}", [Scope.PLATFORMS_WRITE]) -async def update_platform(request: Request) -> MessageResponse: +async def update_platform(request: Request, id: int) -> MessageResponse: """Update platform endpoint Args: request (Request): Fastapi Request object + id (int): Platform id Returns: MessageResponse: Standard message response """ + data = await request.json() + platform_db = db_platform_handler.get_platform(id) - return {"msg": "Enpoint not available yet"} + if not platform_db: + raise PlatformNotFoundInDatabaseException(id) + + platform_db.aspect_ratio = data.get("aspect_ratio", platform_db.aspect_ratio) + db_platform_handler.add_platform(platform_db) + + return {"msg": "Platform updated successfully"} @protected_route(router.delete, "/platforms/{id}", [Scope.PLATFORMS_WRITE]) diff --git a/backend/endpoints/responses/config.py b/backend/endpoints/responses/config.py index 4420a9354..0b537501c 100644 --- a/backend/endpoints/responses/config.py +++ b/backend/endpoints/responses/config.py @@ -10,6 +10,3 @@ class ConfigResponse(TypedDict): EXCLUDED_MULTI_PARTS_FILES: list[str] PLATFORMS_BINDING: dict[str, str] PLATFORMS_VERSIONS: dict[str, str] - ROMS_FOLDER_NAME: str - FIRMWARE_FOLDER_NAME: str - HIGH_PRIO_STRUCTURE_PATH: str diff --git a/backend/endpoints/responses/platform.py b/backend/endpoints/responses/platform.py index 5d7126794..b8827fa50 100644 --- a/backend/endpoints/responses/platform.py +++ b/backend/endpoints/responses/platform.py @@ -1,5 +1,6 @@ from datetime import datetime +from models.platform import DEFAULT_COVER_ASPECT_RATIO from pydantic import BaseModel, Field from .firmware import FirmwareSchema @@ -14,9 +15,15 @@ class PlatformSchema(BaseModel): igdb_id: int | None = None sgdb_id: int | None = None moby_id: int | None = None - logo_path: str | None = "" + category: str | None = None + generation: int | None = None + family_name: str | None = None + family_slug: str | None = None + url: str | None = None + url_logo: str | None = None + logo_path: str | None = None firmware: list[FirmwareSchema] = Field(default_factory=list) - + aspect_ratio: str = DEFAULT_COVER_ASPECT_RATIO created_at: datetime updated_at: datetime diff --git a/backend/endpoints/responses/search.py b/backend/endpoints/responses/search.py index 0e3011ad9..7f1de633d 100644 --- a/backend/endpoints/responses/search.py +++ b/backend/endpoints/responses/search.py @@ -9,6 +9,7 @@ class SearchRomSchema(BaseModel): summary: str igdb_url_cover: str = "" moby_url_cover: str = "" + platform_id: int class SearchCoverSchema(BaseModel): diff --git a/backend/endpoints/search.py b/backend/endpoints/search.py index e6b66b6e7..e07d757f1 100644 --- a/backend/endpoints/search.py +++ b/backend/endpoints/search.py @@ -99,6 +99,7 @@ async def search_rom( "summary": "", "igdb_url_cover": "", "moby_url_cover": "", + "platform_id": rom.platform_id, }, **item, } diff --git a/backend/endpoints/tests/test_config.py b/backend/endpoints/tests/test_config.py index e256089fd..d8ff367c9 100644 --- a/backend/endpoints/tests/test_config.py +++ b/backend/endpoints/tests/test_config.py @@ -21,5 +21,3 @@ def test_config(client): assert config.get("EXCLUDED_MULTI_PARTS_EXT") == [] assert config.get("EXCLUDED_MULTI_PARTS_FILES") == [] assert config.get("PLATFORMS_BINDING") == {} - assert config.get("ROMS_FOLDER_NAME") == "roms" - assert config.get("FIRMWARE_FOLDER_NAME") == "bios" diff --git a/backend/handler/metadata/igdb_handler.py b/backend/handler/metadata/igdb_handler.py index c367101a1..f17604804 100644 --- a/backend/handler/metadata/igdb_handler.py +++ b/backend/handler/metadata/igdb_handler.py @@ -35,6 +35,18 @@ class IGDBPlatform(TypedDict): slug: str igdb_id: int | None name: NotRequired[str] + category: NotRequired[str] + generation: NotRequired[str] + family_name: NotRequired[str] + family_slug: NotRequired[str] + url: NotRequired[str] + url_logo: NotRequired[str] + logo_path: NotRequired[str] + + +class IGDBMetadataPlatform(TypedDict): + igdb_id: int + name: str class IGDBAgeRating(TypedDict): @@ -43,11 +55,6 @@ class IGDBAgeRating(TypedDict): rating_cover_url: str -class IGDBMetadataPlatform(TypedDict): - igdb_id: int - name: str - - class IGDBRelatedGame(TypedDict): id: int name: str @@ -88,12 +95,10 @@ class IGDBRom(TypedDict): igdb_metadata: NotRequired[IGDBMetadata] -def extract_metadata_from_igdb_rom( - rom: dict, video_id: str | None = None -) -> IGDBMetadata: +def extract_metadata_from_igdb_rom(rom: dict) -> IGDBMetadata: return IGDBMetadata( { - "youtube_video_id": video_id, + "youtube_video_id": str(pydash.get(rom, "videos[0].video_id", None)), "total_rating": str(round(rom.get("total_rating", 0.0), 2)), "aggregated_rating": str(round(rom.get("aggregated_rating", 0.0), 2)), "first_release_date": rom.get("first_release_date", None), @@ -120,7 +125,9 @@ def extract_metadata_from_igdb_rom( id=e["id"], slug=e["slug"], name=e["name"], - cover_url=pydash.get(e, "cover.url", ""), + cover_url=MetadataHandler._normalize_cover_url( + pydash.get(e, "cover.url", "").replace("t_thumb", "t_1080p") + ), type="expansion", ) for e in rom.get("expansions", []) @@ -130,7 +137,9 @@ def extract_metadata_from_igdb_rom( id=d["id"], slug=d["slug"], name=d["name"], - cover_url=pydash.get(d, "cover.url", ""), + cover_url=MetadataHandler._normalize_cover_url( + pydash.get(d, "cover.url", "").replace("t_thumb", "t_1080p") + ), type="dlc", ) for d in rom.get("dlcs", []) @@ -140,7 +149,9 @@ def extract_metadata_from_igdb_rom( id=r["id"], slug=r["slug"], name=r["name"], - cover_url=pydash.get(r, "cover.url", ""), + cover_url=MetadataHandler._normalize_cover_url( + pydash.get(r, "cover.url", "").replace("t_thumb", "t_1080p") + ), type="remaster", ) for r in rom.get("remasters", []) @@ -150,7 +161,9 @@ def extract_metadata_from_igdb_rom( id=r["id"], slug=r["slug"], name=r["name"], - cover_url=pydash.get(r, "cover.url", ""), + cover_url=MetadataHandler._normalize_cover_url( + pydash.get(r, "cover.url", "").replace("t_thumb", "t_1080p") + ), type="remake", ) for r in rom.get("remakes", []) @@ -160,7 +173,9 @@ def extract_metadata_from_igdb_rom( id=g["id"], slug=g["slug"], name=g["name"], - cover_url=pydash.get(g, "cover.url", ""), + cover_url=MetadataHandler._normalize_cover_url( + pydash.get(g, "cover.url", "").replace("t_thumb", "t_1080p") + ), type="expanded", ) for g in rom.get("expanded_games", []) @@ -170,7 +185,9 @@ def extract_metadata_from_igdb_rom( id=p["id"], slug=p["slug"], name=p["name"], - cover_url=pydash.get(p, "cover.url", ""), + cover_url=MetadataHandler._normalize_cover_url( + pydash.get(p, "cover.url", "").replace("t_thumb", "t_1080p") + ), type="port", ) for p in rom.get("ports", []) @@ -180,7 +197,9 @@ def extract_metadata_from_igdb_rom( id=s["id"], slug=s["slug"], name=s["name"], - cover_url=pydash.get(s, "cover.url", ""), + cover_url=MetadataHandler._normalize_cover_url( + pydash.get(s, "cover.url", "").replace("t_thumb", "t_1080p") + ), type="similar", ) for s in rom.get("similar_games", []) @@ -199,7 +218,6 @@ class IGDBBaseHandler(MetadataHandler): self.games_fields = GAMES_FIELDS self.search_endpoint = f"{self.BASE_URL}/search" self.search_fields = SEARCH_FIELDS - self.video_endpoint = f"{self.BASE_URL}/game_videos" self.pagination_limit = 200 self.twitch_auth = TwitchAuth() self.headers = { @@ -318,6 +336,7 @@ class IGDBBaseHandler(MetadataHandler): for rom_name in rom_names ) + log.debug("Searching in games endpoint with category %s", category_filter) roms = await self._request( self.games_endpoint, data=f'search "{search_term}"; fields {",".join(self.games_fields)}; where platforms=[{platform_igdb_id}] {category_filter};', @@ -327,11 +346,16 @@ class IGDBBaseHandler(MetadataHandler): if is_exact_match(rom, search_term): return rom + log.debug("Searching expanded in search endpoint") roms_expanded = await self._request( self.search_endpoint, data=f'fields {",".join(self.search_fields)}; where game.platforms=[{platform_igdb_id}] & (name ~ *"{search_term}"* | alternative_name ~ *"{search_term}"*);', ) if roms_expanded: + log.debug( + "Searching expanded in games endpoint for expanded game %s", + roms_expanded[0]["game"], + ) extra_roms = await self._request( self.games_endpoint, f'fields {",".join(self.games_fields)}; where id={roms_expanded[0]["game"]["id"]};', @@ -354,13 +378,24 @@ class IGDBBaseHandler(MetadataHandler): self.platform_endpoint, data=f'fields {",".join(self.platforms_fields)}; where slug="{slug.lower()}";', ) - platform = pydash.get(platforms, "[0]", None) if platform: return IGDBPlatform( - igdb_id=platform["id"], + igdb_id=platform.get("id", None), slug=slug, - name=platform["name"], + name=platform.get("name", slug), + category=IGDB_PLATFORM_CATEGORIES.get( + platform.get("category", 0), "Unknown" + ), + generation=platform.get("generation", None), + family_name=pydash.get(platform, "platform_family.name", None), + family_slug=pydash.get(platform, "platform_family.slug", None), + url=platform.get("url", None), + url_logo=self._normalize_cover_url( + pydash.get(platform, "platform_logo.url", "").replace( + "t_thumb", "t_1080p" + ) + ), ) # Check if platform is a version if not found @@ -448,13 +483,19 @@ class IGDBBaseHandler(MetadataHandler): search_term = self.normalize_search_term(search_term) + log.debug("Searching for %s on IGDB with category", search_term) rom = await self._search_rom(search_term, platform_igdb_id, with_category=True) if not rom: + log.debug("Searching for %s on IGDB without category", search_term) rom = await self._search_rom(search_term, platform_igdb_id) # Split the search term since igdb struggles with colons if not rom and ":" in search_term: for term in search_term.split(":")[::-1]: + log.debug( + "Searching for %s on IGDB without category after splitting semicolon", + term, + ) rom = await self._search_rom(term, platform_igdb_id) if rom: break @@ -462,6 +503,10 @@ class IGDBBaseHandler(MetadataHandler): # Some MAME games have two titles split by a slash if not rom and "/" in search_term: for term in search_term.split("/"): + log.debug( + "Searching for %s on IGDB without category after splitting slash", + term, + ) rom = await self._search_rom(term.strip(), platform_igdb_id) if rom: break @@ -469,13 +514,6 @@ class IGDBBaseHandler(MetadataHandler): if not rom: return fallback_rom - # Get the video ID for the game - video_ids = await self._request( - self.video_endpoint, - f'fields video_id; where game={rom["id"]};', - ) - video_id = pydash.get(video_ids, "[0].video_id", None) - return IGDBRom( igdb_id=rom["id"], slug=rom["slug"], @@ -488,7 +526,7 @@ class IGDBBaseHandler(MetadataHandler): self._normalize_cover_url(s.get("url", "")).replace("t_thumb", "t_720p") for s in rom.get("screenshots", []) ], - igdb_metadata=extract_metadata_from_igdb_rom(rom, video_id), + igdb_metadata=extract_metadata_from_igdb_rom(rom), ) @check_twitch_token @@ -505,13 +543,6 @@ class IGDBBaseHandler(MetadataHandler): if not rom: return IGDBRom(igdb_id=None) - # Get the video ID for the game - video_ids = await self._request( - self.video_endpoint, - f'fields video_id; where game={rom["id"]};', - ) - video_id = pydash.get(video_ids, "[0].video_id", None) - return IGDBRom( igdb_id=rom["id"], slug=rom["slug"], @@ -524,7 +555,7 @@ class IGDBBaseHandler(MetadataHandler): self._normalize_cover_url(s.get("url", "")).replace("t_thumb", "t_720p") for s in rom.get("screenshots", []) ], - igdb_metadata=extract_metadata_from_igdb_rom(rom, video_id), + igdb_metadata=extract_metadata_from_igdb_rom(rom), ) @check_twitch_token @@ -694,7 +725,16 @@ class TwitchAuth(MetadataHandler): return token -PLATFORMS_FIELDS = ["id", "name"] +PLATFORMS_FIELDS = [ + "id", + "name", + "category", + "generation", + "url", + "platform_family.name", + "platform_family.slug", + "platform_logo.url", +] GAMES_FIELDS = [ "id", @@ -745,6 +785,7 @@ GAMES_FIELDS = [ "similar_games.name", "similar_games.cover.url", "age_ratings.rating", + "videos.video_id", ] SEARCH_FIELDS = ["game.id", "name"] @@ -974,6 +1015,16 @@ IGDB_PLATFORM_LIST = [ {"slug": "airconsole", "name": "AirConsole"}, ] +IGDB_PLATFORM_CATEGORIES: dict[int, str] = { + 0: "Unknown", + 1: "Console", + 2: "Arcade", + 3: "Platform", + 4: "Operative System", + 5: "Portable Console", + 6: "Computer", +} + IGDB_AGE_RATINGS: dict[int, IGDBAgeRating] = { 1: { "rating": "Three", diff --git a/backend/models/platform.py b/backend/models/platform.py index 2e0d9ffdc..06ceabdfb 100644 --- a/backend/models/platform.py +++ b/backend/models/platform.py @@ -11,6 +11,9 @@ if TYPE_CHECKING: from models.firmware import Firmware +DEFAULT_COVER_ASPECT_RATIO = "2 / 3" + + class Platform(BaseModel): __tablename__ = "platforms" @@ -21,6 +24,12 @@ class Platform(BaseModel): slug: Mapped[str] = mapped_column(String(length=50)) fs_slug: Mapped[str] = mapped_column(String(length=50)) name: Mapped[str] = mapped_column(String(length=400)) + category: Mapped[str | None] = mapped_column(String(length=50), default="") + generation: Mapped[int | None] + family_name: Mapped[str | None] = mapped_column(String(length=1000), default="") + family_slug: Mapped[str | None] = mapped_column(String(length=1000), default="") + url: Mapped[str | None] = mapped_column(String(length=1000), default="") + url_logo: Mapped[str | None] = mapped_column(String(length=1000), default="") logo_path: Mapped[str | None] = mapped_column(String(length=1000), default="") roms: Mapped[list[Rom]] = relationship(back_populates="platform") @@ -28,6 +37,10 @@ class Platform(BaseModel): lazy="selectin", back_populates="platform" ) + aspect_ratio: Mapped[str] = mapped_column( + String, server_default=DEFAULT_COVER_ASPECT_RATIO + ) + # This runs a subquery to get the count of roms for the platform rom_count = column_property( select(func.count(Rom.id)).where(Rom.platform_id == id).scalar_subquery() diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5b461f6b5..fb60cd9b4 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -29,7 +29,7 @@ "socket.io-client": "^4.7.5", "vue": "^3.4.27", "vue-router": "^4.3.2", - "vuetify": "^3.6.5", + "vuetify": "^3.7.4", "webfontloader": "^1.6.28" }, "devDependencies": { @@ -56,7 +56,7 @@ "vite-plugin-pwa": "^0.14.7", "vite-plugin-static-copy": "0.17.1", "vite-plugin-vuetify": "^1.0.2", - "vue-tsc": "^1.8.27" + "vue-tsc": "^2.1.10" }, "engines": { "node": "18" @@ -434,17 +434,17 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.1", - "dev": true, - "license": "MIT", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.5", - "dev": true, - "license": "MIT", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "engines": { "node": ">=6.9.0" } @@ -562,8 +562,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.5", - "license": "MIT", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", + "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", + "dependencies": { + "@babel/types": "^7.26.0" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -1763,13 +1767,12 @@ } }, "node_modules/@babel/types": { - "version": "7.24.5", - "dev": true, - "license": "MIT", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", + "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", "dependencies": { - "@babel/helper-string-parser": "^7.24.1", - "@babel/helper-validator-identifier": "^7.24.5", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2934,28 +2937,29 @@ } }, "node_modules/@volar/language-core": { - "version": "1.11.1", + "version": "2.4.10", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.10.tgz", + "integrity": "sha512-hG3Z13+nJmGaT+fnQzAkS0hjJRa2FCeqZt6Bd+oGNhUkQ+mTFsDETg5rqUTxyzIh5pSOGY7FHCWUS8G82AzLCA==", "dev": true, - "license": "MIT", "dependencies": { - "@volar/source-map": "1.11.1" + "@volar/source-map": "2.4.10" } }, "node_modules/@volar/source-map": { - "version": "1.11.1", - "dev": true, - "license": "MIT", - "dependencies": { - "muggle-string": "^0.3.1" - } + "version": "2.4.10", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.10.tgz", + "integrity": "sha512-OCV+b5ihV0RF3A7vEvNyHPi4G4kFa6ukPmyVocmqm5QzOd8r5yAtiNvaPEjl8dNvgC/lj4JPryeeHLdXd62rWA==", + "dev": true }, "node_modules/@volar/typescript": { - "version": "1.11.1", + "version": "2.4.10", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.10.tgz", + "integrity": "sha512-F8ZtBMhSXyYKuBfGpYwqA5rsONnOwAVvjyE7KPYJ7wgZqo2roASqNWUnianOomJX5u1cxeRooHV59N0PhvEOgw==", "dev": true, - "license": "MIT", "dependencies": { - "@volar/language-core": "1.11.1", - "path-browserify": "^1.0.1" + "@volar/language-core": "2.4.10", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" } }, "node_modules/@vue/compiler-core": { @@ -3000,24 +3004,34 @@ "@vue/shared": "3.4.27" } }, + "node_modules/@vue/compiler-vue2": { + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", + "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", + "dev": true, + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, "node_modules/@vue/devtools-api": { "version": "6.6.1", "license": "MIT" }, "node_modules/@vue/language-core": { - "version": "1.8.27", + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.1.10.tgz", + "integrity": "sha512-DAI289d0K3AB5TUG3xDp9OuQ71CnrujQwJrQnfuZDwo6eGNf0UoRlPuaVNO+Zrn65PC3j0oB2i7mNmVPggeGeQ==", "dev": true, - "license": "MIT", "dependencies": { - "@volar/language-core": "~1.11.1", - "@volar/source-map": "~1.11.1", - "@vue/compiler-dom": "^3.3.0", - "@vue/shared": "^3.3.0", - "computeds": "^0.0.1", + "@volar/language-core": "~2.4.8", + "@vue/compiler-dom": "^3.5.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.5.0", + "alien-signals": "^0.2.0", "minimatch": "^9.0.3", - "muggle-string": "^0.3.1", - "path-browserify": "^1.0.1", - "vue-template-compiler": "^2.7.14" + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1" }, "peerDependencies": { "typescript": "*" @@ -3028,18 +3042,49 @@ } } }, + "node_modules/@vue/language-core/node_modules/@vue/compiler-core": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz", + "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/shared": "3.5.13", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/language-core/node_modules/@vue/compiler-dom": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz", + "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==", + "dev": true, + "dependencies": { + "@vue/compiler-core": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/language-core/node_modules/@vue/shared": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", + "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", + "dev": true + }, "node_modules/@vue/language-core/node_modules/brace-expansion": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/@vue/language-core/node_modules/minimatch": { - "version": "9.0.4", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -3141,6 +3186,12 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/alien-signals": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-0.2.2.tgz", + "integrity": "sha512-cZIRkbERILsBOXTQmMrxc9hgpxglstn69zm+F1ARf4aPAzdAFYd6sBq87ErO0Fj3DV94tglcyHG5kQz9nDC/8A==", + "dev": true + }, "node_modules/ansi-regex": { "version": "5.0.1", "dev": true, @@ -3577,11 +3628,6 @@ "devOptional": true, "license": "MIT" }, - "node_modules/computeds": { - "version": "0.0.1", - "dev": true, - "license": "MIT" - }, "node_modules/concat-map": { "version": "0.0.1", "dev": true, @@ -3644,9 +3690,10 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, - "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -3733,8 +3780,9 @@ }, "node_modules/de-indent": { "version": "1.0.2", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true }, "node_modules/debug": { "version": "4.3.4", @@ -5502,11 +5550,12 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, - "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -5579,9 +5628,10 @@ "license": "MIT" }, "node_modules/muggle-string": { - "version": "0.3.1", - "dev": true, - "license": "MIT" + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true }, "node_modules/nanoid": { "version": "5.0.7", @@ -5765,8 +5815,9 @@ }, "node_modules/path-browserify": { "version": "1.0.1", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true }, "node_modules/path-exists": { "version": "4.0.0", @@ -6265,9 +6316,10 @@ "license": "Apache-2.0" }, "node_modules/rollup": { - "version": "2.79.1", + "version": "2.79.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", + "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", "devOptional": true, - "license": "MIT", "bin": { "rollup": "dist/bin/rollup" }, @@ -6740,14 +6792,6 @@ "dev": true, "license": "MIT" }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -7079,7 +7123,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.11.tgz", "integrity": "sha512-K/jGKL/PgbIgKCiJo5QbASQhFiV02X9Jh+Qq0AKCRCRKZtOTVi4t6wh75FDpGf2N9rYOnzH87OEFQNaFy6pdxQ==", "devOptional": true, - "license": "MIT", "dependencies": { "esbuild": "^0.15.9", "postcss": "^8.4.18", @@ -7147,9 +7190,10 @@ } }, "node_modules/vite-plugin-pwa/node_modules/rollup": { - "version": "3.29.4", + "version": "3.29.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz", + "integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==", "dev": true, - "license": "MIT", "bin": { "rollup": "dist/bin/rollup" }, @@ -7231,6 +7275,12 @@ "esbuild-windows-arm64": "0.15.18" } }, + "node_modules/vscode-uri": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", + "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", + "dev": true + }, "node_modules/vue": { "version": "3.4.27", "license": "MIT", @@ -7286,34 +7336,27 @@ "vue": "^3.2.0" } }, - "node_modules/vue-template-compiler": { - "version": "2.7.16", - "dev": true, - "license": "MIT", - "dependencies": { - "de-indent": "^1.0.2", - "he": "^1.2.0" - } - }, "node_modules/vue-tsc": { - "version": "1.8.27", + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.1.10.tgz", + "integrity": "sha512-RBNSfaaRHcN5uqVqJSZh++Gy/YUzryuv9u1aFWhsammDJXNtUiJMNoJ747lZcQ68wUQFx6E73y4FY3D8E7FGMA==", "dev": true, - "license": "MIT", "dependencies": { - "@volar/typescript": "~1.11.1", - "@vue/language-core": "1.8.27", + "@volar/typescript": "~2.4.8", + "@vue/language-core": "2.1.10", "semver": "^7.5.4" }, "bin": { "vue-tsc": "bin/vue-tsc.js" }, "peerDependencies": { - "typescript": "*" + "typescript": ">=5.0.0" } }, "node_modules/vuetify": { - "version": "3.6.5", - "license": "MIT", + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.7.4.tgz", + "integrity": "sha512-Y8UU5wUDQXC3oz2uumPb8IOdvB4XMCxtxnmqdOc+LihNuPlkSgxIwf92ndRzbOtJFKHsggFUxpyLqpQp+A+5kg==", "engines": { "node": "^12.20 || >=14.13" }, @@ -7325,7 +7368,6 @@ "typescript": ">=4.7", "vite-plugin-vuetify": ">=1.0.0", "vue": "^3.3.0", - "vue-i18n": "^9.0.0", "webpack-plugin-vuetify": ">=2.0.0" }, "peerDependenciesMeta": { @@ -7335,9 +7377,6 @@ "vite-plugin-vuetify": { "optional": true }, - "vue-i18n": { - "optional": true - }, "webpack-plugin-vuetify": { "optional": true } diff --git a/frontend/package.json b/frontend/package.json index 832899f64..9d0fdafa2 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -47,7 +47,7 @@ "socket.io-client": "^4.7.5", "vue": "^3.4.27", "vue-router": "^4.3.2", - "vuetify": "^3.6.5", + "vuetify": "^3.7.4", "webfontloader": "^1.6.28" }, "devDependencies": { @@ -74,7 +74,7 @@ "vite-plugin-pwa": "^0.14.7", "vite-plugin-static-copy": "0.17.1", "vite-plugin-vuetify": "^1.0.2", - "vue-tsc": "^1.8.27" + "vue-tsc": "^2.1.10" }, "engines": { "node": "18" diff --git a/frontend/src/__generated__/models/ConfigResponse.ts b/frontend/src/__generated__/models/ConfigResponse.ts index 71f01543b..4b60f7097 100644 --- a/frontend/src/__generated__/models/ConfigResponse.ts +++ b/frontend/src/__generated__/models/ConfigResponse.ts @@ -12,8 +12,5 @@ export type ConfigResponse = { EXCLUDED_MULTI_PARTS_FILES: Array; PLATFORMS_BINDING: Record; PLATFORMS_VERSIONS: Record; - ROMS_FOLDER_NAME: string; - FIRMWARE_FOLDER_NAME: string; - HIGH_PRIO_STRUCTURE_PATH: string; }; diff --git a/frontend/src/__generated__/models/PlatformSchema.ts b/frontend/src/__generated__/models/PlatformSchema.ts index 822c036ad..67bfefcc3 100644 --- a/frontend/src/__generated__/models/PlatformSchema.ts +++ b/frontend/src/__generated__/models/PlatformSchema.ts @@ -14,8 +14,15 @@ export type PlatformSchema = { igdb_id?: (number | null); sgdb_id?: (number | null); moby_id?: (number | null); + category?: (string | null); + generation?: (number | null); + family_name?: (string | null); + family_slug?: (string | null); + url?: (string | null); + url_logo?: (string | null); logo_path?: (string | null); firmware?: Array; + aspect_ratio?: string; created_at: string; updated_at: string; }; diff --git a/frontend/src/__generated__/models/SearchRomSchema.ts b/frontend/src/__generated__/models/SearchRomSchema.ts index 8b5a102a3..efff5ede7 100644 --- a/frontend/src/__generated__/models/SearchRomSchema.ts +++ b/frontend/src/__generated__/models/SearchRomSchema.ts @@ -11,5 +11,6 @@ export type SearchRomSchema = { summary: string; igdb_url_cover?: string; moby_url_cover?: string; + platform_id: number; }; diff --git a/frontend/src/components/Details/Info/FileInfo.vue b/frontend/src/components/Details/Info/FileInfo.vue index 551154f5f..18dac9539 100644 --- a/frontend/src/components/Details/Info/FileInfo.vue +++ b/frontend/src/components/Details/Info/FileInfo.vue @@ -5,13 +5,12 @@ import romApi from "@/services/api/rom"; import storeAuth from "@/stores/auth"; import type { Collection } from "@/stores/collections"; import storeDownload from "@/stores/download"; -import type { Platform } from "@/stores/platforms"; import type { DetailedRom } from "@/stores/roms"; import { formatBytes } from "@/utils"; import { ref, watch } from "vue"; // Props -const props = defineProps<{ rom: DetailedRom; platform: Platform }>(); +const props = defineProps<{ rom: DetailedRom }>(); const downloadStore = storeDownload(); const auth = storeAuth(); const romUser = ref(props.rom.rom_user); @@ -47,7 +46,7 @@ watch( - + Info - - + + Size: {{ formatBytes(rom.file_size_bytes) }} SHA-1: {{ rom.sha1_hash }} @@ -127,7 +126,7 @@ watch( v-if="!rom.multi && rom.md5_hash" size="small" label - class="mx-1 my-1" + class="ml-1" > MD5: {{ rom.md5_hash }} @@ -135,7 +134,7 @@ watch( v-if="!rom.multi && rom.crc_hash" size="small" label - class="mx-1 my-1" + class="ml-1" > CRC: {{ rom.crc_hash }} diff --git a/frontend/src/components/Details/Info/GameInfo.vue b/frontend/src/components/Details/Info/GameInfo.vue index 42ebaff42..e8fbf7a0e 100644 --- a/frontend/src/components/Details/Info/GameInfo.vue +++ b/frontend/src/components/Details/Info/GameInfo.vue @@ -1,17 +1,23 @@