Merge branch 'master' into trunk-io

This commit is contained in:
Georges-Antoine Assi
2024-05-24 16:47:19 -04:00
35 changed files with 253 additions and 244 deletions

View File

@@ -8,9 +8,9 @@ from config import (
)
from endpoints.responses.heartbeat import HeartbeatResponse
from fastapi import APIRouter
from handler.github_handler import github_handler
from handler.metadata.igdb_handler import IGDB_API_ENABLED
from handler.metadata.moby_handler import MOBY_API_ENABLED
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,

View File

@@ -23,7 +23,6 @@ class MetadataSourcesDict(TypedDict):
class HeartbeatResponse(TypedDict):
VERSION: str
NEW_VERSION: str
WATCHER: WatcherDict
SCHEDULER: SchedulerDict
ANY_SOURCE_ENABLED: bool

View File

@@ -9,12 +9,12 @@ class PlatformSchema(BaseModel):
id: int
slug: str
fs_slug: str
name: str
rom_count: int
igdb_id: Optional[int] = None
sgdb_id: Optional[int] = None
moby_id: Optional[int] = None
name: str
logo_path: Optional[str] = ""
rom_count: int
firmware: list[FirmwareSchema] = Field(default_factory=list)
class Config:

View File

@@ -34,15 +34,11 @@ class RomNoteSchema(BaseModel):
last_edited_at: datetime
raw_markdown: str
is_public: bool
user__username: str
class Config:
from_attributes = True
@property
@computed_field
def user__username(self) -> str:
return db_user_handler.get_user(self.user_id).username
@classmethod
def for_user(cls, db_rom: Rom, user_id: int) -> list["RomNoteSchema"]:
return [

View File

@@ -157,7 +157,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),
},

View File

@@ -1,7 +1,8 @@
from fastapi.testclient import TestClient
from handler.github_handler import github_handler
from main import app
from utils import get_version
client = TestClient(app)
@@ -10,7 +11,7 @@ def test_heartbeat():
assert response.status_code == 200
heartbeat = response.json()
assert heartbeat.get("VERSION") == github_handler.get_version()
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")

View File

@@ -1,6 +1,6 @@
from decorators.database import begin_session
from models.firmware import Firmware
from sqlalchemy import and_, delete, update
from sqlalchemy import update, delete, and_, select
from sqlalchemy.orm import Session
from .base_handler import DBBaseHandler
@@ -19,19 +19,19 @@ class DBFirmwareHandler(DBBaseHandler):
session: Session = None,
) -> Firmware | list[Firmware] | 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
) -> Firmware | 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

View File

