Merge branch 'master' into trunk-io

This commit is contained in:
Georges-Antoine Assi
2024-05-31 18:39:58 -04:00
81 changed files with 1424 additions and 978 deletions

View File

@@ -31,7 +31,6 @@ class ScreenshotSchema(BaseAsset):
class UploadedScreenshotsResponse(TypedDict):
uploaded: int
screenshots: list[ScreenshotSchema]
url_screenshots: list[str]
merged_screenshots: list[str]

View File

@@ -92,16 +92,8 @@ class RomSchema(BaseModel):
multi: bool
files: list[str]
url_screenshots: list[str]
merged_screenshots: list[str]
full_path: str
sibling_roms: list["RomSchema"] = Field(default_factory=list)
user_saves: list[SaveSchema] = Field(default_factory=list)
user_states: list[StateSchema] = Field(default_factory=list)
user_screenshots: list[ScreenshotSchema] = Field(default_factory=list)
user_notes: list[RomNoteSchema] = Field(default_factory=list)
class Config:
from_attributes = True
@@ -118,8 +110,17 @@ class RomSchema(BaseModel):
.lower()
)
class DetailedRomSchema(RomSchema):
merged_screenshots: list[str]
sibling_roms: list["RomSchema"] = Field(default_factory=list)
user_saves: list[SaveSchema] = Field(default_factory=list)
user_states: list[StateSchema] = Field(default_factory=list)
user_screenshots: list[ScreenshotSchema] = Field(default_factory=list)
user_notes: list[RomNoteSchema] = Field(default_factory=list)
@classmethod
def from_orm_with_request(cls, db_rom: Rom, request: Request) -> "RomSchema":
def from_orm_with_request(cls, db_rom: Rom, request: Request) -> "DetailedRomSchema":
rom = cls.model_validate(db_rom)
user_id = request.user.id

View File

@@ -9,4 +9,3 @@ class SearchRomSchema(BaseModel):
summary: str
igdb_url_cover: str = ""
moby_url_cover: str = ""
url_screenshots: list[str]

View File

