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
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 && \
rm -rf /var/lib/apt/lists/*
@@ -33,7 +33,6 @@ 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 \
UV_LINK_MODE=copy
@@ -47,6 +46,7 @@ ARG BASE_PATH=""
LABEL author="github.com/maxdorninger"
LABEL version=${VERSION}
LABEL description="Docker image for MediaManager"
USER root
ENV PUBLIC_VERSION=${VERSION} \
CONFIG_DIR="/app/config" \

View File

@@ -22,7 +22,7 @@
* [Metadata Provider Configuration](advanced-features/metadata-provider-configuration.md)
* [Custom port](advanced-features/custom-port.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)
* [API Reference](api-reference.md)
* [Screenshots](screenshots.md)

View File

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

View File

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

View File

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

View File

@@ -145,8 +145,30 @@ else
echo "Config file found at: $CONFIG_FILE"
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..."
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 ""
@@ -159,9 +181,16 @@ echo ""
DEVELOPMENT_MODE=${MEDIAMANAGER_MISC__DEVELOPMENT:-FALSE}
PORT=${PORT:-8000}
if [ "$DEVELOPMENT_MODE" == "TRUE" ]; then
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
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

View File

@@ -8,23 +8,25 @@ RUN apt-get update && apt-get install -y ca-certificates && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# Create a non-root user and group
RUN groupadd -g 1000 mediamanager && \
useradd -m -u 1000 -g mediamanager mediamanager
WORKDIR /app
# Ensure mediamanager owns the app directory
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 \
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 . .
RUN --mount=type=cache,target=/home/mediamanager/.cache/uv,uid=1000,gid=1000 \
uv sync --locked
RUN uv sync --frozen --no-dev
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",
"transmission-rpc>=7.0.11",
"libtorrent>=2.0.11",
"pathvalidate>=3.3.1",
"asgi-correlation-id>=4.3.4",
]
[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" },
]
[[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]]
name = "attrs"
version = "25.4.0"
@@ -854,6 +867,7 @@ source = { virtual = "." }
dependencies = [
{ name = "alembic" },
{ name = "apscheduler" },
{ name = "asgi-correlation-id" },
{ name = "bencoder" },
{ name = "cachetools" },
{ name = "fastapi", extra = ["standard"] },
@@ -864,6 +878,7 @@ dependencies = [
{ name = "httpx-oauth" },
{ name = "jsonschema" },
{ name = "libtorrent" },
{ name = "pathvalidate" },
{ name = "patool" },
{ name = "pillow" },
{ name = "psycopg", extra = ["binary"] },
@@ -893,6 +908,7 @@ dev = [
requires-dist = [
{ name = "alembic", specifier = ">=1.16.1" },
{ name = "apscheduler", specifier = ">=3.11.0" },
{ name = "asgi-correlation-id", specifier = ">=4.3.4" },
{ name = "bencoder", specifier = ">=0.2.0" },
{ name = "cachetools", specifier = ">=6.0.0" },
{ name = "fastapi", extras = ["standard"], specifier = ">=0.115.12" },
@@ -903,6 +919,7 @@ requires-dist = [
{ name = "httpx-oauth", specifier = ">=0.16.1" },
{ name = "jsonschema", specifier = ">=4.24.0" },
{ name = "libtorrent", specifier = ">=2.0.11" },
{ name = "pathvalidate", specifier = ">=3.3.1" },
{ name = "patool", specifier = ">=4.0.1" },
{ name = "pillow", specifier = ">=11.3.0" },
{ 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" },
]
[[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]]
name = "patool"
version = "4.0.3"

92
web/package-lock.json generated
View File

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

View File

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

View File

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

View File

@@ -159,7 +159,7 @@
<Card.Description>A list of all torrents associated with this movie.</Card.Description>
</Card.Header>
<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.Root>
</div>

View File

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

View File

@@ -210,7 +210,7 @@
</Card.Header>
<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.Root>
</div>

View File

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