@@ -1,22 +1,24 @@
import functools
from sqlalchemy import delete, or_, select
from sqlalchemy.orm import Session, Query, selectinload
from decorators.database import begin_session
from models.platform import Platform
from models.rom import Rom
from sqlalchemy import delete, or_
from sqlalchemy.orm import Query, Session, joinedload
from .base_handler import DBBaseHandler
def with_query(func):
def with_roms(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
session = kwargs.get("session")
if session is None:
raise ValueError("session is required")
kwargs["query"] = session.query(Platform).options(joinedload(Platform.roms))
kwargs["query"] = select(Platform).options(
selectinload(Platform.roms).load_only(Rom.id)
)
return func(*args, **kwargs)
return wrapper
@@ -24,32 +26,36 @@ def with_query(func):
class DBPlatformsHandler(DBBaseHandler):
@begin_session
@with_query
@with_roms
def add_platform(
self, platform: Platform, query: Query = None, session: Session = None
) -> Platform | None:
session.merge(platform)
platform = session.merge(platform)
session.flush()
return query.filter(Platform.fs_slug == platform.fs_slug).first()
return session.scalar(query.filter_by(id=platform.id).limit(1))
@begin_session
@with_query
@with_roms
def get_platforms(
self, id: int | None = None, query: Query = None, session: Session = None
) -> list[Platform] | Platform | None:
return (
query.get(id)
session.scalar(query.filter_by(id=id).limit(1))
if id
else (session.scalars(query.order_by(Platform.name.asc())).unique().all()) # type: ignore[attr-defined]
else (
session.scalars(select(Platform).order_by(Platform.name.asc()))
.unique()
.all()
)
)
@begin_session
@with_query
@with_roms
def get_platform_by_fs_slug(
self, fs_slug: str, query: Query = None, session: Session = None
) -> Platform | None:
return session.scalars(query.filter_by(fs_slug=fs_slug).limit(1)).first()
return session.scalar(query.filter_by(fs_slug=fs_slug).limit(1))
@begin_session
def delete_platform(self, id: int, session: Session = None) -> int:

View File

@@ -1,11 +1,30 @@
import functools
from decorators.database import begin_session
from models.rom import Rom, RomNote
from sqlalchemy import and_, delete, func, or_, select, update
from sqlalchemy.orm import Session
from sqlalchemy import and_, delete, func, select, update, or_, Select
from sqlalchemy.orm import Session, Query, selectinload
from .base_handler import DBBaseHandler
def with_assets(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
session = kwargs.get("session")
if session is None:
raise ValueError("session is required")
kwargs["query"] = select(Rom).options(
selectinload(Rom.saves),
selectinload(Rom.states),
selectinload(Rom.screenshots),
selectinload(Rom.notes),
)
return func(*args, **kwargs)
return wrapper
class DBRomsHandler(DBBaseHandler):
def _filter(self, data, platform_id: int | None, search_term: str):
if platform_id:
@@ -33,10 +52,15 @@ class DBRomsHandler(DBBaseHandler):
return data.order_by(_column.asc())
@begin_session
def add_rom(self, rom: Rom, session: Session = None) -> Rom:
return session.merge(rom)
@with_assets
def add_rom(self, rom: Rom, query: Query = None, session: Session = None) -> Rom:
rom = session.merge(rom)
session.flush()
return session.scalar(query.filter_by(id=rom.id).limit(1))
@begin_session
@with_assets
def get_roms(
self,
id: int | None = None,
@@ -44,10 +68,11 @@ class DBRomsHandler(DBBaseHandler):
search_term: str = "",
order_by: str = "name",
order_dir: str = "asc",
query: Query = None,
session: Session = None,
) -> Rom | list[Rom] | None:
) -> list[Rom] | Rom | None:
return (
session.get(Rom, id)
session.scalar(query.filter_by(id=id).limit(1))
if id
else self._order(
self._filter(select(Rom), platform_id, search_term),
@@ -57,28 +82,35 @@ class DBRomsHandler(DBBaseHandler):
)
@begin_session
@with_assets
def get_rom_by_filename(
self, platform_id: int, file_name: str, session: Session = None
self,
platform_id: int,
file_name: str,
query: Query = None,
session: Session = None,
) -> Rom | None:
return session.scalars(
select(Rom).filter_by(platform_id=platform_id, file_name=file_name).limit(1)
).first()
return session.scalar(
query.filter_by(platform_id=platform_id, file_name=file_name).limit(1)
)
@begin_session
@with_assets
def get_rom_by_filename_no_tags(
self, file_name_no_tags: str, session: Session = None
self, file_name_no_tags: str, query: Query = None, session: Session = None
) -> Rom | None:
return session.scalars(
select(Rom).filter_by(file_name_no_tags=file_name_no_tags).limit(1)
).first()
return session.scalar(
query.filter_by(file_name_no_tags=file_name_no_tags).limit(1)
)
@begin_session
@with_assets
def get_rom_by_filename_no_ext(
self, file_name_no_ext: str, session: Session = None
self, file_name_no_ext: str, query: Query = None, session: Session = None
) -> Rom | None:
return session.scalars(
select(Rom).filter_by(file_name_no_ext=file_name_no_ext).limit(1)
).first()
return session.scalar(
query.filter_by(file_name_no_ext=file_name_no_ext).limit(1)
)
@begin_session
def update_rom(self, id: int, data: dict, session: Session = None) -> Rom:
@@ -90,7 +122,7 @@ class DBRomsHandler(DBBaseHandler):
)
@begin_session
def delete_rom(self, id: int, session: Session = None) -> None:
def delete_rom(self, id: int, session: Session = None) -> Rom:
return session.execute(
delete(Rom)
.where(Rom.id == id)
@@ -100,7 +132,7 @@ class DBRomsHandler(DBBaseHandler):
@begin_session
def purge_roms(
self, platform_id: int, roms: list[str], session: Session = None
) -> None:
) -> int:
return session.execute(
delete(Rom)
.where(and_(Rom.platform_id == platform_id, Rom.file_name.not_in(roms))) # type: ignore[attr-defined]
@@ -111,9 +143,9 @@ class DBRomsHandler(DBBaseHandler):
def get_rom_note(
self, rom_id: int, user_id: int, session: Session = None
) -> RomNote | None:
return session.scalars(
return session.scalar(
select(RomNote).filter_by(rom_id=rom_id, user_id=user_id).limit(1)
).first()
)
@begin_session
def add_rom_note(

View File

@@ -12,12 +12,8 @@ class DBUsersHandler(DBBaseHandler):
return session.merge(user)
@begin_session
def get_user_by_username(
self, username: str, session: Session = None
) -> User | None:
return session.scalars(
select(User).filter_by(username=username).limit(1)
).first()
def get_user_by_username(self, username: str, session: Session = None):
return session.scalar(select(User).filter_by(username=username).limit(1))
@begin_session
def get_user(self, id: int, session: Session = None) -> User:
@@ -33,7 +29,11 @@ class DBUsersHandler(DBBaseHandler):
)
@begin_session
def delete_user(self, id: int, session: Session = None) -> None:
def get_users(self, session: Session = None):
return session.scalars(select(User)).all()
@begin_session
def delete_user(self, id: int, session: Session = None):
return session.execute(
delete(User)
.where(User.id == id)
@@ -41,9 +41,5 @@ class DBUsersHandler(DBBaseHandler):
)
@begin_session
def get_users(self, session: Session = None) -> list[User]:
return session.scalars(select(User)).all()
@begin_session
def get_admin_users(self, session: Session = None) -> list[User]:
def get_admin_users(self, session: Session = None):
return session.scalars(select(User).filter_by(role=Role.ADMIN)).all()

View File

@@ -1,62 +0,0 @@
import subprocess as sp # nosec B404
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
) # nosec B603, B607
)
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()

