Compare commits

...

6 Commits

Author SHA1 Message Date
maxid
f5761bc661 run the container as the user configured if not starting as root 2026-02-04 18:50:52 +01:00
Maximilian Dorninger
c45c9e5873 add correlation id to logging (#398)
This PR adds Correlation IDs to logs and request responses.

```
2026-02-04 12:40:32,793 - [afd825081d874d6e835b5c59a6ddb371] DEBUG - media_manager.movies - get_importable_movies(): Found 5 importable movies.
2026-02-04 12:40:32,794 - [afd825081d874d6e835b5c59a6ddb371] INFO - uvicorn.access - send(): 172.19.0.1:64094 - "GET /api/v1/movies/importable HTTP/1.1" 200
2026-02-04 12:40:47,322 - [41d30b7003fd45288c6a4bb1cfba5e7a] INFO - uvicorn.access - send(): 127.0.0.1:52964 - "GET /api/v1/health HTTP/1.1" 200
2026-02-04 12:41:17,408 - [157027ea5dde472a9e620f53739ccd53] INFO - uvicorn.access - send(): 127.0.0.1:39850 - "GET /api/v1/health HTTP/1.1" 200
```
2026-02-04 13:55:05 +01:00
Sergey Khruschak
24fcba6bee Torrent file name sanitizing (#390)
Hi, I've added file names sanitization when saving the torrent file, as
previously the import was failing on torrents with special characters in
names. This fixes #367
2026-02-03 17:09:36 +01:00
Maximilian Dorninger
d5994a9037 Fix docker permission issues (#395)
This PR fixes docker permission issues by first starting as root and
then chown-ing all the volumes. This should fix #388 #389
2026-02-03 13:06:18 +01:00
just_Bri
9e0d0c03c0 feat: add links to media detail pages in requests and torrent tables (#352)
Feature Request: https://github.com/maxdorninger/MediaManager/issues/351

[feat: add links to media detail pages in requests and torrent
tables](ac376c0d6d)
2026-02-02 22:48:14 +01:00
Maximilian Dorninger
70ff8f6ace Fix the broken link to the disable ascii art page (#396)
Fix the broken link to the disable ascii art page
2026-02-02 22:22:11 +01:00
16 changed files with 208 additions and 50 deletions

View File

@@ -13,7 +13,7 @@ RUN env PUBLIC_VERSION=${VERSION} PUBLIC_API_URL=${BASE_PATH} BASE_PATH=${BASE_P
FROM ghcr.io/astral-sh/uv:python3.13-trixie-slim AS base FROM ghcr.io/astral-sh/uv:python3.13-trixie-slim AS base
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y ca-certificates bash libtorrent21 gcc bc locales postgresql media-types mailcap curl gzip unzip tar 7zip bzip2 unar && \ apt-get install -y ca-certificates bash libtorrent21 gcc bc locales postgresql media-types mailcap curl gzip unzip tar 7zip bzip2 unar gosu && \
apt-get clean && \ apt-get clean && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
@@ -33,7 +33,6 @@ RUN chown -R mediamanager:mediamanager /app
USER mediamanager USER mediamanager
# Set uv cache to a writable home directory and use copy mode for volume compatibility
ENV UV_CACHE_DIR=/home/mediamanager/.cache/uv \ ENV UV_CACHE_DIR=/home/mediamanager/.cache/uv \
UV_LINK_MODE=copy UV_LINK_MODE=copy
@@ -47,6 +46,7 @@ ARG BASE_PATH=""
LABEL author="github.com/maxdorninger" LABEL author="github.com/maxdorninger"
LABEL version=${VERSION} LABEL version=${VERSION}
LABEL description="Docker image for MediaManager" LABEL description="Docker image for MediaManager"
USER root
ENV PUBLIC_VERSION=${VERSION} \ ENV PUBLIC_VERSION=${VERSION} \
CONFIG_DIR="/app/config" \ CONFIG_DIR="/app/config" \

View File

@@ -22,7 +22,7 @@
* [Metadata Provider Configuration](advanced-features/metadata-provider-configuration.md) * [Metadata Provider Configuration](advanced-features/metadata-provider-configuration.md)
* [Custom port](advanced-features/custom-port.md) * [Custom port](advanced-features/custom-port.md)
* [Follow symlinks in frontend files](advanced-features/follow-symlinks-in-frontend-files.md) * [Follow symlinks in frontend files](advanced-features/follow-symlinks-in-frontend-files.md)
* [Disable startup ascii art](advanced-featured/disable-startup-ascii-art.md) * [Disable startup ascii art](advanced-features/disable-startup-ascii-art.md)
* [Troubleshooting](troubleshooting.md) * [Troubleshooting](troubleshooting.md)
* [API Reference](api-reference.md) * [API Reference](api-reference.md)
* [Screenshots](screenshots.md) * [Screenshots](screenshots.md)

View File

@@ -21,13 +21,20 @@ LOG_FILE = Path(os.getenv("LOG_FILE", "/app/config/media_manager.log"))
LOGGING_CONFIG = { LOGGING_CONFIG = {
"version": 1, "version": 1,
"disable_existing_loggers": False, "disable_existing_loggers": False,
"filters": {
"correlation_id": {
"()": "asgi_correlation_id.CorrelationIdFilter",
"uuid_length": 32,
"default_value": "-",
},
},
"formatters": { "formatters": {
"default": { "default": {
"format": "%(asctime)s - %(levelname)s - %(name)s - %(funcName)s(): %(message)s" "format": "%(asctime)s - [%(correlation_id)s] %(levelname)s - %(name)s - %(funcName)s(): %(message)s"
}, },
"json": { "json": {
"()": ISOJsonFormatter, "()": ISOJsonFormatter,
"format": "%(asctime)s %(levelname)s %(name)s %(message)s", "format": "%(asctime)s %(correlation_id)s %(levelname)s %(name)s %(message)s",
"rename_fields": { "rename_fields": {
"levelname": "level", "levelname": "level",
"asctime": "timestamp", "asctime": "timestamp",
@@ -39,11 +46,13 @@ LOGGING_CONFIG = {
"console": { "console": {
"class": "logging.StreamHandler", "class": "logging.StreamHandler",
"formatter": "default", "formatter": "default",
"filters": ["correlation_id"],
"stream": sys.stdout, "stream": sys.stdout,
}, },
"file": { "file": {
"class": "logging.handlers.RotatingFileHandler", "class": "logging.handlers.RotatingFileHandler",
"formatter": "json", "formatter": "json",
"filters": ["correlation_id"],
"filename": str(LOG_FILE), "filename": str(LOG_FILE),
"maxBytes": 10485760, "maxBytes": 10485760,
"backupCount": 5, "backupCount": 5,

View File

@@ -2,6 +2,7 @@ import logging
import os import os
import uvicorn import uvicorn
from asgi_correlation_id import CorrelationIdMiddleware
from fastapi import APIRouter, FastAPI, Request, Response from fastapi import APIRouter, FastAPI, Request, Response
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
@@ -71,6 +72,7 @@ app.add_middleware(
allow_credentials=True, allow_credentials=True,
allow_methods=["GET", "PUT", "POST", "DELETE", "PATCH", "HEAD", "OPTIONS"], allow_methods=["GET", "PUT", "POST", "DELETE", "PATCH", "HEAD", "OPTIONS"],
) )
app.add_middleware(CorrelationIdMiddleware, header_name="X-Correlation-ID")
api_app = APIRouter(prefix="/api/v1") api_app = APIRouter(prefix="/api/v1")

View File

@@ -9,6 +9,7 @@ import bencoder
import libtorrent import libtorrent
import patoolib import patoolib
import requests import requests
from pathvalidate import sanitize_filename
from requests.exceptions import InvalidSchema from requests.exceptions import InvalidSchema
from media_manager.config import MediaManagerConfig from media_manager.config import MediaManagerConfig
@@ -132,7 +133,7 @@ def get_torrent_hash(torrent: IndexerQueryResult) -> str:
:return: The hash of the torrent. :return: The hash of the torrent.
""" """
torrent_filepath = ( torrent_filepath = (
MediaManagerConfig().misc.torrent_directory / f"{torrent.title}.torrent" MediaManagerConfig().misc.torrent_directory / f"{sanitize_filename(torrent.title)}.torrent"
) )
if torrent_filepath.exists(): if torrent_filepath.exists():
log.warning(f"Torrent file already exists at: {torrent_filepath}") log.warning(f"Torrent file already exists at: {torrent_filepath}")

View File

@@ -145,8 +145,30 @@ else
echo "Config file found at: $CONFIG_FILE" echo "Config file found at: $CONFIG_FILE"
fi fi
# check if running as root, if yes, fix permissions
if [ "$(id -u)" = '0' ]; then
echo "Running as root. Ensuring file permissions for mediamanager user..."
chown -R mediamanager:mediamanager "$CONFIG_DIR"
if [ -d "/data" ]; then
if [ "$(stat -c '%U' /data)" != "mediamanager" ]; then
echo "Fixing ownership of /data (this may take a while for large media libraries)..."
chown -R mediamanager:mediamanager /data
else
echo "/data ownership is already correct."
fi
fi
else
echo "Running as non-root user ($(id -u)). Skipping permission fixes."
echo "Note: Ensure your host volumes are manually set to the correct permissions."
fi
echo "Running DB migrations..." echo "Running DB migrations..."
uv run alembic upgrade head if [ "$(id -u)" = '0' ]; then
gosu mediamanager uv run alembic upgrade head
else
uv run alembic upgrade head
fi
echo "Starting MediaManager backend service..." echo "Starting MediaManager backend service..."
echo "" echo ""
@@ -159,9 +181,16 @@ echo ""
DEVELOPMENT_MODE=${MEDIAMANAGER_MISC__DEVELOPMENT:-FALSE} DEVELOPMENT_MODE=${MEDIAMANAGER_MISC__DEVELOPMENT:-FALSE}
PORT=${PORT:-8000} PORT=${PORT:-8000}
if [ "$DEVELOPMENT_MODE" == "TRUE" ]; then if [ "$DEVELOPMENT_MODE" == "TRUE" ]; then
echo "Development mode is enabled, enabling auto-reload..." echo "Development mode is enabled, enabling auto-reload..."
uv run fastapi run /app/media_manager/main.py --port "$PORT" --proxy-headers --reload DEV_OPTIONS="--reload"
else else
uv run fastapi run /app/media_manager/main.py --port "$PORT" --proxy-headers DEV_OPTIONS=""
fi
if [ "$(id -u)" = '0' ]; then
exec gosu mediamanager uv run fastapi run /app/media_manager/main.py --port "$PORT" --proxy-headers $DEV_OPTIONS
else
exec uv run fastapi run /app/media_manager/main.py --port "$PORT" --proxy-headers $DEV_OPTIONS
fi fi

View File

@@ -8,23 +8,25 @@ RUN apt-get update && apt-get install -y ca-certificates && \
apt-get clean && \ apt-get clean && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
# Create a non-root user and group
RUN groupadd -g 1000 mediamanager && \ RUN groupadd -g 1000 mediamanager && \
useradd -m -u 1000 -g mediamanager mediamanager useradd -m -u 1000 -g mediamanager mediamanager
WORKDIR /app WORKDIR /app
# Ensure mediamanager owns the app directory
RUN chown -R mediamanager:mediamanager /app RUN chown -R mediamanager:mediamanager /app
USER mediamanager
# Set uv cache to a writable home directory and use copy mode for volume compatibility
ENV UV_CACHE_DIR=/home/mediamanager/.cache/uv \ ENV UV_CACHE_DIR=/home/mediamanager/.cache/uv \
UV_LINK_MODE=copy UV_LINK_MODE=copy \
UV_COMPILE_BYTECODE=1
COPY --chown=mediamanager:mediamanager pyproject.toml uv.lock ./
USER mediamanager
RUN --mount=type=cache,target=/home/mediamanager/.cache/uv,uid=1000,gid=1000 \
uv sync --frozen --no-install-project --no-dev
COPY --chown=mediamanager:mediamanager . . COPY --chown=mediamanager:mediamanager . .
RUN --mount=type=cache,target=/home/mediamanager/.cache/uv,uid=1000,gid=1000 \
uv sync --locked RUN uv sync --frozen --no-dev
EXPOSE 8000 EXPOSE 8000
CMD ["uv", "run", "fastapi", "run", "/app/main.py"] CMD ["uv", "run", "fastapi", "run", "/app/main.py", "--port", "8000", "--proxy-headers"]

View File

@@ -33,6 +33,8 @@ dependencies = [
"sabnzbd-api>=0.1.2", "sabnzbd-api>=0.1.2",
"transmission-rpc>=7.0.11", "transmission-rpc>=7.0.11",
"libtorrent>=2.0.11", "libtorrent>=2.0.11",
"pathvalidate>=3.3.1",
"asgi-correlation-id>=4.3.4",
] ]
[dependency-groups] [dependency-groups]

26
uv.lock generated
View File

@@ -105,6 +105,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/42/b9/f8d6fa329ab25128b7e98fd83a3cb34d9db5b059a9847eddb840a0af45dd/argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94", size = 27149, upload-time = "2025-07-30T10:01:59.329Z" }, { url = "https://files.pythonhosted.org/packages/42/b9/f8d6fa329ab25128b7e98fd83a3cb34d9db5b059a9847eddb840a0af45dd/argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94", size = 27149, upload-time = "2025-07-30T10:01:59.329Z" },
] ]
[[package]]
name = "asgi-correlation-id"
version = "4.3.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "packaging" },
{ name = "starlette" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f4/ff/a6538245ac1eaa7733ec6740774e9d5add019e2c63caa29e758c16c0afdd/asgi_correlation_id-4.3.4.tar.gz", hash = "sha256:ea6bc310380373cb9f731dc2e8b2b6fb978a76afe33f7a2384f697b8d6cd811d", size = 20075, upload-time = "2024-10-17T11:44:30.324Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d9/ab/6936e2663c47a926e0659437b9333ad87d1ff49b1375d239026e0a268eba/asgi_correlation_id-4.3.4-py3-none-any.whl", hash = "sha256:36ce69b06c7d96b4acb89c7556a4c4f01a972463d3d49c675026cbbd08e9a0a2", size = 15262, upload-time = "2024-10-17T11:44:28.739Z" },
]
[[package]] [[package]]
name = "attrs" name = "attrs"
version = "25.4.0" version = "25.4.0"
@@ -854,6 +867,7 @@ source = { virtual = "." }
dependencies = [ dependencies = [
{ name = "alembic" }, { name = "alembic" },
{ name = "apscheduler" }, { name = "apscheduler" },
{ name = "asgi-correlation-id" },
{ name = "bencoder" }, { name = "bencoder" },
{ name = "cachetools" }, { name = "cachetools" },
{ name = "fastapi", extra = ["standard"] }, { name = "fastapi", extra = ["standard"] },
@@ -864,6 +878,7 @@ dependencies = [
{ name = "httpx-oauth" }, { name = "httpx-oauth" },
{ name = "jsonschema" }, { name = "jsonschema" },
{ name = "libtorrent" }, { name = "libtorrent" },
{ name = "pathvalidate" },
{ name = "patool" }, { name = "patool" },
{ name = "pillow" }, { name = "pillow" },
{ name = "psycopg", extra = ["binary"] }, { name = "psycopg", extra = ["binary"] },
@@ -893,6 +908,7 @@ dev = [
requires-dist = [ requires-dist = [
{ name = "alembic", specifier = ">=1.16.1" }, { name = "alembic", specifier = ">=1.16.1" },
{ name = "apscheduler", specifier = ">=3.11.0" }, { name = "apscheduler", specifier = ">=3.11.0" },
{ name = "asgi-correlation-id", specifier = ">=4.3.4" },
{ name = "bencoder", specifier = ">=0.2.0" }, { name = "bencoder", specifier = ">=0.2.0" },
{ name = "cachetools", specifier = ">=6.0.0" }, { name = "cachetools", specifier = ">=6.0.0" },
{ name = "fastapi", extras = ["standard"], specifier = ">=0.115.12" }, { name = "fastapi", extras = ["standard"], specifier = ">=0.115.12" },
@@ -903,6 +919,7 @@ requires-dist = [
{ name = "httpx-oauth", specifier = ">=0.16.1" }, { name = "httpx-oauth", specifier = ">=0.16.1" },
{ name = "jsonschema", specifier = ">=4.24.0" }, { name = "jsonschema", specifier = ">=4.24.0" },
{ name = "libtorrent", specifier = ">=2.0.11" }, { name = "libtorrent", specifier = ">=2.0.11" },
{ name = "pathvalidate", specifier = ">=3.3.1" },
{ name = "patool", specifier = ">=4.0.1" }, { name = "patool", specifier = ">=4.0.1" },
{ name = "pillow", specifier = ">=11.3.0" }, { name = "pillow", specifier = ">=11.3.0" },
{ name = "psycopg", extras = ["binary"], specifier = ">=3.2.9" }, { name = "psycopg", extras = ["binary"], specifier = ">=3.2.9" },
@@ -946,6 +963,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
] ]
[[package]]
name = "pathvalidate"
version = "3.3.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/fa/2a/52a8da6fe965dea6192eb716b357558e103aea0a1e9a8352ad575a8406ca/pathvalidate-3.3.1.tar.gz", hash = "sha256:b18c07212bfead624345bb8e1d6141cdcf15a39736994ea0b94035ad2b1ba177", size = 63262, upload-time = "2025-06-15T09:07:20.736Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9a/70/875f4a23bfc4731703a5835487d0d2fb999031bd415e7d17c0ae615c18b7/pathvalidate-3.3.1-py3-none-any.whl", hash = "sha256:5263baab691f8e1af96092fa5137ee17df5bdfbd6cff1fcac4d6ef4bc2e1735f", size = 24305, upload-time = "2025-06-15T09:07:19.117Z" },
]
[[package]] [[package]]
name = "patool" name = "patool"
version = "4.0.3" version = "4.0.3"

92
web/package-lock.json generated
View File

@@ -146,6 +146,7 @@
"os": [ "os": [
"aix" "aix"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
@@ -163,6 +164,7 @@
"os": [ "os": [
"android" "android"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
@@ -180,6 +182,7 @@
"os": [ "os": [
"android" "android"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
@@ -197,6 +200,7 @@
"os": [ "os": [
"android" "android"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
@@ -214,6 +218,7 @@
"os": [ "os": [
"darwin" "darwin"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
@@ -231,6 +236,7 @@
"os": [ "os": [
"darwin" "darwin"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
@@ -248,6 +254,7 @@
"os": [ "os": [
"freebsd" "freebsd"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
@@ -265,6 +272,7 @@
"os": [ "os": [
"freebsd" "freebsd"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
@@ -282,6 +290,7 @@
"os": [ "os": [
"linux" "linux"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
@@ -299,6 +308,7 @@
"os": [ "os": [
"linux" "linux"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
@@ -316,6 +326,7 @@
"os": [ "os": [
"linux" "linux"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
@@ -333,6 +344,7 @@
"os": [ "os": [
"linux" "linux"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
@@ -350,6 +362,7 @@
"os": [ "os": [
"linux" "linux"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
@@ -367,6 +380,7 @@
"os": [ "os": [
"linux" "linux"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
@@ -384,6 +398,7 @@
"os": [ "os": [
"linux" "linux"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
@@ -401,6 +416,7 @@
"os": [ "os": [
"linux" "linux"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
@@ -418,6 +434,7 @@
"os": [ "os": [
"linux" "linux"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
@@ -435,6 +452,7 @@
"os": [ "os": [
"netbsd" "netbsd"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
@@ -452,6 +470,7 @@
"os": [ "os": [
"netbsd" "netbsd"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
@@ -469,6 +488,7 @@
"os": [ "os": [
"openbsd" "openbsd"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
@@ -486,6 +506,7 @@
"os": [ "os": [
"openbsd" "openbsd"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
@@ -503,6 +524,7 @@
"os": [ "os": [
"openharmony" "openharmony"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
@@ -520,6 +542,7 @@
"os": [ "os": [
"sunos" "sunos"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
@@ -537,6 +560,7 @@
"os": [ "os": [
"win32" "win32"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
@@ -554,6 +578,7 @@
"os": [ "os": [
"win32" "win32"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
@@ -571,6 +596,7 @@
"os": [ "os": [
"win32" "win32"
], ],
"peer": true,
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
@@ -1974,7 +2000,6 @@
"integrity": "sha512-Vp3zX/qlwerQmHMP6x0Ry1oY7eKKRcOWGc2P59srOp4zcqyn+etJyQpELgOi4+ZSUgteX8Y387NuwruLgGXLUQ==", "integrity": "sha512-Vp3zX/qlwerQmHMP6x0Ry1oY7eKKRcOWGc2P59srOp4zcqyn+etJyQpELgOi4+ZSUgteX8Y387NuwruLgGXLUQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@standard-schema/spec": "^1.0.0", "@standard-schema/spec": "^1.0.0",
"@sveltejs/acorn-typescript": "^1.0.5", "@sveltejs/acorn-typescript": "^1.0.5",
@@ -2014,7 +2039,6 @@
"integrity": "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==", "integrity": "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0",
"debug": "^4.4.1", "debug": "^4.4.1",
@@ -2485,7 +2509,6 @@
"integrity": "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==", "integrity": "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.49.0", "@typescript-eslint/scope-manager": "8.49.0",
"@typescript-eslint/types": "8.49.0", "@typescript-eslint/types": "8.49.0",
@@ -2732,7 +2755,6 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
@@ -2973,7 +2995,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"baseline-browser-mapping": "^2.9.0", "baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759", "caniuse-lite": "^1.0.30001759",
@@ -3086,7 +3107,6 @@
"integrity": "sha512-rXXekcjofVN1LTOSw+u4u9WXVEUvNBVjORW154q/IdmYWy1nMbOU9aNtZB0t8m+FJQ9q91jlr2f9CwwUFdFMRA==", "integrity": "sha512-rXXekcjofVN1LTOSw+u4u9WXVEUvNBVjORW154q/IdmYWy1nMbOU9aNtZB0t8m+FJQ9q91jlr2f9CwwUFdFMRA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@types/validator": "^13.15.3", "@types/validator": "^13.15.3",
"libphonenumber-js": "^1.11.1", "libphonenumber-js": "^1.11.1",
@@ -3270,8 +3290,7 @@
"resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz", "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz",
"integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==", "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT"
"peer": true
}, },
"node_modules/embla-carousel-reactive-utils": { "node_modules/embla-carousel-reactive-utils": {
"version": "8.6.0", "version": "8.6.0",
@@ -3311,6 +3330,50 @@
"node": ">=10.13.0" "node": ">=10.13.0"
} }
}, },
"node_modules/esbuild": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz",
"integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"peer": true,
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.27.2",
"@esbuild/android-arm": "0.27.2",
"@esbuild/android-arm64": "0.27.2",
"@esbuild/android-x64": "0.27.2",
"@esbuild/darwin-arm64": "0.27.2",
"@esbuild/darwin-x64": "0.27.2",
"@esbuild/freebsd-arm64": "0.27.2",
"@esbuild/freebsd-x64": "0.27.2",
"@esbuild/linux-arm": "0.27.2",
"@esbuild/linux-arm64": "0.27.2",
"@esbuild/linux-ia32": "0.27.2",
"@esbuild/linux-loong64": "0.27.2",
"@esbuild/linux-mips64el": "0.27.2",
"@esbuild/linux-ppc64": "0.27.2",
"@esbuild/linux-riscv64": "0.27.2",
"@esbuild/linux-s390x": "0.27.2",
"@esbuild/linux-x64": "0.27.2",
"@esbuild/netbsd-arm64": "0.27.2",
"@esbuild/netbsd-x64": "0.27.2",
"@esbuild/openbsd-arm64": "0.27.2",
"@esbuild/openbsd-x64": "0.27.2",
"@esbuild/openharmony-arm64": "0.27.2",
"@esbuild/sunos-x64": "0.27.2",
"@esbuild/win32-arm64": "0.27.2",
"@esbuild/win32-ia32": "0.27.2",
"@esbuild/win32-x64": "0.27.2"
}
},
"node_modules/esbuild-runner": { "node_modules/esbuild-runner": {
"version": "2.2.2", "version": "2.2.2",
"resolved": "https://registry.npmjs.org/esbuild-runner/-/esbuild-runner-2.2.2.tgz", "resolved": "https://registry.npmjs.org/esbuild-runner/-/esbuild-runner-2.2.2.tgz",
@@ -3366,7 +3429,6 @@
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1", "@eslint-community/regexpp": "^4.12.1",
@@ -4815,7 +4877,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"nanoid": "^3.3.11", "nanoid": "^3.3.11",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
@@ -4956,7 +5017,6 @@
"integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"prettier": "bin/prettier.cjs" "prettier": "bin/prettier.cjs"
}, },
@@ -4973,7 +5033,6 @@
"integrity": "sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ==", "integrity": "sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"peerDependencies": { "peerDependencies": {
"prettier": "^3.0.0", "prettier": "^3.0.0",
"svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0"
@@ -5133,7 +5192,6 @@
"integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@types/estree": "1.0.8" "@types/estree": "1.0.8"
}, },
@@ -5386,7 +5444,6 @@
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.45.8.tgz", "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.45.8.tgz",
"integrity": "sha512-1Jh7FwVh/2Uxg0T7SeE1qFKMhwYH45b2v53bcZpW7qHa6O8iU1ByEj56PF0IQ6dU4HE5gRkic6h+vx+tclHeiw==", "integrity": "sha512-1Jh7FwVh/2Uxg0T7SeE1qFKMhwYH45b2v53bcZpW7qHa6O8iU1ByEj56PF0IQ6dU4HE5gRkic6h+vx+tclHeiw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@jridgewell/remapping": "^2.3.4", "@jridgewell/remapping": "^2.3.4",
"@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/sourcemap-codec": "^1.5.0",
@@ -5560,7 +5617,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"devalue": "^5.3.2", "devalue": "^5.3.2",
"memoize-weak": "^1.0.2", "memoize-weak": "^1.0.2",
@@ -5767,8 +5823,7 @@
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz",
"integrity": "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==", "integrity": "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT"
"peer": true
}, },
"node_modules/tapable": { "node_modules/tapable": {
"version": "2.3.0", "version": "2.3.0",
@@ -5913,7 +5968,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
@@ -6056,7 +6110,6 @@
"integrity": "sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==", "integrity": "sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"esbuild": "^0.25.0", "esbuild": "^0.25.0",
"fdir": "^6.5.0", "fdir": "^6.5.0",
@@ -6739,7 +6792,6 @@
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"funding": { "funding": {
"url": "https://github.com/sponsors/colinhacks" "url": "https://github.com/sponsors/colinhacks"
} }

View File

@@ -127,13 +127,27 @@
<Table.Row> <Table.Row>
<Table.Cell> <Table.Cell>
{#if isShow} {#if isShow}
<a
href={resolve('/dashboard/tv/[showId]', {
showId: (request as components['schemas']['RichSeasonRequest']).show.id!
})}
class="text-primary hover:underline"
>
{getFullyQualifiedMediaName( {getFullyQualifiedMediaName(
(request as components['schemas']['RichSeasonRequest']).show (request as components['schemas']['RichSeasonRequest']).show
)} )}
</a>
{:else} {:else}
<a
href={resolve('/dashboard/movies/[movieId]', {
movieId: (request as components['schemas']['RichMovieRequest']).movie.id!
})}
class="text-primary hover:underline"
>
{getFullyQualifiedMediaName( {getFullyQualifiedMediaName(
(request as components['schemas']['RichMovieRequest']).movie (request as components['schemas']['RichMovieRequest']).movie
)} )}
</a>
{/if} {/if}
</Table.Cell> </Table.Cell>
{#if isShow} {#if isShow}

View File

@@ -14,14 +14,19 @@
import DeleteTorrentDialog from '$lib/components/torrents/delete-torrent-dialog.svelte'; import DeleteTorrentDialog from '$lib/components/torrents/delete-torrent-dialog.svelte';
import EditTorrentDialog from '$lib/components/torrents/edit-torrent-dialog.svelte'; import EditTorrentDialog from '$lib/components/torrents/edit-torrent-dialog.svelte';
import { invalidateAll } from '$app/navigation'; import { invalidateAll } from '$app/navigation';
import { resolve } from '$app/paths';
let { let {
torrents, torrents,
isShow = true isShow = true,
showId,
movieId
}: { }: {
torrents: torrents:
| components['schemas']['MovieTorrent'][] | components['schemas']['MovieTorrent'][]
| components['schemas']['RichSeasonTorrent'][]; | components['schemas']['RichSeasonTorrent'][];
isShow: boolean; isShow: boolean;
showId?: string;
movieId?: string;
} = $props(); } = $props();
let user: () => components['schemas']['UserRead'] = getContext('user'); let user: () => components['schemas']['UserRead'] = getContext('user');
@@ -68,7 +73,23 @@
{#each torrents as torrent (torrent.torrent_id)} {#each torrents as torrent (torrent.torrent_id)}
<Table.Row> <Table.Row>
<Table.Cell class="font-medium"> <Table.Cell class="font-medium">
{#if isShow && showId}
<a
href={resolve('/dashboard/tv/[showId]', { showId })}
class="text-primary hover:underline"
>
{torrent.torrent_title} {torrent.torrent_title}
</a>
{:else if !isShow && movieId}
<a
href={resolve('/dashboard/movies/[movieId]', { movieId })}
class="text-primary hover:underline"
>
{torrent.torrent_title}
</a>
{:else}
{torrent.torrent_title}
{/if}
</Table.Cell> </Table.Cell>
{#if isShow} {#if isShow}
<Table.Cell> <Table.Cell>

View File

@@ -159,7 +159,7 @@
<Card.Description>A list of all torrents associated with this movie.</Card.Description> <Card.Description>A list of all torrents associated with this movie.</Card.Description>
</Card.Header> </Card.Header>
<Card.Content class="flex flex-col gap-4"> <Card.Content class="flex flex-col gap-4">
<TorrentTable isShow={false} torrents={movie.torrents} /> <TorrentTable isShow={false} torrents={movie.torrents} movieId={movie.id} />
</Card.Content> </Card.Content>
</Card.Root> </Card.Root>
</div> </div>

View File

@@ -55,7 +55,7 @@
</Card.Title> </Card.Title>
</Card.Header> </Card.Header>
<Card.Content> <Card.Content>
<TorrentTable isShow={false} torrents={movie.torrents} /> <TorrentTable isShow={false} torrents={movie.torrents} movieId={movie.movie_id} />
</Card.Content> </Card.Content>
</Card.Root> </Card.Root>
</div> </div>

View File

@@ -210,7 +210,7 @@
</Card.Header> </Card.Header>
<Card.Content class="w-full overflow-x-auto"> <Card.Content class="w-full overflow-x-auto">
<TorrentTable isShow={true} torrents={torrents.torrents} /> <TorrentTable isShow={true} torrents={torrents.torrents} showId={show.id} />
</Card.Content> </Card.Content>
</Card.Root> </Card.Root>
</div> </div>

View File

@@ -55,7 +55,7 @@
</Card.Title> </Card.Title>
</Card.Header> </Card.Header>
<Card.Content> <Card.Content>
<TorrentTable isShow={true} torrents={show.torrents} /> <TorrentTable isShow={true} torrents={show.torrents} showId={show.show_id} />
</Card.Content> </Card.Content>
</Card.Root> </Card.Root>
</div> </div>