@@ -10,14 +10,13 @@ from endpoints.responses import MessageResponse
from endpoints.responses.rom import (
AddRomsResponse,
CustomStreamingResponse,
DetailedRomSchema,
RomNoteSchema,
RomSchema,
)
from exceptions.fs_exceptions import RomAlreadyExistsException
from fastapi import APIRouter, HTTPException, Query, Request, UploadFile, status
from fastapi import APIRouter, HTTPException, Query, Request, UploadFile, File, status
from fastapi.responses import FileResponse
from fastapi_pagination.cursor import CursorPage, CursorParams
from fastapi_pagination.ext.sqlalchemy import paginate
from handler.database import db_platform_handler, db_rom_handler
from handler.filesystem import fs_resource_handler, fs_rom_handler
from handler.filesystem.base_handler import CoverSize
@@ -87,13 +86,12 @@ def add_roms(
@protected_route(router.get, "/roms", ["roms.read"])
def get_roms(
request: Request,
platform_id: int | None = None,
size: int = 60,
cursor: str = "",
platform_id: int = None,
search_term: str = "",
limit: int = None,
order_by: str = "name",
order_dir: str = "asc",
) -> CursorPage[RomSchema]:
) -> list[RomSchema]:
"""Get roms endpoint
Args:
@@ -101,18 +99,18 @@ def get_roms(
id (int, optional): Rom internal id
Returns:
RomSchema: Rom stored in the database
list[RomSchema]: List of roms stored in the database
"""
with db_rom_handler.session.begin() as session:
cursor_params = CursorParams(size=size, cursor=cursor)
qq = db_rom_handler.get_roms(
platform_id=platform_id,
search_term=search_term.lower(),
order_by=order_by.lower(),
order_dir=order_dir.lower(),
)
return paginate(session, qq, cursor_params)
return session.scalars(
db_rom_handler.get_roms(
platform_id=platform_id,
search_term=search_term.lower(),
order_by=order_by.lower(),
order_dir=order_dir.lower(),
).limit(limit)
).all()
@protected_route(
@@ -120,7 +118,7 @@ def get_roms(
"/roms/{id}",
[] if DISABLE_DOWNLOAD_ENDPOINT_AUTH else ["roms.read"],
)
def get_rom(request: Request, id: int) -> RomSchema:
def get_rom(request: Request, id: int) -> DetailedRomSchema:
"""Get rom endpoint
Args:
@@ -128,9 +126,9 @@ def get_rom(request: Request, id: int) -> RomSchema:
id (int): Rom internal id
Returns:
RomSchema: Rom stored in the database
DetailedRomSchema: Rom stored in the database
"""
return RomSchema.from_orm_with_request(db_rom_handler.get_roms(id), request)
return DetailedRomSchema.from_orm_with_request(db_rom_handler.get_roms(id), request)
@protected_route(
@@ -250,8 +248,8 @@ async def update_rom(
id: int,
rename_as_igdb: bool = False,
remove_cover: bool = False,
artwork: Optional[UploadFile] = None,
) -> RomSchema:
artwork: Optional[UploadFile] = File(None),
) -> DetailedRomSchema:
"""Update rom endpoint
Args:
@@ -264,7 +262,7 @@ async def update_rom(
HTTPException: If a rom already have that name when enabling the rename_as_igdb flag
Returns:
RomSchema: Rom stored in the database
DetailedRomSchema: Rom stored in the database
"""
data = await request.form()
@@ -378,7 +376,7 @@ async def update_rom(
db_rom_handler.update_rom(id, cleaned_data)
return db_rom_handler.get_roms(id)
return DetailedRomSchema.from_orm_with_request(db_rom_handler.get_roms(id), request)
@protected_route(router.post, "/roms/delete", ["roms.write"])

View File

@@ -55,6 +55,5 @@ def add_screenshots(
return {
"uploaded": len(screenshots),
"screenshots": [s for s in rom.screenshots if s.user_id == current_user.id],
"url_screenshots": rom.url_screenshots,
"merged_screenshots": rom.merged_screenshots,
}

View File

@@ -97,7 +97,6 @@ async def search_rom(
"summary": "",
"igdb_url_cover": "",
"moby_url_cover": "",
"url_screenshots": [],
},
**item,
}

View File

@@ -92,7 +92,9 @@ async def scan_platforms(
if platform and scan_type == ScanType.NEW_PLATFORMS:
continue
scanned_platform = scan_platform(platform_slug, fs_platforms)
scanned_platform = scan_platform(
platform_slug, fs_platforms, metadata_sources=metadata_sources
)
if platform:
scanned_platform.id = platform.id
# Keep the existing ids if they exist on the platform

View File

@@ -1,7 +1,6 @@
import json
from fastapi.testclient import TestClient
from unittest.mock import patch
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
@@ -27,8 +26,8 @@ def test_get_all_roms(access_token, rom, platform):
assert response.status_code == 200
body = response.json()
assert len(body["items"]) == 1
assert body["items"][0]["id"] == rom.id
assert len(body) == 1
assert body[0]["id"] == rom.id
@patch("endpoints.rom.fs_rom_handler.rename_file")
@@ -45,12 +44,6 @@ def test_update_rom(rename_file_mock, get_rom_by_id_mock, access_token, rom):
"file_name": "Metroid Prime Remastered.zip",
"summary": "summary test",
"url_cover": "https://images.igdb.com/igdb/image/upload/t_cover_big/co2l7z.jpg",
"url_screenshots": json.dumps(
[
"https://images.igdb.com/igdb/image/upload/t_original/qhiqlmwvvuaqxxn4cxlr.jpg",
"https://images.igdb.com/igdb/image/upload/t_original/kqkixazzsokqgoxmuish.jpg",
]
),
"genres": '[{"id": 5, "name": "Shooter"}, {"id": 8, "name": "Platform"}, {"id": 31, "name": "Adventure"}]',
"franchises": '[{"id": 756, "name": "Metroid"}]',
"collections": '[{"id": 243, "name": "Metroid"}, {"id": 6240, "name": "Metroid Prime"}]',

View File

@@ -21,7 +21,11 @@ class DBFirmwareHandler(DBBaseHandler):
return (
session.scalar(select(Firmware).filter_by(id=id).limit(1))
if id
else select(Firmware).filter_by(platform_id=platform_id).all()
else session.scalars(
select(Firmware)
.filter_by(platform_id=platform_id)
.order_by(Firmware.file_name.asc())
).all()
)
@begin_session

View File

@@ -11,8 +11,8 @@ from handler.filesystem import (
fs_rom_handler,
)
from handler.metadata import meta_igdb_handler, meta_moby_handler
from handler.metadata.igdb_handler import IGDBRom
from handler.metadata.moby_handler import MobyGamesRom
from handler.metadata.igdb_handler import IGDBRom, IGDBPlatform
from handler.metadata.moby_handler import MobyGamesRom, MobyGamesPlatform
from logger.logger import log
from models.assets import Save, Screenshot, State
from models.firmware import Firmware
@@ -51,6 +51,7 @@ def _get_main_platform_igdb_id(platform: Platform):
def scan_platform(
fs_slug: str,
fs_platforms: list[str],
metadata_sources: list[str] = ["igdb", "moby"],
) -> Platform:
"""Get platform details
@@ -86,8 +87,16 @@ def scan_platform(
except (KeyError, TypeError, AttributeError):
platform_attrs["slug"] = fs_slug
igdb_platform = meta_igdb_handler.get_platform(platform_attrs["slug"])
moby_platform = meta_moby_handler.get_platform(platform_attrs["slug"])
igdb_platform = (
meta_igdb_handler.get_platform(platform_attrs["slug"])
if "igdb" in metadata_sources
else IGDBPlatform(igdb_id=None, slug=platform_attrs["slug"])
)
moby_platform = (
meta_moby_handler.get_platform(platform_attrs["slug"])
if "moby" in metadata_sources
else MobyGamesPlatform(moby_id=None, slug=platform_attrs["slug"])
)
platform_attrs["name"] = platform_attrs["slug"].replace("-", " ").title()
platform_attrs.update({**moby_platform, **igdb_platform}) # Reverse order

View File

@@ -25,7 +25,6 @@ from endpoints import (
)
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi_pagination import add_pagination
from handler.auth import auth_handler
from handler.auth.base_handler import ALGORITHM
from handler.auth.hybrid_auth import HybridAuthBackend
@@ -95,7 +94,6 @@ app.include_router(raw.router)
app.include_router(screenshots.router)
app.include_router(firmware.router)
add_pagination(app)
app.mount("/ws", socket_handler.socket_app)

View File

@@ -16,6 +16,9 @@ from sqlalchemy import (
Text,
UniqueConstraint,
func,
select,
and_,
or_,
)
from sqlalchemy.dialects.mysql.json import JSON as MySQLJSON
from sqlalchemy.orm import Mapped, relationship
@@ -108,14 +111,17 @@ class Rom(BaseModel):
def get_sibling_roms(self) -> list["Rom"]:
from handler.database import db_rom_handler
if not self.igdb_id:
return []
with db_rom_handler.session.begin() as session:
return session.scalars(
db_rom_handler.get_roms(platform_id=self.platform_id).filter(
Rom.id != self.id,
Rom.igdb_id == self.igdb_id,
select(Rom).where(
and_(
Rom.platform_id == self.platform_id,
Rom.id != self.id,
or_(
and_(Rom.igdb_id == self.igdb_id, Rom.igdb_id != None), # noqa
and_(Rom.moby_id == self.moby_id, Rom.moby_id != None), # noqa
),
)
)
).all()

View File

@@ -34,45 +34,55 @@ error_log() {
exit 1
}
wait_for_gunicorn_socket () {
info_log "waiting for gunicorn socket file..."
while [ ! -S /tmp/gunicorn.sock ]; do
sleep 1
done
info_log "gunicorn socket file found"
}
# function that runs or main process and creates a corresponding PID file,
start_bin_gunicorn() {
# cleanup potentially leftover socket
rm /tmp/gunicorn.sock -f
# Commands to start our main application and store its PID to check for crashes
info_log "starting gunicorn"
gunicorn \
--access-logfile - \
--error-logfile - \
--worker-class uvicorn.workers.UvicornWorker \
--bind=0.0.0.0:5000 \
--bind=unix:/tmp/gunicorn.sock \
--pid=/tmp/gunicorn.pid \
--workers "${GUNICORN_WORKERS:=2}" \
main:app &
start_bin_gunicorn () {
# cleanup potentially leftover socket
rm /tmp/gunicorn.sock -f
# commands to start our main application and store its PID to check for crashes
info_log "starting gunicorn"
gunicorn \
--access-logfile - \
--error-logfile - \
--worker-class uvicorn.workers.UvicornWorker \
--bind=0.0.0.0:5000 \
--bind=unix:/tmp/gunicorn.sock \
--pid=/tmp/gunicorn.pid \
--workers ${GUNICORN_WORKERS:=2} \
main:app &
}
# Commands to start nginx (handling PID creation internally)
start_bin_nginx() {
info_log "starting nginx"
if [[ ${EUID} -ne 0 ]]; then
nginx
else
# if container runs as root, drop permissions
nginx -g 'user romm;'
fi
start_bin_nginx () {
wait_for_gunicorn_socket
info_log "starting nginx"
if [ "$EUID" -ne 0 ]; then
nginx
else
# if container runs as root, drop permissions
nginx -g 'user romm;'
fi
}
start_bin_redis-server() {
info_log "starting redis-server"
# Check if /usr/local/etc/redis/redis.conf exists and use it if so
if [[ -f /usr/local/etc/redis/redis.conf ]]; then
redis-server /usr/local/etc/redis/redis.conf &
else
redis-server --dir /redis-data &
fi
REDIS_PID=$!
echo "${REDIS_PID}" >/tmp/redis-server.pid
start_bin_redis-server () {
info_log "starting redis-server"
# Check if /usr/local/etc/redis/redis.conf exists and use it if so
if [ -f /usr/local/etc/redis/redis.conf ]; then
redis-server /usr/local/etc/redis/redis.conf &
else
redis-server --dir /redis-data &
fi
REDIS_PID=$!
echo $REDIS_PID > /tmp/redis-server.pid
}
# function that runs our independent python scripts and creates corresponding PID files,
@@ -113,6 +123,9 @@ cd /backend || { error_log "/backend directory doesn't seem to exist"; }
info_log "Starting up, please wait..."
# clear any leftover PID files
rm /tmp/*.pid -f
# function definition done, lets start our main loop
while true; do
# check for died processes every 5 seconds
@@ -137,17 +150,17 @@ while true; do
debug_log "database schema already upgraded during current container lifecycle"
fi
# Start nginx if we dont have a corresponding PID file
watchdog_process_pid bin nginx
# Start gunicorn if we dont have a corresponding PID file
watchdog_process_pid bin gunicorn
# Start nginx if we dont have a corresponding PID file
watchdog_process_pid bin nginx
# Start uvicorn if we dont have a corresponding PID file
watchdog_process_pid bin gunicorn
# only start the watcher.py if we actually want to use the rescan on fs change feature
if [[ ${ENABLE_RESCAN_ON_FILESYSTEM_CHANGE} == "true" ]]; then
# Start watcher if we dont have a corresponding PID file
watchdog_process_pid python watcher
fi
# only start the watcher.py if we actually want to use the rescan on fs change feature
if [[ ${ENABLE_RESCAN_ON_FILESYSTEM_CHANGE} == "true" ]]; then
# Start watcher if we dont have a corresponding PID file
watchdog_process_pid python watcher
fi
# Start background worker processes
debug_log "Starting worker and scheduler"

View File

@@ -0,0 +1,131 @@
# This config file is compatible with the ES-DE Emulation Frontend
# Rename this file to `config.yml`, copy it to a `config` folder, and mount that folder as per the docker-compose.example.yml
# Only uncomment the lines you want to use/modify, or add new ones where needed
exclude:
roms:
single_file:
extensions:
- 'xml'
- 'txt'
names:
- 'info.txt'
- 'metadata.txt'
- 'systeminfo.txt'
- 'psy.ggjud.iso'
- 'psy.ggjud.nfo'
multi_file:
names:
- 'roms'
- 'pfx'
- 'mlc01'
- '.Trash*'
system:
platforms:
3do: '3do' # 3DO Interactive Multiplayer
n3ds: '3ds' # Nintendo 3DS
amstradcpc: 'acpc' # Amstrad CPC
amiga: 'amiga' # Amiga
amigacd32: 'amiga-cd32' # Amiga CD32
android: 'android' # Android
apple2gs: 'apple2gs' # Apple IIGD
apple2: 'appleii' # Apple II
arcade: 'arcade' # Arcade
arcadia: 'arcadia-2001' # Arcadia 2001
arduboy: 'arduboy' # Arduboy
astrocde: 'astrocade' # Bally Astrocade
apfm1000: 'apf' # APF-M1000/Imagination Machine
atarijaguarcd: 'atari-jaguar-cd' # Atari Jaguar CD
atarist: 'atari-st' # Atari ST/STE
atari2600: 'atari2600' # Atari 2600
atari5200: 'atari5200' # Atari 5200
atari7800: 'atari7800' # Atari 7800
atari800: 'atari8bit' # Atari 8-bit
c16: 'c16' # Commodore 16
c64: 'c64' # Commodore C64/128/MAX
pv1000: 'casio-pv-1000' # Casio PV-1000
colecovision: 'colecovision' # ColecoVision
cdtv: 'commodore-cdtv' # CDTV
crvision: 'creativision' # CreatiVision
dreamcast: 'dc' # Dreamcast
dos: 'dos' # DOS
dragon32: 'dragon-32-slash-64' # Dragon 32/64
channelf: 'fairchild-channel-f' # Channel F
famicom: 'famicom' # Family Computer
fds: 'fds' # Family Computer Disk System
fmtowns: 'fm-towns' # FM Towns
gameandwatch: 'g-and-w' # Game & Watch
gamecom: 'game-dot-com' # Game.Com
gb: 'gb' # Game Boy
gba: 'gba' # Game Boy Advance
gbc: 'gbc' # Game Boy Color
megadrive: 'genesis-slash-megadrive' # Genesis/Mega Drive
lcdgames: 'handheld-electronic-lcd' # Handheld Electronic LCD
intellivision: 'intellivision' # Intellivision
j2me: 'j2me' # J2ME
atarijaguar: 'jaguar' # Atari Jaguar
atarilynx: 'lynx' # Atari Lynx
macintosh: 'mac' # Mac
megaduck: 'mega-duck-slash-cougar-boy' # Mega Duck/Cougar Boy
msx: 'msx' # MSX
msx2: 'msx2' # MSX2
n64: 'n64' # Nintendo 64
nds: 'nds' # Nintendo DS
neogeocd: 'neo-geo-cd' # Neo Geo CD
ngp: 'neo-geo-pocket' # Neo Geo Pocket
ngpc: 'neo-geo-pocket-color' # Neo Geo Pocket Color
neogeo: 'neogeoaes' # Neo Geo
nes: 'nes' # Nintendo Entertainment System
gc: 'ngc' # GameCube
n64dd: 'nintendo-64dd' # Nintendo 64DD
odyssey2: 'odyssey-2-slash-videopac-g7000' # Odyssey 2/Videopac G7000
oric: 'oric' # Oric
palm: 'palm-os' # Palm OS
pc88: 'pc-8800-series' # PC-8800 Series
pc98: 'pc-9800-series' # PC-9800 Series
pcfx: 'pc-fx' # PC-FX
pokemini: 'pokemon-mini' # Pokémon mini
psx: 'ps' # PlayStation
ps2: 'ps2' # PlayStation 2
ps3: 'ps3' # PlayStation 3
ps4: 'ps4--1' # PlayStation 4
psp: 'psp' # PlayStation Portable
samcoupe: 'sam-coupe' # SAM Coupé
satellaview: 'satellaview' # Satellaview
sega32x: 'sega-32x' # SEGA 32X
mastersystem: 'sega-master-system' # SEGA Master System
sega32x: 'sega32' # Sega 32X
megacd: 'segacd' # SEGA CD
sfc: 'sfam' # Super Famicom
sg-1000: 'sg1000' # SG-1000
x68000: 'sharp-x68000' # Sharp X68000
snes: 'snes' # Super Nintendo Entertainment System
spectravideo: 'spectravideo' # Spectravideo
supergrafx: 'supergrafx' # PC Engine SuperGrafx
supervision: 'supervision' # Supervision
switch: 'switch' # Nintendo Switch
symbian: 'symbian' # Symbian
ti99: 'ti-99' # Texas Instruments TI-99
trs-80: 'trs-80' # TRS-80
tg-cd: 'turbografx-16-slash-pc-engine-cd' # TurboGrafx CD
tg16: 'turbografx16--1' # TurboGrafx-16
vectrex: 'vectrex' # Vectrex
vic20: 'vic-20' # Commodore VIC-20
videopac: 'videopac-g7400' # Videopac+ G7400
virtualboy: 'virtualboy' # Virtual Boy
vsmile: 'vsmile' # V.Smile
wii: 'wii' # Wii
wiiu: 'wiiu' # Wii U
pc: 'win' # PC (Microsoft Windows)
wonderswan: 'wonderswan' # WonderSwan
wonderswancolor: 'wonderswan-color' # WonderSwan Color
x1: 'x1' # Sharp X1
xbox: 'xbox' # Xbox
xbox360: 'xbox360' # Xbox 360
zmachine: 'z-machine' # Z-machine
zx81: 'zx80' # ZX80
zxspectrum: 'zxs' # ZX Spectrum
naomi: 'arcade'
naomi2: 'arcade'
naomigd: 'arcade'

View File

@@ -1,51 +1,56 @@
# This is a generic example of a configuration file
# Rename this file to `config.yml`, copy it to a `config` folder, and mount that folder as per the docker-compose.example.yml
# Only uncomment the lines you want to use/modify, or add new ones where needed
exclude:
# Exclude platforms to be scanned
platforms:
- "romm"
# - 'romm'
# Exclude roms or parts of roms to be scanned
roms:
## Single file games section.
# Single file games section.
# Will not apply to files that are in sub-folders (multi-disc roms, games with updates, DLC, patches, etc.)
single_file:
# Exclude all files with certain extensions to be scanned
extensions:
- "xml"
# - 'xml'
# Exclude matched file names to be scanned
names:
- "info.txt"
- "._*" # Supports unix filename pattern matching
- "*.nfo" # Can also exclude files by extension
# - 'info.txt'
# - '._*' # Supports unix filename pattern matching
# - '*.nfo' # Can also exclude files by extension
## Multi files games section
# 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)
names:
- "my_multi_file_game"
- "DLC"
# - 'my_multi_file_game'
# - 'DLC'
# Exclude files within sub-folders.
parts:
# Exclude matched file names to be scanned from multi file roms
# Keep in mind that RomM doesn't scan folders inside multi files games,
# so there is no need to exclude folders from inside of multi files games.
names:
- "data.xml"
- "._*" # Supports unix filename pattern matching
# - 'data.xml'
# - '._*' # Supports unix filename pattern matching
# Exclude all files with certain extensions to be scanned from multi file roms
extensions:
- "txt"
# - 'txt'
system:
# Asociate different platform names to your current file system platform names
# [your custom platform folder name]: [RomM platform name]
platforms:
# [your custom platform folder name]: [RomM platform name]
gc: "ngc" # In this example if you have a 'gc' folder, RomM will treat it like the 'ngc' folder
psx: "ps"
# gc: 'ngc' # In this example if you have a 'gc' folder, RomM will treat it like the 'ngc' folder
# psx: 'ps' # In this example if you have a 'psx' folder, RomM will treat it like the 'ps' folder
# Asociate one platform to it's main version
versions:
naomi: "arcade"
# naomi: 'arcade'
# The folder name where your roms are located
filesystem:
# The folder name where your roms are located
roms_folder: "roms" # For example if your folder structure is /home/user/library/roms_folder
# roms_folder: 'roms' # For example if your folder structure is /home/user/library/roms_folder

View File

@@ -3,6 +3,7 @@
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<link rel="preconnect" href="https://fonts.gstatic.com" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>RomM</title>
</head>

View File

@@ -7,7 +7,7 @@ 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 storeRoms, { type SimpleRom } from "@/stores/roms";
import storePlatforms from "@/stores/platforms";
import storeAuth from "@/stores/auth";
import storeScanning from "@/stores/scanning";
@@ -42,7 +42,7 @@ socket.on(
}
);
socket.on("scan:scanning_rom", (rom: Rom) => {
socket.on("scan:scanning_rom", (rom: SimpleRom) => {
scanningStore.set(true);
if (romsStore.platformID === rom.platform_id) {
romsStore.add([rom]);

View File

@@ -14,7 +14,7 @@ export type { Body_token_token_post } from "./models/Body_token_token_post";
export type { Body_update_rom_roms__id__put } from "./models/Body_update_rom_roms__id__put";
export type { Body_update_user_users__id__put } from "./models/Body_update_user_users__id__put";
export type { ConfigResponse } from "./models/ConfigResponse";
export type { CursorPage_RomSchema_ } from "./models/CursorPage_RomSchema_";
export type { DetailedRomSchema } from "./models/DetailedRomSchema";
export type { FirmwareSchema } from "./models/FirmwareSchema";
export type { HeartbeatResponse } from "./models/HeartbeatResponse";
export type { HTTPValidationError } from "./models/HTTPValidationError";

View File

@@ -1,30 +0,0 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { RomSchema } from "./RomSchema";
export type CursorPage_RomSchema_ = {
items: Array<RomSchema>;
/**
* Total items
*/
total?: number | null;
/**
* Cursor to refetch the current page
*/
current_page?: string | null;
/**
* Cursor to refetch the current page starting from the last item
*/
current_page_backwards?: string | null;
/**
* Cursor for the previous page
*/
previous_page?: string | null;
/**
* Cursor for the next page
*/
next_page?: string | null;
};

View File

@@ -0,0 +1,57 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { RomIGDBMetadata } from "./RomIGDBMetadata";
import type { RomMobyMetadata } from "./RomMobyMetadata";
import type { RomNoteSchema } from "./RomNoteSchema";
import type { RomSchema } from "./RomSchema";
import type { SaveSchema } from "./SaveSchema";
import type { ScreenshotSchema } from "./ScreenshotSchema";
import type { StateSchema } from "./StateSchema";
export type DetailedRomSchema = {
id: number;
igdb_id: number | null;
sgdb_id: number | null;
moby_id: number | null;
platform_id: number;
platform_slug: string;
platform_name: string;
file_name: string;
file_name_no_tags: string;
file_name_no_ext: string;
file_extension: string;
file_path: string;
file_size_bytes: number;
name: string | null;
slug: string | null;
summary: string | null;
first_release_date: number | null;
alternative_names: Array<string>;
genres: Array<string>;
franchises: Array<string>;
collections: Array<string>;
companies: Array<string>;
game_modes: Array<string>;
igdb_metadata: RomIGDBMetadata | null;
moby_metadata: RomMobyMetadata | null;
path_cover_s: string | null;
path_cover_l: string | null;
has_cover: boolean;
url_cover: string | null;
revision: string | null;
regions: Array<string>;
languages: Array<string>;
tags: Array<string>;
multi: boolean;
files: Array<string>;
full_path: string;
merged_screenshots: Array<string>;
sibling_roms?: Array<RomSchema>;
user_saves?: Array<SaveSchema>;
user_states?: Array<StateSchema>;
user_screenshots?: Array<ScreenshotSchema>;
user_notes?: Array<RomNoteSchema>;
};

View File

@@ -9,11 +9,11 @@ export type PlatformSchema = {
id: number;
slug: string;
fs_slug: string;
name: string;
rom_count: number;
igdb_id?: number | null;
sgdb_id?: number | null;
moby_id?: number | null;
name: string;
logo_path?: string | null;
rom_count: number;
firmware?: Array<FirmwareSchema>;
};

View File

@@ -10,4 +10,5 @@ export type RomNoteSchema = {
last_edited_at: string;
raw_markdown: string;
is_public: boolean;
user__username: string;
};

View File

@@ -5,10 +5,6 @@
import type { RomIGDBMetadata } from "./RomIGDBMetadata";
import type { RomMobyMetadata } from "./RomMobyMetadata";
import type { RomNoteSchema } from "./RomNoteSchema";
import type { SaveSchema } from "./SaveSchema";
import type { ScreenshotSchema } from "./ScreenshotSchema";
import type { StateSchema } from "./StateSchema";
export type RomSchema = {
id: number;
@@ -46,12 +42,5 @@ export type RomSchema = {
tags: Array<string>;
multi: boolean;
files: Array<string>;
url_screenshots: Array<string>;
merged_screenshots: Array<string>;
full_path: string;
sibling_roms?: Array<RomSchema>;
user_saves?: Array<SaveSchema>;
user_states?: Array<StateSchema>;
user_screenshots?: Array<ScreenshotSchema>;
user_notes?: Array<RomNoteSchema>;
};

View File

@@ -11,5 +11,4 @@ export type SearchRomSchema = {
summary: string;
igdb_url_cover?: string;
moby_url_cover?: string;
url_screenshots: Array<string>;
};

View File

@@ -8,6 +8,5 @@ import type { ScreenshotSchema } from "./ScreenshotSchema";
export type UploadedScreenshotsResponse = {
uploaded: number;
screenshots: Array<ScreenshotSchema>;
url_screenshots: Array<string>;
merged_screenshots: Array<string>;
};

View File

@@ -10,7 +10,7 @@ const emitter = inject<Emitter<Events>>("emitter");
<template>
<v-btn @click="emitter?.emit('toggleDrawer', null)" rounded="0" icon>
<v-avatar rounded="0">
<v-img src="/assets/isotipo.svg" />
<img src="/assets/isotipo.svg" />
</v-avatar>
</v-btn>
</template>

View File

@@ -19,7 +19,7 @@ onMounted(() => {
romApi
.getRecentRoms()
.then(({ data: recentData }) => {
romsStore.setRecentRoms(recentData.items);
romsStore.setRecentRoms(recentData);
})
.catch((error) => {
console.error(error);

View File

@@ -3,20 +3,19 @@ import AdminMenu from "@/components/Game/AdminMenu/Base.vue";
import romApi from "@/services/api/rom";
import storeAuth from "@/stores/auth";
import storeDownload from "@/stores/download";
import type { Rom } from "@/stores/roms";
import type { DetailedRom } from "@/stores/roms";
import type { Events } from "@/types/emitter";
import { getDownloadLink, platformSlugEJSCoreMap } from "@/utils";
import { getDownloadLink, isEmulationSupported } from "@/utils";
import type { Emitter } from "mitt";
import { inject, ref } from "vue";
const props = defineProps<{ rom: Rom }>();
const props = defineProps<{ rom: DetailedRom }>();
const downloadStore = storeDownload();
const emitter = inject<Emitter<Events>>("emitter");
const auth = storeAuth();
const emulation = ref(false);
const playInfoIcon = ref("mdi-play");
const emulationSupported =
props.rom.platform_slug.toLowerCase() in platformSlugEJSCoreMap;
const emulationSupported = isEmulationSupported(props.rom.platform_slug);
function toggleEmulation() {
emulation.value = !emulation.value;
@@ -24,7 +23,7 @@ function toggleEmulation() {
emitter?.emit("showEmulation", null);
}
async function copyDownloadLink(rom: Rom) {
async function copyDownloadLink(rom: DetailedRom) {
const downloadLink =
location.protocol +
"//" +

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import type { Rom } from "@/stores/roms";
import type { DetailedRom } from "@/stores/roms";
const props = defineProps<{ rom: Rom }>();
const props = defineProps<{ rom: DetailedRom }>();
import { useTheme } from "vuetify";
const theme = useTheme();
</script>
@@ -41,23 +41,16 @@ const theme = useTheme();
)}`
: `/assets/default/cover/big_${theme.global.name.value}_missing_cover.png`
"
:lazy-src="
`${expansion.cover_url}`
? `https:${expansion.cover_url.replace(
't_thumb',
't_cover_big'
)}`
: `/assets/default/cover/big_${theme.global.name.value}_missing_cover.png`
"
:aspect-ratio="3 / 4"
><v-chip
>
<v-chip
class="px-2 position-absolute chip-type text-white translucent"
density="compact"
label
>
<span>expansion</span>
</v-chip></v-img
>
</v-chip>
</v-img>
</v-card>
</a>
</v-col>
@@ -94,12 +87,8 @@ const theme = useTheme();
? `https:${dlc.cover_url.replace('t_thumb', 't_cover_big')}`
: `/assets/default/cover/big_${theme.global.name.value}_missing_cover.png`
"
:lazy-src="
`${dlc.cover_url}`
? `https:${dlc.cover_url.replace('t_thumb', 't_cover_small')}`
: `/assets/default/cover/small_${theme.global.name.value}_missing_cover.png`
"
:aspect-ratio="3 / 4"
lazy
><v-chip
class="px-2 position-absolute chip-type text-white translucent"
density="compact"

View File

@@ -1,26 +1,39 @@
<script setup lang="ts">
import type { RomSchema } from "@/__generated__";
import type { DetailedRom } from "@/stores/roms";
import { useTheme } from "vuetify";
const theme = useTheme();
const props = defineProps<{ rom: RomSchema }>();
const imgSrc =
!props.rom.igdb_id && !props.rom.moby_id && !props.rom.has_cover
? `/assets/default/cover/big_${theme.global.name.value}_unmatched.png`
: !props.rom.has_cover
? `/assets/default/cover/big_${theme.global.name.value}_missing_cover.png`
: `/assets/romm/resources/${props.rom.path_cover_s}`;
const imgSrcLazy =
!props.rom.igdb_id && !props.rom.moby_id && !props.rom.has_cover
? `/assets/default/cover/small_${theme.global.name.value}_unmatched.png`
: !props.rom.has_cover
? `/assets/default/cover/small_${theme.global.name.value}_missing_cover.png`
: `/assets/romm/resources/${props.rom.path_cover_s}`;
const props = defineProps<{ rom: DetailedRom }>();
</script>
<template>
<v-card rounded="0">
<v-img :src="imgSrc" :lazy-src="imgSrcLazy" id="background-header" />
<v-img
:src="
!rom.igdb_id && !rom.moby_id
? `/assets/default/cover/big_${theme.global.name.value}_unmatched.png`
: `/assets/romm/resources/${rom.path_cover_l}`
"
id="background-header"
lazy
>
<template v-slot:error>
<v-img
:src="`/assets/default/cover/big_${theme.global.name.value}_missing_cover.png`"
:aspect-ratio="3 / 4"
></v-img>
</template>
<template v-slot:placeholder>
<div class="d-flex align-center justify-center fill-height">
<v-progress-circular
:width="2"
:size="40"
color="romm-accent-1"
indeterminate
/>
</div>
</template>
</v-img>
</v-card>
</template>
<style scoped>

View File

@@ -1,28 +1,41 @@
<script setup lang="ts">
import storeDownload from "@/stores/download";
import { type SimpleRom } from "@/stores/roms";
defineProps<{ romId: number; src: string; lazySrc: string }>();
defineProps<{ rom: SimpleRom }>();
const downloadStore = storeDownload();
import { useTheme } from "vuetify";
const theme = useTheme();
</script>
<template>
<v-card
elevation="2"
:loading="downloadStore.value.includes(romId) ? 'romm-accent-1' : false"
:loading="downloadStore.value.includes(rom.id) ? 'romm-accent-1' : false"
>
<v-img
:value="romId"
:key="romId"
:src="src"
:lazy-src="lazySrc"
:value="rom.id"
:key="rom.id"
:src="
!rom.igdb_id && !rom.moby_id
? `/assets/default/cover/big_${theme.global.name.value}_unmatched.png`
: `/assets/romm/resources/${rom.path_cover_l}`
"
:aspect-ratio="3 / 4"
lazy
>
<slot name="editable"></slot>
<template v-slot:error>
<v-img
:src="`/assets/default/cover/big_${theme.global.name.value}_missing_cover.png`"
:aspect-ratio="3 / 4"
></v-img>
</template>
<template v-slot:placeholder>
<div class="d-flex align-center justify-center fill-height">
<v-progress-circular
color="romm-accent-1"
:width="2"
:size="20"
:size="40"
color="romm-accent-1"
indeterminate
/>
</div>

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import type { Rom } from "@/stores/roms";
import type { DetailedRom } from "@/stores/roms";
defineProps<{ rom: Rom }>();
defineProps<{ rom: DetailedRom }>();
</script>
<template>
<v-row no-gutters>

View File

@@ -2,18 +2,21 @@
import { isNull } from "lodash";
import type { FirmwareSchema, SaveSchema, StateSchema } from "@/__generated__";
import rom from "@/services/api/rom";
import type { Rom } from "@/stores/roms";
import { formatBytes } from "@/utils";
import type { DetailedRom } from "@/stores/roms";
import { formatBytes, getSupportedCores } from "@/utils";
import Player from "@/views/Play/Player.vue";
import { ref } from "vue";
import type { Platform } from "@/stores/platforms";
const props = defineProps<{ rom: Rom; platform: Platform }>();
const props = defineProps<{ rom: DetailedRom; platform: Platform }>();
const biosRef = ref<FirmwareSchema | null>(null);
const saveRef = ref<SaveSchema | null>(null);
const stateRef = ref<StateSchema | null>(null);
const gameRunning = ref(false);
const supportedCores = getSupportedCores(props.platform.slug);
const coreRef = ref<string | null>(supportedCores[0]);
const storedFSOP = localStorage.getItem("fullScreenOnPlay");
const fullScreenOnPlay = ref(isNull(storedFSOP) ? true : storedFSOP === "true");
@@ -35,6 +38,22 @@ function onFullScreenChange() {
<template>
<v-row v-if="rom && !gameRunning" no-gutters class="align-center">
<v-col cols="5" class="text-truncate mx-1">
<v-select
v-if="supportedCores.length > 1"
density="compact"
class="my-2"
hide-details
variant="outlined"
clearable
label="Core"
v-model="coreRef"
:items="
supportedCores.map((c) => ({
title: c,
value: c,
}))
"
/>
<v-select
density="compact"
class="my-2"
@@ -137,7 +156,13 @@ function onFullScreenChange() {
<v-row no-gutters>
<v-col v-if="gameRunning" cols="12" rounded id="game-wrapper">
<player :rom="props.rom" :state="stateRef" :save="saveRef" :bios="biosRef" />
<player
:rom="props.rom"
:state="stateRef"
:save="saveRef"
:bios="biosRef"
:core="coreRef"
/>
</v-col>
</v-row>
</template>

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import type { Rom } from "@/stores/roms";
import type { DetailedRom } from "@/stores/roms";
defineProps<{ rom: Rom }>();
defineProps<{ rom: DetailedRom }>();
</script>
<template>
<v-col class="pa-0" cols="4" sm="2" md="3" lg="3" v-for="game in rom.igdb_metadata?.expanded_games">

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import type { Rom } from "@/stores/roms";
import type { DetailedRom } from "@/stores/roms";
defineProps<{ rom: Rom }>();
defineProps<{ rom: DetailedRom }>();
</script>
<template>
<v-row no-gutters>

View File

@@ -1,10 +1,10 @@
<script setup lang="ts">
import type { PlatformSchema } from "@/__generated__";
import type { Platform } from "@/stores/platforms";
import FileInfo from "@/components/Details/Info/FileInfo.vue";
import GameInfo from "@/components/Details/Info/GameInfo.vue";
import type { Rom } from "@/stores/roms";
import type { DetailedRom } from "@/stores/roms";
defineProps<{ rom: Rom; platform: PlatformSchema }>();
defineProps<{ rom: DetailedRom; platform: Platform }>();
</script>
<template>
<file-info :rom="rom" :platform="platform" />

View File

@@ -1,11 +1,11 @@
<script setup lang="ts">
import type { PlatformSchema } from "@/__generated__";
import type { Platform } from "@/stores/platforms";
import VersionSwitcher from "@/components/Details/VersionSwitcher.vue";
import storeDownload from "@/stores/download";
import type { Rom } from "@/stores/roms";
import type { DetailedRom } from "@/stores/roms";
import { formatBytes } from "@/utils";
defineProps<{ rom: Rom; platform: PlatformSchema }>();
defineProps<{ rom: DetailedRom; platform: Platform }>();
const downloadStore = storeDownload();
</script>
<template>

View File

@@ -1,9 +1,9 @@
<script setup lang="ts">
import storeGalleryFilter from "@/stores/galleryFilter";
import type { Rom } from "@/stores/roms";
import type { DetailedRom } from "@/stores/roms";
import { useDisplay } from "vuetify";
defineProps<{ rom: Rom }>();
defineProps<{ rom: DetailedRom }>();
const { xs } = useDisplay();
const galleryFilter = storeGalleryFilter();
</script>

View File

@@ -2,13 +2,13 @@
import { ref } from "vue";
import { useTheme } from "vuetify";
import { type Rom } from "@/stores/roms";
import type { DetailedRom } from "@/stores/roms";
import storeAuth from "@/stores/auth";
import romApi from "@/services/api/rom";
import { MdEditor, MdPreview } from "md-editor-v3";
import "md-editor-v3/lib/style.css";
const props = defineProps<{ rom: Rom }>();
const props = defineProps<{ rom: DetailedRom }>();
const auth = storeAuth();
const theme = useTheme();

View File

@@ -1,8 +1,8 @@
<script setup lang="ts">
import type { Rom } from "@/stores/roms";
import type { DetailedRom } from "@/stores/roms";
import { ref } from "vue";
const props = defineProps<{ rom: Rom }>();
const props = defineProps<{ rom: DetailedRom }>();
const combined = ref([
...(props.rom.igdb_metadata?.remakes ?? []),
...(props.rom.igdb_metadata?.remasters ?? []),
@@ -39,12 +39,8 @@ const theme = useTheme();
? `https:${game.cover_url.replace('t_thumb', 't_cover_big')}`
: `/assets/default/cover/big_${theme.global.name.value}_missing_cover.png`
"
:lazy-src="
`${game.cover_url}`
? `https:${game.cover_url.replace('t_thumb', 't_cover_small')}`
: `/assets/default/cover/small_${theme.global.name.value}_missing_cover.png`
"
:aspect-ratio="3 / 4"
lazy
><v-chip
class="px-2 position-absolute chip-type text-white translucent"
density="compact"

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import type { Rom } from "@/stores/roms";
import type { DetailedRom } from "@/stores/roms";
defineProps<{ rom: Rom }>();
defineProps<{ rom: DetailedRom }>();
</script>
<template>
<v-row no-gutters>

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import type { Rom } from "@/stores/roms";
import type { DetailedRom } from "@/stores/roms";
defineProps<{ rom: Rom }>();
defineProps<{ rom: DetailedRom }>();
</script>
<template>
<v-row class="mb-3" no-gutters>

View File

@@ -1,13 +1,13 @@
<script setup lang="ts">
import type { SaveSchema } from "@/__generated__";
import saveApi from "@/services/api/save";
import storeRoms, { type Rom } from "@/stores/roms";
import storeRoms, { type DetailedRom } from "@/stores/roms";
import type { Events } from "@/types/emitter";
import { formatBytes } from "@/utils";
import type { Emitter } from "mitt";
import { inject, ref } from "vue";
const props = defineProps<{ rom: Rom }>();
const props = defineProps<{ rom: DetailedRom }>();
const savesToUpload = ref<File[]>([]);
const selectedSaves = ref<SaveSchema[]>([]);
const emitter = inject<Emitter<Events>>("emitter");

View File

@@ -1,8 +1,8 @@
<script setup lang="ts">
import type { Rom } from "@/stores/roms";
import type { DetailedRom } from "@/stores/roms";
import { useDisplay } from "vuetify";
defineProps<{ rom: Rom }>();
defineProps<{ rom: DetailedRom }>();
const { xs } = useDisplay();
</script>
<template>

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import type { Rom } from "@/stores/roms";
import type { DetailedRom } from "@/stores/roms";
defineProps<{ rom: Rom }>();
defineProps<{ rom: DetailedRom }>();
</script>
<template>

View File

@@ -5,11 +5,11 @@ import type { Emitter } from "mitt";
import type { Events } from "@/types/emitter";
import { formatBytes } from "@/utils";
import stateApi from "@/services/api/state";
import storeRoms, { type Rom } from "@/stores/roms";
import storeRoms, { type DetailedRom } from "@/stores/roms";
import type { StateSchema } from "@/__generated__";
const props = defineProps<{ rom: Rom }>();
const props = defineProps<{ rom: DetailedRom }>();
const statesToUpload = ref<File[]>([]);
const selectedStates = ref<StateSchema[]>([]);
const emitter = inject<Emitter<Events>>("emitter");

View File

@@ -2,10 +2,11 @@
import { identity } from "lodash";
import PlatformIcon from "@/components/Platform/PlatformIcon.vue";
import { regionToEmoji, languageToEmoji } from "@/utils";
import type { RomSchema, PlatformSchema } from "@/__generated__/";
import type { DetailedRom } from "@/stores/roms";
import type { Platform } from "@/stores/platforms";
import { useDisplay } from "vuetify";
defineProps<{ rom: RomSchema; platform: PlatformSchema }>();
defineProps<{ rom: DetailedRom; platform: Platform }>();
const { smAndDown } = useDisplay();
</script>
<template>

View File

@@ -1,15 +1,15 @@
<script setup lang="ts">
import type { PlatformSchema } from "@/__generated__";
import type { Rom } from "@/stores/roms";
import type { Platform } from "@/stores/platforms";
import type { DetailedRom } from "@/stores/roms";
import { languageToEmoji, regionToEmoji } from "@/utils";
import { ref } from "vue";
import { useRouter } from "vue-router";
const props = defineProps<{ rom: Rom; platform: PlatformSchema }>();
const props = defineProps<{ rom: DetailedRom; platform: Platform }>();
const router = useRouter();
const version = ref(props.rom.id);
function formatItem(rom: Rom) {
function formatItem(rom: DetailedRom) {
const langs = rom.languages.map((l) => languageToEmoji(l)).join(" ");
const regions = rom.regions.map((r) => regionToEmoji(r)).join(" ");
const tags = rom.tags.map((t) => `(${t})`).join(" ");

View File

@@ -4,7 +4,7 @@ import { useDisplay } from "vuetify";
import type { Emitter } from "mitt";
import type { Events } from "@/types/emitter";
import storeRoms, { type Rom } from "@/stores/roms";
import storeRoms, { type DetailedRom } from "@/stores/roms";
import saveApi from "@/services/api/save";
import stateApi from "@/services/api/state";
import type { SaveSchema, StateSchema } from "@/__generated__";
@@ -13,7 +13,7 @@ const { xs, mdAndDown, lgAndUp } = useDisplay();
const romsStore = storeRoms();
const show = ref(false);
const assetType = ref<"user_saves" | "user_states">("user_saves");
const romRef = ref<Rom | null>(null);
const romRef = ref<DetailedRom | null>(null);
const assets = ref<(SaveSchema | StateSchema)[]>([]);
const deleteFromFs = ref(false);

View File

@@ -21,15 +21,11 @@ emitter?.on("showLoadingDialog", (args) => {
width="auto"
persistent
>
<v-card outlined color="transparent">
<v-card-text class="pa-4">
<v-progress-circular
:width="3"
:size="70"
color="romm-accent-1"
indeterminate
/>
</v-card-text>
</v-card>
<v-progress-circular
:width="3"
:size="70"
color="romm-accent-1"
indeterminate
/>
</v-dialog>
</template>

View File

@@ -179,25 +179,7 @@ function closeDialog() {
<v-col cols="12" md="4" lg="3">
<cover
:class="{ 'mx-16': smAndDown, 'ml-2': md, 'my-4': smAndDown }"
:romId="rom.id"
:src="
imagePreviewUrl
? imagePreviewUrl
: !rom.igdb_id && !rom.moby_id && !rom.has_cover
? `/assets/default/cover/big_${theme.global.name.value}_unmatched.png`
: !rom.has_cover
? `/assets/default/cover/big_${theme.global.name.value}_missing_cover.png`
: `/assets/romm/resources/${rom.path_cover_l}`
"
:lazy-src="
imagePreviewUrl
? imagePreviewUrl
: !rom.igdb_id && !rom.moby_id && !rom.has_cover
? `/assets/default/cover/small_${theme.global.name.value}_unmatched.png`
: !rom.has_cover
? `/assets/default/cover/small_${theme.global.name.value}_missing_cover.png`
: `/assets/romm/resources/${rom.path_cover_s}`
"
:rom="rom"
>
<template v-slot:editable>
<v-chip-group class="position-absolute edit-cover pa-0">

View File

@@ -3,7 +3,7 @@ import type { SearchRomSchema } from "@/__generated__";
import SelectSourceDialog from "@/components/Dialog/Rom/MatchRom/SelectSource.vue";
import romApi from "@/services/api/rom";
import storeHeartbeat from "@/stores/heartbeat";
import storeRoms, { type Rom } from "@/stores/roms";
import storeRoms, { type SimpleRom } from "@/stores/roms";
import type { Events } from "@/types/emitter";
import type { Emitter } from "mitt";
import { inject, onBeforeUnmount, ref } from "vue";
@@ -11,7 +11,7 @@ import { useDisplay, useTheme } from "vuetify";
const { xs, mdAndDown, lgAndUp } = useDisplay();
const show = ref(false);
const rom = ref<Rom | null>(null);
const rom = ref<SimpleRom | null>(null);
const romsStore = storeRoms();
const renameAsIGDB = ref(false);
const searching = ref(false);
@@ -358,13 +358,6 @@ onBeforeUnmount(() => {
? matchedRom.igdb_url_cover
: matchedRom.moby_url_cover
"
:lazy-src="
!matchedRom.igdb_url_cover && !matchedRom.moby_url_cover
? `/assets/default/cover/small_${theme.global.name.value}_missing_cover.png`
: matchedRom.igdb_url_cover
? matchedRom.igdb_url_cover
: matchedRom.moby_url_cover
"
:aspect-ratio="3 / 4"
>
<template v-slot:placeholder>

View File

@@ -1,5 +1,4 @@
<script setup lang="ts">
import type { SearchRomSchema } from "@/__generated__";
import type { Events } from "@/types/emitter";
import type { Emitter } from "mitt";
@@ -59,7 +58,7 @@ function closeDialog() {
<v-row class="justify-center" no-gutters>
<v-col
class="pa-2"
:class="{'cover':!xs, 'cover-xs': xs}"
:class="{ cover: !xs, 'cover-xs': xs }"
v-for="source in sources"
>
<v-hover v-slot="{ isHovering, props }">
@@ -76,12 +75,8 @@ function closeDialog() {
? `/assets/default/cover/big_${theme.global.name.value}_missing_cover.png`
: source.url_cover
"
:lazy-src="
!source.url_cover
? `/assets/default/cover/small_${theme.global.name.value}_missing_cover.png`
: source.url_cover
"
:aspect-ratio="3 / 4"
lazy
>
<template v-slot:placeholder>
<div class="d-flex align-center justify-center fill-height">

View File

@@ -1,5 +1,4 @@
<script setup lang="ts">
import type { RomSchema } from "@/__generated__";
import PlatformIcon from "@/components/Platform/PlatformIcon.vue";
import romApi from "@/services/api/rom";
import storeGalleryView from "@/stores/galleryView";
@@ -10,6 +9,7 @@ import type { Emitter } from "mitt";
import { inject, onBeforeUnmount, ref } from "vue";
import { useRouter } from "vue-router";
import { useDisplay, useTheme } from "vuetify";
import { type SimpleRom } from "@/stores/roms";
const theme = useTheme();
const { xs, mdAndDown, lgAndUp } = useDisplay();
@@ -47,8 +47,8 @@ async function searchRoms() {
inputElement?.blur();
searching.value = true;
searchedRoms.value = (
await romApi.getRoms({ searchTerm: searchValue.value, size: 250 })
).data.items.sort((a, b) => {
await romApi.getRoms({ searchTerm: searchValue.value })
).data.sort((a, b) => {
return a.platform_name.localeCompare(b.platform_name);
});
platforms.value = [
@@ -73,7 +73,7 @@ async function filterRoms() {
}
}
function romDetails(rom: RomSchema) {
function romDetails(rom: SimpleRom) {
router.push({
name: "rom",
params: { rom: rom.id },
@@ -249,28 +249,27 @@ onBeforeUnmount(() => {
<v-img
v-bind="props"
:src="
!rom.igdb_id && !rom.moby_id && !rom.has_cover
!rom.igdb_id && !rom.moby_id
? `/assets/default/cover/big_${theme.global.name.value}_unmatched.png`
: !rom.has_cover
? `/assets/default/cover/big_${theme.global.name.value}_missing_cover.png`
: `/assets/romm/resources/${rom.path_cover_l}`
"
:lazy-src="
!rom.igdb_id && !rom.moby_id && !rom.has_cover
? `/assets/default/cover/small_${theme.global.name.value}_unmatched.png`
: !rom.has_cover
? `/assets/default/cover/small_${theme.global.name.value}_missing_cover.png`
: `/assets/romm/resources/${rom.path_cover_s}`
"
:aspect-ratio="3 / 4"
lazy
>
<template v-slot:error>
<v-img
:src="`/assets/default/cover/big_${theme.global.name.value}_missing_cover.png`"
:aspect-ratio="3 / 4"
></v-img>
</template>
<template v-slot:placeholder>
<div
class="d-flex align-center justify-center fill-height"
>
<v-progress-circular
color="romm-accent-1"
:width="2"
:size="40"
color="romm-accent-1"
indeterminate
/>
</div>

View File

@@ -29,10 +29,10 @@ onBeforeMount(async () => {
"https://api.github.com/repos/rommapp/romm/releases/latest"
);
const json = await response.json();
GITHUB_VERSION.value = json.name;
GITHUB_VERSION.value = json.tag_name;
latestVersionDismissed.value =
VERSION === "development" ||
json.name === localStorage.getItem("dismissedVersion");
json.tag_name === localStorage.getItem("dismissedVersion");
});
async function logout() {
@@ -108,7 +108,7 @@ async function logout() {
<v-col class="py-1">
<span
>New version available
<span class="text-romm-accent-1">{{ GITHUB_VERSION }}</span></span
<span class="text-romm-accent-1">v{{ GITHUB_VERSION }}</span></span
>
</v-col>
</v-row>
@@ -119,7 +119,7 @@ async function logout() {
><span class="ml-4"
><a
target="_blank"
:href="`https://github.com/rommapp/romm/releases/tag/v${GITHUB_VERSION}`"
:href="`https://github.com/rommapp/romm/releases/tag/${GITHUB_VERSION}`"
>See what's new!</a
></span
>

View File

@@ -4,7 +4,7 @@ import storeGalleryFilter from "@/stores/galleryFilter";
import type { Events } from "@/types/emitter";
import type { Emitter } from "mitt";
import { storeToRefs } from "pinia";
import { inject, ref } from "vue";
import { inject, ref, nextTick } from "vue";
// Props
const showFilterBar = ref(false);
@@ -27,50 +27,50 @@ const {
<div v-if="showFilterBar">
<v-row no-gutters class="pa-1">
<filter-unmatched-btn />
<v-select
<v-autocomplete
hide-details
clearable
label="Genre"
density="compact"
variant="outlined"
class="ma-1"
@update:model-value="emitter?.emit('filter', null);"
@update:model-value="nextTick(() => emitter?.emit('filter', null))"
v-model="selectedGenre"
:items="galleryFilterStore.filterGenres"
></v-select>
<v-select
></v-autocomplete>
<v-autocomplete
hide-details
clearable
label="Franchise"
density="compact"
variant="outlined"
class="ma-1"
@update:model-value="emitter?.emit('filter', null)"
@update:model-value="nextTick(() => emitter?.emit('filter', null))"
v-model="selectedFranchise"
:items="galleryFilterStore.filterFranchises"
></v-select>
<v-select
></v-autocomplete>
<v-autocomplete
hide-details
clearable
label="Collection"
density="compact"
variant="outlined"
class="ma-1"
@update:model-value="emitter?.emit('filter', null)"
@update:model-value="nextTick(() => emitter?.emit('filter', null))"
v-model="selectedCollection"
:items="galleryFilterStore.filterCollections"
></v-select>
<v-select
></v-autocomplete>
<v-autocomplete
hide-details
clearable
label="Company"
density="compact"
variant="outlined"
class="ma-1"
@update:model-value="emitter?.emit('filter', null)"
@update:model-value="nextTick(() => emitter?.emit('filter', null))"
v-model="selectedCompany"
:items="galleryFilterStore.filterCompanies"
></v-select>
></v-autocomplete>
</v-row>
<v-divider
:thickness="2"

View File

@@ -3,7 +3,6 @@ import socket from "@/services/socket";
import storeHeartbeat from "@/stores/heartbeat";
import storeRoms from "@/stores/roms";
import storeScanning from "@/stores/scanning";
import { computed, ref } from "vue";
// Props
const scanningStore = storeScanning();

View File

@@ -2,10 +2,10 @@
import { inject } from "vue";
import type { Emitter } from "mitt";
import type { Events } from "@/types/emitter";
import type { Rom } from "@/stores/roms";
import type { SimpleRom } from "@/stores/roms";
import storeHeartbeat from "@/stores/heartbeat";
defineProps<{ rom: Rom }>();
defineProps<{ rom: SimpleRom }>();
const emitter = inject<Emitter<Events>>("emitter");
const heartbeat = storeHeartbeat();
</script>

View File

@@ -3,11 +3,11 @@ import romApi from "@/services/api/rom";
import storeDownload from "@/stores/download";
import storeAuth from "@/stores/auth";
import AdminMenu from "@/components/Game/AdminMenu/Base.vue";
import type { Rom } from "@/stores/roms";
import { platformSlugEJSCoreMap } from "@/utils";
import type { SimpleRom } from "@/stores/roms";
import { isEmulationSupported } from "@/utils";
// Props
defineProps<{ rom: Rom }>();
defineProps<{ rom: SimpleRom }>();
const auth = storeAuth();
const downloadStore = storeDownload();
</script>
@@ -25,7 +25,7 @@ const downloadStore = storeDownload();
variant="text"
/>
<v-btn
v-if="rom.platform_slug.toLowerCase() in platformSlugEJSCoreMap"
v-if="isEmulationSupported(rom.platform_slug)"
class="action-bar-btn"
:href="`/play/${rom.id}`"
icon="mdi-play"

View File

@@ -1,11 +1,11 @@
<script setup lang="ts">
import storeRoms, { type Rom } from "@/stores/roms.js";
import storeRoms, { type SimpleRom } from "@/stores/roms.js";
import ActionBar from "@/components/Game/Card/ActionBar.vue";
import Cover from "@/components/Game/Card/Cover.vue";
// Props
const props = defineProps<{
rom: Rom;
rom: SimpleRom;
index: number;
selected: boolean;
showSelector: boolean;

View File

@@ -1,15 +1,16 @@
<script setup lang="ts">
import LazyImage from "@/components/LazyImage.vue";
import storeDownload from "@/stores/download";
import storeGalleryView from "@/stores/galleryView";
import storeRoms, { type Rom } from "@/stores/roms";
import storeRoms, { type SimpleRom } from "@/stores/roms";
import { languageToEmoji, regionToEmoji } from "@/utils";
import { identity, isNull } from "lodash";
import { ref } from "vue";
import { ref, onMounted, onUnmounted } from "vue";
import { useTheme } from "vuetify";
// Props
defineProps<{
rom: Rom;
rom: SimpleRom;
isHoveringTop: boolean;
showSelector: boolean;
selected: boolean;
@@ -57,6 +58,18 @@ function onTouchStart(event: TouchEvent) {
function onTouchEnd() {
clearTimeout(timeout);
}
function onScroll() {
clearTimeout(timeout);
}
onMounted(() => {
window.addEventListener("scroll", onScroll);
});
onUnmounted(() => {
window.removeEventListener("scroll", onScroll);
});
</script>
<template>
@@ -86,74 +99,80 @@ function onTouchEnd() {
:value="rom.id"
:key="rom.id"
v-bind="props"
:placeholder="`/assets/default/cover/big_${theme.global.name.value}_missing_cover.png`"
:src="
!rom.igdb_id && !rom.moby_id && !rom.has_cover
!rom.igdb_id && !rom.moby_id
? `/assets/default/cover/big_${theme.global.name.value}_unmatched.png`
: !rom.has_cover
? `/assets/default/cover/big_${theme.global.name.value}_missing_cover.png`
: `/assets/romm/resources/${rom.path_cover_l}`
"
:lazy-src="
!rom.igdb_id && !rom.moby_id && !rom.has_cover
? `/assets/default/cover/small_${theme.global.name.value}_unmatched.png`
: !rom.has_cover
? `/assets/default/cover/small_${theme.global.name.value}_missing_cover.png`
!rom.igdb_id && !rom.moby_id
? `/assets/default/cover/big_${theme.global.name.value}_unmatched.png`
: `/assets/romm/resources/${rom.path_cover_s}`
"
:aspect-ratio="3 / 4"
>
<div v-bind="props" style="position: absolute; top: 0; width: 100%">
<v-expand-transition>
<div
v-if="isHovering || !rom.has_cover"
class="translucent text-caption"
:class="{
'text-truncate': galleryViewStore.current == 0 && !isHovering,
}"
>
<v-list-item>{{ rom.name }}</v-list-item>
</div>
</v-expand-transition>
<v-row no-gutters class="text-white px-1">
<v-chip
v-if="rom.regions.filter(identity).length > 0 && showRegions"
:title="`Regions: ${rom.regions.join(', ')}`"
class="translucent mr-1 mt-1 px-1"
:class="{ 'emoji-collection': rom.regions.length > 3 }"
density="compact"
>
<span class="emoji" v-for="region in rom.regions.slice(0, 3)">
{{ regionToEmoji(region) }}
</span>
</v-chip>
<v-chip
v-if="rom.languages.filter(identity).length > 0 && showLanguages"
:title="`Languages: ${rom.languages.join(', ')}`"
class="translucent mr-1 mt-1 px-1"
:class="{ 'emoji-collection': rom.languages.length > 3 }"
density="compact"
>
<span class="emoji" v-for="language in rom.languages.slice(0, 3)">
{{ languageToEmoji(language) }}
</span>
</v-chip>
<v-chip
v-if="rom.siblings && rom.siblings.length > 0 && showSiblings"
:title="`${rom.siblings.length + 1} versions`"
class="translucent mr-1 mt-1"
density="compact"
>
+{{ rom.siblings.length }}
</v-chip>
</v-row>
</div>
<template v-slot:error>
<v-img
:src="`/assets/default/cover/big_${theme.global.name.value}_missing_cover.png`"
:aspect-ratio="3 / 4"
></v-img>
</template>
<template v-slot:placeholder>
<div class="d-flex align-center justify-center fill-height">
<v-progress-circular
color="romm-accent-1"
:width="2"
:size="40"
color="romm-accent-1"
indeterminate
/>
</div>
</template>
<v-expand-transition>
<div
v-if="isHovering || !rom.has_cover"
class="translucent text-caption"
:class="{
'text-truncate': galleryViewStore.current == 0 && !isHovering,
}"
>
<v-list-item>{{ rom.name }}</v-list-item>
</div>
</v-expand-transition>
<v-row no-gutters class="text-white px-1">
<v-chip
v-if="rom.regions.filter(identity).length > 0 && showRegions"
:title="`Regions: ${rom.regions.join(', ')}`"
class="translucent mr-1 mt-1 px-1"
:class="{ 'emoji-collection': rom.regions.length > 3 }"
density="compact"
>
<span class="emoji" v-for="region in rom.regions.slice(0, 3)">
{{ regionToEmoji(region) }}
</span>
</v-chip>
<v-chip
v-if="rom.languages.filter(identity).length > 0 && showLanguages"
:title="`Languages: ${rom.languages.join(', ')}`"
class="translucent mr-1 mt-1 px-1"
:class="{ 'emoji-collection': rom.languages.length > 3 }"
density="compact"
>
<span class="emoji" v-for="language in rom.languages.slice(0, 3)">
{{ languageToEmoji(language) }}
</span>
</v-chip>
<v-chip
v-if="rom.siblings && rom.siblings.length > 0 && showSiblings"
:title="`${rom.siblings.length + 1} versions`"
class="translucent mr-1 mt-1"
density="compact"
>
+{{ rom.siblings.length }}
</v-chip>
</v-row>
</v-img>
</v-hover>
</router-link>

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref } from "vue";
import { ref, inject, onMounted, watch } from "vue";
import { useRouter } from "vue-router";
import AdminMenu from "@/components/Game/AdminMenu/Base.vue";
@@ -7,10 +7,12 @@ import romApi from "@/services/api/rom";
import storeAuth from "@/stores/auth";
import storeDownload from "@/stores/download";
import storeRoms from "@/stores/roms";
import type { Events } from "@/types/emitter";
import type { Emitter } from "mitt";
import {
formatBytes,
languageToEmoji,
platformSlugEJSCoreMap,
isEmulationSupported,
regionToEmoji,
} from "@/utils";
import { useTheme } from "vuetify";
@@ -62,21 +64,39 @@ const HEADERS = [
{ title: "", align: "end", key: "actions", sortable: false },
] as const;
const PER_PAGE_OPTIONS = [
{ value: -1, title: "$vuetify.dataFooter.itemsPerPageAll" },
] as const;
const PER_PAGE_OPTIONS = [10, 25, 50, 100];
const emitter = inject<Emitter<Events>>("emitter");
emitter?.on("updateDataTablePages", updateDataTablePages);
// Props
const router = useRouter();
const downloadStore = storeDownload();
const romsStore = storeRoms();
const auth = storeAuth();
const romsPerPage = ref(-1);
const page = ref(1);
const storedRomsPerPage = parseInt(localStorage.getItem("romsPerPage") ?? "");
const romsPerPage = ref(isNaN(storedRomsPerPage) ? 25 : storedRomsPerPage);
const pageCount = ref(0);
// Functions
function rowClick(_: Event, row: any) {
router.push({ name: "rom", params: { rom: row.item.id } });
}
function updateDataTablePages() {
pageCount.value = Math.ceil(
romsStore.filteredRoms.length / romsPerPage.value
);
}
watch(romsPerPage, async () => {
localStorage.setItem("romsPerPage", romsPerPage.value.toString());
updateDataTablePages();
});
onMounted(() => {
updateDataTablePages();
});
</script>
<template>
@@ -91,6 +111,7 @@ function rowClick(_: Event, row: any) {
@click:row="rowClick"
show-select
v-model="romsStore._selectedIDs"
v-model:page="page"
>
<template v-slot:item.path_cover_s="{ item }">
<v-avatar :rounded="0">
@@ -102,23 +123,44 @@ function rowClick(_: Event, row: any) {
/>
<v-img
:src="
!item.igdb_id && !item.has_cover
? `/assets/default/cover/small_${theme.global.name.value}_unmatched.png`
: !item.has_cover
? `/assets/default/cover/small_${theme.global.name.value}_missing_cover.png`
: `/assets/romm/resources/${item.path_cover_s}`
!item.igdb_id && !item.moby_id
? `/assets/default/cover/big_${theme.global.name.value}_unmatched.png`
: `/assets/romm/resources/${item.path_cover_l}`
"
:lazy-src="
!item.igdb_id && !item.has_cover
? `/assets/default/cover/small_${theme.global.name.value}_unmatched.png`
: !item.has_cover
? `/assets/default/cover/small_${theme.global.name.value}_missing_cover.png`
!item.igdb_id && !item.moby_id
? `/assets/default/cover/big_${theme.global.name.value}_unmatched.png`
: `/assets/romm/resources/${item.path_cover_s}`
"
min-height="150"
/>
>
<template v-slot:error>
<v-img
:src="`/assets/default/cover/big_${theme.global.name.value}_missing_cover.png`"
></v-img>
</template>
<template v-slot:placeholder>
<div class="d-flex align-center justify-center fill-height">
<v-progress-circular
:width="2"
:size="20"
color="romm-accent-1"
indeterminate
/>
</div>
</template>
</v-img>
</v-avatar>
</template>
<template v-slot:item.name="{ item }">
<span>
{{ item.name }}
</span>
</template>
<template v-slot:item.file_name="{ item }">
<span>
{{ item.file_name }}
</span>
</template>
<template v-slot:item.file_size_bytes="{ item }">
<span>
{{ formatBytes(item.file_size_bytes) }}
@@ -147,7 +189,7 @@ function rowClick(_: Event, row: any) {
<v-icon>mdi-download</v-icon>
</v-btn>
<v-btn
v-if="item.platform_slug.toLowerCase() in platformSlugEJSCoreMap"
v-if="isEmulationSupported(item.platform_slug)"
size="small"
variant="text"
:href="`/play/${item.id}`"
@@ -171,5 +213,31 @@ function rowClick(_: Event, row: any) {
<admin-menu :rom="item" />
</v-menu>
</template>
<template v-slot:bottom>
<v-divider class="border-opacity-25" />
<v-row no-gutters class="pt-2 align-center">
<v-col cols="11" class="px-6">
<v-pagination
rounded="0"
:show-first-last-page="true"
active-color="romm-accent-1"
v-model="page"
:length="pageCount"
></v-pagination>
</v-col>
<v-col cols="5" sm="2" xl="1">
<v-select
class="pa-2"
label="Roms per page"
density="compact"
variant="outlined"
:items="PER_PAGE_OPTIONS"
v-model="romsPerPage"
hide-details
/>
</v-col>
</v-row>
</template>
</v-data-table>
</template>

View File

@@ -0,0 +1,98 @@
<script setup lang="ts">
import {
reactive,
computed,
ref,
onMounted,
onBeforeUnmount,
useAttrs,
} from "vue";
const props = defineProps<{
src: string;
placeholder: string;
srcset?: string;
intersectionOptions?: IntersectionObserverInit;
usePicture?: boolean;
}>();
const emit = defineEmits(["load", "error", "intersect"]);
const attrs = useAttrs();
const root = ref<HTMLPictureElement | HTMLImageElement | null>(null);
const state = reactive<{
observer: IntersectionObserver | null;
intersected: boolean;
loaded: boolean;
}>({
observer: null,
intersected: false,
loaded: false,
});
const srcImage = computed(() =>
state.intersected && props.src ? props.src : props.placeholder
);
const srcsetImage = computed(() =>
state.intersected && props.srcset ? props.srcset : ""
);
const load = () => {
if (root.value && root.value.getAttribute("src") !== props.placeholder) {
state.loaded = true;
emit("load", root.value);
}
};
const error = () => emit("error", root.value);
// Hooks
onMounted(() => {
if ("IntersectionObserver" in window) {
state.observer = new IntersectionObserver((entries) => {
const image = entries[0];
if (image.isIntersecting) {
state.intersected = true;
state.observer?.disconnect();
emit("intersect");
}
}, props.intersectionOptions ?? {});
if (root.value) state.observer.observe(root.value);
}
});
onBeforeUnmount(() => {
if ("IntersectionObserver" in window && state.observer) {
state.observer.disconnect();
}
});
</script>
<template>
<picture v-if="usePicture" ref="root" @load="load">
<slot v-if="state.intersected"></slot>
<img
v-else
:src="srcImage"
:srcSet="srcsetImage"
v-bind="attrs"
:class="[attrs.class, 'v-responsive v-img v-lazy-image', { 'v-lazy-image-loaded': state.loaded }]"
@load="load"
@error="error"
>
<slot />
</img>
</picture>
<img
v-else
ref="root"
:src="srcImage"
:srcSet="srcsetImage"
v-bind="attrs"
:class="[attrs.class, 'v-responsive v-img v-lazy-image', { 'v-lazy-image-loaded': state.loaded }]"
@load="load"
@error="error"
>
<slot />
</img>
</template>

View File

@@ -1,5 +1,6 @@
import type { MessageResponse, PlatformSchema } from "@/__generated__";
import type { MessageResponse } from "@/__generated__";
import api from "@/services/api/index";
import type { Platform } from "@/stores/platforms";
export const platformApi = api;
@@ -7,28 +8,28 @@ async function uploadPlatform({
fsSlug,
}: {
fsSlug: string;
}): Promise<{ data: PlatformSchema }> {
}): Promise<{ data: Platform }> {
return api.post("/platforms", { fs_slug: fsSlug });
}
async function getPlatforms(): Promise<{ data: PlatformSchema[] }> {
async function getPlatforms(): Promise<{ data: Platform[] }> {
return api.get("/platforms");
}
async function getPlatform(
id: number | undefined,
): Promise<{ data: PlatformSchema }> {
): Promise<{ data: Platform }> {
return api.get(`/platforms/${id}`);
}
async function getSupportedPlatforms(): Promise<{ data: PlatformSchema[] }> {
async function getSupportedPlatforms(): Promise<{ data: Platform[] }> {
return api.get("/platforms/supported");
}
async function updatePlatform({
platform,
}: {
platform: PlatformSchema;
platform: Platform;
}): Promise<{ data: MessageResponse }> {
return api.delete(`/platforms/${platform.id}`);
}
@@ -36,7 +37,7 @@ async function updatePlatform({
async function deletePlatform({
platform,
}: {
platform: PlatformSchema;
platform: Platform;
}): Promise<{ data: MessageResponse }> {
return api.delete(`/platforms/${platform.id}`);
}

View File

@@ -1,6 +1,5 @@
import type {
AddRomsResponse,
CursorPage_RomSchema_,
MessageResponse,
RomSchema,
SearchRomSchema,
@@ -8,7 +7,7 @@ import type {
import api from "@/services/api/index";
import socket from "@/services/socket";
import storeDownload from "@/stores/download";
import type { Rom } from "@/stores/roms";
import type { SimpleRom, DetailedRom } from "@/stores/roms";
import { getDownloadLink } from "@/utils";
export const romApi = api;
@@ -33,24 +32,18 @@ async function uploadRoms({
async function getRoms({
platformId = null,
size = 60,
cursor = "",
searchTerm = "",
orderBy = "name",
orderDir = "asc",
}: {
platformId?: number | null;
size?: number | null;
cursor?: string | null;
searchTerm?: string | null;
orderBy?: string | null;
orderDir?: string | null;
}): Promise<{ data: CursorPage_RomSchema_ }> {
}): Promise<{ data: SimpleRom[] }> {
return api.get(`/roms`, {
params: {
platform_id: platformId,
size: size,
cursor: cursor,
search_term: searchTerm,
order_by: orderBy,
order_dir: orderDir,
@@ -58,13 +51,13 @@ async function getRoms({
});
}
async function getRecentRoms(): Promise<{ data: CursorPage_RomSchema_ }> {
async function getRecentRoms(): Promise<{ data: SimpleRom[] }> {
return api.get("/roms", {
params: { size: 15, order_by: "id", order_dir: "desc" },
params: { order_by: "id", order_dir: "desc", limit: 15 },
});
}
async function getRom({ romId }: { romId: number }): Promise<{ data: Rom }> {
async function getRom({ romId }: { romId: number }): Promise<{ data: DetailedRom }> {
return api.get(`/roms/${romId}`);
}
@@ -105,7 +98,7 @@ async function downloadRom({
rom,
files = [],
}: {
rom: Rom;
rom: SimpleRom;
files?: string[];
}) {
const a = document.createElement("a");
@@ -124,7 +117,7 @@ async function downloadRom({
}
}
export type UpdateRom = Rom & {
export type UpdateRom = SimpleRom & {
artwork?: File;
};
@@ -136,7 +129,7 @@ async function updateRom({
rom: UpdateRom;
renameAsIGDB?: boolean;
removeCover?: boolean;
}): Promise<{ data: RomSchema }> {
}): Promise<{ data: DetailedRom }> {
var formData = new FormData();
if (rom.igdb_id) formData.append("igdb_id", rom.igdb_id.toString());
if (rom.moby_id) formData.append("moby_id", rom.moby_id.toString());
@@ -155,7 +148,7 @@ async function deleteRoms({
roms,
deleteFromFs = false,
}: {
roms: Rom[];
roms: SimpleRom[];
deleteFromFs: boolean;
}): Promise<{ data: MessageResponse }> {
return api.post("/roms/delete", {
@@ -172,7 +165,7 @@ async function updateRomNote({
romId: number;
rawMarkdown: string;
isPublic: boolean;
}): Promise<{ data: RomSchema }> {
}): Promise<{ data: DetailedRom }> {
return api.put(`/roms/${romId}/note`, {
raw_markdown: rawMarkdown,
is_public: isPublic,

View File

@@ -1,6 +1,6 @@
import type { SaveSchema, UploadedSavesResponse } from "@/__generated__";
import api from "@/services/api/index";
import type { Rom } from "@/stores/roms";
import type { DetailedRom } from "@/stores/roms";
export const saveApi = api;
@@ -9,7 +9,7 @@ async function uploadSaves({
saves,
emulator,
}: {
rom: Rom;
rom: DetailedRom;
saves: File[];
emulator?: string;
}): Promise<{ data: UploadedSavesResponse }> {

View File

@@ -3,7 +3,7 @@ import type {
UploadedScreenshotsResponse,
} from "@/__generated__";
import api from "@/services/api/index";
import type { Rom } from "@/stores/roms";
import type { DetailedRom } from "@/stores/roms";
export const screenshotApi = api;
@@ -11,7 +11,7 @@ async function uploadScreenshots({
rom,
screenshots,
}: {
rom: Rom;
rom: DetailedRom;
screenshots: File[];
}): Promise<{ data: UploadedScreenshotsResponse }> {
let formData = new FormData();

View File

@@ -1,6 +1,6 @@
import type { StateSchema, UploadedStatesResponse } from "@/__generated__";
import api from "@/services/api/index";
import type { Rom } from "@/stores/roms";
import type { DetailedRom } from "@/stores/roms";
export const stateApi = api;
@@ -9,7 +9,7 @@ async function uploadStates({
states,
emulator,
}: {
rom: Rom;
rom: DetailedRom;
states: File[];
emulator?: string;
}): Promise<{ data: UploadedStatesResponse }> {

View File

@@ -48,10 +48,11 @@ export default defineStore("galleryFilter", {
this.filterUnmatched = !this.filterUnmatched;
},
isFiltered() {
return (
return Boolean(
normalizeString(this.filterSearch).trim() != "" ||
this.filterUnmatched ||
this.selectedGenre ||
this.filterFranchises ||
this.selectedFranchise ||
this.selectedCollection ||
this.selectedCompany
);

View File

@@ -1,5 +1,6 @@
import { defineStore } from "pinia";
import type { PlatformSchema } from "@/__generated__";
import { uniqBy } from "lodash";
export type Platform = PlatformSchema;
@@ -15,11 +16,18 @@ export default defineStore("platforms", {
filledPlatforms: ({ value }) => value.filter((p) => p.rom_count > 0),
},
actions: {
_reorder() {
this.value = this.value.sort((a, b) => {
return a.name.localeCompare(b.name);
});
this.value = uniqBy(this.value, "id");
},
set(platforms: Platform[]) {
this.value = platforms;
},
add(platform: Platform) {
this.value.push(platform);
this._reorder();
},
exists(platform: Platform) {
return this.value.filter((p) => p.fs_slug == platform.fs_slug).length > 0;

View File

@@ -1,29 +1,30 @@
import type { PlatformSchema, RomSchema } from "@/__generated__/";
import { groupBy, isNull, uniqBy } from "lodash";
import type { RomSchema, DetailedRomSchema } from "@/__generated__/";
import { groupBy, uniqBy } from "lodash";
import { nanoid } from "nanoid";
import { defineStore, type Store } from "pinia";
import { defineStore } from "pinia";
import storeGalleryFilter from "./galleryFilter";
import type { ExtractPiniaStoreType } from "@/types";
type GalleryFilterStore = ExtractPiniaStoreType<typeof storeGalleryFilter>;
export type Rom = RomSchema & {
export type SimpleRom = RomSchema & {
siblings?: RomSchema[]; // Added by the frontend
};
export type DetailedRom = DetailedRomSchema;
export default defineStore("roms", {
state: () => ({
_platformID: 0,
_all: [] as Rom[],
_grouped: [] as Rom[],
_all: [] as SimpleRom[],
_grouped: [] as SimpleRom[],
_filteredIDs: [] as number[],
_searchIDs: [] as number[],
_selectedIDs: [] as number[],
recentRoms: [] as Rom[],
recentRoms: [] as SimpleRom[],
lastSelectedIndex: -1,
cursor: "" as string | null,
searchCursor: "" as string | null,
selecting: false,
itemsPerBatch: 72,
}),
getters: {
@@ -62,7 +63,7 @@ export default defineStore("roms", {
),
)
.map((games) => ({
...(games.shift() as Rom),
...(games.shift() as SimpleRom),
siblings: games,
}))
.sort((a, b) => {
@@ -72,23 +73,23 @@ export default defineStore("roms", {
setPlatformID(platformID: number) {
this._platformID = platformID;
},
setRecentRoms(roms: Rom[]) {
setRecentRoms(roms: SimpleRom[]) {
this.recentRoms = roms;
},
// All roms
set(roms: Rom[]) {
set(roms: SimpleRom[]) {
this._all = roms;
this._reorder();
},
add(roms: Rom[]) {
add(roms: SimpleRom[]) {
this._all = this._all.concat(roms);
this._reorder();
},
update(rom: Rom) {
update(rom: SimpleRom) {
this._all = this._all.map((value) => (value.id === rom.id ? rom : value));
this._reorder();
},
remove(roms: Rom[]) {
remove(roms: SimpleRom[]) {
this._all = this._all.filter((value) => {
return !roms.find((rom) => {
return rom.id === value.id;
@@ -114,7 +115,7 @@ export default defineStore("roms", {
this.lastSelectedIndex = -1;
},
// Filter roms by gallery filter store state
setFiltered(roms: Rom[], galleryFilter: GalleryFilterStore) {
setFiltered(roms: SimpleRom[], galleryFilter: GalleryFilterStore) {
this._filteredIDs = roms.map((rom) => rom.id);
if (galleryFilter.filterUnmatched) this.filterUnmatched();
if (galleryFilter.selectedGenre) {
@@ -164,17 +165,17 @@ export default defineStore("roms", {
.map((rom) => rom.id);
},
// Search roms
setSearch(roms: Rom[]) {
setSearch(roms: SimpleRom[]) {
this._searchIDs = roms.map((rom) => rom.id);
},
// Selected roms
setSelection(roms: Rom[]) {
setSelection(roms: SimpleRom[]) {
this._selectedIDs = roms.map((rom) => rom.id);
},
addToSelection(rom: Rom) {
addToSelection(rom: SimpleRom) {
this._selectedIDs.push(rom.id);
},
removeFromSelection(rom: Rom) {
removeFromSelection(rom: SimpleRom) {
this._selectedIDs = this._selectedIDs.filter((id) => {
return id !== rom.id;
});

View File

@@ -1,5 +1,5 @@
import { defineStore } from "pinia";
import type { Rom } from "@/stores/roms";
import type { SimpleRom } from "@/stores/roms";
export default defineStore("scanning", {
state: () => ({
@@ -8,7 +8,7 @@ export default defineStore("scanning", {
name: string;
slug: string;
id: number;
roms: Rom[];
roms: SimpleRom[];
}[],
scanStats: {
scanned_platforms: 0,

View File

@@ -1,6 +1,6 @@
import type { SaveSchema, SearchRomSchema, StateSchema } from "@/__generated__";
import type { Platform } from "@/stores/platforms";
import type { Rom } from "@/stores/roms";
import type { SimpleRom } from "@/stores/roms";
import type { User } from "@/stores/users";
export type UserItem = User & {
@@ -17,12 +17,12 @@ export type SnackbarStatus = {
export type Events = {
showDeletePlatformDialog: Platform;
showMatchRomDialog: Rom;
showMatchRomDialog: SimpleRom;
showSelectSourceDialog: SearchRomSchema;
showSearchRomDialog: null;
showEditRomDialog: Rom;
showEditRomDialog: SimpleRom;
showCopyDownloadLinkDialog: string;
showDeleteRomDialog: Rom[];
showDeleteRomDialog: SimpleRom[];
showUploadRomDialog: Platform | null;
showFirmwareDialog: Platform;
showAddPlatformDialog: null;
@@ -47,11 +47,11 @@ export type Events = {
showEditUserDialog: User;
showDeleteUserDialog: User;
showDeleteSavesDialog: {
rom: Rom;
rom: DetailedRom;
saves: SaveSchema[];
};
showDeleteStatesDialog: {
rom: Rom;
rom: DetailedRom;
states: StateSchema[];
};
showEmulation: null;
@@ -68,6 +68,7 @@ export type Events = {
filter: null;
filterBarShow: null;
filterBarReset: null;
updateDataTablePages: null;
sortBarShow: null;
romUpdated: Rom;
romUpdated: DetailedRom;
};

View File

@@ -1,5 +1,5 @@
import cronstrue from "cronstrue";
import type { Rom } from "@/stores/roms";
import type { SimpleRom } from "@/stores/roms";
export const views: Record<
number,
@@ -48,14 +48,6 @@ export const views: Record<
export const defaultAvatarPath = "/assets/default/user.png";
export function toTop() {
window.scrollTo({
top: 0,
left: 0,
behavior: "smooth",
});
}
export function normalizeString(s: string) {
return s
.toLowerCase()
@@ -75,19 +67,16 @@ export function getDownloadLink({
rom,
files = [],
}: {
rom: Rom;
rom: SimpleRom;
files?: string[];
}) {
// Force download of all multirom-parts when no part is selected
if (files.length == 0) {
files = rom.files;
const queryParams = new URLSearchParams();
if (files.length) {
files.forEach((file) => queryParams.append("files", file));
}
var filesParams = "";
files.forEach((file) => {
filesParams += `files=${file}&`;
});
return `/api/roms/${rom.id}/content/${rom.file_name}?${filesParams}`;
return `/api/roms/${rom.id}/content/${
rom.file_name
}?${queryParams.toString()}`;
}
/**
@@ -277,68 +266,89 @@ export function languageToEmoji(language: string) {
}
}
export const platformSlugEJSCoreMap = {
"3do": "opera",
amiga: "puae",
arcade: "mame2003_plus",
atari2600: "stella2014",
"atari-2600-plus": "stella2014",
atari5200: "a5200",
atari7800: "prosystem",
c64: "vice_x64",
"commodore-64c": "vice_x64",
colecovision: "gearcoleco",
jaguar: "virtualjaguar",
lynx: "handy",
"atari-lynx-mkii": "handy",
"neo-geo-pocket": "mednafen_ngp",
"neo-geo-pocket-color": "mednafen_ngp",
nes: "fceumm",
famicom: "fceumm",
fds: "fceumm",
"game-televisison": "fceumm",
"new-style-nes": "fceumm",
n64: "mupen64plus_next",
"ique-player": "mupen64plus_next",
nds: "melonds",
"nintendo-ds-lite": "melonds",
"nintendo-dsi": "melonds",
"nintendo-dsi-xl": "melonds",
gb: "gambatte",
"game-boy-pocket": "gambatte",
"game-boy-light": "gambatte",
gba: "mgba",
"game-boy-adavance-sp": "mgba",
"game-boy-micro": "mgba",
gbc: "gambatte",
"pc-fx": "mednafen_pcfx",
ps: "pcsx_rearmed",
psp: "ppsspp",
segacd: "genesis_plus_gx",
// sega32: "picodrive", // Broken: https://github.com/EmulatorJS/EmulatorJS/issues/579
gamegear: "genesis_plus_gx",
sms: "genesis_plus_gx",
"sega-mark-iii": "genesis_plus_gx",
"sega-game-box-9": "genesis_plus_gx",
"sega-master-system-ii": "genesis_plus_gx",
"master-system-super-compact": "genesis_plus_gx",
"master-system-girl": "genesis_plus_gx",
"genesis-slash-megadrive": "genesis_plus_gx",
"sega-mega-drive-2-slash-genesis": "genesis_plus_gx",
"sega-mega-jet": "genesis_plus_gx",
"mega-pc": "genesis_plus_gx",
"tera-drive": "genesis_plus_gx",
"sega-nomad": "genesis_plus_gx",
saturn: "yabause",
snes: "snes9x",
sfam: "snes9x",
"super-nintendo-original-european-version": "snes9x",
"super-famicom-shvc-001": "snes9x",
"super-famicom-jr-model-shvc-101": "snes9x",
"new-style-super-nes-model-sns-101": "snes9x",
"turbografx16--1": "mednafen_pce",
virtualboy: "beetle_vb",
wonderswan: "mednafen_wswan",
swancrystal: "mednafen_wswan",
"wonderswan-color": "mednafen_wswan",
const _EJS_CORES_MAP = {
"3do": ["opera"],
amiga: ["puae"],
arcade: [
"mame2003",
"mame2003_plus",
"fbneo",
"fbalpha2012_cps1",
"fbalpha2012_cps2",
],
atari2600: ["stella2014"],
"atari-2600-plus": ["stella2014"],
atari5200: ["a5200"],
atari7800: ["prosystem"],
"c-plus-4": ["vice_xplus4"],
c64: ["vice_x64sc", "vice_x64"],
cpet: ["vice_xpet"],
"commodore-64c": ["vice_x64sc", "vice_x64"],
c128: ["vice_x128"],
"commmodore-128": ["vice_x128"],
colecovision: ["gearcoleco"],
jaguar: ["virtualjaguar"],
lynx: ["handy"],
"atari-lynx-mkii": ["handy"],
"neo-geo-pocket": ["mednafen_ngp"],
"neo-geo-pocket-color": ["mednafen_ngp"],
nes: ["fceumm", "nestopia"],
famicom: ["fceumm", "nestopia"],
fds: ["fceumm", "nestopia"],
"game-televisison": ["fceumm"],
"new-style-nes": ["fceumm"],
n64: ["mupen64plus_next"],
"ique-player": ["mupen64plus_next"],
nds: ["melonds", "desmume2015"],
"nintendo-ds-lite": ["melonds", "desmume2015"],
"nintendo-dsi": ["melonds", "desmume2015"],
"nintendo-dsi-xl": ["melonds", "desmume2015"],
gb: ["gambatte", "mgba"],
"game-boy-pocket": ["gambatte", "mgba"],
"game-boy-light": ["gambatte", "mgba"],
gba: ["mgba"],
"game-boy-adavance-sp": ["mgba"],
"game-boy-micro": ["mgba"],
gbc: ["gambatte", "mgba"],
"pc-fx": ["mednafen_pcfx"],
ps: ["pcsx_rearmed", "mednafen_psx"],
psp: ["ppsspp"],
segacd: ["genesis_plus_gx", "picodrive"],
// sega32: ["picodrive"], // Broken: https://github.com/EmulatorJS/EmulatorJS/issues/579
gamegear: ["genesis_plus_gx"],
sms: ["genesis_plus_gx"],
"sega-mark-iii": ["genesis_plus_gx"],
"sega-game-box-9": ["genesis_plus_gx"],
"sega-master-system-ii": ["genesis_plus_gx", "smsplus"],
"master-system-super-compact": ["genesis_plus_gx"],
"master-system-girl": ["genesis_plus_gx"],
"genesis-slash-megadrive": ["genesis_plus_gx"],
"sega-mega-drive-2-slash-genesis": ["genesis_plus_gx"],
"sega-mega-jet": ["genesis_plus_gx"],
"mega-pc": ["genesis_plus_gx"],
"tera-drive": ["genesis_plus_gx"],
"sega-nomad": ["genesis_plus_gx"],
saturn: ["yabause"],
snes: ["snes9x"],
sfam: ["snes9x"],
"super-nintendo-original-european-version": ["snes9x"],
"super-famicom-shvc-001": ["snes9x"],
"super-famicom-jr-model-shvc-101": ["snes9x"],
"new-style-super-nes-model-sns-101": ["snes9x"],
"turbografx16--1": ["mednafen_pce"],
"vic-20": ["vice_xvic"],
virtualboy: ["beetle_vb"],
wonderswan: ["mednafen_wswan"],
swancrystal: ["mednafen_wswan"],
"wonderswan-color": ["mednafen_wswan"],
} as const;
export type EJSPlatformSlug = keyof typeof _EJS_CORES_MAP;
export function getSupportedCores(platformSlug: string) {
return _EJS_CORES_MAP[platformSlug.toLowerCase() as EJSPlatformSlug] || [];
}
export function isEmulationSupported(platformSlug: string) {
return platformSlug.toLowerCase() in _EJS_CORES_MAP;
}

View File

@@ -1,5 +1,4 @@
<script setup lang="ts">
import type { PlatformSchema } from "@/__generated__";
import ActionBar from "@/components/Details/ActionBar.vue";
import AdditionalContent from "@/components/Details/AdditionalContent.vue";
import BackgroundHeader from "@/components/Details/BackgroundHeader.vue";
@@ -14,16 +13,17 @@ import TitleInfo from "@/components/Details/Title.vue";
import platformApi from "@/services/api/platform";
import romApi from "@/services/api/rom";
import storeDownload from "@/stores/download";
import type { Rom } from "@/stores/roms";
import type { DetailedRom } from "@/stores/roms";
import type { Platform } from "@/stores/platforms";
import type { Events } from "@/types/emitter";
import type { Emitter } from "mitt";
import { inject, onBeforeMount, ref, watch } from "vue";
import { useRoute } from "vue-router";
import { useDisplay, useTheme } from "vuetify";
import { useDisplay } from "vuetify";
const route = useRoute();
const rom = ref<Rom>();
const platform = ref<PlatformSchema>();
const rom = ref<DetailedRom>();
const platform = ref<Platform>();
const tab = ref<
| "details"
| "saves"
@@ -34,7 +34,6 @@ const tab = ref<
| "relatedgames"
| "emulation"
>("details");
const theme = useTheme();
const { smAndDown, mdAndDown, mdAndUp, lgAndUp } = useDisplay();
const emitter = inject<Emitter<Events>>("emitter");
const showEmulation = ref(false);
@@ -42,6 +41,7 @@ emitter?.on("showEmulation", () => {
showEmulation.value = !showEmulation.value;
tab.value = showEmulation.value ? "emulation" : "details";
});
const noRomError = ref(false);
async function fetchDetails() {
if (!route.params.rom) return;
@@ -69,6 +69,7 @@ async function fetchDetails() {
platform.value = response.data;
})
.catch((error) => {
noRomError.value = true;
console.log(error);
emitter?.emit("snackbarShow", {
msg: error.response.data.detail,
@@ -118,23 +119,7 @@ watch(
'cover-xs': smAndDown,
}"
>
<cover
:romId="rom.id"
:src="
!rom.igdb_id && !rom.moby_id && !rom.has_cover
? `/assets/default/cover/big_${theme.global.name.value}_unmatched.png`
: !rom.has_cover
? `/assets/default/cover/big_${theme.global.name.value}_missing_cover.png`
: `/assets/romm/resources/${rom.path_cover_l}`
"
:lazy-src="
!rom.igdb_id && !rom.moby_id && !rom.has_cover
? `/assets/default/cover/small_${theme.global.name.value}_unmatched.png`
: !rom.has_cover
? `/assets/default/cover/small_${theme.global.name.value}_missing_cover.png`
: `/assets/romm/resources/${rom.path_cover_s}`
"
/>
<cover :rom="rom" />
<action-bar class="mt-2" :rom="rom" />
<related-games class="mt-3 px-2" v-if="mdAndUp" :rom="rom" />
</v-col>
@@ -269,6 +254,15 @@ watch(
</template>
</v-row>
</template>
<template v-if="noRomError">
<v-empty-state
headline="Whoops, 404"
title="Game not found"
text="The game you were looking for does not exist"
icon="mdi-disc-alert"
></v-empty-state>
</template>
</template>
<style scoped>

View File

@@ -11,7 +11,7 @@ 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";
import { normalizeString, views } from "@/utils";
import type { Emitter } from "mitt";
import { storeToRefs } from "pinia";
import { inject, onBeforeUnmount, onMounted, ref } from "vue";
@@ -31,10 +31,11 @@ const {
filteredRoms,
selectedRoms,
searchRoms,
cursor,
searchCursor,
platformID,
itemsPerBatch,
} = storeToRefs(romsStore);
const itemsShown = ref(itemsPerBatch.value);
const noPlatformError = ref(false);
// Event listeners bus
const emitter = inject<Emitter<Events>>("emitter");
@@ -45,12 +46,7 @@ emitter?.on("openFabMenu", (open) => {
// Functions
async function fetchRoms() {
if (
(searchCursor.value === null && galleryFilterStore.isFiltered()) ||
(cursor.value === null && !galleryFilterStore.isFiltered()) ||
gettingRoms.value
)
return;
if (gettingRoms.value) return;
gettingRoms.value = true;
emitter?.emit("showLoadingDialog", {
@@ -61,25 +57,18 @@ async function fetchRoms() {
await romApi
.getRoms({
platformId: platformID.value,
cursor: galleryFilterStore.isFiltered()
? searchCursor.value
: cursor.value,
searchTerm: normalizeString(galleryFilterStore.filterSearch),
})
.then(({ data }) => {
// Add any new roms to the store
const allRomsSet = [...allRoms.value, ...data.items];
const allRomsSet = [...allRoms.value, ...data];
romsStore.set(allRomsSet);
romsStore.setFiltered(allRomsSet, galleryFilterStore);
if (galleryFilterStore.isFiltered()) {
if (data.next_page !== undefined) searchCursor.value = data.next_page;
const serchedRomsSet = [...searchRoms.value, ...data.items];
const serchedRomsSet = [...searchRoms.value, ...data];
romsStore.setSearch(serchedRomsSet);
romsStore.setFiltered(serchedRomsSet, galleryFilterStore);
} else if (data.next_page !== undefined) {
cursor.value = data.next_page;
}
})
.catch((error) => {
@@ -89,7 +78,9 @@ async function fetchRoms() {
color: "red",
timeout: 4000,
});
console.error(`Couldn't fetch roms for platform ID ${platformID.value}: ${error}`);
console.error(
`Couldn't fetch roms for platform ID ${platformID.value}: ${error}`
);
})
.finally(() => {
gettingRoms.value = false;
@@ -101,13 +92,13 @@ async function fetchRoms() {
}
async function onFilterChange() {
searchCursor.value = "";
romsStore.setSearch([]);
if (!galleryFilterStore.isFiltered()) {
romsStore.setFiltered(allRoms.value, galleryFilterStore);
return;
}
await fetchRoms();
emitter?.emit("updateDataTablePages", null);
}
function selectRom({ event, index, selected }: RomSelectEvent) {
@@ -163,23 +154,32 @@ function setFilters() {
}
function resetGallery() {
cursor.value = "";
searchCursor.value = "";
romsStore.reset();
scrolledToTop.value = true;
galleryFilterStore.reset();
itemsShown.value = itemsPerBatch.value;
}
function scrollToTop() {
window.scrollTo({
top: 0,
left: 0,
behavior: "smooth",
});
scrolledToTop.value = true;
}
function onScroll() {
window.setTimeout(async () => {
const { scrollTop, scrollHeight, clientHeight } = document.documentElement;
scrolledToTop.value = scrollTop === 0;
if (!cursor.value && !searchCursor.value) return;
const scrollOffset = 60;
if (scrollTop + clientHeight + scrollOffset >= scrollHeight) {
await fetchRoms();
const totalScrollableHeight = scrollHeight - clientHeight;
const ninetyPercentPoint = totalScrollableHeight * 0.9;
if (
scrollTop >= ninetyPercentPoint &&
itemsShown.value < filteredRoms.value.length
) {
itemsShown.value = itemsShown.value + itemsPerBatch.value;
setFilters();
}
}, 100);
@@ -188,13 +188,21 @@ function onScroll() {
onMounted(async () => {
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);
// const { data } =
await platformApi
.getPlatform(platformID)
.then((data) => {
platforms.add(data.data);
})
.catch((error) => {
console.log(error);
noPlatformError.value = true;
});
}
// If platform is different, reset store and fetch roms
@@ -210,10 +218,12 @@ onMounted(async () => {
setFilters();
window.addEventListener("wheel", onScroll);
window.addEventListener("touchstart", onScroll);
});
onBeforeUnmount(() => {
window.removeEventListener("wheel", onScroll);
window.removeEventListener("touchstart", onScroll);
});
onBeforeRouteLeave((to, from, next) => {
@@ -227,13 +237,13 @@ onBeforeRouteUpdate(async (to, _) => {
// Triggers when change query param of the same route
// Reset store if switching to another platform
resetGallery();
const platformID = Number(to.params.platform);
romsStore.setPlatformID(platformID);
const platform = platforms.get(platformID);
if (!platform) {
const { data } = await platformApi.getPlatform(platformID)
const { data } = await platformApi.getPlatform(platformID);
platforms.add(data);
}
@@ -248,6 +258,7 @@ onBeforeRouteUpdate(async (to, _) => {
<template v-if="filteredRoms.length > 0">
<v-row class="pa-1" no-gutters>
<!-- Gallery cards view -->
<!-- v-show instead of v-if to avoid recalculate on view change -->
<v-col
class="pa-1"
v-show="galleryViewStore.current != 2"
@@ -257,7 +268,7 @@ onBeforeRouteUpdate(async (to, _) => {
:md="views[galleryViewStore.current]['size-md']"
:lg="views[galleryViewStore.current]['size-lg']"
:xl="views[galleryViewStore.current]['size-xl']"
v-for="rom in filteredRoms"
v-for="rom in filteredRoms.slice(0, itemsShown)"
:key="rom.id"
>
<game-card
@@ -276,50 +287,71 @@ onBeforeRouteUpdate(async (to, _) => {
</v-row>
</template>
<template v-else>
<v-empty-state
v-if="!gettingRoms && galleryFilterStore.isFiltered()"
headline="No games to show"
icon="mdi-disc-alert"
></v-empty-state>
</template>
<template v-if="noPlatformError">
<v-empty-state
headline="Whoops, 404"
title="Platform not found"
text="The platform you were looking for does not exist"
icon="mdi-controller-off"
></v-empty-state>
</template>
<v-layout-item
class="text-end"
v-show="!scrolledToTop || romsStore._selectedIDs.length > 0"
class="text-end pr-2"
:model-value="true"
position="bottom"
size="88"
size="65"
>
<div class="ma-4">
<v-scroll-y-reverse-transition>
<v-btn
id="scrollToTop"
v-show="!scrolledToTop"
color="primary"
elevation="8"
icon
class="mr-2"
size="large"
@click="toTop()"
><v-icon color="romm-accent-1">mdi-chevron-up</v-icon></v-btn
<v-row no-gutters>
<v-col>
<v-scroll-y-reverse-transition>
<v-btn
v-show="!scrolledToTop"
id="scrollToTop"
color="primary"
elevation="8"
icon
class="ml-2"
size="large"
@click="scrollToTop()"
><v-icon color="romm-accent-1">mdi-chevron-up</v-icon></v-btn
>
</v-scroll-y-reverse-transition>
<v-menu
location="top"
v-model="fabMenu"
:transition="
fabMenu ? 'scroll-y-reverse-transition' : 'scroll-y-transition'
"
>
</v-scroll-y-reverse-transition>
<v-menu
location="top"
v-model="fabMenu"
:transition="
fabMenu ? 'scroll-y-reverse-transition' : 'scroll-y-transition'
"
>
<template v-slot:activator="{ props }">
<v-fab-transition>
<v-btn
v-show="romsStore._selectedIDs.length > 0"
color="romm-accent-1"
v-bind="props"
elevation="8"
icon
size="large"
>{{ romsStore._selectedIDs.length }}</v-btn
>
</v-fab-transition>
</template>
<template v-slot:activator="{ props }">
<v-fab-transition>
<v-btn
v-show="romsStore._selectedIDs.length > 0"
color="romm-accent-1"
v-bind="props"
elevation="8"
class="ml-2"
icon
size="large"
>{{ romsStore._selectedIDs.length }}</v-btn
>
</v-fab-transition>
</template>
<fab-menu />
</v-menu>
</div>
<fab-menu />
</v-menu>
</v-col>
</v-row>
</v-layout-item>
</template>

View File

@@ -2,20 +2,22 @@
import { onMounted, ref } from "vue";
import { isNull } from "lodash";
import type { FirmwareSchema, SaveSchema, StateSchema } from "@/__generated__";
import { formatBytes } from "@/utils";
import { formatBytes, getSupportedCores } from "@/utils";
import romApi from "@/services/api/rom";
import firmwareApi from "@/services/api/firmware";
import { useRoute } from "vue-router";
import Player from "@/views/Play/Player.vue";
import type { Rom } from "@/stores/roms";
import type { DetailedRom } from "@/stores/roms";
const route = useRoute();
const rom = ref<Rom | null>(null);
const rom = ref<DetailedRom | null>(null);
const firmwareOptions = ref<FirmwareSchema[]>([]);
const biosRef = ref<FirmwareSchema | null>(null);
const saveRef = ref<SaveSchema | null>(null);
const stateRef = ref<StateSchema | null>(null);
const supportedCores = ref<string[]>([]);
const coreRef = ref<string | null>(null);
const gameRunning = ref(false);
const storedFSOP = localStorage.getItem("fullScreenOnPlay");
@@ -30,6 +32,8 @@ onMounted(async () => {
romId: parseInt(route.params.rom as string),
});
rom.value = romResponse.data;
supportedCores.value = [...getSupportedCores(rom.value.platform_slug)];
coreRef.value = supportedCores.value[0];
const firmwareResponse = await firmwareApi.getFirmware({
platformId: romResponse.data.platform_id,
@@ -56,6 +60,22 @@ function onFullScreenChange() {
width="250"
src="/assets/emulatorjs/powered_by_emulatorjs.png"
/>
<v-select
v-if="supportedCores.length > 1"
density="compact"
class="my-2"
hide-details
variant="outlined"
clearable
label="Core"
v-model="coreRef"
:items="
supportedCores.map((c) => ({
title: c,
value: c,
}))
"
/>
<v-select
class="my-1"
hide-details
@@ -132,7 +152,13 @@ function onFullScreenChange() {
</v-col>
<v-col class="bg-primary" rounded id="game-wrapper">
<player :rom="rom" :state="stateRef" :save="saveRef" :bios="biosRef" />
<player
:rom="rom"
:state="stateRef"
:save="saveRef"
:bios="biosRef"
:core="coreRef"
/>
</v-col>
</v-row>
</template>

View File

@@ -3,16 +3,16 @@ import { ref, onBeforeUnmount } from "vue";
import stateApi from "@/services/api/state";
import saveApi, { saveApi as api } from "@/services/api/save";
import screenshotApi from "@/services/api/screenshot";
import { platformSlugEJSCoreMap } from "@/utils";
import { getSupportedCores } from "@/utils";
import type { FirmwareSchema, SaveSchema, StateSchema } from "@/__generated__";
import type { Rom } from "@/stores/roms";
import type { ValueOf } from "@/types";
import type { DetailedRom } from "@/stores/roms";
const props = defineProps<{
rom: Rom;
rom: DetailedRom;
save: SaveSchema | null;
state: StateSchema | null;
bios: FirmwareSchema | null;
core: string | null;
}>();
const saveRef = ref<SaveSchema | null>(props.save);
const stateRef = ref<StateSchema | null>(props.state);
@@ -21,13 +21,10 @@ onBeforeUnmount(() => {
window.location.reload();
});
type EJSPlatformSlug = keyof typeof platformSlugEJSCoreMap;
type EJSCore = ValueOf<typeof platformSlugEJSCoreMap>;
// Declare global variables for EmulatorJS
declare global {
interface Window {
EJS_core: EJSCore;
EJS_core: string;
EJS_biosUrl: string;
EJS_player: string;
EJS_pathtodata: string;
@@ -53,10 +50,9 @@ declare global {
}
}
const supportedCores = getSupportedCores(props.rom.platform_slug);
window.EJS_core =
platformSlugEJSCoreMap[
props.rom.platform_slug.toLowerCase() as EJSPlatformSlug
];
supportedCores.find((core) => core === props.core) ?? supportedCores[0];
window.EJS_gameID = props.rom.id;
window.EJS_gameUrl = `/api/roms/${props.rom.id}/content/${props.rom.file_name}`;
window.EJS_biosUrl = props.bios
@@ -200,7 +196,6 @@ window.EJS_onSaveState = function ({
if (stateRef.value)
stateRef.value.screenshot = data.screenshots[0];
props.rom.user_screenshots = data.screenshots;
props.rom.url_screenshots = data.url_screenshots;
props.rom.merged_screenshots = data.merged_screenshots;
})
.catch((e) => console.log(e));
@@ -243,7 +238,6 @@ window.EJS_onSaveState = function ({
})
.then(({ data }) => {
props.rom.user_screenshots = data.screenshots;
props.rom.url_screenshots = data.url_screenshots;
props.rom.merged_screenshots = data.merged_screenshots;
})
.catch((e) => console.log(e));
@@ -330,7 +324,6 @@ window.EJS_onSaveSave = function ({
.then(({ data }) => {
if (saveRef.value) saveRef.value.screenshot = data.screenshots[0];
props.rom.user_screenshots = data.screenshots;
props.rom.url_screenshots = data.url_screenshots;
props.rom.merged_screenshots = data.merged_screenshots;
})
.catch((e) => console.log(e));
@@ -368,7 +361,6 @@ window.EJS_onSaveSave = function ({
})
.then(({ data }) => {
props.rom.user_screenshots = data.screenshots;
props.rom.url_screenshots = data.url_screenshots;
props.rom.merged_screenshots = data.merged_screenshots;
})
.catch((e) => console.log(e));
@@ -400,6 +392,12 @@ window.EJS_onGameStart = async () => {
}
</style>
<style>
#game .ejs_cheat_code {
background-color: white;
}
</style>
<!-- Other config options: https://emulatorjs.org/docs/Options.html -->
<!-- window.EJS_biosUrl; -->

View File

@@ -41,20 +41,18 @@ const HEADERS = [
},
{ title: "", align: "end", key: "actions", sortable: false },
] as const;
const PER_PAGE_OPTIONS = [
{ value: 5, title: "5" },
{ value: 10, title: "10" },
{ value: 25, title: "25" },
{ value: -1, title: "$vuetify.dataFooter.itemsPerPageAll" },
];
const PER_PAGE_OPTIONS = [10, 25, 50, 100];
// Props
const emitter = inject<Emitter<Events>>("emitter");
const auth = storeAuth();
const usersStore = storeUsers();
const usersPerPage = ref(5);
const userSearch = ref("");
const storedUsersPerPage = parseInt(localStorage.getItem("usersPerPage") ?? "");
const usersPerPage = ref(isNaN(storedUsersPerPage) ? 25 : storedUsersPerPage);
const page = ref(1);
const pageCount = ref(0);
emitter?.on("updateDataTablePages", updateDataTablePages);
function disableUser(user: User) {
userApi.updateUser(user).catch(({ response, message }) => {
@@ -69,6 +67,10 @@ function disableUser(user: User) {
});
}
function updateDataTablePages() {
pageCount.value = Math.ceil(usersStore.all.length / usersPerPage.value);
}
onMounted(() => {
userApi
.fetchUsers()
@@ -116,6 +118,7 @@ onMounted(() => {
:search="userSearch"
:headers="HEADERS"
:items="usersStore.all"
v-model:page="page"
:sort-by="[{ key: 'username', order: 'asc' }]"
>
<template v-slot:item.avatar_path="{ item }">
@@ -160,6 +163,32 @@ onMounted(() => {
><v-icon>mdi-delete</v-icon></v-btn
>
</template>
<template v-slot:bottom>
<v-divider class="border-opacity-25" />
<v-row no-gutters class="pt-2 align-center">
<v-col cols="11" class="px-6">
<v-pagination
rounded="0"
:show-first-last-page="true"
active-color="romm-accent-1"
v-model="page"
:length="pageCount"
></v-pagination>
</v-col>
<v-col cols="5" sm="2" xl="1">
<v-select
class="pa-2"
label="Users per page"
density="compact"
variant="outlined"
:items="PER_PAGE_OPTIONS"
v-model="usersPerPage"
hide-details
/>
</v-col>
</v-row>
</template>
</v-data-table>
</v-card-text>
</v-card>

572
poetry.lock generated
View File

@@ -39,24 +39,24 @@ tz = ["backports.zoneinfo"]
[[package]]
name = "annotated-types"
version = "0.6.0"
version = "0.7.0"
description = "Reusable constraint types to use with typing.Annotated"
optional = false
python-versions = ">=3.8"
files = [
{file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"},
{file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"},
{file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
{file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
]
[[package]]
name = "anyio"
version = "4.3.0"
version = "4.4.0"
description = "High level compatibility layer for multiple asynchronous event loop implementations"
optional = false
python-versions = ">=3.8"
files = [
{file = "anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8"},
{file = "anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6"},
{file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"},
{file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"},
]
[package.dependencies]
@@ -99,38 +99,38 @@ files = [
[[package]]
name = "bcrypt"
version = "4.1.2"
version = "4.1.3"
description = "Modern password hashing for your software and your servers"
optional = false
python-versions = ">=3.7"
files = [
{file = "bcrypt-4.1.2-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:ac621c093edb28200728a9cca214d7e838529e557027ef0581685909acd28b5e"},
{file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea505c97a5c465ab8c3ba75c0805a102ce526695cd6818c6de3b1a38f6f60da1"},
{file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57fa9442758da926ed33a91644649d3e340a71e2d0a5a8de064fb621fd5a3326"},
{file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:eb3bd3321517916696233b5e0c67fd7d6281f0ef48e66812db35fc963a422a1c"},
{file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6cad43d8c63f34b26aef462b6f5e44fdcf9860b723d2453b5d391258c4c8e966"},
{file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:44290ccc827d3a24604f2c8bcd00d0da349e336e6503656cb8192133e27335e2"},
{file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:732b3920a08eacf12f93e6b04ea276c489f1c8fb49344f564cca2adb663b3e4c"},
{file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1c28973decf4e0e69cee78c68e30a523be441972c826703bb93099868a8ff5b5"},
{file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b8df79979c5bae07f1db22dcc49cc5bccf08a0380ca5c6f391cbb5790355c0b0"},
{file = "bcrypt-4.1.2-cp37-abi3-win32.whl", hash = "sha256:fbe188b878313d01b7718390f31528be4010fed1faa798c5a1d0469c9c48c369"},
{file = "bcrypt-4.1.2-cp37-abi3-win_amd64.whl", hash = "sha256:9800ae5bd5077b13725e2e3934aa3c9c37e49d3ea3d06318010aa40f54c63551"},
{file = "bcrypt-4.1.2-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:71b8be82bc46cedd61a9f4ccb6c1a493211d031415a34adde3669ee1b0afbb63"},
{file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e3c6642077b0c8092580c819c1684161262b2e30c4f45deb000c38947bf483"},
{file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:387e7e1af9a4dd636b9505a465032f2f5cb8e61ba1120e79a0e1cd0b512f3dfc"},
{file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f70d9c61f9c4ca7d57f3bfe88a5ccf62546ffbadf3681bb1e268d9d2e41c91a7"},
{file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2a298db2a8ab20056120b45e86c00a0a5eb50ec4075b6142db35f593b97cb3fb"},
{file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:ba55e40de38a24e2d78d34c2d36d6e864f93e0d79d0b6ce915e4335aa81d01b1"},
{file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:3566a88234e8de2ccae31968127b0ecccbb4cddb629da744165db72b58d88ca4"},
{file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b90e216dc36864ae7132cb151ffe95155a37a14e0de3a8f64b49655dd959ff9c"},
{file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:69057b9fc5093ea1ab00dd24ede891f3e5e65bee040395fb1e66ee196f9c9b4a"},
{file = "bcrypt-4.1.2-cp39-abi3-win32.whl", hash = "sha256:02d9ef8915f72dd6daaef40e0baeef8a017ce624369f09754baf32bb32dba25f"},
{file = "bcrypt-4.1.2-cp39-abi3-win_amd64.whl", hash = "sha256:be3ab1071662f6065899fe08428e45c16aa36e28bc42921c4901a191fda6ee42"},
{file = "bcrypt-4.1.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d75fc8cd0ba23f97bae88a6ec04e9e5351ff3c6ad06f38fe32ba50cbd0d11946"},
{file = "bcrypt-4.1.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:a97e07e83e3262599434816f631cc4c7ca2aa8e9c072c1b1a7fec2ae809a1d2d"},
{file = "bcrypt-4.1.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e51c42750b7585cee7892c2614be0d14107fad9581d1738d954a262556dd1aab"},
{file = "bcrypt-4.1.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ba4e4cc26610581a6329b3937e02d319f5ad4b85b074846bf4fef8a8cf51e7bb"},
{file = "bcrypt-4.1.2.tar.gz", hash = "sha256:33313a1200a3ae90b75587ceac502b048b840fc69e7f7a0905b5f87fac7a1258"},
{file = "bcrypt-4.1.3-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:48429c83292b57bf4af6ab75809f8f4daf52aa5d480632e53707805cc1ce9b74"},
{file = "bcrypt-4.1.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a8bea4c152b91fd8319fef4c6a790da5c07840421c2b785084989bf8bbb7455"},
{file = "bcrypt-4.1.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d3b317050a9a711a5c7214bf04e28333cf528e0ed0ec9a4e55ba628d0f07c1a"},
{file = "bcrypt-4.1.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:094fd31e08c2b102a14880ee5b3d09913ecf334cd604af27e1013c76831f7b05"},
{file = "bcrypt-4.1.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:4fb253d65da30d9269e0a6f4b0de32bd657a0208a6f4e43d3e645774fb5457f3"},
{file = "bcrypt-4.1.3-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:193bb49eeeb9c1e2db9ba65d09dc6384edd5608d9d672b4125e9320af9153a15"},
{file = "bcrypt-4.1.3-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:8cbb119267068c2581ae38790e0d1fbae65d0725247a930fc9900c285d95725d"},
{file = "bcrypt-4.1.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6cac78a8d42f9d120b3987f82252bdbeb7e6e900a5e1ba37f6be6fe4e3848286"},
{file = "bcrypt-4.1.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:01746eb2c4299dd0ae1670234bf77704f581dd72cc180f444bfe74eb80495b64"},
{file = "bcrypt-4.1.3-cp37-abi3-win32.whl", hash = "sha256:037c5bf7c196a63dcce75545c8874610c600809d5d82c305dd327cd4969995bf"},
{file = "bcrypt-4.1.3-cp37-abi3-win_amd64.whl", hash = "sha256:8a893d192dfb7c8e883c4576813bf18bb9d59e2cfd88b68b725990f033f1b978"},
{file = "bcrypt-4.1.3-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0d4cf6ef1525f79255ef048b3489602868c47aea61f375377f0d00514fe4a78c"},
{file = "bcrypt-4.1.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5698ce5292a4e4b9e5861f7e53b1d89242ad39d54c3da451a93cac17b61921a"},
{file = "bcrypt-4.1.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec3c2e1ca3e5c4b9edb94290b356d082b721f3f50758bce7cce11d8a7c89ce84"},
{file = "bcrypt-4.1.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3a5be252fef513363fe281bafc596c31b552cf81d04c5085bc5dac29670faa08"},
{file = "bcrypt-4.1.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:5f7cd3399fbc4ec290378b541b0cf3d4398e4737a65d0f938c7c0f9d5e686611"},
{file = "bcrypt-4.1.3-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:c4c8d9b3e97209dd7111bf726e79f638ad9224b4691d1c7cfefa571a09b1b2d6"},
{file = "bcrypt-4.1.3-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:31adb9cbb8737a581a843e13df22ffb7c84638342de3708a98d5c986770f2834"},
{file = "bcrypt-4.1.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:551b320396e1d05e49cc18dd77d970accd52b322441628aca04801bbd1d52a73"},
{file = "bcrypt-4.1.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6717543d2c110a155e6821ce5670c1f512f602eabb77dba95717ca76af79867d"},
{file = "bcrypt-4.1.3-cp39-abi3-win32.whl", hash = "sha256:6004f5229b50f8493c49232b8e75726b568535fd300e5039e255d919fc3a07f2"},
{file = "bcrypt-4.1.3-cp39-abi3-win_amd64.whl", hash = "sha256:2505b54afb074627111b5a8dc9b6ae69d0f01fea65c2fcaea403448c503d3991"},
{file = "bcrypt-4.1.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:cb9c707c10bddaf9e5ba7cdb769f3e889e60b7d4fea22834b261f51ca2b89fed"},
{file = "bcrypt-4.1.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9f8ea645eb94fb6e7bea0cf4ba121c07a3a182ac52876493870033141aa687bc"},
{file = "bcrypt-4.1.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:f44a97780677e7ac0ca393bd7982b19dbbd8d7228c1afe10b128fd9550eef5f1"},
{file = "bcrypt-4.1.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d84702adb8f2798d813b17d8187d27076cca3cd52fe3686bb07a9083930ce650"},
{file = "bcrypt-4.1.3.tar.gz", hash = "sha256:2ee15dd749f5952fe3f0430d0ff6b74082e159c50332a1413d51b5689cf06623"},
]
[package.extras]
@@ -359,43 +359,43 @@ files = [
[[package]]
name = "cryptography"
version = "42.0.5"
version = "42.0.7"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
optional = false
python-versions = ">=3.7"
files = [
{file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:a30596bae9403a342c978fb47d9b0ee277699fa53bbafad14706af51fe543d16"},
{file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b7ffe927ee6531c78f81aa17e684e2ff617daeba7f189f911065b2ea2d526dec"},
{file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2424ff4c4ac7f6b8177b53c17ed5d8fa74ae5955656867f5a8affaca36a27abb"},
{file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:329906dcc7b20ff3cad13c069a78124ed8247adcac44b10bea1130e36caae0b4"},
{file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:b03c2ae5d2f0fc05f9a2c0c997e1bc18c8229f392234e8a0194f202169ccd278"},
{file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8837fe1d6ac4a8052a9a8ddab256bc006242696f03368a4009be7ee3075cdb7"},
{file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:0270572b8bd2c833c3981724b8ee9747b3ec96f699a9665470018594301439ee"},
{file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:b8cac287fafc4ad485b8a9b67d0ee80c66bf3574f655d3b97ef2e1082360faf1"},
{file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:16a48c23a62a2f4a285699dba2e4ff2d1cff3115b9df052cdd976a18856d8e3d"},
{file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2bce03af1ce5a5567ab89bd90d11e7bbdff56b8af3acbbec1faded8f44cb06da"},
{file = "cryptography-42.0.5-cp37-abi3-win32.whl", hash = "sha256:b6cd2203306b63e41acdf39aa93b86fb566049aeb6dc489b70e34bcd07adca74"},
{file = "cryptography-42.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:98d8dc6d012b82287f2c3d26ce1d2dd130ec200c8679b6213b3c73c08b2b7940"},
{file = "cryptography-42.0.5-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:5e6275c09d2badf57aea3afa80d975444f4be8d3bc58f7f80d2a484c6f9485c8"},
{file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4985a790f921508f36f81831817cbc03b102d643b5fcb81cd33df3fa291a1a1"},
{file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cde5f38e614f55e28d831754e8a3bacf9ace5d1566235e39d91b35502d6936e"},
{file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7367d7b2eca6513681127ebad53b2582911d1736dc2ffc19f2c3ae49997496bc"},
{file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cd2030f6650c089aeb304cf093f3244d34745ce0cfcc39f20c6fbfe030102e2a"},
{file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a2913c5375154b6ef2e91c10b5720ea6e21007412f6437504ffea2109b5a33d7"},
{file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:c41fb5e6a5fe9ebcd58ca3abfeb51dffb5d83d6775405305bfa8715b76521922"},
{file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3eaafe47ec0d0ffcc9349e1708be2aaea4c6dd4978d76bf6eb0cb2c13636c6fc"},
{file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1b95b98b0d2af784078fa69f637135e3c317091b615cd0905f8b8a087e86fa30"},
{file = "cryptography-42.0.5-cp39-abi3-win32.whl", hash = "sha256:1f71c10d1e88467126f0efd484bd44bca5e14c664ec2ede64c32f20875c0d413"},
{file = "cryptography-42.0.5-cp39-abi3-win_amd64.whl", hash = "sha256:a011a644f6d7d03736214d38832e030d8268bcff4a41f728e6030325fea3e400"},
{file = "cryptography-42.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9481ffe3cf013b71b2428b905c4f7a9a4f76ec03065b05ff499bb5682a8d9ad8"},
{file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:ba334e6e4b1d92442b75ddacc615c5476d4ad55cc29b15d590cc6b86efa487e2"},
{file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ba3e4a42397c25b7ff88cdec6e2a16c2be18720f317506ee25210f6d31925f9c"},
{file = "cryptography-42.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:111a0d8553afcf8eb02a4fea6ca4f59d48ddb34497aa8706a6cf536f1a5ec576"},
{file = "cryptography-42.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cd65d75953847815962c84a4654a84850b2bb4aed3f26fadcc1c13892e1e29f6"},
{file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e807b3188f9eb0eaa7bbb579b462c5ace579f1cedb28107ce8b48a9f7ad3679e"},
{file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f12764b8fffc7a123f641d7d049d382b73f96a34117e0b637b80643169cec8ac"},
{file = "cryptography-42.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:37dd623507659e08be98eec89323469e8c7b4c1407c85112634ae3dbdb926fdd"},
{file = "cryptography-42.0.5.tar.gz", hash = "sha256:6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da607bfe1"},
{file = "cryptography-42.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:a987f840718078212fdf4504d0fd4c6effe34a7e4740378e59d47696e8dfb477"},
{file = "cryptography-42.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:bd13b5e9b543532453de08bcdc3cc7cebec6f9883e886fd20a92f26940fd3e7a"},
{file = "cryptography-42.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a79165431551042cc9d1d90e6145d5d0d3ab0f2d66326c201d9b0e7f5bf43604"},
{file = "cryptography-42.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a47787a5e3649008a1102d3df55424e86606c9bae6fb77ac59afe06d234605f8"},
{file = "cryptography-42.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:02c0eee2d7133bdbbc5e24441258d5d2244beb31da5ed19fbb80315f4bbbff55"},
{file = "cryptography-42.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:5e44507bf8d14b36b8389b226665d597bc0f18ea035d75b4e53c7b1ea84583cc"},
{file = "cryptography-42.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7f8b25fa616d8b846aef64b15c606bb0828dbc35faf90566eb139aa9cff67af2"},
{file = "cryptography-42.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:93a3209f6bb2b33e725ed08ee0991b92976dfdcf4e8b38646540674fc7508e13"},
{file = "cryptography-42.0.7-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e6b8f1881dac458c34778d0a424ae5769de30544fc678eac51c1c8bb2183e9da"},
{file = "cryptography-42.0.7-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3de9a45d3b2b7d8088c3fbf1ed4395dfeff79d07842217b38df14ef09ce1d8d7"},
{file = "cryptography-42.0.7-cp37-abi3-win32.whl", hash = "sha256:789caea816c6704f63f6241a519bfa347f72fbd67ba28d04636b7c6b7da94b0b"},
{file = "cryptography-42.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:8cb8ce7c3347fcf9446f201dc30e2d5a3c898d009126010cbd1f443f28b52678"},
{file = "cryptography-42.0.7-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:a3a5ac8b56fe37f3125e5b72b61dcde43283e5370827f5233893d461b7360cd4"},
{file = "cryptography-42.0.7-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:779245e13b9a6638df14641d029add5dc17edbef6ec915688f3acb9e720a5858"},
{file = "cryptography-42.0.7-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d563795db98b4cd57742a78a288cdbdc9daedac29f2239793071fe114f13785"},
{file = "cryptography-42.0.7-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:31adb7d06fe4383226c3e963471f6837742889b3c4caa55aac20ad951bc8ffda"},
{file = "cryptography-42.0.7-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:efd0bf5205240182e0f13bcaea41be4fdf5c22c5129fc7ced4a0282ac86998c9"},
{file = "cryptography-42.0.7-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a9bc127cdc4ecf87a5ea22a2556cab6c7eda2923f84e4f3cc588e8470ce4e42e"},
{file = "cryptography-42.0.7-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:3577d029bc3f4827dd5bf8bf7710cac13527b470bbf1820a3f394adb38ed7d5f"},
{file = "cryptography-42.0.7-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2e47577f9b18723fa294b0ea9a17d5e53a227867a0a4904a1a076d1646d45ca1"},
{file = "cryptography-42.0.7-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1a58839984d9cb34c855197043eaae2c187d930ca6d644612843b4fe8513c886"},
{file = "cryptography-42.0.7-cp39-abi3-win32.whl", hash = "sha256:e6b79d0adb01aae87e8a44c2b64bc3f3fe59515280e00fb6d57a7267a2583cda"},
{file = "cryptography-42.0.7-cp39-abi3-win_amd64.whl", hash = "sha256:16268d46086bb8ad5bf0a2b5544d8a9ed87a0e33f5e77dd3c3301e63d941a83b"},
{file = "cryptography-42.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2954fccea107026512b15afb4aa664a5640cd0af630e2ee3962f2602693f0c82"},
{file = "cryptography-42.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:362e7197754c231797ec45ee081f3088a27a47c6c01eff2ac83f60f85a50fe60"},
{file = "cryptography-42.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4f698edacf9c9e0371112792558d2f705b5645076cc0aaae02f816a0171770fd"},
{file = "cryptography-42.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5482e789294854c28237bba77c4c83be698be740e31a3ae5e879ee5444166582"},
{file = "cryptography-42.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e9b2a6309f14c0497f348d08a065d52f3020656f675819fc405fb63bbcd26562"},
{file = "cryptography-42.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d8e3098721b84392ee45af2dd554c947c32cc52f862b6a3ae982dbb90f577f14"},
{file = "cryptography-42.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c65f96dad14f8528a447414125e1fc8feb2ad5a272b8f68477abbcc1ea7d94b9"},
{file = "cryptography-42.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:36017400817987670037fbb0324d71489b6ead6231c9604f8fc1f7d008087c68"},
{file = "cryptography-42.0.7.tar.gz", hash = "sha256:ecbfbc00bf55888edda9868a4cf927205de8499e7fabe6c050322298382953f2"},
]
[package.dependencies]
@@ -452,13 +452,13 @@ tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipyth
[[package]]
name = "fakeredis"
version = "2.21.3"
version = "2.23.2"
description = "Python implementation of redis API, can be used for testing purposes."
optional = false
python-versions = ">=3.7,<4.0"
python-versions = "<4.0,>=3.7"
files = [
{file = "fakeredis-2.21.3-py3-none-any.whl", hash = "sha256:033fe5882a20ec308ed0cf67a86c1cd982a1bffa63deb0f52eaa625bd8ce305f"},
{file = "fakeredis-2.21.3.tar.gz", hash = "sha256:e9e1c309d49d83c4ce1ab6f3ee2e56787f6a5573a305109017bf140334dd396d"},
{file = "fakeredis-2.23.2-py3-none-any.whl", hash = "sha256:3721946b955930c065231befd24a9cdc68b339746e93848ef01a010d98e4eb4f"},
{file = "fakeredis-2.23.2.tar.gz", hash = "sha256:d649c409abe46c63690b6c35d3c460e4ce64c69a52cea3f02daff2649378f878"},
]
[package.dependencies]
@@ -469,7 +469,7 @@ sortedcontainers = ">=2,<3"
bf = ["pyprobables (>=0.6,<0.7)"]
cf = ["pyprobables (>=0.6,<0.7)"]
json = ["jsonpath-ng (>=1.6,<2.0)"]
lua = ["lupa (>=1.14,<3.0)"]
lua = ["lupa (>=2.1,<3.0)"]
probabilistic = ["pyprobables (>=0.6,<0.7)"]
[[package]]
@@ -491,48 +491,15 @@ typing-extensions = ">=4.8.0"
[package.extras]
all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
[[package]]
name = "fastapi-pagination"
version = "0.12.21"
description = "FastAPI pagination"
optional = false
python-versions = ">=3.8,<4.0"
files = [
{file = "fastapi_pagination-0.12.21-py3-none-any.whl", hash = "sha256:5715b3dec31f9f9a0df6e08a53d7efe8c185d1fc8b392438d60e15349d7478d1"},
{file = "fastapi_pagination-0.12.21.tar.gz", hash = "sha256:ba0bd1023ae37cb32946e91b1356f2454809e15393911d68e318f5c7aa6887c4"},
]
[package.dependencies]
fastapi = ">=0.93.0"
pydantic = ">=1.9.1"
typing-extensions = ">=4.8.0,<5.0.0"
[package.extras]
all = ["SQLAlchemy (>=1.3.20)", "asyncpg (>=0.24.0)", "beanie (>=1.25.0)", "bunnet (>=1.1.0,<2.0.0)", "databases (>=0.6.0)", "django (<5.0.0)", "mongoengine (>=0.23.1,<0.29.0)", "motor (>=2.5.1,<4.0.0)", "orm (>=0.3.1)", "ormar (>=0.11.2)", "piccolo (>=0.89,<0.122)", "pony (>=0.7.16,<0.8.0)", "scylla-driver (>=3.25.6,<4.0.0)", "sqlakeyset (>=2.0.1680321678,<3.0.0)", "sqlmodel (>=0.0.8,<0.0.15)", "tortoise-orm (>=0.16.18,<0.21.0)"]
asyncpg = ["SQLAlchemy (>=1.3.20)", "asyncpg (>=0.24.0)"]
beanie = ["beanie (>=1.25.0)"]
bunnet = ["bunnet (>=1.1.0,<2.0.0)"]
databases = ["databases (>=0.6.0)"]
django = ["databases (>=0.6.0)", "django (<5.0.0)"]
mongoengine = ["mongoengine (>=0.23.1,<0.29.0)"]
motor = ["motor (>=2.5.1,<4.0.0)"]
orm = ["databases (>=0.6.0)", "orm (>=0.3.1)"]
ormar = ["ormar (>=0.11.2)"]
piccolo = ["piccolo (>=0.89,<0.122)"]
scylla-driver = ["scylla-driver (>=3.25.6,<4.0.0)"]
sqlalchemy = ["SQLAlchemy (>=1.3.20)", "sqlakeyset (>=2.0.1680321678,<3.0.0)"]
sqlmodel = ["sqlakeyset (>=2.0.1680321678,<3.0.0)", "sqlmodel (>=0.0.8,<0.0.15)"]
tortoise = ["tortoise-orm (>=0.16.18,<0.21.0)"]
[[package]]
name = "freezegun"
version = "1.4.0"
version = "1.5.1"
description = "Let your Python tests travel through time"
optional = false
python-versions = ">=3.7"
files = [
{file = "freezegun-1.4.0-py3-none-any.whl", hash = "sha256:55e0fc3c84ebf0a96a5aa23ff8b53d70246479e9a68863f1fcac5a3e52f19dd6"},
{file = "freezegun-1.4.0.tar.gz", hash = "sha256:10939b0ba0ff5adaecf3b06a5c2f73071d9678e507c5eaedb23c761d56ac774b"},
{file = "freezegun-1.5.1-py3-none-any.whl", hash = "sha256:bf111d7138a8abe55ab48a71755673dbaa4ab87f4cff5634a4442dfec34c15f1"},
{file = "freezegun-1.5.1.tar.gz", hash = "sha256:b29dedfcda6d5e8e083ce71b2b542753ad48cfec44037b3fc79702e2980a89e9"},
]
[package.dependencies]
@@ -643,13 +610,13 @@ files = [
[[package]]
name = "httpcore"
version = "1.0.4"
version = "1.0.5"
description = "A minimal low-level HTTP client."
optional = false
python-versions = ">=3.8"
files = [
{file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"},
{file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"},
{file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"},
{file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"},
]
[package.dependencies]
@@ -660,7 +627,7 @@ h11 = ">=0.13,<0.15"
asyncio = ["anyio (>=4.0,<5.0)"]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"]
trio = ["trio (>=0.22.0,<0.25.0)"]
trio = ["trio (>=0.22.0,<0.26.0)"]
[[package]]
name = "httpx"
@@ -725,13 +692,13 @@ ipython = {version = ">=7.31.1", markers = "python_version >= \"3.11\""}
[[package]]
name = "ipython"
version = "8.24.0"
version = "8.25.0"
description = "IPython: Productive Interactive Computing"
optional = false
python-versions = ">=3.10"
files = [
{file = "ipython-8.24.0-py3-none-any.whl", hash = "sha256:d7bf2f6c4314984e3e02393213bab8703cf163ede39672ce5918c51fe253a2a3"},
{file = "ipython-8.24.0.tar.gz", hash = "sha256:010db3f8a728a578bb641fdd06c063b9fb8e96a9464c63aec6310fbcb5e80501"},
{file = "ipython-8.25.0-py3-none-any.whl", hash = "sha256:53eee7ad44df903a06655871cbab66d156a051fd86f3ec6750470ac9604ac1ab"},
{file = "ipython-8.25.0.tar.gz", hash = "sha256:c6ed726a140b6e725b911528f80439c534fac915246af3efc39440a6b0f9d716"},
]
[package.dependencies]
@@ -749,7 +716,7 @@ typing-extensions = {version = ">=4.6", markers = "python_version < \"3.12\""}
[package.extras]
all = ["ipython[black,doc,kernel,matplotlib,nbconvert,nbformat,notebook,parallel,qtconsole]", "ipython[test,test-extra]"]
black = ["black"]
doc = ["docrepr", "exceptiongroup", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "stack-data", "typing-extensions"]
doc = ["docrepr", "exceptiongroup", "intersphinx-registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "tomli", "typing-extensions"]
kernel = ["ipykernel"]
matplotlib = ["matplotlib"]
nbconvert = ["nbconvert"]
@@ -762,13 +729,13 @@ test-extra = ["curio", "ipython[test]", "matplotlib (!=3.2.0)", "nbformat", "num
[[package]]
name = "itsdangerous"
version = "2.1.2"
version = "2.2.0"
description = "Safely pass data to untrusted environments and back."
optional = false
python-versions = ">=3.7"
python-versions = ">=3.8"
files = [
{file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"},
{file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"},
{file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"},
{file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"},
]
[[package]]
@@ -809,13 +776,13 @@ drafts = ["pycryptodome"]
[[package]]
name = "mako"
version = "1.3.2"
version = "1.3.5"
description = "A super-fast templating language that borrows the best ideas from the existing templating languages."
optional = false
python-versions = ">=3.8"
files = [
{file = "Mako-1.3.2-py3-none-any.whl", hash = "sha256:32a99d70754dfce237019d17ffe4a282d2d3351b9c476e90d8a60e63f133b80c"},
{file = "Mako-1.3.2.tar.gz", hash = "sha256:2a0c8ad7f6274271b3bb7467dd37cf9cc6dab4bc19cb69a4ef10669402de698e"},
{file = "Mako-1.3.5-py3-none-any.whl", hash = "sha256:260f1dbc3a519453a9c856dedfe4beb4e50bd5a26d96386cb6c80856556bb91a"},
{file = "Mako-1.3.5.tar.gz", hash = "sha256:48dbc20568c1d276a2698b36d968fa76161bf127194907ea6fc594fa81f943bc"},
]
[package.dependencies]
@@ -1236,13 +1203,13 @@ xmp = ["defusedxml"]
[[package]]
name = "pluggy"
version = "1.4.0"
version = "1.5.0"
description = "plugin and hook calling mechanisms for python"
optional = false
python-versions = ">=3.8"
files = [
{file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"},
{file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"},
{file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
{file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
]
[package.extras]
@@ -1251,13 +1218,13 @@ testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "prompt-toolkit"
version = "3.0.43"
version = "3.0.45"
description = "Library for building powerful interactive command lines in Python"
optional = false
python-versions = ">=3.7.0"
files = [
{file = "prompt_toolkit-3.0.43-py3-none-any.whl", hash = "sha256:a11a29cb3bf0a28a387fe5122cdb649816a957cd9261dcedf8c9f1fef33eacf6"},
{file = "prompt_toolkit-3.0.43.tar.gz", hash = "sha256:3527b7af26106cbc65a040bcc84839a3566ec1b051bb0bfe953631e704b0ff7d"},
{file = "prompt_toolkit-3.0.45-py3-none-any.whl", hash = "sha256:a29b89160e494e3ea8622b09fa5897610b437884dcdcd054fdc1308883326c2a"},
{file = "prompt_toolkit-3.0.45.tar.gz", hash = "sha256:07c60ee4ab7b7e90824b61afa840c8f5aad2d46b3e2e10acc33d8ecc94a49089"},
]
[package.dependencies]
@@ -1290,13 +1257,13 @@ tests = ["pytest"]
[[package]]
name = "pycparser"
version = "2.21"
version = "2.22"
description = "C parser in Python"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
python-versions = ">=3.8"
files = [
{file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
{file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
{file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
{file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
]
[[package]]
@@ -1342,18 +1309,18 @@ files = [
[[package]]
name = "pydantic"
version = "2.6.4"
version = "2.7.2"
description = "Data validation using Python type hints"
optional = false
python-versions = ">=3.8"
files = [
{file = "pydantic-2.6.4-py3-none-any.whl", hash = "sha256:cc46fce86607580867bdc3361ad462bab9c222ef042d3da86f2fb333e1d916c5"},
{file = "pydantic-2.6.4.tar.gz", hash = "sha256:b1704e0847db01817624a6b86766967f552dd9dbf3afba4004409f908dcc84e6"},
{file = "pydantic-2.7.2-py3-none-any.whl", hash = "sha256:834ab954175f94e6e68258537dc49402c4a5e9d0409b9f1b86b7e934a8372de7"},
{file = "pydantic-2.7.2.tar.gz", hash = "sha256:71b2945998f9c9b7919a45bde9a50397b289937d215ae141c1d0903ba7149fd7"},
]
[package.dependencies]
annotated-types = ">=0.4.0"
pydantic-core = "2.16.3"
pydantic-core = "2.18.3"
typing-extensions = ">=4.6.1"
[package.extras]
@@ -1361,90 +1328,90 @@ email = ["email-validator (>=2.0.0)"]
[[package]]
name = "pydantic-core"
version = "2.16.3"
description = ""
version = "2.18.3"
description = "Core functionality for Pydantic validation and serialization"
optional = false
python-versions = ">=3.8"
files = [
{file = "pydantic_core-2.16.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:75b81e678d1c1ede0785c7f46690621e4c6e63ccd9192af1f0bd9d504bbb6bf4"},
{file = "pydantic_core-2.16.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9c865a7ee6f93783bd5d781af5a4c43dadc37053a5b42f7d18dc019f8c9d2bd1"},
{file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:162e498303d2b1c036b957a1278fa0899d02b2842f1ff901b6395104c5554a45"},
{file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2f583bd01bbfbff4eaee0868e6fc607efdfcc2b03c1c766b06a707abbc856187"},
{file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b926dd38db1519ed3043a4de50214e0d600d404099c3392f098a7f9d75029ff8"},
{file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:716b542728d4c742353448765aa7cdaa519a7b82f9564130e2b3f6766018c9ec"},
{file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc4ad7f7ee1a13d9cb49d8198cd7d7e3aa93e425f371a68235f784e99741561f"},
{file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bd87f48924f360e5d1c5f770d6155ce0e7d83f7b4e10c2f9ec001c73cf475c99"},
{file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0df446663464884297c793874573549229f9eca73b59360878f382a0fc085979"},
{file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4df8a199d9f6afc5ae9a65f8f95ee52cae389a8c6b20163762bde0426275b7db"},
{file = "pydantic_core-2.16.3-cp310-none-win32.whl", hash = "sha256:456855f57b413f077dff513a5a28ed838dbbb15082ba00f80750377eed23d132"},
{file = "pydantic_core-2.16.3-cp310-none-win_amd64.whl", hash = "sha256:732da3243e1b8d3eab8c6ae23ae6a58548849d2e4a4e03a1924c8ddf71a387cb"},
{file = "pydantic_core-2.16.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:519ae0312616026bf4cedc0fe459e982734f3ca82ee8c7246c19b650b60a5ee4"},
{file = "pydantic_core-2.16.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b3992a322a5617ded0a9f23fd06dbc1e4bd7cf39bc4ccf344b10f80af58beacd"},
{file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d62da299c6ecb04df729e4b5c52dc0d53f4f8430b4492b93aa8de1f541c4aac"},
{file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2acca2be4bb2f2147ada8cac612f8a98fc09f41c89f87add7256ad27332c2fda"},
{file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b662180108c55dfbf1280d865b2d116633d436cfc0bba82323554873967b340"},
{file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e7c6ed0dc9d8e65f24f5824291550139fe6f37fac03788d4580da0d33bc00c97"},
{file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1bb0827f56654b4437955555dc3aeeebeddc47c2d7ed575477f082622c49e"},
{file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e56f8186d6210ac7ece503193ec84104da7ceb98f68ce18c07282fcc2452e76f"},
{file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:936e5db01dd49476fa8f4383c259b8b1303d5dd5fb34c97de194560698cc2c5e"},
{file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:33809aebac276089b78db106ee692bdc9044710e26f24a9a2eaa35a0f9fa70ba"},
{file = "pydantic_core-2.16.3-cp311-none-win32.whl", hash = "sha256:ded1c35f15c9dea16ead9bffcde9bb5c7c031bff076355dc58dcb1cb436c4721"},
{file = "pydantic_core-2.16.3-cp311-none-win_amd64.whl", hash = "sha256:d89ca19cdd0dd5f31606a9329e309d4fcbb3df860960acec32630297d61820df"},
{file = "pydantic_core-2.16.3-cp311-none-win_arm64.whl", hash = "sha256:6162f8d2dc27ba21027f261e4fa26f8bcb3cf9784b7f9499466a311ac284b5b9"},
{file = "pydantic_core-2.16.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0f56ae86b60ea987ae8bcd6654a887238fd53d1384f9b222ac457070b7ac4cff"},
{file = "pydantic_core-2.16.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9bd22a2a639e26171068f8ebb5400ce2c1bc7d17959f60a3b753ae13c632975"},
{file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4204e773b4b408062960e65468d5346bdfe139247ee5f1ca2a378983e11388a2"},
{file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f651dd19363c632f4abe3480a7c87a9773be27cfe1341aef06e8759599454120"},
{file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aaf09e615a0bf98d406657e0008e4a8701b11481840be7d31755dc9f97c44053"},
{file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8e47755d8152c1ab5b55928ab422a76e2e7b22b5ed8e90a7d584268dd49e9c6b"},
{file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:500960cb3a0543a724a81ba859da816e8cf01b0e6aaeedf2c3775d12ee49cade"},
{file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cf6204fe865da605285c34cf1172879d0314ff267b1c35ff59de7154f35fdc2e"},
{file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d33dd21f572545649f90c38c227cc8631268ba25c460b5569abebdd0ec5974ca"},
{file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:49d5d58abd4b83fb8ce763be7794d09b2f50f10aa65c0f0c1696c677edeb7cbf"},
{file = "pydantic_core-2.16.3-cp312-none-win32.whl", hash = "sha256:f53aace168a2a10582e570b7736cc5bef12cae9cf21775e3eafac597e8551fbe"},
{file = "pydantic_core-2.16.3-cp312-none-win_amd64.whl", hash = "sha256:0d32576b1de5a30d9a97f300cc6a3f4694c428d956adbc7e6e2f9cad279e45ed"},
{file = "pydantic_core-2.16.3-cp312-none-win_arm64.whl", hash = "sha256:ec08be75bb268473677edb83ba71e7e74b43c008e4a7b1907c6d57e940bf34b6"},
{file = "pydantic_core-2.16.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:b1f6f5938d63c6139860f044e2538baeee6f0b251a1816e7adb6cbce106a1f01"},
{file = "pydantic_core-2.16.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2a1ef6a36fdbf71538142ed604ad19b82f67b05749512e47f247a6ddd06afdc7"},
{file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:704d35ecc7e9c31d48926150afada60401c55efa3b46cd1ded5a01bdffaf1d48"},
{file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d937653a696465677ed583124b94a4b2d79f5e30b2c46115a68e482c6a591c8a"},
{file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9803edf8e29bd825f43481f19c37f50d2b01899448273b3a7758441b512acf8"},
{file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:72282ad4892a9fb2da25defeac8c2e84352c108705c972db82ab121d15f14e6d"},
{file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f752826b5b8361193df55afcdf8ca6a57d0232653494ba473630a83ba50d8c9"},
{file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4384a8f68ddb31a0b0c3deae88765f5868a1b9148939c3f4121233314ad5532c"},
{file = "pydantic_core-2.16.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a4b2bf78342c40b3dc830880106f54328928ff03e357935ad26c7128bbd66ce8"},
{file = "pydantic_core-2.16.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:13dcc4802961b5f843a9385fc821a0b0135e8c07fc3d9949fd49627c1a5e6ae5"},
{file = "pydantic_core-2.16.3-cp38-none-win32.whl", hash = "sha256:e3e70c94a0c3841e6aa831edab1619ad5c511199be94d0c11ba75fe06efe107a"},
{file = "pydantic_core-2.16.3-cp38-none-win_amd64.whl", hash = "sha256:ecdf6bf5f578615f2e985a5e1f6572e23aa632c4bd1dc67f8f406d445ac115ed"},
{file = "pydantic_core-2.16.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:bda1ee3e08252b8d41fa5537413ffdddd58fa73107171a126d3b9ff001b9b820"},
{file = "pydantic_core-2.16.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:21b888c973e4f26b7a96491c0965a8a312e13be108022ee510248fe379a5fa23"},
{file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be0ec334369316fa73448cc8c982c01e5d2a81c95969d58b8f6e272884df0074"},
{file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5b6079cc452a7c53dd378c6f881ac528246b3ac9aae0f8eef98498a75657805"},
{file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ee8d5f878dccb6d499ba4d30d757111847b6849ae07acdd1205fffa1fc1253c"},
{file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7233d65d9d651242a68801159763d09e9ec96e8a158dbf118dc090cd77a104c9"},
{file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6119dc90483a5cb50a1306adb8d52c66e447da88ea44f323e0ae1a5fcb14256"},
{file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:578114bc803a4c1ff9946d977c221e4376620a46cf78da267d946397dc9514a8"},
{file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d8f99b147ff3fcf6b3cc60cb0c39ea443884d5559a30b1481e92495f2310ff2b"},
{file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4ac6b4ce1e7283d715c4b729d8f9dab9627586dafce81d9eaa009dd7f25dd972"},
{file = "pydantic_core-2.16.3-cp39-none-win32.whl", hash = "sha256:e7774b570e61cb998490c5235740d475413a1f6de823169b4cf94e2fe9e9f6b2"},
{file = "pydantic_core-2.16.3-cp39-none-win_amd64.whl", hash = "sha256:9091632a25b8b87b9a605ec0e61f241c456e9248bfdcf7abdf344fdb169c81cf"},
{file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:36fa178aacbc277bc6b62a2c3da95226520da4f4e9e206fdf076484363895d2c"},
{file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:dcca5d2bf65c6fb591fff92da03f94cd4f315972f97c21975398bd4bd046854a"},
{file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a72fb9963cba4cd5793854fd12f4cfee731e86df140f59ff52a49b3552db241"},
{file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b60cc1a081f80a2105a59385b92d82278b15d80ebb3adb200542ae165cd7d183"},
{file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cbcc558401de90a746d02ef330c528f2e668c83350f045833543cd57ecead1ad"},
{file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:fee427241c2d9fb7192b658190f9f5fd6dfe41e02f3c1489d2ec1e6a5ab1e04a"},
{file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f4cb85f693044e0f71f394ff76c98ddc1bc0953e48c061725e540396d5c8a2e1"},
{file = "pydantic_core-2.16.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b29eeb887aa931c2fcef5aa515d9d176d25006794610c264ddc114c053bf96fe"},
{file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a425479ee40ff021f8216c9d07a6a3b54b31c8267c6e17aa88b70d7ebd0e5e5b"},
{file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5c5cbc703168d1b7a838668998308018a2718c2130595e8e190220238addc96f"},
{file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99b6add4c0b39a513d323d3b93bc173dac663c27b99860dd5bf491b240d26137"},
{file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f76ee558751746d6a38f89d60b6228fa174e5172d143886af0f85aa306fd89"},
{file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:00ee1c97b5364b84cb0bd82e9bbf645d5e2871fb8c58059d158412fee2d33d8a"},
{file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:287073c66748f624be4cef893ef9174e3eb88fe0b8a78dc22e88eca4bc357ca6"},
{file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ed25e1835c00a332cb10c683cd39da96a719ab1dfc08427d476bce41b92531fc"},
{file = "pydantic_core-2.16.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:86b3d0033580bd6bbe07590152007275bd7af95f98eaa5bd36f3da219dcd93da"},
{file = "pydantic_core-2.16.3.tar.gz", hash = "sha256:1cac689f80a3abab2d3c0048b29eea5751114054f032a941a32de4c852c59cad"},
{file = "pydantic_core-2.18.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:744697428fcdec6be5670460b578161d1ffe34743a5c15656be7ea82b008197c"},
{file = "pydantic_core-2.18.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37b40c05ced1ba4218b14986fe6f283d22e1ae2ff4c8e28881a70fb81fbfcda7"},
{file = "pydantic_core-2.18.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:544a9a75622357076efb6b311983ff190fbfb3c12fc3a853122b34d3d358126c"},
{file = "pydantic_core-2.18.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e2e253af04ceaebde8eb201eb3f3e3e7e390f2d275a88300d6a1959d710539e2"},
{file = "pydantic_core-2.18.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:855ec66589c68aa367d989da5c4755bb74ee92ccad4fdb6af942c3612c067e34"},
{file = "pydantic_core-2.18.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d3e42bb54e7e9d72c13ce112e02eb1b3b55681ee948d748842171201a03a98a"},
{file = "pydantic_core-2.18.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6ac9ffccc9d2e69d9fba841441d4259cb668ac180e51b30d3632cd7abca2b9b"},
{file = "pydantic_core-2.18.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c56eca1686539fa0c9bda992e7bd6a37583f20083c37590413381acfc5f192d6"},
{file = "pydantic_core-2.18.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:17954d784bf8abfc0ec2a633108207ebc4fa2df1a0e4c0c3ccbaa9bb01d2c426"},
{file = "pydantic_core-2.18.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:98ed737567d8f2ecd54f7c8d4f8572ca7c7921ede93a2e52939416170d357812"},
{file = "pydantic_core-2.18.3-cp310-none-win32.whl", hash = "sha256:9f9e04afebd3ed8c15d67a564ed0a34b54e52136c6d40d14c5547b238390e779"},
{file = "pydantic_core-2.18.3-cp310-none-win_amd64.whl", hash = "sha256:45e4ffbae34f7ae30d0047697e724e534a7ec0a82ef9994b7913a412c21462a0"},
{file = "pydantic_core-2.18.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:b9ebe8231726c49518b16b237b9fe0d7d361dd221302af511a83d4ada01183ab"},
{file = "pydantic_core-2.18.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b8e20e15d18bf7dbb453be78a2d858f946f5cdf06c5072453dace00ab652e2b2"},
{file = "pydantic_core-2.18.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0d9ff283cd3459fa0bf9b0256a2b6f01ac1ff9ffb034e24457b9035f75587cb"},
{file = "pydantic_core-2.18.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2f7ef5f0ebb77ba24c9970da18b771711edc5feaf00c10b18461e0f5f5949231"},
{file = "pydantic_core-2.18.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73038d66614d2e5cde30435b5afdced2b473b4c77d4ca3a8624dd3e41a9c19be"},
{file = "pydantic_core-2.18.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6afd5c867a74c4d314c557b5ea9520183fadfbd1df4c2d6e09fd0d990ce412cd"},
{file = "pydantic_core-2.18.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd7df92f28d351bb9f12470f4c533cf03d1b52ec5a6e5c58c65b183055a60106"},
{file = "pydantic_core-2.18.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:80aea0ffeb1049336043d07799eace1c9602519fb3192916ff525b0287b2b1e4"},
{file = "pydantic_core-2.18.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaee40f25bba38132e655ffa3d1998a6d576ba7cf81deff8bfa189fb43fd2bbe"},
{file = "pydantic_core-2.18.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9128089da8f4fe73f7a91973895ebf2502539d627891a14034e45fb9e707e26d"},
{file = "pydantic_core-2.18.3-cp311-none-win32.whl", hash = "sha256:fec02527e1e03257aa25b1a4dcbe697b40a22f1229f5d026503e8b7ff6d2eda7"},
{file = "pydantic_core-2.18.3-cp311-none-win_amd64.whl", hash = "sha256:58ff8631dbab6c7c982e6425da8347108449321f61fe427c52ddfadd66642af7"},
{file = "pydantic_core-2.18.3-cp311-none-win_arm64.whl", hash = "sha256:3fc1c7f67f34c6c2ef9c213e0f2a351797cda98249d9ca56a70ce4ebcaba45f4"},
{file = "pydantic_core-2.18.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f0928cde2ae416a2d1ebe6dee324709c6f73e93494d8c7aea92df99aab1fc40f"},
{file = "pydantic_core-2.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0bee9bb305a562f8b9271855afb6ce00223f545de3d68560b3c1649c7c5295e9"},
{file = "pydantic_core-2.18.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e862823be114387257dacbfa7d78547165a85d7add33b446ca4f4fae92c7ff5c"},
{file = "pydantic_core-2.18.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6a36f78674cbddc165abab0df961b5f96b14461d05feec5e1f78da58808b97e7"},
{file = "pydantic_core-2.18.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba905d184f62e7ddbb7a5a751d8a5c805463511c7b08d1aca4a3e8c11f2e5048"},
{file = "pydantic_core-2.18.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fdd362f6a586e681ff86550b2379e532fee63c52def1c666887956748eaa326"},
{file = "pydantic_core-2.18.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24b214b7ee3bd3b865e963dbed0f8bc5375f49449d70e8d407b567af3222aae4"},
{file = "pydantic_core-2.18.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:691018785779766127f531674fa82bb368df5b36b461622b12e176c18e119022"},
{file = "pydantic_core-2.18.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:60e4c625e6f7155d7d0dcac151edf5858102bc61bf959d04469ca6ee4e8381bd"},
{file = "pydantic_core-2.18.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4e651e47d981c1b701dcc74ab8fec5a60a5b004650416b4abbef13db23bc7be"},
{file = "pydantic_core-2.18.3-cp312-none-win32.whl", hash = "sha256:ffecbb5edb7f5ffae13599aec33b735e9e4c7676ca1633c60f2c606beb17efc5"},
{file = "pydantic_core-2.18.3-cp312-none-win_amd64.whl", hash = "sha256:2c8333f6e934733483c7eddffdb094c143b9463d2af7e6bd85ebcb2d4a1b82c6"},
{file = "pydantic_core-2.18.3-cp312-none-win_arm64.whl", hash = "sha256:7a20dded653e516a4655f4c98e97ccafb13753987434fe7cf044aa25f5b7d417"},
{file = "pydantic_core-2.18.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:eecf63195be644b0396f972c82598cd15693550f0ff236dcf7ab92e2eb6d3522"},
{file = "pydantic_core-2.18.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c44efdd3b6125419c28821590d7ec891c9cb0dff33a7a78d9d5c8b6f66b9702"},
{file = "pydantic_core-2.18.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e59fca51ffbdd1638b3856779342ed69bcecb8484c1d4b8bdb237d0eb5a45e2"},
{file = "pydantic_core-2.18.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:70cf099197d6b98953468461d753563b28e73cf1eade2ffe069675d2657ed1d5"},
{file = "pydantic_core-2.18.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:63081a49dddc6124754b32a3774331467bfc3d2bd5ff8f10df36a95602560361"},
{file = "pydantic_core-2.18.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:370059b7883485c9edb9655355ff46d912f4b03b009d929220d9294c7fd9fd60"},
{file = "pydantic_core-2.18.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a64faeedfd8254f05f5cf6fc755023a7e1606af3959cfc1a9285744cc711044"},
{file = "pydantic_core-2.18.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19d2e725de0f90d8671f89e420d36c3dd97639b98145e42fcc0e1f6d492a46dc"},
{file = "pydantic_core-2.18.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:67bc078025d70ec5aefe6200ef094576c9d86bd36982df1301c758a9fff7d7f4"},
{file = "pydantic_core-2.18.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:adf952c3f4100e203cbaf8e0c907c835d3e28f9041474e52b651761dc248a3c0"},
{file = "pydantic_core-2.18.3-cp38-none-win32.whl", hash = "sha256:9a46795b1f3beb167eaee91736d5d17ac3a994bf2215a996aed825a45f897558"},
{file = "pydantic_core-2.18.3-cp38-none-win_amd64.whl", hash = "sha256:200ad4e3133cb99ed82342a101a5abf3d924722e71cd581cc113fe828f727fbc"},
{file = "pydantic_core-2.18.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:304378b7bf92206036c8ddd83a2ba7b7d1a5b425acafff637172a3aa72ad7083"},
{file = "pydantic_core-2.18.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c826870b277143e701c9ccf34ebc33ddb4d072612683a044e7cce2d52f6c3fef"},
{file = "pydantic_core-2.18.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e201935d282707394f3668380e41ccf25b5794d1b131cdd96b07f615a33ca4b1"},
{file = "pydantic_core-2.18.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5560dda746c44b48bf82b3d191d74fe8efc5686a9ef18e69bdabccbbb9ad9442"},
{file = "pydantic_core-2.18.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b32c2a1f8032570842257e4c19288eba9a2bba4712af542327de9a1204faff8"},
{file = "pydantic_core-2.18.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:929c24e9dea3990bc8bcd27c5f2d3916c0c86f5511d2caa69e0d5290115344a9"},
{file = "pydantic_core-2.18.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1a8376fef60790152564b0eab376b3e23dd6e54f29d84aad46f7b264ecca943"},
{file = "pydantic_core-2.18.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dccf3ef1400390ddd1fb55bf0632209d39140552d068ee5ac45553b556780e06"},
{file = "pydantic_core-2.18.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:41dbdcb0c7252b58fa931fec47937edb422c9cb22528f41cb8963665c372caf6"},
{file = "pydantic_core-2.18.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:666e45cf071669fde468886654742fa10b0e74cd0fa0430a46ba6056b24fb0af"},
{file = "pydantic_core-2.18.3-cp39-none-win32.whl", hash = "sha256:f9c08cabff68704a1b4667d33f534d544b8a07b8e5d039c37067fceb18789e78"},
{file = "pydantic_core-2.18.3-cp39-none-win_amd64.whl", hash = "sha256:4afa5f5973e8572b5c0dcb4e2d4fda7890e7cd63329bd5cc3263a25c92ef0026"},
{file = "pydantic_core-2.18.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:77319771a026f7c7d29c6ebc623de889e9563b7087911b46fd06c044a12aa5e9"},
{file = "pydantic_core-2.18.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:df11fa992e9f576473038510d66dd305bcd51d7dd508c163a8c8fe148454e059"},
{file = "pydantic_core-2.18.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d531076bdfb65af593326ffd567e6ab3da145020dafb9187a1d131064a55f97c"},
{file = "pydantic_core-2.18.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d33ce258e4e6e6038f2b9e8b8a631d17d017567db43483314993b3ca345dcbbb"},
{file = "pydantic_core-2.18.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1f9cd7f5635b719939019be9bda47ecb56e165e51dd26c9a217a433e3d0d59a9"},
{file = "pydantic_core-2.18.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:cd4a032bb65cc132cae1fe3e52877daecc2097965cd3914e44fbd12b00dae7c5"},
{file = "pydantic_core-2.18.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f2718430098bcdf60402136c845e4126a189959d103900ebabb6774a5d9fdb"},
{file = "pydantic_core-2.18.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c0037a92cf0c580ed14e10953cdd26528e8796307bb8bb312dc65f71547df04d"},
{file = "pydantic_core-2.18.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b95a0972fac2b1ff3c94629fc9081b16371dad870959f1408cc33b2f78ad347a"},
{file = "pydantic_core-2.18.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a62e437d687cc148381bdd5f51e3e81f5b20a735c55f690c5be94e05da2b0d5c"},
{file = "pydantic_core-2.18.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b367a73a414bbb08507da102dc2cde0fa7afe57d09b3240ce82a16d608a7679c"},
{file = "pydantic_core-2.18.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ecce4b2360aa3f008da3327d652e74a0e743908eac306198b47e1c58b03dd2b"},
{file = "pydantic_core-2.18.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bd4435b8d83f0c9561a2a9585b1de78f1abb17cb0cef5f39bf6a4b47d19bafe3"},
{file = "pydantic_core-2.18.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:616221a6d473c5b9aa83fa8982745441f6a4a62a66436be9445c65f241b86c94"},
{file = "pydantic_core-2.18.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:7e6382ce89a92bc1d0c0c5edd51e931432202b9080dc921d8d003e616402efd1"},
{file = "pydantic_core-2.18.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ff58f379345603d940e461eae474b6bbb6dab66ed9a851ecd3cb3709bf4dcf6a"},
{file = "pydantic_core-2.18.3.tar.gz", hash = "sha256:432e999088d85c8f36b9a3f769a8e2b57aabd817bbb729a90d1fe7f18f6f1f39"},
]
[package.dependencies]
@@ -1483,33 +1450,33 @@ windows-terminal = ["colorama (>=0.4.6)"]
[[package]]
name = "pytest"
version = "8.1.1"
version = "8.2.1"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "pytest-8.1.1-py3-none-any.whl", hash = "sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7"},
{file = "pytest-8.1.1.tar.gz", hash = "sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044"},
{file = "pytest-8.2.1-py3-none-any.whl", hash = "sha256:faccc5d332b8c3719f40283d0d44aa5cf101cec36f88cde9ed8f2bc0538612b1"},
{file = "pytest-8.2.1.tar.gz", hash = "sha256:5046e5b46d8e4cac199c373041f26be56fdb81eb4e67dc11d4e10811fc3408fd"},
]
[package.dependencies]
colorama = {version = "*", markers = "sys_platform == \"win32\""}
iniconfig = "*"
packaging = "*"
pluggy = ">=1.4,<2.0"
pluggy = ">=1.5,<2.0"
[package.extras]
testing = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
[[package]]
name = "pytest-asyncio"
version = "0.23.6"
version = "0.23.7"
description = "Pytest support for asyncio"
optional = false
python-versions = ">=3.8"
files = [
{file = "pytest-asyncio-0.23.6.tar.gz", hash = "sha256:ffe523a89c1c222598c76856e76852b787504ddb72dd5d9b6617ffa8aa2cde5f"},
{file = "pytest_asyncio-0.23.6-py3-none-any.whl", hash = "sha256:68516fdd1018ac57b846c9846b954f0393b26f094764a28c955eabb0536a4e8a"},
{file = "pytest_asyncio-0.23.7-py3-none-any.whl", hash = "sha256:009b48127fbe44518a547bddd25611551b0e43ccdbf1e67d12479f569832c20b"},
{file = "pytest_asyncio-0.23.7.tar.gz", hash = "sha256:5f5c72948f4c49e7db4f29f2521d4031f1c27f86e57b046126654083d4770268"},
]
[package.dependencies]
@@ -1598,13 +1565,13 @@ cli = ["click (>=5.0)"]
[[package]]
name = "python-engineio"
version = "4.9.0"
version = "4.9.1"
description = "Engine.IO server and client for Python"
optional = false
python-versions = ">=3.6"
files = [
{file = "python-engineio-4.9.0.tar.gz", hash = "sha256:e87459c15638e567711fd156e6f9c4a402668871bed79523f0ecfec744729ec7"},
{file = "python_engineio-4.9.0-py3-none-any.whl", hash = "sha256:979859bff770725b75e60353d7ae53b397e8b517d05ba76733b404a3dcca3e4c"},
{file = "python_engineio-4.9.1-py3-none-any.whl", hash = "sha256:f995e702b21f6b9ebde4e2000cd2ad0112ba0e5116ec8d22fe3515e76ba9dddd"},
{file = "python_engineio-4.9.1.tar.gz", hash = "sha256:7631cf5563086076611e494c643b3fa93dd3a854634b5488be0bba0ef9b99709"},
]
[package.dependencies]
@@ -1711,13 +1678,13 @@ files = [
[[package]]
name = "redis"
version = "5.0.3"
version = "5.0.4"
description = "Python client for Redis database and key-value store"
optional = false
python-versions = ">=3.7"
files = [
{file = "redis-5.0.3-py3-none-any.whl", hash = "sha256:5da9b8fe9e1254293756c16c008e8620b3d15fcc6dde6babde9541850e72a32d"},
{file = "redis-5.0.3.tar.gz", hash = "sha256:4973bae7444c0fbed64a06b87446f79361cb7e4ec1538c022d696ed7a5015580"},
{file = "redis-5.0.4-py3-none-any.whl", hash = "sha256:7adc2835c7a9b5033b7ad8f8918d09b7344188228809c98df07af226d39dec91"},
{file = "redis-5.0.4.tar.gz", hash = "sha256:ec31f2ed9675cc54c21ba854cfe0462e6faf1d83c8ce5944709db8a4700b9c61"},
]
[package.dependencies]
@@ -1729,13 +1696,13 @@ ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"
[[package]]
name = "requests"
version = "2.32.2"
version = "2.32.3"
description = "Python HTTP for Humans."
optional = false
python-versions = ">=3.8"
files = [
{file = "requests-2.32.2-py3-none-any.whl", hash = "sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c"},
{file = "requests-2.32.2.tar.gz", hash = "sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289"},
{file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"},
{file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"},
]
[package.dependencies]
@@ -1750,13 +1717,13 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
[[package]]
name = "rq"
version = "1.16.1"
version = "1.16.2"
description = "RQ is a simple, lightweight, library for creating background jobs, and processing them."
optional = false
python-versions = ">=3.7"
files = [
{file = "rq-1.16.1-py3-none-any.whl", hash = "sha256:273de33f10bb9f18cd1e8ccc0a4e8dba2b8eb86a6ab2a91ae674f99bd68025f1"},
{file = "rq-1.16.1.tar.gz", hash = "sha256:d9a6314bc759a743b4a5d89aa467eaa3a31dbbc0a34bcd0ee82e8852d9ec166d"},
{file = "rq-1.16.2-py3-none-any.whl", hash = "sha256:52e619f6cb469b00e04da74305045d244b75fecb2ecaa4f26422add57d3c5f09"},
{file = "rq-1.16.2.tar.gz", hash = "sha256:5c5b9ad5fbaf792b8fada25cc7627f4d206a9a4455aced371d4f501cc3f13b34"},
]
[package.dependencies]
@@ -1832,13 +1799,13 @@ files = [
[[package]]
name = "sqlakeyset"
version = "2.0.1708907391"
version = "2.0.1716332987"
description = "offset-free paging for sqlalchemy"
optional = false
python-versions = ">=3.7,<4.0"
python-versions = "<4.0,>=3.7"
files = [
{file = "sqlakeyset-2.0.1708907391-py3-none-any.whl", hash = "sha256:3eb6bd0ccd599ab44aa5c0b0f9a710d5c4d24813c7053adc9398bf539b68859e"},
{file = "sqlakeyset-2.0.1708907391.tar.gz", hash = "sha256:376af65f0faecea6e19b16dd5dd047439d4314c5ae1d85d8a46bbb1ee44f32e3"},
{file = "sqlakeyset-2.0.1716332987-py3-none-any.whl", hash = "sha256:e28916828cdaaaaaee3905ba55722959ad2808d47b0bb3235e1d15c5480c17fa"},
{file = "sqlakeyset-2.0.1716332987.tar.gz", hash = "sha256:504c20747c3f05484fa67b16dcc7c7b0d5367f7e5d3f335a8845184e61425d94"},
]
[package.dependencies]
@@ -2019,30 +1986,45 @@ files = [
docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"]
test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"]
[[package]]
name = "types-cffi"
version = "1.16.0.20240331"
description = "Typing stubs for cffi"
optional = false
python-versions = ">=3.8"
files = [
{file = "types-cffi-1.16.0.20240331.tar.gz", hash = "sha256:b8b20d23a2b89cfed5f8c5bc53b0cb8677c3aac6d970dbc771e28b9c698f5dee"},
{file = "types_cffi-1.16.0.20240331-py3-none-any.whl", hash = "sha256:a363e5ea54a4eb6a4a105d800685fde596bc318089b025b27dee09849fe41ff0"},
]
[package.dependencies]
types-setuptools = "*"
[[package]]
name = "types-passlib"
version = "1.7.7.20240311"
version = "1.7.7.20240327"
description = "Typing stubs for passlib"
optional = false
python-versions = ">=3.8"
files = [
{file = "types-passlib-1.7.7.20240311.tar.gz", hash = "sha256:287dd27cec5421daf6be5c295f681baf343c146038c8bde4db783bcac1beccb7"},
{file = "types_passlib-1.7.7.20240311-py3-none-any.whl", hash = "sha256:cd44166e9347ae516f4830046cd1673c1ef90a5cc7ddd1356cf8a14892f29249"},
{file = "types-passlib-1.7.7.20240327.tar.gz", hash = "sha256:4cce6a1a3a6afee9fc4728b4d9784300764ac2be747f5bcc01646d904b85f4bb"},
{file = "types_passlib-1.7.7.20240327-py3-none-any.whl", hash = "sha256:3a3b7f4258b71034d2e2f4f307d6810f9904f906cdf375514c8bdbdb28a4ad23"},
]
[[package]]
name = "types-pyopenssl"
version = "24.0.0.20240311"
version = "24.1.0.20240425"
description = "Typing stubs for pyOpenSSL"
optional = false
python-versions = ">=3.8"
files = [
{file = "types-pyOpenSSL-24.0.0.20240311.tar.gz", hash = "sha256:7bca00cfc4e7ef9c5d2663c6a1c068c35798e59670595439f6296e7ba3d58083"},
{file = "types_pyOpenSSL-24.0.0.20240311-py3-none-any.whl", hash = "sha256:6e8e8bfad34924067333232c93f7fc4b369856d8bea0d5c9d1808cb290ab1972"},
{file = "types-pyOpenSSL-24.1.0.20240425.tar.gz", hash = "sha256:0a7e82626c1983dc8dc59292bf20654a51c3c3881bcbb9b337c1da6e32f0204e"},
{file = "types_pyOpenSSL-24.1.0.20240425-py3-none-any.whl", hash = "sha256:f51a156835555dd2a1f025621e8c4fbe7493470331afeef96884d1d29bf3a473"},
]
[package.dependencies]
cryptography = ">=35.0.0"
types-cffi = "*"
[[package]]
name = "types-pyyaml"
@@ -2057,13 +2039,13 @@ files = [
[[package]]
name = "types-redis"
version = "4.6.0.20240311"
version = "4.6.0.20240425"
description = "Typing stubs for redis"
optional = false
python-versions = ">=3.8"
files = [
{file = "types-redis-4.6.0.20240311.tar.gz", hash = "sha256:e049bbdff0e0a1f8e701b64636811291d21bff79bf1e7850850a44055224a85f"},
{file = "types_redis-4.6.0.20240311-py3-none-any.whl", hash = "sha256:6b9d68a29aba1ee400c823d8e5fe88675282eb69d7211e72fe65dbe54b33daca"},
{file = "types-redis-4.6.0.20240425.tar.gz", hash = "sha256:9402a10ee931d241fdfcc04592ebf7a661d7bb92a8dea631279f0d8acbcf3a22"},
{file = "types_redis-4.6.0.20240425-py3-none-any.whl", hash = "sha256:ac5bc19e8f5997b9e76ad5d9cf15d0392d9f28cf5fc7746ea4a64b989c45c6a8"},
]
[package.dependencies]
@@ -2072,27 +2054,38 @@ types-pyOpenSSL = "*"
[[package]]
name = "types-requests"
version = "2.31.0.20240311"
version = "2.32.0.20240523"
description = "Typing stubs for requests"
optional = false
python-versions = ">=3.8"
files = [
{file = "types-requests-2.31.0.20240311.tar.gz", hash = "sha256:b1c1b66abfb7fa79aae09097a811c4aa97130eb8831c60e47aee4ca344731ca5"},
{file = "types_requests-2.31.0.20240311-py3-none-any.whl", hash = "sha256:47872893d65a38e282ee9f277a4ee50d1b28bd592040df7d1fdaffdf3779937d"},
{file = "types-requests-2.32.0.20240523.tar.gz", hash = "sha256:26b8a6de32d9f561192b9942b41c0ab2d8010df5677ca8aa146289d11d505f57"},
{file = "types_requests-2.32.0.20240523-py3-none-any.whl", hash = "sha256:f19ed0e2daa74302069bbbbf9e82902854ffa780bc790742a810a9aaa52f65ec"},
]
[package.dependencies]
urllib3 = ">=2"
[[package]]
name = "types-setuptools"
version = "70.0.0.20240524"
description = "Typing stubs for setuptools"
optional = false
python-versions = ">=3.8"
files = [
{file = "types-setuptools-70.0.0.20240524.tar.gz", hash = "sha256:e31fee7b9d15ef53980526579ac6089b3ae51a005a281acf97178e90ac71aff6"},
{file = "types_setuptools-70.0.0.20240524-py3-none-any.whl", hash = "sha256:8f5379b9948682d72a9ab531fbe52932e84c4f38deda570255f9bae3edd766bc"},
]
[[package]]
name = "typing-extensions"
version = "4.10.0"
version = "4.12.0"
description = "Backported and Experimental Type Hints for Python 3.8+"
optional = false
python-versions = ">=3.8"
files = [
{file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"},
{file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"},
{file = "typing_extensions-4.12.0-py3-none-any.whl", hash = "sha256:b349c66bea9016ac22978d800cfff206d5f9816951f12a7d0ec5578b0a819594"},
{file = "typing_extensions-4.12.0.tar.gz", hash = "sha256:8cbcdc8606ebcb0d95453ad7dc5065e6237b6aa230a31e81d0f440c30fed5fd8"},
]
[[package]]
@@ -2159,40 +2152,43 @@ yarl = "*"
[[package]]
name = "watchdog"
version = "4.0.0"
version = "4.0.1"
description = "Filesystem events monitoring"
optional = false
python-versions = ">=3.8"
files = [
{file = "watchdog-4.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:39cb34b1f1afbf23e9562501673e7146777efe95da24fab5707b88f7fb11649b"},
{file = "watchdog-4.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c522392acc5e962bcac3b22b9592493ffd06d1fc5d755954e6be9f4990de932b"},
{file = "watchdog-4.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6c47bdd680009b11c9ac382163e05ca43baf4127954c5f6d0250e7d772d2b80c"},
{file = "watchdog-4.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8350d4055505412a426b6ad8c521bc7d367d1637a762c70fdd93a3a0d595990b"},
{file = "watchdog-4.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c17d98799f32e3f55f181f19dd2021d762eb38fdd381b4a748b9f5a36738e935"},
{file = "watchdog-4.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4986db5e8880b0e6b7cd52ba36255d4793bf5cdc95bd6264806c233173b1ec0b"},
{file = "watchdog-4.0.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:11e12fafb13372e18ca1bbf12d50f593e7280646687463dd47730fd4f4d5d257"},
{file = "watchdog-4.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5369136a6474678e02426bd984466343924d1df8e2fd94a9b443cb7e3aa20d19"},
{file = "watchdog-4.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76ad8484379695f3fe46228962017a7e1337e9acadafed67eb20aabb175df98b"},
{file = "watchdog-4.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:45cc09cc4c3b43fb10b59ef4d07318d9a3ecdbff03abd2e36e77b6dd9f9a5c85"},
{file = "watchdog-4.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:eed82cdf79cd7f0232e2fdc1ad05b06a5e102a43e331f7d041e5f0e0a34a51c4"},
{file = "watchdog-4.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba30a896166f0fee83183cec913298151b73164160d965af2e93a20bbd2ab605"},
{file = "watchdog-4.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d18d7f18a47de6863cd480734613502904611730f8def45fc52a5d97503e5101"},
{file = "watchdog-4.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2895bf0518361a9728773083908801a376743bcc37dfa252b801af8fd281b1ca"},
{file = "watchdog-4.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87e9df830022488e235dd601478c15ad73a0389628588ba0b028cb74eb72fed8"},
{file = "watchdog-4.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6e949a8a94186bced05b6508faa61b7adacc911115664ccb1923b9ad1f1ccf7b"},
{file = "watchdog-4.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6a4db54edea37d1058b08947c789a2354ee02972ed5d1e0dca9b0b820f4c7f92"},
{file = "watchdog-4.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d31481ccf4694a8416b681544c23bd271f5a123162ab603c7d7d2dd7dd901a07"},
{file = "watchdog-4.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8fec441f5adcf81dd240a5fe78e3d83767999771630b5ddfc5867827a34fa3d3"},
{file = "watchdog-4.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:6a9c71a0b02985b4b0b6d14b875a6c86ddea2fdbebd0c9a720a806a8bbffc69f"},
{file = "watchdog-4.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:557ba04c816d23ce98a06e70af6abaa0485f6d94994ec78a42b05d1c03dcbd50"},
{file = "watchdog-4.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:d0f9bd1fd919134d459d8abf954f63886745f4660ef66480b9d753a7c9d40927"},
{file = "watchdog-4.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:f9b2fdca47dc855516b2d66eef3c39f2672cbf7e7a42e7e67ad2cbfcd6ba107d"},
{file = "watchdog-4.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:73c7a935e62033bd5e8f0da33a4dcb763da2361921a69a5a95aaf6c93aa03a87"},
{file = "watchdog-4.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:6a80d5cae8c265842c7419c560b9961561556c4361b297b4c431903f8c33b269"},
{file = "watchdog-4.0.0-py3-none-win32.whl", hash = "sha256:8f9a542c979df62098ae9c58b19e03ad3df1c9d8c6895d96c0d51da17b243b1c"},
{file = "watchdog-4.0.0-py3-none-win_amd64.whl", hash = "sha256:f970663fa4f7e80401a7b0cbeec00fa801bf0287d93d48368fc3e6fa32716245"},
{file = "watchdog-4.0.0-py3-none-win_ia64.whl", hash = "sha256:9a03e16e55465177d416699331b0f3564138f1807ecc5f2de9d55d8f188d08c7"},
{file = "watchdog-4.0.0.tar.gz", hash = "sha256:e3e7065cbdabe6183ab82199d7a4f6b3ba0a438c5a512a68559846ccb76a78ec"},
{file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:da2dfdaa8006eb6a71051795856bedd97e5b03e57da96f98e375682c48850645"},
{file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e93f451f2dfa433d97765ca2634628b789b49ba8b504fdde5837cdcf25fdb53b"},
{file = "watchdog-4.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ef0107bbb6a55f5be727cfc2ef945d5676b97bffb8425650dadbb184be9f9a2b"},
{file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:17e32f147d8bf9657e0922c0940bcde863b894cd871dbb694beb6704cfbd2fb5"},
{file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:03e70d2df2258fb6cb0e95bbdbe06c16e608af94a3ffbd2b90c3f1e83eb10767"},
{file = "watchdog-4.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:123587af84260c991dc5f62a6e7ef3d1c57dfddc99faacee508c71d287248459"},
{file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:093b23e6906a8b97051191a4a0c73a77ecc958121d42346274c6af6520dec175"},
{file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:611be3904f9843f0529c35a3ff3fd617449463cb4b73b1633950b3d97fa4bfb7"},
{file = "watchdog-4.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:62c613ad689ddcb11707f030e722fa929f322ef7e4f18f5335d2b73c61a85c28"},
{file = "watchdog-4.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d4925e4bf7b9bddd1c3de13c9b8a2cdb89a468f640e66fbfabaf735bd85b3e35"},
{file = "watchdog-4.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cad0bbd66cd59fc474b4a4376bc5ac3fc698723510cbb64091c2a793b18654db"},
{file = "watchdog-4.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a3c2c317a8fb53e5b3d25790553796105501a235343f5d2bf23bb8649c2c8709"},
{file = "watchdog-4.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c9904904b6564d4ee8a1ed820db76185a3c96e05560c776c79a6ce5ab71888ba"},
{file = "watchdog-4.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:667f3c579e813fcbad1b784db7a1aaa96524bed53437e119f6a2f5de4db04235"},
{file = "watchdog-4.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d10a681c9a1d5a77e75c48a3b8e1a9f2ae2928eda463e8d33660437705659682"},
{file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0144c0ea9997b92615af1d94afc0c217e07ce2c14912c7b1a5731776329fcfc7"},
{file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:998d2be6976a0ee3a81fb8e2777900c28641fb5bfbd0c84717d89bca0addcdc5"},
{file = "watchdog-4.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e7921319fe4430b11278d924ef66d4daa469fafb1da679a2e48c935fa27af193"},
{file = "watchdog-4.0.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:f0de0f284248ab40188f23380b03b59126d1479cd59940f2a34f8852db710625"},
{file = "watchdog-4.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bca36be5707e81b9e6ce3208d92d95540d4ca244c006b61511753583c81c70dd"},
{file = "watchdog-4.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ab998f567ebdf6b1da7dc1e5accfaa7c6992244629c0fdaef062f43249bd8dee"},
{file = "watchdog-4.0.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:dddba7ca1c807045323b6af4ff80f5ddc4d654c8bce8317dde1bd96b128ed253"},
{file = "watchdog-4.0.1-py3-none-manylinux2014_armv7l.whl", hash = "sha256:4513ec234c68b14d4161440e07f995f231be21a09329051e67a2118a7a612d2d"},
{file = "watchdog-4.0.1-py3-none-manylinux2014_i686.whl", hash = "sha256:4107ac5ab936a63952dea2a46a734a23230aa2f6f9db1291bf171dac3ebd53c6"},
{file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64.whl", hash = "sha256:6e8c70d2cd745daec2a08734d9f63092b793ad97612470a0ee4cbb8f5f705c57"},
{file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:f27279d060e2ab24c0aa98363ff906d2386aa6c4dc2f1a374655d4e02a6c5e5e"},
{file = "watchdog-4.0.1-py3-none-manylinux2014_s390x.whl", hash = "sha256:f8affdf3c0f0466e69f5b3917cdd042f89c8c63aebdb9f7c078996f607cdb0f5"},
{file = "watchdog-4.0.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ac7041b385f04c047fcc2951dc001671dee1b7e0615cde772e84b01fbf68ee84"},
{file = "watchdog-4.0.1-py3-none-win32.whl", hash = "sha256:206afc3d964f9a233e6ad34618ec60b9837d0582b500b63687e34011e15bb429"},
{file = "watchdog-4.0.1-py3-none-win_amd64.whl", hash = "sha256:7577b3c43e5909623149f76b099ac49a1a01ca4e167d1785c76eb52fa585745a"},
{file = "watchdog-4.0.1-py3-none-win_ia64.whl", hash = "sha256:d7b9f5f3299e8dd230880b6c55504a1f69cf1e4316275d1b215ebdd8187ec88d"},
{file = "watchdog-4.0.1.tar.gz", hash = "sha256:eebaacf674fa25511e8867028d281e602ee6500045b57f43b08778082f7f8b44"},
]
[package.extras]
@@ -2489,4 +2485,4 @@ multidict = ">=4.0"
[metadata]
lock-version = "2.0"
python-versions = "^3.11"
content-hash = "0ff8ecf16515c8921e1bef2e6be3a9b2d26d505d7d561f134e1b4a0458a90a47"
content-hash = "74e7f44309266b2588cfd1bea95274d0298f0615bbc012537d0918eb86d80200"

View File

@@ -21,7 +21,6 @@ PyYAML = "6.0.1"
Unidecode = "1.3.8"
emoji = "2.10.1"
python-dotenv = "1.0.1"
fastapi-pagination = "^0.12.19"
sqlakeyset = "^2.0.1708907391"
pydash = "^7.0.7"
mariadb = "1.1.10"