Merge branch 'master' into fix/ui_tweaks
2
.github/ISSUE_TEMPLATE/custom.md
vendored
@@ -6,5 +6,3 @@ labels: other
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/ui-ux.md
vendored
@@ -6,5 +6,3 @@ labels: ui/ux
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
2
.github/resources/isotipo.svg
vendored
@@ -228,4 +228,4 @@
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
2
.github/resources/logotipo.svg
vendored
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
2
.github/resources/romm_complete.svg
vendored
@@ -256,4 +256,4 @@
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
21
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
# See https://pre-commit.com for more information
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
repos:
|
||||
- repo: 'https://github.com/pre-commit/pre-commit-hooks'
|
||||
rev: v3.2.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
- id: check-yaml
|
||||
- id: check-added-large-files
|
||||
- repo: 'https://github.com/astral-sh/ruff-pre-commit'
|
||||
rev: v0.4.4
|
||||
hooks:
|
||||
- id: ruff
|
||||
args:
|
||||
- '--fix'
|
||||
- id: ruff-format
|
||||
# - repo: 'https://github.com/pre-commit/mirrors-mypy'
|
||||
# rev: v1.10.0
|
||||
# hooks:
|
||||
# - id: mypy
|
||||
26
CHANGELOG.md
@@ -1,13 +1,13 @@
|
||||
# v3.0.3 (_25-03-2024_)
|
||||
|
||||
## Fixed
|
||||
## Fixed
|
||||
- Removed ``PUID/GUID`` environment variables. Instead, use the ``user: "XXXX:XXXX"`` entry in docker-compose to set the owner of newly created file/folders and mounted volumes
|
||||
|
||||
<br>
|
||||
|
||||
# v3.0.2 (_25-03-2024_)
|
||||
|
||||
> [!WARNING]
|
||||
> [!WARNING]
|
||||
> RomM has been organized in ``github`` and ``dockerhub``! New images will be upload to ``rommapp/romm`` repository on dockerhub and ghcr.
|
||||
|
||||
## Added
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
# v3.0.1 (_13-03-2024_)
|
||||
|
||||
> [!WARNING]
|
||||
> [!WARNING]
|
||||
> This hotfix requires a few changes:
|
||||
|
||||
* A new volume has to be bound to `/redis-data`, check the [docker-compose.example.yml file](https://github.com/rommapp/romm/blob/3.0.1/examples/docker-compose.example.yml)
|
||||
@@ -89,7 +89,7 @@
|
||||
|
||||
## Changed
|
||||
- Platforms can only be deleted from RomM's database. Checkbox to delete platform from filesystem have been removed.
|
||||
|
||||
|
||||
<br>
|
||||
|
||||
# v2.3.0 (_08-01-2024_)
|
||||
@@ -123,7 +123,7 @@
|
||||
|
||||
## Changed
|
||||
- Now region and language tags are case insensitive to show ``emojis``.
|
||||
|
||||
|
||||
<br>
|
||||
|
||||
# v2.2.0 (_31-12-2023_)
|
||||
@@ -132,7 +132,7 @@
|
||||
- Support for ``productID`` in the file name for ``switch`` titles.
|
||||
- Rom name sorting now sorts smarter, avoiding leading articles such ``The`` or ``A``, like in ``The Legend of Zelda``. Closes [#449](https://github.com/rommapp/romm/issues/449) and [#450](https://github.com/rommapp/romm/issues/450)
|
||||
- Support for file names with multiple ``regions`` and ``languages``. Also uses ``emojis`` to display them. Closes [#473](https://github.com/rommapp/romm/issues/473)
|
||||
- Added a button to manually ``run`` all tasks. Closes [#437](https://github.com/rommapp/romm/issues/437)
|
||||
- Added a button to manually ``run`` all tasks. Closes [#437](https://github.com/rommapp/romm/issues/437)
|
||||
- Now if a game doesn't have cover, it will show a screenshot if available. Closes [#455](https://github.com/rommapp/romm/issues/455)
|
||||
- Added a little warning icon in the platform selector if the platform is not found by IGDB.
|
||||
- Now if a platform is not found by IGDB, the platform name is ``titleized``. Ex: ``pocket-challenge-v2 -> Pocket Challenge V2``. Closes [#486](https://github.com/rommapp/romm/issues/486)
|
||||
@@ -140,7 +140,7 @@
|
||||
- Support for support for AES/MVS. Closes [#503](https://github.com/rommapp/romm/issues/503)
|
||||
- Added [Helm Chart](https://artifacthub.io/packages/helm/crystalnet/romm) to deploy on Kubernetes by @psych0d0g
|
||||
- ``Rescan unidentified`` added to the scan view, allowing to rescan only those entries that IGDB couldn't identify in previous scans. Closes [#519](https://github.com/rommapp/romm/issues/519)
|
||||
- ``Config file`` visualization added to the new ``Config`` tab in the ``Control Panel``. Partially implements some concepts of [#457](https://github.com/rommapp/romm/issues/457)
|
||||
- ``Config file`` visualization added to the new ``Config`` tab in the ``Control Panel``. Partially implements some concepts of [#457](https://github.com/rommapp/romm/issues/457)
|
||||
|
||||
## Fixed
|
||||
- Now sorting by size in the gallery table view works as expected. Closes [#423](https://github.com/rommapp/romm/issues/423)
|
||||
@@ -156,7 +156,7 @@
|
||||
- Fixed multi-part games download when any part of the game contains a ``comma`` in the name. Closes [#520](https://github.com/rommapp/romm/issues/520)
|
||||
|
||||
## Changed
|
||||
- Improved the docker ``init scripts`` handling by @psych0d0g.
|
||||
- Improved the docker ``init scripts`` handling by @psych0d0g.
|
||||
- Now the ``scan`` can continue after failing finding roms for one platform. Closes [#460](https://github.com/rommapp/romm/issues/460)
|
||||
- Logs improved a lot.
|
||||
|
||||
@@ -382,7 +382,7 @@
|
||||
## Fixed
|
||||
- Download feature is now fixed for RomM structucture 1
|
||||
## Changed
|
||||
- Library path binding changed from ``/library`` to ``/romm/library``. Check [docker-compose](examples/docker-compose.example.yml)
|
||||
- Library path binding changed from ``/library`` to ``/romm/library``. Check [docker-compose](examples/docker-compose.example.yml)
|
||||
|
||||
<br>
|
||||
|
||||
@@ -403,7 +403,7 @@
|
||||
|
||||
# v1.5 (_30-03-2023_)
|
||||
|
||||
> [!WARNING]
|
||||
> [!WARNING]
|
||||
> In order to make the new features to work, it is mandatory this time to drop all the database. This will only make you need to re-scan, but you won't lose the cover changes or file changes you made. I apologize for the inconveniences this may cause, as this is a new software, it may change a little bit the first weeks, at least until I can develop a proper way to migrate between versions. I hope you can understand these initial wipes in order to make a better tool.
|
||||
|
||||
## Added
|
||||
@@ -435,7 +435,7 @@
|
||||
## Fixed
|
||||
**`Breaking change`** - **This breaking change only applies for mariaDB users**:
|
||||
|
||||
Some users reported errors when scanning files with large names because file_names are limited to 100 characters in the database. As I want to give as much flexibility as possible I changed some database columns.
|
||||
Some users reported errors when scanning files with large names because file_names are limited to 100 characters in the database. As I want to give as much flexibility as possible I changed some database columns.
|
||||
|
||||
If you didn't make a lot of manual changes you can just get rid of the database and recreate it, scanning your library again. If you did some changes and don't want to lose the progress, you should do this changes manually from the mariadb container (or wherever you have your mariadb database) since there is not any kind of CLI for this migration.
|
||||
|
||||
@@ -486,8 +486,8 @@ Columns to modify (examples in case that you set it with database name as romm,
|
||||
- Game names parentheses are now omitted when searching game in IGDB, allowing game names to have tags.
|
||||
|
||||
<br>
|
||||
|
||||
|
||||
# v1.0 (_27-03-2023_)
|
||||
|
||||
|
||||
## Added
|
||||
- Birth of RomM
|
||||
|
||||
@@ -24,7 +24,7 @@ cp env.template .env
|
||||
### - Install system dependencies
|
||||
|
||||
```sh
|
||||
# https://mariadb.com/docs/skysql-previous-release/connect/programming-languages/c/install/#Installation_via_Package_Repository_(Linux):
|
||||
# https://mariadb.com/docs/skysql-previous-release/connect/programming-languages/c/install/#Installation_via_Package_Repository_(Linux):
|
||||
sudo apt install libmariadb3 libmariadb-dev pipx
|
||||
```
|
||||
|
||||
|
||||
20
README.md
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
<img src=".github/resources/romm_complete.svg" height="220px" width="auto" alt="romm logo">
|
||||
|
||||
|
||||
<h3 style="font-size: 25px;">
|
||||
A beautiful, powerful, self-hosted rom manager.
|
||||
</h3>
|
||||
@@ -18,7 +18,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
> [!WARNING]
|
||||
> [!WARNING]
|
||||
> Version 3.0 introduces exciting new fetures that require some changes to how RomM is setup and configured. **If you're currently running a 2.x version, please review the [migration guide](https://github.com/rommapp/romm/wiki/Upgrading-to-3.0) before upgrading.**
|
||||
|
||||
# Overview
|
||||
@@ -51,7 +51,7 @@ Before running the [image][docker-tags], ensure that Docker is installed and run
|
||||
3. Create a docker-compose.yml file by referring to the example [docker-compose.yml][docker-compose-example] file for guidance, and customize it for your setup with [the available environment variables][wiki-env-variables].
|
||||
4. Launch the container(s) with `docker-compose up -d`.
|
||||
|
||||
> [!NOTE]
|
||||
> [!NOTE]
|
||||
> **If you are having issues with RomM, please review the [wiki page][wiki-troubleshooting-url] for troubleshooting steps and common issues.**
|
||||
|
||||
# Configuration
|
||||
@@ -92,6 +92,14 @@ As mentioned in the installation section, RomM requires a specific folder struct
|
||||
│ │ ├─ my_game_cd2.iso
|
||||
│ │
|
||||
│ ├─ rom_1.iso
|
||||
├─ bios/
|
||||
│ ├─ gba/
|
||||
│ │ ├─ gba_bios.bin
|
||||
│ │
|
||||
│ ├─ ps/
|
||||
│ ├─ scph1001.bin
|
||||
| ├─ scph5501.bin
|
||||
| ├─ scph5502.bin
|
||||
</pre>
|
||||
</td>
|
||||
<td>
|
||||
@@ -106,6 +114,8 @@ As mentioned in the installation section, RomM requires a specific folder struct
|
||||
│ ├─ roms/
|
||||
│ ├─ rom_1.gba
|
||||
│ ├─ rom_2.gba
|
||||
| ├─ bios/
|
||||
| ├─ gba_bios.bin
|
||||
│
|
||||
├─ ps/
|
||||
│ ├─ roms/
|
||||
@@ -114,6 +124,10 @@ As mentioned in the installation section, RomM requires a specific folder struct
|
||||
│ │ ├─ my_game_cd2.iso
|
||||
│ │
|
||||
│ ├─ rom_1.iso
|
||||
| ├─ bios/
|
||||
| ├─ scph1001.bin
|
||||
| ├─ scph5501.bin
|
||||
| ├─ scph5502.bin
|
||||
</pre>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -5,6 +5,7 @@ Revises: 2.0.0
|
||||
Create Date: 2023-09-12 18:18:27.158732
|
||||
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.exc import OperationalError
|
||||
@@ -87,7 +88,7 @@ def upgrade() -> None:
|
||||
nullable=True,
|
||||
existing_server_default=sa.text("'[]'"),
|
||||
)
|
||||
|
||||
|
||||
try:
|
||||
with op.batch_alter_table("roms", schema=None) as batch_op:
|
||||
batch_op.create_foreign_key(
|
||||
|
||||
@@ -5,6 +5,7 @@ Revises: 0009_models_refactor
|
||||
Create Date: 2023-09-14 09:57:13.487331
|
||||
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
@@ -5,6 +5,7 @@ Revises: 0010_igdb_id_integerr
|
||||
Create Date: 2023-09-16 15:28:29.221475
|
||||
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
@@ -5,40 +5,43 @@ Revises: 0011_drop_has_cover
|
||||
Create Date: 2023-12-03 10:54:46.859106
|
||||
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '0012_add_regions_languages'
|
||||
down_revision = '0011_drop_has_cover'
|
||||
revision = "0012_add_regions_languages"
|
||||
down_revision = "0011_drop_has_cover"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('roms', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('regions', sa.JSON(), nullable=True))
|
||||
batch_op.add_column(sa.Column('languages', sa.JSON(), nullable=True))
|
||||
with op.batch_alter_table("roms", schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column("regions", sa.JSON(), nullable=True))
|
||||
batch_op.add_column(sa.Column("languages", sa.JSON(), nullable=True))
|
||||
|
||||
with op.batch_alter_table('roms', schema=None) as batch_op:
|
||||
with op.batch_alter_table("roms", schema=None) as batch_op:
|
||||
# Set default values for languages and regions
|
||||
batch_op.execute("UPDATE roms SET languages = '[]'")
|
||||
batch_op.execute("UPDATE roms SET regions = JSON_ARRAY(region)")
|
||||
batch_op.drop_column('region')
|
||||
batch_op.drop_column("region")
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('roms', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('region', mysql.VARCHAR(length=20), nullable=True))
|
||||
with op.batch_alter_table("roms", schema=None) as batch_op:
|
||||
batch_op.add_column(
|
||||
sa.Column("region", mysql.VARCHAR(length=20), nullable=True)
|
||||
)
|
||||
|
||||
with op.batch_alter_table('roms', schema=None) as batch_op:
|
||||
with op.batch_alter_table("roms", schema=None) as batch_op:
|
||||
batch_op.execute("UPDATE roms SET region = JSON_EXTRACT(regions, '$[0]')")
|
||||
batch_op.drop_column('languages')
|
||||
batch_op.drop_column('regions')
|
||||
batch_op.drop_column("languages")
|
||||
batch_op.drop_column("regions")
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
@@ -62,7 +62,9 @@ def migrate_to_mysql() -> None:
|
||||
if table_name == "alembic_version":
|
||||
continue
|
||||
|
||||
table_data = sqlite_conn.execute(text(f"SELECT * FROM {table_name}")).fetchall()
|
||||
table_data = sqlite_conn.execute(
|
||||
text(f"SELECT * FROM {table_name}")
|
||||
).fetchall()
|
||||
|
||||
# Insert data into MariaDB table
|
||||
for row in table_data:
|
||||
|
||||
@@ -5,6 +5,7 @@ Revises: 0014_asset_files
|
||||
Create Date: 2024-02-13 17:57:25.936825
|
||||
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
@@ -5,35 +5,42 @@ Revises: 0016_user_last_login_active
|
||||
Create Date: 2024-04-28 11:58:18.927734
|
||||
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '0017_rom_notes'
|
||||
down_revision = '0016_user_last_login_active'
|
||||
revision = "0017_rom_notes"
|
||||
down_revision = "0016_user_last_login_active"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('rom_notes',
|
||||
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column('last_edited_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('raw_markdown', sa.Text(), nullable=False),
|
||||
sa.Column('is_public', sa.Boolean(), nullable=True),
|
||||
sa.Column('rom_id', sa.Integer(), nullable=False),
|
||||
sa.Column('user_id', sa.Integer(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['rom_id'], ['roms.id'], ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('rom_id', 'user_id', name='unique_rom_user_note')
|
||||
op.create_table(
|
||||
"rom_notes",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column(
|
||||
"last_edited_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column("raw_markdown", sa.Text(), nullable=False),
|
||||
sa.Column("is_public", sa.Boolean(), nullable=True),
|
||||
sa.Column("rom_id", sa.Integer(), nullable=False),
|
||||
sa.Column("user_id", sa.Integer(), nullable=False),
|
||||
sa.ForeignKeyConstraint(["rom_id"], ["roms.id"], ondelete="CASCADE"),
|
||||
sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="CASCADE"),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint("rom_id", "user_id", name="unique_rom_user_note"),
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('rom_notes')
|
||||
op.drop_table("rom_notes")
|
||||
# ### end Alembic commands ###
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
"""init to 1.6.2
|
||||
|
||||
Revision ID: 1.6.2
|
||||
Revises:
|
||||
Revises:
|
||||
Create Date: 2023-04-10 23:02:37.472055
|
||||
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.exc import OperationalError
|
||||
|
||||
@@ -5,6 +5,7 @@ Revises: 1.6.2
|
||||
Create Date: 2023-04-10 23:13:43.591414
|
||||
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ Revises: 1.6.3
|
||||
Create Date: 2023-04-15 02:28:24.023871
|
||||
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ Revises: 1.8
|
||||
Create Date: 2023-04-17 12:03:19.163501
|
||||
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ Revises: 1.8.1
|
||||
Create Date: 2023-05-09 00:00:19.143526
|
||||
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ Revises: 1.8.2
|
||||
Create Date: 2023-05-17 12:59:44.344356
|
||||
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
|
||||
@@ -5,6 +5,7 @@ Revises: 1.7.1
|
||||
Create Date: 2023-04-17 12:03:19.163501
|
||||
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ Revises: 1.8.3
|
||||
Create Date: 2023-08-10 22:18:24.012779
|
||||
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
@@ -6,10 +6,11 @@ from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
# UVICORN
|
||||
# GUNICORN
|
||||
DEV_PORT: Final = int(os.environ.get("VITE_BACKEND_DEV_PORT", "5000"))
|
||||
DEV_HOST: Final = "0.0.0.0"
|
||||
ROMM_HOST: Final = os.environ.get("ROMM_HOST", DEV_HOST)
|
||||
GUNICORN_WORKERS: Final = int(os.environ.get("GUNICORN_WORKERS", 2))
|
||||
|
||||
# PATHS
|
||||
ROMM_BASE_PATH: Final = os.environ.get("ROMM_BASE_PATH", "/romm")
|
||||
|
||||
@@ -13,7 +13,7 @@ def begin_session(func):
|
||||
|
||||
try:
|
||||
with args[0].session.begin() as s:
|
||||
kwargs['session'] = s
|
||||
kwargs["session"] = s
|
||||
return func(*args, **kwargs)
|
||||
except ProgrammingError as e:
|
||||
log.critical(str(e))
|
||||
|
||||
@@ -10,7 +10,7 @@ from endpoints.responses.heartbeat import HeartbeatResponse
|
||||
from handler.metadata.igdb_handler import IGDB_API_ENABLED
|
||||
from handler.metadata.moby_handler import MOBY_API_ENABLED
|
||||
from fastapi import APIRouter
|
||||
from handler.github_handler import github_handler
|
||||
from utils import get_version
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -24,8 +24,7 @@ def heartbeat() -> HeartbeatResponse:
|
||||
"""
|
||||
|
||||
return {
|
||||
"VERSION": github_handler.get_version(),
|
||||
"NEW_VERSION": github_handler.check_new_version(),
|
||||
"VERSION": get_version(),
|
||||
"ANY_SOURCE_ENABLED": IGDB_API_ENABLED or MOBY_API_ENABLED,
|
||||
"METADATA_SOURCES": {
|
||||
"IGDB_API_ENABLED": IGDB_API_ENABLED,
|
||||
@@ -49,5 +48,5 @@ def heartbeat() -> HeartbeatResponse:
|
||||
"TITLE": "Scheduled Switch TitleDB update",
|
||||
"MESSAGE": "Updates the Nintendo Switch TitleDB file",
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -88,9 +88,7 @@ def get_platform(request: Request, id: int) -> PlatformSchema:
|
||||
PlatformSchema: Platform
|
||||
"""
|
||||
|
||||
return PlatformSchema.from_orm_with_request(
|
||||
db_platform_handler.get_platforms(id), request
|
||||
)
|
||||
return db_platform_handler.get_platforms(id)
|
||||
|
||||
|
||||
@protected_route(router.put, "/platforms/{id}", ["platforms.write"])
|
||||
|
||||
@@ -23,7 +23,6 @@ class MetadataSourcesDict(TypedDict):
|
||||
|
||||
class HeartbeatResponse(TypedDict):
|
||||
VERSION: str
|
||||
NEW_VERSION: str
|
||||
WATCHER: WatcherDict
|
||||
SCHEDULER: SchedulerDict
|
||||
ANY_SOURCE_ENABLED: bool
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel, Field
|
||||
from fastapi import Request
|
||||
|
||||
from models.platform import Platform
|
||||
from .firmware import FirmwareSchema
|
||||
|
||||
|
||||
class PlatformSchema(BaseModel):
|
||||
id: int
|
||||
slug: str
|
||||
@@ -14,16 +14,7 @@ class PlatformSchema(BaseModel):
|
||||
name: str
|
||||
logo_path: Optional[str] = ""
|
||||
rom_count: int
|
||||
|
||||
firmware_files: list[FirmwareSchema] = Field(default_factory=list)
|
||||
firmware: list[FirmwareSchema] = Field(default_factory=list)
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
@classmethod
|
||||
def from_orm_with_request(cls, db_platform: Platform, request: Request) -> "PlatformSchema":
|
||||
platform = cls.model_validate(db_platform)
|
||||
platform.firmware_files = [
|
||||
FirmwareSchema.model_validate(f) for f in sorted(db_platform.firmware, key=lambda x: x.file_name)
|
||||
]
|
||||
return platform
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class SearchRomSchema(BaseModel):
|
||||
igdb_id: int | None = None
|
||||
moby_id: int | None = None
|
||||
|
||||
@@ -23,7 +23,7 @@ from handler.filesystem.base_handler import CoverSize
|
||||
from handler.metadata import meta_igdb_handler, meta_moby_handler
|
||||
from logger.logger import log
|
||||
from stream_zip import ZIP_AUTO, stream_zip # type: ignore[import]
|
||||
|
||||
from urllib.parse import quote
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@@ -156,7 +156,7 @@ def head_rom_content(request: Request, id: int, file_name: str):
|
||||
path=rom_path if not rom.multi else f"{rom_path}/{rom.files[0]}",
|
||||
filename=file_name,
|
||||
headers={
|
||||
"Content-Disposition": f'attachment; filename="{rom.name}.zip"',
|
||||
"Content-Disposition": f'attachment; filename="{quote(rom.name)}.zip"',
|
||||
"Content-Type": "application/zip",
|
||||
"Content-Length": str(rom.file_size_bytes),
|
||||
},
|
||||
@@ -236,7 +236,7 @@ def get_rom_content(
|
||||
return CustomStreamingResponse(
|
||||
zipped_chunks,
|
||||
media_type="application/zip",
|
||||
headers={"Content-Disposition": f'attachment; filename="{file_name}.zip"'},
|
||||
headers={"Content-Disposition": f'attachment; filename="{quote(file_name)}.zip"'},
|
||||
emit_body={"id": rom.id},
|
||||
)
|
||||
|
||||
|
||||
@@ -30,9 +30,7 @@ async def run_task(request: Request, task: str) -> MessageResponse:
|
||||
RunTasksResponse: Standard message response
|
||||
"""
|
||||
|
||||
tasks = {
|
||||
"switch_titledb": update_switch_titledb_task
|
||||
}
|
||||
|
||||
tasks = {"switch_titledb": update_switch_titledb_task}
|
||||
|
||||
await tasks[task].run()
|
||||
return {"msg": f"Task {task} run successfully!"}
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
import pytest
|
||||
|
||||
from datetime import timedelta
|
||||
from handler.tests.conftest import setup_database, clear_database, admin_user, editor_user, viewer_user, platform, rom, save, state # noqa
|
||||
from handler.auth import oauth_handler
|
||||
from handler.tests.conftest import ( # noqa
|
||||
setup_database,
|
||||
clear_database,
|
||||
admin_user,
|
||||
editor_user,
|
||||
viewer_user,
|
||||
platform,
|
||||
rom,
|
||||
save,
|
||||
state,
|
||||
)
|
||||
from ..auth import ACCESS_TOKEN_EXPIRE_MINUTES, REFRESH_TOKEN_EXPIRE_DAYS
|
||||
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ def test_delete_saves(access_token, save):
|
||||
assert response.status_code == 200
|
||||
|
||||
body = response.json()
|
||||
assert body['msg'] == "Successfully deleted 1 saves"
|
||||
assert body["msg"] == "Successfully deleted 1 saves"
|
||||
|
||||
|
||||
def test_delete_states(access_token, state):
|
||||
@@ -26,4 +26,4 @@ def test_delete_states(access_token, state):
|
||||
assert response.status_code == 200
|
||||
|
||||
body = response.json()
|
||||
assert body['msg'] == "Successfully deleted 1 states"
|
||||
assert body["msg"] == "Successfully deleted 1 states"
|
||||
|
||||
@@ -10,12 +10,12 @@ def test_config():
|
||||
assert response.status_code == 200
|
||||
|
||||
config = response.json()
|
||||
assert config.get('EXCLUDED_PLATFORMS') == []
|
||||
assert config.get('EXCLUDED_SINGLE_EXT') == []
|
||||
assert config.get('EXCLUDED_SINGLE_FILES') == []
|
||||
assert config.get('EXCLUDED_MULTI_FILES') == []
|
||||
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'
|
||||
assert config.get("EXCLUDED_PLATFORMS") == []
|
||||
assert config.get("EXCLUDED_SINGLE_EXT") == []
|
||||
assert config.get("EXCLUDED_SINGLE_FILES") == []
|
||||
assert config.get("EXCLUDED_MULTI_FILES") == []
|
||||
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"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from main import app
|
||||
from handler.github_handler import github_handler
|
||||
from utils import get_version
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
@@ -9,14 +9,17 @@ client = TestClient(app)
|
||||
def test_heartbeat():
|
||||
response = client.get("/heartbeat")
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
heartbeat = response.json()
|
||||
assert heartbeat.get('VERSION') == github_handler.get_version()
|
||||
assert heartbeat.get('WATCHER').get('ENABLED')
|
||||
assert heartbeat.get('WATCHER').get('TITLE') == "Rescan on filesystem change"
|
||||
assert heartbeat.get('SCHEDULER').get('RESCAN').get('ENABLED')
|
||||
assert heartbeat.get('SCHEDULER').get('RESCAN').get('CRON') == "0 3 * * *"
|
||||
assert heartbeat.get('SCHEDULER').get('RESCAN').get('TITLE') == "Scheduled rescan"
|
||||
assert heartbeat.get('SCHEDULER').get('SWITCH_TITLEDB').get('ENABLED')
|
||||
assert heartbeat.get('SCHEDULER').get('SWITCH_TITLEDB').get('CRON') == "0 4 * * *"
|
||||
assert heartbeat.get('SCHEDULER').get('SWITCH_TITLEDB').get('TITLE') == "Scheduled Switch TitleDB update"
|
||||
assert heartbeat.get("VERSION") == get_version()
|
||||
assert heartbeat.get("WATCHER").get("ENABLED")
|
||||
assert heartbeat.get("WATCHER").get("TITLE") == "Rescan on filesystem change"
|
||||
assert heartbeat.get("SCHEDULER").get("RESCAN").get("ENABLED")
|
||||
assert heartbeat.get("SCHEDULER").get("RESCAN").get("CRON") == "0 3 * * *"
|
||||
assert heartbeat.get("SCHEDULER").get("RESCAN").get("TITLE") == "Scheduled rescan"
|
||||
assert heartbeat.get("SCHEDULER").get("SWITCH_TITLEDB").get("ENABLED")
|
||||
assert heartbeat.get("SCHEDULER").get("SWITCH_TITLEDB").get("CRON") == "0 4 * * *"
|
||||
assert (
|
||||
heartbeat.get("SCHEDULER").get("SWITCH_TITLEDB").get("TITLE")
|
||||
== "Scheduled Switch TitleDB update"
|
||||
)
|
||||
|
||||
@@ -6,7 +6,9 @@ client = TestClient(app)
|
||||
|
||||
|
||||
def test_get_raw_asset(access_token):
|
||||
response = client.get("/raw/assets/users/557365723a31/saves/n64/mupen64/Super Mario 64 (J) (Rev A).sav")
|
||||
response = client.get(
|
||||
"/raw/assets/users/557365723a31/saves/n64/mupen64/Super Mario 64 (J) (Rev A).sav"
|
||||
)
|
||||
assert response.status_code == 403
|
||||
|
||||
response = client.get(
|
||||
|
||||
@@ -112,9 +112,7 @@ def update_user(
|
||||
cleaned_data = {}
|
||||
|
||||
if form_data.username and form_data.username != user.username:
|
||||
existing_user = db_user_handler.get_user_by_username(
|
||||
form_data.username.lower()
|
||||
)
|
||||
existing_user = db_user_handler.get_user_by_username(form_data.username.lower())
|
||||
if existing_user:
|
||||
raise HTTPException(
|
||||
status_code=400, detail="Username already in use by another user"
|
||||
|
||||
@@ -19,4 +19,4 @@ OAuthCredentialsException = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Could not validate credentials",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
)
|
||||
|
||||
@@ -5,7 +5,8 @@ class ConfigNotReadableException(Exception):
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return self.message
|
||||
|
||||
|
||||
|
||||
class ConfigNotWritableException(Exception):
|
||||
def __init__(self):
|
||||
self.message = "Config file is not writable. Check config.yml permissions"
|
||||
|
||||
@@ -45,14 +45,18 @@ class RomAlreadyExistsException(Exception):
|
||||
def __repr__(self):
|
||||
return self.message
|
||||
|
||||
|
||||
class FirmwareNotFoundException(Exception):
|
||||
def __init__(self, platform: str):
|
||||
self.message = f"Firmware not found for platform {platform}. {folder_struct_msg}"
|
||||
self.message = (
|
||||
f"Firmware not found for platform {platform}. {folder_struct_msg}"
|
||||
)
|
||||
super().__init__(self.message)
|
||||
|
||||
def __repr__(self):
|
||||
return self.message
|
||||
|
||||
|
||||
class FirmwareAlreadyExistsException(Exception):
|
||||
def __init__(self, firmware_name: str):
|
||||
self.message = f"Can't rename: {firmware_name} already exists"
|
||||
|
||||
@@ -1 +1,7 @@
|
||||
from handler.tests.conftest import setup_database, clear_database, admin_user, editor_user, viewer_user # noqa
|
||||
from handler.tests.conftest import ( # noqa
|
||||
setup_database,
|
||||
clear_database,
|
||||
admin_user,
|
||||
editor_user,
|
||||
viewer_user,
|
||||
)
|
||||
|
||||
@@ -10,8 +10,12 @@ from handler.auth.hybrid_auth import HybridAuthBackend
|
||||
|
||||
|
||||
def test_verify_password():
|
||||
assert auth_handler.verify_password("password", auth_handler.get_password_hash("password"))
|
||||
assert not auth_handler.verify_password("password", auth_handler.get_password_hash("notpassword"))
|
||||
assert auth_handler.verify_password(
|
||||
"password", auth_handler.get_password_hash("password")
|
||||
)
|
||||
assert not auth_handler.verify_password(
|
||||
"password", auth_handler.get_password_hash("notpassword")
|
||||
)
|
||||
|
||||
|
||||
def test_authenticate_user(admin_user: User):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from decorators.database import begin_session
|
||||
from models.firmware import Firmware
|
||||
from sqlalchemy import update, delete, and_
|
||||
from sqlalchemy import update, delete, and_, select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from .base_handler import DBBaseHandler
|
||||
@@ -16,19 +16,19 @@ class DBFirmwareHandler(DBBaseHandler):
|
||||
self, id: int = None, platform_id: int = None, session: Session = None
|
||||
):
|
||||
return (
|
||||
session.get(Firmware, id)
|
||||
session.scalar(select(Firmware).filter_by(id=id).limit(1))
|
||||
if id
|
||||
else session.query(Firmware).filter_by(platform_id=platform_id).all()
|
||||
else select(Firmware).filter_by(platform_id=platform_id).all()
|
||||
)
|
||||
|
||||
@begin_session
|
||||
def get_firmware_by_filename(
|
||||
self, platform_id: int, file_name: str, session: Session = None
|
||||
):
|
||||
return (
|
||||
session.query(Firmware)
|
||||
return session.scalar(
|
||||
select(Firmware)
|
||||
.filter_by(platform_id=platform_id, file_name=file_name)
|
||||
.first()
|
||||
.limit(1)
|
||||
)
|
||||
|
||||
@begin_session
|
||||
|
||||
@@ -1,21 +1,26 @@
|
||||
from sqlalchemy import delete, or_, select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from decorators.database import begin_session
|
||||
from models.platform import Platform
|
||||
from models.rom import Rom
|
||||
from sqlalchemy import delete, func, or_, select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from .base_handler import DBBaseHandler
|
||||
|
||||
|
||||
class DBPlatformsHandler(DBBaseHandler):
|
||||
@begin_session
|
||||
def add_platform(self, platform: Platform, session: Session = None):
|
||||
def add_platform(
|
||||
self, platform: Platform, session: Session = None
|
||||
) -> Platform | None:
|
||||
return session.merge(platform)
|
||||
|
||||
@begin_session
|
||||
def get_platforms(self, id: int = None, session: Session = None):
|
||||
def get_platforms(
|
||||
self, id: int = None, session: Session = None
|
||||
) -> list[Platform] | Platform | None:
|
||||
return (
|
||||
session.get(Platform, id)
|
||||
session.scalar(select(Platform).filter_by(id=id).limit(1))
|
||||
if id
|
||||
else (
|
||||
session.scalars(select(Platform).order_by(Platform.name.asc()))
|
||||
@@ -25,13 +30,13 @@ class DBPlatformsHandler(DBBaseHandler):
|
||||
)
|
||||
|
||||
@begin_session
|
||||
def get_platform_by_fs_slug(self, fs_slug: str, session: Session = None):
|
||||
return session.scalars(
|
||||
select(Platform).filter_by(fs_slug=fs_slug).limit(1)
|
||||
).first()
|
||||
def get_platform_by_fs_slug(
|
||||
self, fs_slug: str, session: Session = None
|
||||
) -> Platform | None:
|
||||
return session.scalar(select(Platform).filter_by(fs_slug=fs_slug).limit(1))
|
||||
|
||||
@begin_session
|
||||
def delete_platform(self, id: int, session: Session = None):
|
||||
def delete_platform(self, id: int, session: Session = None) -> int:
|
||||
# Remove all roms from that platforms first
|
||||
session.execute(
|
||||
delete(Rom)
|
||||
@@ -45,22 +50,9 @@ class DBPlatformsHandler(DBBaseHandler):
|
||||
)
|
||||
|
||||
@begin_session
|
||||
def get_rom_count(self, platform_id: int, session: Session = None):
|
||||
return session.scalar(
|
||||
select(func.count()).select_from(Rom).filter_by(platform_id=platform_id)
|
||||
)
|
||||
|
||||
@begin_session
|
||||
def purge_platforms(self, fs_platforms: list[str], session: Session = None):
|
||||
def purge_platforms(self, fs_platforms: list[str], session: Session = None) -> int:
|
||||
return session.execute(
|
||||
delete(Platform)
|
||||
.where(or_(Platform.fs_slug.not_in(fs_platforms), Platform.slug.is_(None)))
|
||||
.where(
|
||||
select(func.count())
|
||||
.select_from(Rom)
|
||||
.filter_by(platform_id=Platform.id)
|
||||
.as_scalar()
|
||||
== 0
|
||||
)
|
||||
.execution_options(synchronize_session="fetch")
|
||||
)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import os
|
||||
import re
|
||||
import fnmatch
|
||||
from abc import ABC
|
||||
from enum import Enum
|
||||
from typing import Final
|
||||
@@ -113,3 +114,28 @@ class FSHandler(ABC):
|
||||
def parse_file_extension(self, file_name) -> str:
|
||||
match = re.search(EXTENSION_REGEX, file_name)
|
||||
return match.group(1) if match else ""
|
||||
|
||||
def _exclude_files(self, files, filetype) -> list[str]:
|
||||
cnfg = cm.get_config()
|
||||
excluded_extensions = getattr(cnfg, f"EXCLUDED_{filetype.upper()}_EXT")
|
||||
excluded_names = getattr(cnfg, f"EXCLUDED_{filetype.upper()}_FILES")
|
||||
excluded_files: list = []
|
||||
|
||||
for file_name in files:
|
||||
# Split the file name to get the extension.
|
||||
ext = self.parse_file_extension(file_name)
|
||||
|
||||
# Exclude the file if it has no extension or the extension is in the excluded list.
|
||||
if not ext or ext in excluded_extensions:
|
||||
excluded_files.append(file_name)
|
||||
|
||||
# Additionally, check if the file name mathes a pattern in the excluded list.
|
||||
if len(excluded_names) > 0:
|
||||
[
|
||||
excluded_files.append(file_name)
|
||||
for name in excluded_names
|
||||
if file_name == name or fnmatch.fnmatch(file_name, name)
|
||||
]
|
||||
|
||||
# Return files that are not in the filtered list.
|
||||
return [f for f in files if f not in excluded_files]
|
||||
|
||||
@@ -3,8 +3,8 @@ import shutil
|
||||
import hashlib
|
||||
import binascii
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import UploadFile
|
||||
|
||||
from exceptions.fs_exceptions import (
|
||||
FirmwareNotFoundException,
|
||||
FirmwareAlreadyExistsException,
|
||||
@@ -41,7 +41,7 @@ class FSFirmwareHandler(FSHandler):
|
||||
except IndexError as exc:
|
||||
raise FirmwareNotFoundException(platform.fs_slug) from exc
|
||||
|
||||
return fs_firmware_files
|
||||
return [f for f in self._exclude_files(fs_firmware_files, "single")]
|
||||
|
||||
def get_firmware_file_size(self, firmware_path: str, file_name: str):
|
||||
files = [f"{LIBRARY_BASE_PATH}/{firmware_path}/{file_name}"]
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import fnmatch
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
@@ -72,31 +71,6 @@ class FSRomsHandler(FSHandler):
|
||||
other_tags.append(tag)
|
||||
return regs, rev, langs, other_tags
|
||||
|
||||
def _exclude_files(self, files, filetype) -> list[str]:
|
||||
cnfg = cm.get_config()
|
||||
excluded_extensions = getattr(cnfg, f"EXCLUDED_{filetype.upper()}_EXT")
|
||||
excluded_names = getattr(cnfg, f"EXCLUDED_{filetype.upper()}_FILES")
|
||||
excluded_files: list = []
|
||||
|
||||
for file_name in files:
|
||||
# Split the file name to get the extension.
|
||||
ext = self.parse_file_extension(file_name)
|
||||
|
||||
# Exclude the file if it has no extension or the extension is in the excluded list.
|
||||
if not ext or ext in excluded_extensions:
|
||||
excluded_files.append(file_name)
|
||||
|
||||
# Additionally, check if the file name mathes a pattern in the excluded list.
|
||||
if len(excluded_names) > 0:
|
||||
[
|
||||
excluded_files.append(file_name)
|
||||
for name in excluded_names
|
||||
if file_name == name or fnmatch.fnmatch(file_name, name)
|
||||
]
|
||||
|
||||
# Return files that are not in the filtered list.
|
||||
return [f for f in files if f not in excluded_files]
|
||||
|
||||
def _exclude_multi_roms(self, roms) -> list[str]:
|
||||
excluded_names = cm.get_config().EXCLUDED_MULTI_FILES
|
||||
filtered_files: list = []
|
||||
|
||||
@@ -226,8 +226,7 @@ def test_get_file_name_with_no_tags():
|
||||
|
||||
file_name = "007 - Agent Under Fire.nkit.iso"
|
||||
assert (
|
||||
fs_rom_handler.get_file_name_with_no_tags(file_name)
|
||||
== "007 - Agent Under Fire"
|
||||
fs_rom_handler.get_file_name_with_no_tags(file_name) == "007 - Agent Under Fire"
|
||||
)
|
||||
|
||||
file_name = "Jimmy Houston's Bass Tournament U.S.A..zip"
|
||||
@@ -306,9 +305,7 @@ def test_get_file_name_with_no_extension():
|
||||
|
||||
|
||||
def test_get_file_extension():
|
||||
assert (
|
||||
fs_rom_handler.parse_file_extension("Super Mario Bros. (World).nes") == "nes"
|
||||
)
|
||||
assert fs_rom_handler.parse_file_extension("Super Mario Bros. (World).nes") == "nes"
|
||||
assert (
|
||||
fs_rom_handler.parse_file_extension("007 - Agent Under Fire.nkit.iso")
|
||||
== "nkit.iso"
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
import subprocess as sp
|
||||
|
||||
import requests
|
||||
from __version__ import __version__
|
||||
from logger.logger import log
|
||||
from packaging.version import InvalidVersion, parse
|
||||
from requests.exceptions import ReadTimeout
|
||||
|
||||
|
||||
class GithubHandler:
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
def get_version(self) -> str:
|
||||
"""Returns current version or branch name."""
|
||||
if not __version__ == "<version>":
|
||||
return __version__
|
||||
else:
|
||||
try:
|
||||
output = str(
|
||||
sp.check_output(["git", "branch"], universal_newlines=True)
|
||||
)
|
||||
except (sp.CalledProcessError, FileNotFoundError):
|
||||
return "1.0.0"
|
||||
|
||||
branch = [a for a in output.split("\n") if a.find("*") >= 0][0]
|
||||
return branch[branch.find("*") + 2 :]
|
||||
|
||||
def check_new_version(self) -> str:
|
||||
"""Check for new RomM versions
|
||||
|
||||
Returns:
|
||||
str: New RomM version or empty if in dev mode
|
||||
"""
|
||||
|
||||
try:
|
||||
response = requests.get(
|
||||
"https://api.github.com/repos/rommapp/romm/releases/latest", timeout=5
|
||||
)
|
||||
except ReadTimeout:
|
||||
log.warning("Timeout while connecting to Github")
|
||||
return ""
|
||||
except requests.exceptions.ConnectionError:
|
||||
log.warning("Could not connect to Github, check your internet connection")
|
||||
return ""
|
||||
try:
|
||||
last_version = response.json()["name"][
|
||||
1:
|
||||
] # remove leading 'v' from 'vX.X.X'
|
||||
except KeyError: # rate limit reached
|
||||
return ""
|
||||
try:
|
||||
if parse(self.get_version()) < parse(last_version):
|
||||
return last_version
|
||||
except InvalidVersion:
|
||||
pass
|
||||
return ""
|
||||
|
||||
|
||||
github_handler = GithubHandler()
|
||||
@@ -416,7 +416,8 @@ class IGDBBaseHandler(MetadataHandler):
|
||||
if not IGDB_API_ENABLED:
|
||||
return []
|
||||
|
||||
return [self.get_rom_by_id(igdb_id)]
|
||||
rom = self.get_rom_by_id(igdb_id)
|
||||
return [rom] if rom["igdb_id"] else []
|
||||
|
||||
@check_twitch_token
|
||||
def get_matched_roms_by_name(
|
||||
|
||||
@@ -253,14 +253,14 @@ class MobyGamesHandler(MetadataHandler):
|
||||
|
||||
def get_rom_by_id(self, moby_id: int) -> MobyGamesRom:
|
||||
if not MOBY_API_ENABLED:
|
||||
return MobyGamesRom(moby_id=moby_id)
|
||||
return MobyGamesRom(moby_id=None)
|
||||
|
||||
url = yarl.URL(self.games_url).with_query(id=moby_id)
|
||||
roms = self._request(str(url)).get("games", [])
|
||||
res = pydash.get(roms, "[0]", None)
|
||||
|
||||
if not res:
|
||||
return MobyGamesRom(moby_id=moby_id)
|
||||
return MobyGamesRom(moby_id=None)
|
||||
|
||||
rom = {
|
||||
"moby_id": res["game_id"],
|
||||
@@ -278,7 +278,8 @@ class MobyGamesHandler(MetadataHandler):
|
||||
if not MOBY_API_ENABLED:
|
||||
return []
|
||||
|
||||
return [self.get_rom_by_id(moby_id)]
|
||||
rom = self.get_rom_by_id(moby_id)
|
||||
return [rom] if rom["moby_id"] else []
|
||||
|
||||
def get_matched_roms_by_name(
|
||||
self, search_term: str, platform_moby_id: int
|
||||
|
||||
@@ -14,8 +14,18 @@ class QueuePrio(Enum):
|
||||
LOW = "low"
|
||||
|
||||
|
||||
redis_client = Redis(host=REDIS_HOST, port=REDIS_PORT, password=REDIS_PASSWORD, username=REDIS_USERNAME, db=REDIS_DB)
|
||||
redis_url = f"redis://{REDIS_USERNAME}:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB}" if REDIS_PASSWORD else f"redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB}"
|
||||
redis_client = Redis(
|
||||
host=REDIS_HOST,
|
||||
port=REDIS_PORT,
|
||||
password=REDIS_PASSWORD,
|
||||
username=REDIS_USERNAME,
|
||||
db=REDIS_DB,
|
||||
)
|
||||
redis_url = (
|
||||
f"redis://{REDIS_USERNAME}:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB}"
|
||||
if REDIS_PASSWORD
|
||||
else f"redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB}"
|
||||
)
|
||||
|
||||
high_prio_queue = Queue(name=QueuePrio.HIGH.value, connection=redis_client)
|
||||
default_queue = Queue(name=QueuePrio.DEFAULT.value, connection=redis_client)
|
||||
|
||||
@@ -308,9 +308,7 @@ def _scan_asset(file_name: str, path: str):
|
||||
"file_path": path,
|
||||
"file_name": file_name,
|
||||
"file_name_no_tags": fs_asset_handler.get_file_name_with_no_tags(file_name),
|
||||
"file_name_no_ext": fs_asset_handler.get_file_name_with_no_extension(
|
||||
file_name
|
||||
),
|
||||
"file_name_no_ext": fs_asset_handler.get_file_name_with_no_extension(file_name),
|
||||
"file_extension": fs_asset_handler.parse_file_extension(file_name),
|
||||
"file_size_bytes": file_size,
|
||||
}
|
||||
|
||||
@@ -12,6 +12,9 @@ class SocketHandler:
|
||||
client_manager=socketio.AsyncRedisManager(redis_url),
|
||||
)
|
||||
|
||||
self.socket_app = socketio.ASGIApp(self.socket_server, socketio_path="/ws/socket.io")
|
||||
self.socket_app = socketio.ASGIApp(
|
||||
self.socket_server, socketio_path="/ws/socket.io"
|
||||
)
|
||||
|
||||
|
||||
socket_handler = SocketHandler()
|
||||
|
||||
@@ -32,7 +32,7 @@ async def test_scan_rom():
|
||||
"multi": False,
|
||||
"files": ["Paper Mario (USA).z64"],
|
||||
},
|
||||
ScanType.QUICK
|
||||
ScanType.QUICK,
|
||||
)
|
||||
|
||||
assert rom.__class__ == Rom
|
||||
|
||||
@@ -3,7 +3,7 @@ import sys
|
||||
|
||||
from logger.stdout_formatter import StdoutFormatter
|
||||
|
||||
# Get logger
|
||||
# Set up logger
|
||||
log = logging.getLogger("romm")
|
||||
log.setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
@@ -22,11 +22,56 @@ class StdoutFormatter(logging.Formatter):
|
||||
msg: str = "%(message)s"
|
||||
date: str = "[%(asctime)s] "
|
||||
FORMATS: dict = {
|
||||
logging.DEBUG: COLORS['pink'] + level + COLORS['reset'] + dots + COLORS['blue'] + identifier + COLORS['cyan'] + date + COLORS['reset'] + msg,
|
||||
logging.INFO: COLORS['grey'] + level + COLORS['reset'] + dots + COLORS['blue'] + identifier + COLORS['cyan'] + date + COLORS['reset'] + msg,
|
||||
logging.WARNING: COLORS['orange'] + level + COLORS['reset'] + dots + COLORS['blue'] + identifier_warning + COLORS['cyan'] + date + COLORS['reset'] + msg,
|
||||
logging.ERROR: COLORS['red'] + level + COLORS['reset'] + dots + COLORS['blue'] + identifier + COLORS['cyan'] + date + COLORS['reset'] + msg,
|
||||
logging.CRITICAL: COLORS['bold_red'] + level + COLORS['reset'] + dots + COLORS['blue'] + identifier_critical + COLORS['cyan'] + date + COLORS['reset'] + msg
|
||||
logging.DEBUG: COLORS["pink"]
|
||||
+ level
|
||||
+ COLORS["reset"]
|
||||
+ dots
|
||||
+ COLORS["blue"]
|
||||
+ identifier
|
||||
+ COLORS["cyan"]
|
||||
+ date
|
||||
+ COLORS["reset"]
|
||||
+ msg,
|
||||
logging.INFO: COLORS["grey"]
|
||||
+ level
|
||||
+ COLORS["reset"]
|
||||
+ dots
|
||||
+ COLORS["blue"]
|
||||
+ identifier
|
||||
+ COLORS["cyan"]
|
||||
+ date
|
||||
+ COLORS["reset"]
|
||||
+ msg,
|
||||
logging.WARNING: COLORS["orange"]
|
||||
+ level
|
||||
+ COLORS["reset"]
|
||||
+ dots
|
||||
+ COLORS["blue"]
|
||||
+ identifier_warning
|
||||
+ COLORS["cyan"]
|
||||
+ date
|
||||
+ COLORS["reset"]
|
||||
+ msg,
|
||||
logging.ERROR: COLORS["red"]
|
||||
+ level
|
||||
+ COLORS["reset"]
|
||||
+ dots
|
||||
+ COLORS["blue"]
|
||||
+ identifier
|
||||
+ COLORS["cyan"]
|
||||
+ date
|
||||
+ COLORS["reset"]
|
||||
+ msg,
|
||||
logging.CRITICAL: COLORS["bold_red"]
|
||||
+ level
|
||||
+ COLORS["reset"]
|
||||
+ dots
|
||||
+ COLORS["blue"]
|
||||
+ identifier_critical
|
||||
+ COLORS["cyan"]
|
||||
+ date
|
||||
+ COLORS["reset"]
|
||||
+ msg,
|
||||
}
|
||||
|
||||
def format(self, record):
|
||||
|
||||
@@ -2,17 +2,17 @@ from logger.logger import log
|
||||
|
||||
|
||||
def test_logger(caplog):
|
||||
log.debug("Testing debug message")
|
||||
assert "debug message" in caplog.text
|
||||
log.debug("Testing debug message")
|
||||
assert "debug message" in caplog.text
|
||||
|
||||
log.info("Testing info message")
|
||||
assert "info message" in caplog.text
|
||||
log.info("Testing info message")
|
||||
assert "info message" in caplog.text
|
||||
|
||||
log.warning("Testing warning message")
|
||||
assert "warning message" in caplog.text
|
||||
log.warning("Testing warning message")
|
||||
assert "warning message" in caplog.text
|
||||
|
||||
log.error("Testing error message")
|
||||
assert "error message" in caplog.text
|
||||
log.error("Testing error message")
|
||||
assert "error message" in caplog.text
|
||||
|
||||
log.critical("Testing critical message")
|
||||
assert "critical message" in caplog.text
|
||||
log.critical("Testing critical message")
|
||||
assert "critical message" in caplog.text
|
||||
|
||||
@@ -27,13 +27,13 @@ from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi_pagination import add_pagination
|
||||
from handler.database import db_user_handler
|
||||
from handler.github_handler import github_handler
|
||||
from handler.socket_handler import socket_handler
|
||||
from handler.auth import auth_handler
|
||||
from handler.auth.base_handler import ALGORITHM
|
||||
from handler.auth.hybrid_auth import HybridAuthBackend
|
||||
from handler.auth.middleware import CustomCSRFMiddleware, SessionMiddleware
|
||||
from starlette.middleware.authentication import AuthenticationMiddleware
|
||||
from utils import get_version
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
@@ -46,7 +46,7 @@ async def lifespan(app: FastAPI):
|
||||
yield
|
||||
|
||||
|
||||
app = FastAPI(title="RomM API", version=github_handler.get_version(), lifespan=lifespan)
|
||||
app = FastAPI(title="RomM API", version=get_version(), lifespan=lifespan)
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
from sqlalchemy import Column, Integer, String, select, func
|
||||
from sqlalchemy.orm import Mapped, relationship, column_property
|
||||
|
||||
from models.base import BaseModel
|
||||
from models.rom import Rom
|
||||
from models.firmware import Firmware
|
||||
from sqlalchemy import Column, Integer, String
|
||||
from sqlalchemy.orm import Mapped, relationship
|
||||
|
||||
|
||||
class Platform(BaseModel):
|
||||
@@ -17,18 +18,14 @@ class Platform(BaseModel):
|
||||
name: str = Column(String(length=400))
|
||||
logo_path: str = Column(String(length=1000), default="")
|
||||
|
||||
roms: Mapped[set[Rom]] = relationship(
|
||||
"Rom", lazy="selectin", back_populates="platform"
|
||||
)
|
||||
firmware: Mapped[set[Firmware]] = relationship(
|
||||
"Firmware", lazy="selectin", back_populates="platform"
|
||||
)
|
||||
|
||||
@property
|
||||
def rom_count(self) -> int:
|
||||
from handler.database import db_platform_handler
|
||||
|
||||
return db_platform_handler.get_rom_count(self.id)
|
||||
# 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()
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return self.name
|
||||
|
||||
@@ -66,7 +66,7 @@ class Rom(BaseModel):
|
||||
nullable=False,
|
||||
)
|
||||
|
||||
platform = relationship("Platform", lazy="selectin", back_populates="roms")
|
||||
platform = relationship("Platform", lazy="immediate")
|
||||
|
||||
saves: Mapped[list[Save]] = relationship(
|
||||
"Save",
|
||||
|
||||
@@ -1 +1,12 @@
|
||||
from handler.tests.conftest import setup_database, clear_database, rom, platform, admin_user, editor_user, viewer_user, save, state, screenshot # noqa
|
||||
from handler.tests.conftest import ( # noqa
|
||||
setup_database,
|
||||
clear_database,
|
||||
rom,
|
||||
platform,
|
||||
admin_user,
|
||||
editor_user,
|
||||
viewer_user,
|
||||
save,
|
||||
state,
|
||||
screenshot,
|
||||
)
|
||||
|
||||
@@ -1,13 +1,25 @@
|
||||
from models.assets import Save, State, Screenshot
|
||||
|
||||
|
||||
def test_save(save: Save):
|
||||
assert "test_platform_slug/saves/test_emulator/test_save.sav" in save.full_path
|
||||
assert "/api/raw/assets/test_platform_slug/saves/test_emulator/test_save.sav" in save.download_path
|
||||
assert (
|
||||
"/api/raw/assets/test_platform_slug/saves/test_emulator/test_save.sav"
|
||||
in save.download_path
|
||||
)
|
||||
|
||||
|
||||
def test_state(state: State):
|
||||
assert "test_platform_slug/states/test_emulator/test_state.state" in state.full_path
|
||||
assert "/api/raw/assets/test_platform_slug/states/test_emulator/test_state.state" in state.download_path
|
||||
assert (
|
||||
"/api/raw/assets/test_platform_slug/states/test_emulator/test_state.state"
|
||||
in state.download_path
|
||||
)
|
||||
|
||||
|
||||
def test_screenshot(screenshot: Screenshot):
|
||||
assert "test_platform_slug/screenshots/test_screenshot.png" in screenshot.full_path
|
||||
assert "/api/raw/assets/test_platform_slug/screenshots/test_screenshot.png" in screenshot.download_path
|
||||
assert (
|
||||
"/api/raw/assets/test_platform_slug/screenshots/test_screenshot.png"
|
||||
in screenshot.download_path
|
||||
)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
def test_rom(rom):
|
||||
assert rom.file_path == "test_platform_slug/roms"
|
||||
assert rom.full_path == "test_platform_slug/roms/test_rom.zip"
|
||||
assert rom.full_path == "test_platform_slug/roms/test_rom.zip"
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
from __version__ import __version__
|
||||
|
||||
def get_version() -> str:
|
||||
"""Returns current version tag"""
|
||||
if not __version__ == "<version>":
|
||||
return __version__
|
||||
|
||||
return "development"
|
||||
|
||||
@@ -99,7 +99,9 @@ if __name__ == "__main__":
|
||||
# Sort platforms by key
|
||||
supported_platforms = dict(sorted(supported_platforms.items()))
|
||||
|
||||
print("Below is a list of all supported platforms/systems/consoles and their respective folder names. **The folder name is case-sensitive and must be used exactly as it appears in the list below.**")
|
||||
print(
|
||||
"Below is a list of all supported platforms/systems/consoles and their respective folder names. **The folder name is case-sensitive and must be used exactly as it appears in the list below.**"
|
||||
)
|
||||
print("\n")
|
||||
print("|Platform Name|Folder Name|IGDB|Mobygames|")
|
||||
print("|---|---|---|---|")
|
||||
|
||||
@@ -46,7 +46,7 @@ start_bin_gunicorn () {
|
||||
--bind=0.0.0.0:5000 \
|
||||
--bind=unix:/tmp/gunicorn.sock \
|
||||
--pid=/tmp/gunicorn.pid \
|
||||
--workers 2 \
|
||||
--workers ${GUNICORN_WORKERS:=2} \
|
||||
main:app &
|
||||
}
|
||||
|
||||
|
||||
@@ -7,11 +7,11 @@ events {
|
||||
}
|
||||
|
||||
http {
|
||||
client_body_temp_path /tmp/client_body 1 2;
|
||||
fastcgi_temp_path /tmp/fastcgi 1 2;
|
||||
proxy_temp_path /tmp/proxy;
|
||||
uwsgi_temp_path /tmp/uwsgi;
|
||||
scgi_temp_path /tmp/scgi;
|
||||
client_body_temp_path /tmp/client_body 1 2;
|
||||
fastcgi_temp_path /tmp/fastcgi 1 2;
|
||||
proxy_temp_path /tmp/proxy;
|
||||
uwsgi_temp_path /tmp/uwsgi;
|
||||
scgi_temp_path /tmp/scgi;
|
||||
|
||||
sendfile on;
|
||||
client_max_body_size 0;
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
ROMM_BASE_PATH=/path/to/romm_mock
|
||||
VITE_BACKEND_DEV_PORT=5000
|
||||
|
||||
# Gunicorn (optional)
|
||||
ROMM_HOST=localhost
|
||||
GUNICORN_WORKERS=4 # (2 × CPU cores) + 1
|
||||
|
||||
# IGDB credentials
|
||||
IGDB_CLIENT_ID=
|
||||
IGDB_CLIENT_SECRET=
|
||||
|
||||
# Mobygames
|
||||
MOBYGAMES_API_KEY=
|
||||
MOBYGAMES_API_KEY=
|
||||
|
||||
# Database config
|
||||
DB_HOST=127.0.0.1
|
||||
|
||||
@@ -21,7 +21,7 @@ exclude:
|
||||
## Multi files games section
|
||||
# Will apply to files that are in sub-folders (multi-disc roms, games with updates, DLC, patches, etc.)
|
||||
multi_file:
|
||||
# Exclude matched 'folder' names to be scanned (RomM identifies folders as multi file games)
|
||||
# Exclude matched 'folder' names to be scanned (RomM identifies folders as multi file games)
|
||||
names:
|
||||
- 'my_multi_file_game'
|
||||
- 'DLC'
|
||||
|
||||
@@ -7,4 +7,3 @@ info@brittneymurphydesign.com
|
||||
It is free for personal and non-profit use. If you would like to use this font commercially, please purchase a commercial license (only $5) at www.brittneymurphydesign.com.
|
||||
|
||||
You are free to redistribute this font as long as you include this ReadMe file with the font file and don't try to claim the font as free or as your own.
|
||||
|
||||
|
||||
@@ -228,4 +228,4 @@
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
BIN
frontend/assets/platforms/amiibo.ico
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
@@ -1,17 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import Notification from "@/components/Notification.vue";
|
||||
import api from "@/services/api/index";
|
||||
import userApi from "@/services/api/user";
|
||||
import platformApi from "@/services/api/platform";
|
||||
import socket from "@/services/socket";
|
||||
import storeConfig from "@/stores/config";
|
||||
import storeGalleryFilter from "@/stores/galleryFilter";
|
||||
import storeHeartbeat from "@/stores/heartbeat";
|
||||
import storeRoms, { type Rom } from "@/stores/roms";
|
||||
import storePlatforms from "@/stores/platforms";
|
||||
import storeAuth from "@/stores/auth";
|
||||
import storeScanning from "@/stores/scanning";
|
||||
import type { Events } from "@/types/emitter";
|
||||
import { normalizeString } from "@/utils";
|
||||
import type { Emitter } from "mitt";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { inject, onBeforeMount, onBeforeUnmount } from "vue";
|
||||
import { inject, onMounted, onBeforeUnmount } from "vue";
|
||||
|
||||
// Props
|
||||
const scanningStore = storeScanning();
|
||||
@@ -24,6 +28,8 @@ const emitter = inject<Emitter<Events>>("emitter");
|
||||
// Props
|
||||
const heartbeat = storeHeartbeat();
|
||||
const configStore = storeConfig();
|
||||
const auth = storeAuth();
|
||||
const platformsStore = storePlatforms();
|
||||
|
||||
socket.on(
|
||||
"scan:scanning_platform",
|
||||
@@ -95,13 +101,32 @@ onBeforeUnmount(() => {
|
||||
socket.off("scan:done_ko");
|
||||
});
|
||||
|
||||
onBeforeMount(() => {
|
||||
onMounted(() => {
|
||||
api.get("/heartbeat").then(({ data: data }) => {
|
||||
heartbeat.set(data);
|
||||
});
|
||||
|
||||
api.get("/config").then(({ data: data }) => {
|
||||
configStore.set(data);
|
||||
});
|
||||
|
||||
userApi
|
||||
.fetchCurrentUser()
|
||||
.then(({ data: user }) => {
|
||||
auth.setUser(user);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
platformApi
|
||||
.getPlatforms()
|
||||
.then(({ data: platforms }) => {
|
||||
platformsStore.set(platforms);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -9,4 +9,3 @@ export type AddFirmwareResponse = {
|
||||
uploaded: number;
|
||||
firmware: Array<FirmwareSchema>;
|
||||
};
|
||||
|
||||
|
||||
@@ -7,4 +7,3 @@ export type AddRomsResponse = {
|
||||
uploaded_roms: Array<string>;
|
||||
skipped_roms: Array<string>;
|
||||
};
|
||||
|
||||
|
||||
@@ -6,4 +6,3 @@
|
||||
export type Body_add_firmware_firmware_post = {
|
||||
files: Array<Blob>;
|
||||
};
|
||||
|
||||
|
||||
@@ -6,4 +6,3 @@
|
||||
export type Body_add_roms_roms_post = {
|
||||
roms: Array<Blob>;
|
||||
};
|
||||
|
||||
|
||||
@@ -6,4 +6,3 @@
|
||||
export type Body_add_saves_saves_post = {
|
||||
saves: Array<Blob>;
|
||||
};
|
||||
|
||||
|
||||
@@ -6,4 +6,3 @@
|
||||
export type Body_add_screenshots_screenshots_post = {
|
||||
screenshots: Array<Blob>;
|
||||
};
|
||||
|
||||
|
||||
@@ -6,4 +6,3 @@
|
||||
export type Body_add_states_states_post = {
|
||||
states: Array<Blob>;
|
||||
};
|
||||
|
||||
|
||||
@@ -12,4 +12,3 @@ export type Body_token_token_post = {
|
||||
client_secret?: (string | null);
|
||||
refresh_token?: (string | null);
|
||||
};
|
||||
|
||||
|
||||
@@ -6,4 +6,3 @@
|
||||
export type Body_update_rom_roms__id__put = {
|
||||
artwork?: (Blob | null);
|
||||
};
|
||||
|
||||
|
||||
@@ -6,4 +6,3 @@
|
||||
export type Body_update_user_users__id__put = {
|
||||
avatar?: (Blob | null);
|
||||
};
|
||||
|
||||
|
||||
@@ -16,4 +16,3 @@ export type ConfigResponse = {
|
||||
FIRMWARE_FOLDER_NAME: string;
|
||||
HIGH_PRIO_STRUCTURE_PATH: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -28,4 +28,3 @@ export type CursorPage_RomSchema_ = {
|
||||
*/
|
||||
next_page?: (string | null);
|
||||
};
|
||||
|
||||
|
||||
@@ -17,4 +17,3 @@ export type FirmwareSchema = {
|
||||
md5_hash: string;
|
||||
sha1_hash: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -8,4 +8,3 @@ import type { ValidationError } from './ValidationError';
|
||||
export type HTTPValidationError = {
|
||||
detail?: Array<ValidationError>;
|
||||
};
|
||||
|
||||
|
||||
@@ -9,10 +9,8 @@ import type { WatcherDict } from './WatcherDict';
|
||||
|
||||
export type HeartbeatResponse = {
|
||||
VERSION: string;
|
||||
NEW_VERSION: string;
|
||||
WATCHER: WatcherDict;
|
||||
SCHEDULER: SchedulerDict;
|
||||
ANY_SOURCE_ENABLED: boolean;
|
||||
METADATA_SOURCES: MetadataSourcesDict;
|
||||
};
|
||||
|
||||
|
||||
@@ -7,4 +7,3 @@ export type IGDBPlatform = {
|
||||
igdb_id: number;
|
||||
name?: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -10,4 +10,3 @@ export type IGDBRelatedGame = {
|
||||
type: string;
|
||||
cover_url: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -6,4 +6,3 @@
|
||||
export type MessageResponse = {
|
||||
msg: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -7,4 +7,3 @@ export type MetadataSourcesDict = {
|
||||
IGDB_API_ENABLED: boolean;
|
||||
MOBY_API_ENABLED: boolean;
|
||||
};
|
||||
|
||||
|
||||
@@ -7,4 +7,3 @@ export type MobyGamesPlatform = {
|
||||
moby_id: number;
|
||||
name?: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -15,6 +15,5 @@ export type PlatformSchema = {
|
||||
name: string;
|
||||
logo_path?: (string | null);
|
||||
rom_count: number;
|
||||
firmware_files?: Array<FirmwareSchema>;
|
||||
firmware?: Array<FirmwareSchema>;
|
||||
};
|
||||
|
||||
|
||||
@@ -25,4 +25,3 @@ export type RomIGDBMetadata = {
|
||||
ports?: Array<IGDBRelatedGame>;
|
||||
similar_games?: Array<IGDBRelatedGame>;
|
||||
};
|
||||
|
||||
|
||||