View File

@@ -3,14 +3,19 @@ import sys
from logger.stdout_formatter import StdoutFormatter
# Get logger
# Set up logger
log = logging.getLogger("romm")
log.setLevel(logging.DEBUG)
# Set up sqlachemy logger
# sql_log = logging.getLogger("sqlalchemy.engine")
# sql_log.setLevel(logging.DEBUG)
# Define stdout handler
stdout_handler = logging.StreamHandler(sys.stdout)
stdout_handler.setFormatter(StdoutFormatter())
log.addHandler(stdout_handler)
# sql_log.addHandler(stdout_handler)
# Hush passlib warnings
logging.getLogger("passlib").setLevel(logging.ERROR)

View File

@@ -31,9 +31,9 @@ from handler.auth.base_handler import ALGORITHM
from handler.auth.hybrid_auth import HybridAuthBackend
from handler.auth.middleware import CustomCSRFMiddleware, SessionMiddleware
from handler.database import db_user_handler
from handler.github_handler import github_handler
from handler.socket_handler import socket_handler
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,

View File

@@ -53,8 +53,8 @@ class Save(RomAsset):
emulator = Column(String(length=50), nullable=True)
rom = relationship("Rom", lazy="selectin", back_populates="saves")
user = relationship("User", lazy="selectin", back_populates="saves")
rom = relationship("Rom", lazy="joined", back_populates="saves")
user = relationship("User", lazy="joined", back_populates="saves")
@cached_property
def screenshot(self) -> Optional["Screenshot"]:
@@ -74,8 +74,8 @@ class State(RomAsset):
emulator = Column(String(length=50), nullable=True)
rom = relationship("Rom", lazy="selectin", back_populates="states")
user = relationship("User", lazy="selectin", back_populates="states")
rom = relationship("Rom", lazy="joined", back_populates="states")
user = relationship("User", lazy="joined", back_populates="states")
@cached_property
def screenshot(self) -> Optional["Screenshot"]:
@@ -93,5 +93,5 @@ class Screenshot(RomAsset):
__tablename__ = "screenshots"
__table_args__ = {"extend_existing": True}
rom = relationship("Rom", lazy="selectin", back_populates="screenshots")
user = relationship("User", lazy="selectin", back_populates="screenshots")
rom = relationship("Rom", lazy="joined", back_populates="screenshots")
user = relationship("User", lazy="joined", back_populates="screenshots")

View File

@@ -17,9 +17,7 @@ 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"
)
roms: Mapped[set[Rom]] = relationship("Rom", back_populates="platform")
firmware: Mapped[set[Firmware]] = relationship(
"Firmware", lazy="selectin", back_populates="platform"
)

View File

@@ -66,22 +66,17 @@ class Rom(BaseModel):
nullable=False,
)
platform = relationship("Platform", lazy="selectin", back_populates="roms")
platform = relationship("Platform", lazy="immediate")
saves: Mapped[list[Save]] = relationship(
"Save",
lazy="selectin",
back_populates="rom",
)
states: Mapped[list[State]] = relationship(
"State", lazy="selectin", back_populates="rom"
)
states: Mapped[list[State]] = relationship("State", back_populates="rom")
screenshots: Mapped[list[Screenshot]] = relationship(
"Screenshot", lazy="selectin", back_populates="rom"
)
notes: Mapped[list["RomNote"]] = relationship(
"RomNote", lazy="selectin", back_populates="rom"
"Screenshot", back_populates="rom"
)
notes: Mapped[list["RomNote"]] = relationship("RomNote", back_populates="rom")
@property
def platform_slug(self) -> str:
@@ -189,5 +184,9 @@ class RomNote(BaseModel):
nullable=False,
)
rom = relationship("Rom", back_populates="notes")
user = relationship("User", back_populates="notes")
rom = relationship("Rom", lazy="joined", back_populates="notes")
user = relationship("User", lazy="joined", back_populates="notes")
@property
def user__username(self) -> str:
return self.user.username

View File

@@ -32,18 +32,13 @@ class User(BaseModel, SimpleUser):
saves: Mapped[list[Save]] = relationship(
"Save",
lazy="selectin",
back_populates="user",
)
states: Mapped[list[State]] = relationship(
"State", lazy="selectin", back_populates="user"
)
states: Mapped[list[State]] = relationship("State", back_populates="user")
screenshots: Mapped[list[Screenshot]] = relationship(
"Screenshot", lazy="selectin", back_populates="user"
)
notes: Mapped[list[RomNote]] = relationship(
"RomNote", lazy="selectin", back_populates="user"
"Screenshot", back_populates="user"
)
notes: Mapped[list[RomNote]] = relationship("RomNote", back_populates="user")
@property
def oauth_scopes(self):

View File

@@ -0,0 +1,8 @@
from __version__ import __version__
def get_version() -> str:
"""Returns current version tag"""
if not __version__ == "<version>":
return __version__
return "development"

View File

Before

Width:  |  Height:  |  Size: 264 KiB

After

Width:  |  Height:  |  Size: 264 KiB

View File

@@ -44,7 +44,7 @@ socket.on(
socket.on("scan:scanning_rom", (rom: Rom) => {
scanningStore.set(true);
if (romsStore.platform.name === rom.platform_name) {
if (romsStore.platformID === rom.platform_id) {
romsStore.add([rom]);
romsStore.setFiltered(
isFiltered ? romsStore.filteredRoms : romsStore.allRoms,
@@ -99,32 +99,16 @@ onBeforeUnmount(() => {
socket.off("scan:scanning_rom");
socket.off("scan:done");
socket.off("scan:done_ko");
document.removeEventListener("network-quiesced", fetchHomeData);
});
onMounted(() => {
api.get("/config").then(({ data: data }) => {
configStore.set(data);
});
});
function fetchHomeData() {
// Remove it so it's not called multiple times
document.removeEventListener("network-quiesced", fetchHomeData);
api.get("/heartbeat").then(({ data: data }) => {
heartbeat.set(data);
});
platformApi
.getPlatforms()
.then(({ data: platforms }) => {
platformsStore.set(platforms);
})
.catch((error) => {
console.error(error);
});
api.get("/config").then(({ data: data }) => {
configStore.set(data);
});
userApi
.fetchCurrentUser()
@@ -134,9 +118,16 @@ function fetchHomeData() {
.catch((error) => {
console.error(error);
});
}
document.addEventListener("network-quiesced", fetchHomeData);
platformApi
.getPlatforms()
.then(({ data: platforms }) => {
platformsStore.set(platforms);
})
.catch((error) => {
console.error(error);
});
});
</script>
<template>

View File

@@ -8,10 +8,9 @@ import type { SchedulerDict } from "./SchedulerDict";
import type { WatcherDict } from "./WatcherDict";
export type HeartbeatResponse = {
VERSION: string;
NEW_VERSION: string;
WATCHER: WatcherDict;
SCHEDULER: SchedulerDict;
ANY_SOURCE_ENABLED: boolean;
METADATA_SOURCES: MetadataSourcesDict;
VERSION: string;
WATCHER: WatcherDict;
SCHEDULER: SchedulerDict;
ANY_SOURCE_ENABLED: boolean;
METADATA_SOURCES: MetadataSourcesDict;
};

View File

@@ -5,7 +5,7 @@ import storeHeartbeat from "@/stores/heartbeat";
import type { Events } from "@/types/emitter";
import { defaultAvatarPath } from "@/utils";
import type { Emitter } from "mitt";
import { inject, ref } from "vue";
import { inject, ref, onBeforeMount } from "vue";
import { useRouter } from "vue-router";
// Props
@@ -14,18 +14,27 @@ const router = useRouter();
const emitter = inject<Emitter<Events>>("emitter");
const auth = storeAuth();
const heartbeat = storeHeartbeat();
const newVersion = heartbeat.value.NEW_VERSION;
localStorage.setItem("newVersion", newVersion);
const newVersionDismissed = ref(
localStorage.getItem("dismissNewVersion") === newVersion
);
// Functions
function dismissNewVersion() {
localStorage.setItem("dismissNewVersion", newVersion);
newVersionDismissed.value = true;
const { VERSION } = heartbeat.value;
const GITHUB_VERSION = ref(VERSION);
const latestVersionDismissed = ref(VERSION === "development");
function dismissVersionBanner() {
localStorage.setItem("dismissedVersion", GITHUB_VERSION.value);
latestVersionDismissed.value = true;
}
onBeforeMount(async () => {
const response = await fetch(
"https://api.github.com/repos/rommapp/romm/releases/latest"
);
const json = await response.json();
GITHUB_VERSION.value = json.name;
latestVersionDismissed.value =
VERSION === "development" ||
json.name === localStorage.getItem("dismissedVersion");
});
async function logout() {
identityApi
.logout()
@@ -86,7 +95,12 @@ async function logout() {
></v-btn>
<v-list-item
class="bg-terciary py-1 px-1 text-subtitle-2"
v-if="newVersion && !newVersionDismissed && !rail"
v-if="
GITHUB_VERSION &&
VERSION !== GITHUB_VERSION &&
!latestVersionDismissed &&
!rail
"
>
<v-card>
<v-card-text class="py-2 px-4">
@@ -94,18 +108,18 @@ async function logout() {
<v-col class="py-1">
<span
>New version available
<span class="text-romm-accent-1">{{ newVersion }}</span></span
<span class="text-romm-accent-1">{{ GITHUB_VERSION }}</span></span
>
</v-col>
</v-row>
<v-row no-gutters>
<v-col class="py-1">
<span @click="dismissNewVersion()" class="pointer text-grey"
<span @click="dismissVersionBanner()" class="pointer text-grey"
>Dismiss</span
><span class="ml-4"
><a
target="_blank"
:href="`https://github.com/rommapp/romm/releases/tag/v${newVersion}`"
:href="`https://github.com/rommapp/romm/releases/tag/v${GITHUB_VERSION}`"
>See what's new!</a
></span
>

View File

@@ -7,8 +7,8 @@ import DeleteBtn from "@/components/Gallery/AppBar/DeleteBtn.vue";
<template>
<v-list rounded="0" class="pa-0">
<view-firmware-btn />
<upload-rom-btn />
<view-firmware-btn />
<scan-btn />
<v-divider class="border-opacity-25" />
<delete-btn />

View File

@@ -1,19 +1,26 @@
<script setup lang="ts">
import storeRoms from "@/stores/roms";
import type { Events } from "@/types/emitter";
import type { Emitter } from "mitt";
import { inject } from "vue";
import { useRoute } from "vue-router";
import type { Emitter } from "mitt";
import type { Events } from "@/types/emitter";
import storePlatforms, { type Platform } from "@/stores/platforms";
// Props
const emitter = inject<Emitter<Events>>("emitter");
const romsStore = storeRoms();
const platforms = storePlatforms();
const route = useRoute();
</script>
<template>
<v-list-item
v-if="romsStore.platform"
v-if="route.params.platform"
class="py-4 pr-5 text-romm-red"
@click="emitter?.emit('showDeletePlatformDialog', romsStore.platform)"
@click="
emitter?.emit(
'showDeletePlatformDialog',
platforms.get(Number(route.params.platform)) as Platform
)
"
>
<v-list-item-title class="d-flex">
<v-icon icon="mdi-delete" color="red" class="mr-2" />

View File

@@ -16,7 +16,7 @@ async function scan() {
if (!socket.connected) socket.connect();
socket.emit("scan", {
platforms: [romsStore.platform.id],
platforms: [romsStore.platformID],
type: "quick",
apis: heartbeat.getMetadataOptions().map((s) => s.value),
});
@@ -24,7 +24,7 @@ async function scan() {
</script>
<template>
<v-list-item v-if="romsStore.platform" @click="scan" class="py-4 pr-5">
<v-list-item v-if="romsStore.platformID" @click="scan" class="py-4 pr-5">
<v-list-item-title class="d-flex">
<v-icon icon="mdi-magnify-scan" class="mr-2" />
Scan platform

View File

@@ -1,18 +1,25 @@
<script setup lang="ts">
import { inject } from "vue";
import { useRoute } from "vue-router";
import type { Emitter } from "mitt";
import type { Events } from "@/types/emitter";
import storeRoms from "@/stores/roms";
import storePlatforms, { type Platform } from "@/stores/platforms";
// Props
const emitter = inject<Emitter<Events>>("emitter");
const romsStore = storeRoms();
const platforms = storePlatforms();
const route = useRoute();
</script>
<template>
<v-list-item
v-if="romsStore.platform"
@click="emitter?.emit('showUploadRomDialog', romsStore.platform)"
v-if="route.params.platform"
@click="
emitter?.emit(
'showUploadRomDialog',
platforms.get(Number(route.params.platform)) as Platform
)
"
class="py-4 pr-5"
>
<v-list-item-title class="d-flex"

View File

@@ -1,18 +1,25 @@
<script setup lang="ts">
import { inject } from "vue";
import { useRoute } from "vue-router";
import type { Emitter } from "mitt";
import type { Events } from "@/types/emitter";
import storeRoms from "@/stores/roms";
import storePlatforms, { type Platform } from "@/stores/platforms";
// Props
const emitter = inject<Emitter<Events>>("emitter");
const romsStore = storeRoms();
const platforms = storePlatforms();
const route = useRoute();
</script>
<template>
<v-list-item
v-if="romsStore.platform"
@click="emitter?.emit('showFirmwareDialog', romsStore.platform)"
v-if="route.params.platform"
@click="
emitter?.emit(
'showFirmwareDialog',
platforms.get(Number(route.params.platform)) as Platform
)
"
class="py-4 pr-5"
>
<v-list-item-title class="d-flex"

View File

@@ -9,7 +9,7 @@ const inflightRequests = new Set();
const networkQuiesced = debounce(() => {
document.dispatchEvent(new CustomEvent("network-quiesced"));
}, 300);
}, 250);
api.interceptors.request.use((config) => {
// Add request to set of inflight requests

View File

@@ -125,7 +125,7 @@ async function downloadRom({
}
export type UpdateRom = Rom & {
artwork?: File[];
artwork?: File;
};
async function updateRom({
@@ -144,7 +144,7 @@ async function updateRom({
formData.append("file_name", rom.file_name);
formData.append("summary", rom.summary || "");
formData.append("url_cover", rom.url_cover || "");
if (rom.artwork) formData.append("artwork", rom.artwork[0]);
if (rom.artwork) formData.append("artwork", rom.artwork);
return api.put(`/roms/${rom.id}`, formData, {
params: { rename_as_igdb: renameAsIGDB, remove_cover: removeCover },

View File

@@ -33,7 +33,7 @@ async function updateUser({
avatar,
...attrs
}: UserSchema & {
avatar?: File[];
avatar?: File;
password?: string;
}): Promise<{ data: UserSchema }> {
return api.put(

View File

@@ -9,7 +9,7 @@ export default defineStore("heartbeat", {
actions: {
set(data: HeartbeatResponse) {
this.value = data;
this.value = { ...this.value, ...data };
},
getMetadataOptions() {
return computed(() => [

View File

@@ -13,7 +13,7 @@ export type Rom = RomSchema & {
export default defineStore("roms", {
state: () => ({
_platform: {} as PlatformSchema,
_platformID: 0,
_all: [] as Rom[],
_grouped: [] as Rom[],
_filteredIDs: [] as number[],
@@ -27,7 +27,7 @@ export default defineStore("roms", {
}),
getters: {
platform: (state) => state._platform,
platformID: (state) => state._platformID,
allRoms: (state) => state._all,
filteredRoms: (state) =>
state._grouped.filter((rom) => state._filteredIDs.includes(rom.id)),
@@ -69,8 +69,8 @@ export default defineStore("roms", {
return a.sort_comparator.localeCompare(b.sort_comparator);
});
},
setPlatform(platform: PlatformSchema) {
this._platform = platform;
setPlatformID(platformID: number) {
this._platformID = platformID;
},
setRecentRoms(roms: Rom[]) {
this.recentRoms = roms;

View File

@@ -5,7 +5,7 @@ import type { User } from "@/stores/users";
export type UserItem = User & {
password: string;
avatar?: File[];
avatar?: File;
};
export type SnackbarStatus = {

View File

@@ -8,6 +8,7 @@ import romApi from "@/services/api/rom";
import storeGalleryFilter from "@/stores/galleryFilter";
import storeGalleryView from "@/stores/galleryView";
import storeRoms from "@/stores/roms";
import storePlatforms from "@/stores/platforms";
import type { Events } from "@/types/emitter";
import type { RomSelectEvent } from "@/types/rom";
import { normalizeString, toTop, views } from "@/utils";
@@ -23,6 +24,7 @@ const galleryFilterStore = storeGalleryFilter();
const gettingRoms = ref(false);
const fabMenu = ref(false);
const scrolledToTop = ref(true);
const platforms = storePlatforms();
const romsStore = storeRoms();
const {
allRoms,
@@ -31,7 +33,7 @@ const {
searchRoms,
cursor,
searchCursor,
platform,
platformID,
} = storeToRefs(romsStore);
// Event listeners bus
@@ -58,7 +60,7 @@ async function fetchRoms() {
await romApi
.getRoms({
platformId: platform.value.id,
platformId: platformID.value,
cursor: galleryFilterStore.isFiltered()
? searchCursor.value
: cursor.value,
@@ -82,12 +84,12 @@ async function fetchRoms() {
})
.catch((error) => {
emitter?.emit("snackbarShow", {
msg: `Couldn't fetch roms for ${platform.value.name}: ${error}`,
msg: `Couldn't fetch roms for platform ID ${platformID.value}: ${error}`,
icon: "mdi-close-circle",
color: "red",
timeout: 4000,
});
console.error(`Couldn't fetch roms for ${platform.value.name}: ${error}`);
console.error(`Couldn't fetch roms for platform ID ${platformID.value}: ${error}`);
})
.finally(() => {
gettingRoms.value = false;
@@ -184,13 +186,19 @@ function onScroll() {
}
onMounted(async () => {
const { data: platform } = await platformApi.getPlatform(
Number(route.params.platform)
);
romsStore.setPlatform(platform);
const storedPlatformID = romsStore.platformID;
const platformID = Number(route.params.platform);
romsStore.setPlatformID(platformID);
const platform = platforms.get(platformID);
if (!platform) {
const { data } = await platformApi.getPlatform(platformID)
platforms.add(data);
}
// If platform is different, reset store and fetch roms
if (platform.id != romsStore.platform.id) {
if (storedPlatformID != platformID) {
resetGallery();
await fetchRoms();
}
@@ -219,10 +227,16 @@ onBeforeRouteUpdate(async (to, _) => {
// Triggers when change query param of the same route
// Reset store if switching to another platform
resetGallery();
const { data: newPlatform } = await platformApi.getPlatform(
Number(to.params.platform)
);
romsStore.setPlatform(newPlatform);
const platformID = Number(to.params.platform);
romsStore.setPlatformID(platformID);
const platform = platforms.get(platformID);
if (!platform) {
const { data } = await platformApi.getPlatform(platformID)
platforms.add(data);
}
await fetchRoms();
setFilters();
});

View File

@@ -100,7 +100,6 @@ watch(metadataOptions, (newOptions) => {
:items="platforms.value"
variant="outlined"
density="comfortable"
rounded="0"
multiple
return-object
clearable
@@ -138,7 +137,6 @@ watch(metadataOptions, (newOptions) => {
return-object
clearable
hide-details
rounded="0"
chips
>
<template v-slot:item="{ props, item }">