From 51b10438a932e3a5c2229b1bb59ac3b658c13ccf Mon Sep 17 00:00:00 2001 From: Alex Blank Date: Thu, 30 Apr 2026 21:48:46 +0200 Subject: [PATCH] Restructure into backend/ and frontend/ subprojects - backend/ uses proper Python src layout (src/media_library_viewer_api/) with pyproject.toml, hatchling build, and PYTHONPATH=src convention - frontend/ is a Vite + React + TypeScript SPA - archive/ preserves the original Streamlit prototype for reference - Cleaned up root to only contain docs, license, and subproject dirs - Updated README for the new dual-subproject architecture --- README.md | 146 ++++++------------ app.py => archive/app.py | 0 pyproject.toml => archive/pyproject.toml | 0 requirements.txt => archive/requirements.txt | 0 .../src}/media_library_viewer/__init__.py | 0 .../src}/media_library_viewer/app.py | 0 .../media_library_viewer/clients/__init__.py | 0 .../media_library_viewer}/clients/jellyfin.py | 0 .../media_library_viewer/clients/resources.py | 0 .../src/media_library_viewer}/clients/ssh.py | 0 .../src}/media_library_viewer/config.py | 0 .../media_library_viewer/domain/__init__.py | 0 .../src}/media_library_viewer/domain/media.py | 0 .../src}/media_library_viewer/jobs.py | 0 .../media_library_viewer/services/__init__.py | 0 .../services/media_index.py | 0 .../src}/media_library_viewer/ui/__init__.py | 0 .../src}/media_library_viewer/ui/dashboard.py | 0 .../media_library_viewer/ui/file_browser.py | 0 .../src}/media_library_viewer/ui/media.py | 0 .../src}/media_library_viewer/ui/preview.py | 0 .../src/media_library_viewer}/utils.py | 0 {tests => archive/tests}/.gitkeep | 0 backend/README.md | 49 +++++- backend/pyproject.toml | 12 ++ .../media_library_viewer_api}/__init__.py | 0 .../clients/__init__.py | 0 .../clients/jellyfin.py | 0 .../clients/resources.py | 2 +- .../media_library_viewer_api}/clients/ssh.py | 0 .../media_library_viewer_api}/config.py | 0 .../media_library_viewer_api}/dependencies.py | 6 +- .../domain/__init__.py | 0 .../media_library_viewer_api}/domain/media.py | 2 +- .../media_library_viewer_api}/jobs.py | 2 +- .../media_library_viewer_api}/main.py | 2 +- .../media_library_viewer_api}/path_utils.py | 0 .../routers/__init__.py | 0 .../routers/dashboard.py | 4 +- .../routers/files.py | 8 +- .../media_library_viewer_api}/routers/jobs.py | 6 +- .../routers/media.py | 6 +- .../routers/monitoring.py | 8 +- .../services/__init__.py | 0 .../services/media_index.py | 4 +- .../src/media_library_viewer_api}/utils.py | 0 backend/tests/__init__.py | 0 47 files changed, 127 insertions(+), 130 deletions(-) rename app.py => archive/app.py (100%) rename pyproject.toml => archive/pyproject.toml (100%) rename requirements.txt => archive/requirements.txt (100%) rename {src => archive/src}/media_library_viewer/__init__.py (100%) rename {src => archive/src}/media_library_viewer/app.py (100%) rename {src => archive/src}/media_library_viewer/clients/__init__.py (100%) rename {backend => archive/src/media_library_viewer}/clients/jellyfin.py (100%) rename {src => archive/src}/media_library_viewer/clients/resources.py (100%) rename {backend => archive/src/media_library_viewer}/clients/ssh.py (100%) rename {src => archive/src}/media_library_viewer/config.py (100%) rename {src => archive/src}/media_library_viewer/domain/__init__.py (100%) rename {src => archive/src}/media_library_viewer/domain/media.py (100%) rename {src => archive/src}/media_library_viewer/jobs.py (100%) rename {src => archive/src}/media_library_viewer/services/__init__.py (100%) rename {src => archive/src}/media_library_viewer/services/media_index.py (100%) rename {src => archive/src}/media_library_viewer/ui/__init__.py (100%) rename {src => archive/src}/media_library_viewer/ui/dashboard.py (100%) rename {src => archive/src}/media_library_viewer/ui/file_browser.py (100%) rename {src => archive/src}/media_library_viewer/ui/media.py (100%) rename {src => archive/src}/media_library_viewer/ui/preview.py (100%) rename {backend => archive/src/media_library_viewer}/utils.py (100%) rename {tests => archive/tests}/.gitkeep (100%) rename backend/{ => src/media_library_viewer_api}/__init__.py (100%) rename backend/{ => src/media_library_viewer_api}/clients/__init__.py (100%) rename {src/media_library_viewer => backend/src/media_library_viewer_api}/clients/jellyfin.py (100%) rename backend/{ => src/media_library_viewer_api}/clients/resources.py (99%) rename {src/media_library_viewer => backend/src/media_library_viewer_api}/clients/ssh.py (100%) rename backend/{ => src/media_library_viewer_api}/config.py (100%) rename backend/{ => src/media_library_viewer_api}/dependencies.py (87%) rename backend/{ => src/media_library_viewer_api}/domain/__init__.py (100%) rename backend/{ => src/media_library_viewer_api}/domain/media.py (98%) rename backend/{ => src/media_library_viewer_api}/jobs.py (96%) rename backend/{ => src/media_library_viewer_api}/main.py (93%) rename backend/{ => src/media_library_viewer_api}/path_utils.py (100%) rename backend/{ => src/media_library_viewer_api}/routers/__init__.py (100%) rename backend/{ => src/media_library_viewer_api}/routers/dashboard.py (93%) rename backend/{ => src/media_library_viewer_api}/routers/files.py (88%) rename backend/{ => src/media_library_viewer_api}/routers/jobs.py (86%) rename backend/{ => src/media_library_viewer_api}/routers/media.py (91%) rename backend/{ => src/media_library_viewer_api}/routers/monitoring.py (90%) rename backend/{ => src/media_library_viewer_api}/services/__init__.py (100%) rename backend/{ => src/media_library_viewer_api}/services/media_index.py (98%) rename {src/media_library_viewer => backend/src/media_library_viewer_api}/utils.py (100%) create mode 100644 backend/tests/__init__.py diff --git a/README.md b/README.md index f20e36a..b4a5f65 100644 --- a/README.md +++ b/README.md @@ -1,148 +1,100 @@ # Media Library Viewer -Small Streamlit app for browsing a remote Jellyfin library, inspecting files on disk over SSH, previewing media metadata with `ffprobe`, and running safe remote job templates. +Media library management tool with Jellyfin integration, SSH file inspection, server monitoring, and safe remote job templates. See `docs/REQUIREMENTS.md` for the living requirements, decisions, and planning history. +See `docs/MIGRATION_PLAN.md` for the FastAPI + React architecture plan. Project policy/docs: - License: `LICENSE` (MIT) - Contributing guide: `CONTRIBUTING.md` -## Phase 1 features +## Architecture -- Dashboard media counts for movies, series, and episodes, plus now-playing sessions (user, title, playback state, transcoding) -- SQLite-indexed Media tab with full-library sort/filter for runtime, size, bitrate, explicit HDR yes/no flag, date added, codec, resolution, series, season, episode, path, and row-based selection that automatically syncs File browser to the selected item's folder -- Server monitoring dashboard plus detailed Monitoring tab for CPU, IO wait, RAM, network, disk I/O, and disk space -- Jellyfin API-key connection using `GET /Users` plus user-scoped library endpoints -- Compact SSH remote directory browser with clickable rows, search, filters, sorting, pagination, and `[UP] ..` navigation -- Blocking selected-file `ffprobe` preview for known video files -- Separate container, video, audio, and subtitle metadata sections -- `stat` inspection -- Safe/read-only job templates, designed to extend later +The project consists of two subprojects: -## Project structure +- **`backend/`** — FastAPI Python API (see `backend/README.md`) +- **`frontend/`** — React + TypeScript SPA (see `frontend/README.md`) +- **`archive/`** — Original Streamlit prototype (preserved for reference) -```text -. -├── app.py # Thin Streamlit entrypoint -├── pyproject.toml # Package metadata, dependencies, tool config -├── requirements.txt # Convenience install file (-e .) -├── docs/ -│ └── REQUIREMENTS.md # Living requirements and decision log -├── src/ -│ └── media_library_viewer/ -│ ├── app.py # Thin Streamlit orchestration layer -│ ├── config.py # Environment/.env config -│ ├── jobs.py # Remote job templates -│ ├── utils.py # Formatting and ffprobe summaries -│ ├── domain/ # UI-independent normalization/domain helpers -│ ├── services/ # UI-independent application services/indexes -│ ├── ui/ # Streamlit UI modules split by feature area -│ └── clients/ -│ ├── jellyfin.py # Jellyfin API client -│ └── ssh.py # SSH/ffprobe client -└── tests/ -``` +## Features -## Setup +- Dashboard with now-playing sessions, server monitoring overview, and per-library media counts +- Server monitoring with CPU, IO wait, RAM, network, and disk I/O charts +- SQLite-indexed media table with full-library sort/filter +- Remote file browser with ffprobe preview and job execution +- Jellyfin API integration for library metadata +- SSH-based file inspection and remote job templates + +## Quick Start + +### Backend ```bash +cd backend python -m venv .venv source .venv/bin/activate -pip install -r requirements.txt +pip install -e '.[dev]' +uvicorn media_library_viewer_api.main:app --reload --port 8000 ``` -For development tools: +### Frontend ```bash -pip install -e '.[dev]' +cd frontend +npm install +npm run dev ``` -Create `.env` or use Streamlit secrets/env vars: +Frontend runs on http://localhost:5173 and proxies API requests to http://localhost:8000. + +## Configuration + +Create a `.env` file in the project root: ```bash JELLYFIN_URL=https://jellyfin.example.com JELLYFIN_API_KEY=your-api-key -# Optional fallback if user selection via GET /Users does not work. JELLYFIN_USER_ID= SSH_HOST=media-server.example.com SSH_USERNAME=username SSH_PORT=22 SSH_KEY_FILENAME=/home/username/.ssh/id_rsa -# SSH_PASSWORD=optional-password-or-key-passphrase -REMOTE_MEDIA_ROOT=/mnt/media -# Optional fallback prefix when REMOTE_MEDIA_ROOT mapping is not enough. -# Example fallback: Jellyfin /media/... -> SSH /srv/media/... -REMOTE_PATH_PREFIX=/srv +SSH_PASSWORD= + +REMOTE_MEDIA_ROOT=/srv/media +REMOTE_PATH_PREFIX= ``` -Run: - -```bash -streamlit run app.py -``` - -## Before publishing / committing - -Keep these out of git: - -- `.env` and any real secrets/tokens/passwords -- `.streamlit/secrets.toml` -- `.venv/`, local IDE files (`.idea/`, `.vscode/`) -- local cache/index files under `.cache/` -- logs and temporary files - -The included `.gitignore` already excludes these. - ## Remote server requirements -For file browsing, resource metrics, and media metadata: +The remote server needs: -```bash -ffprobe -version -python3 --version -find --version -stat --version -df --version -awk --version || true -``` +- Linux `/proc` and `/sys/block` for monitoring +- `/bin/sh` (POSIX shell) +- `python3`, `ffprobe`, `find`, `stat`, `df`, `awk` -The resource dashboard uses Linux `/proc`, `/sys/block`, `/bin/sh`, and a lightweight collector started over SSH. No full monitoring stack is required, but last-hour charts require the collector to have been running long enough to gather samples. Network is shown as download/upload in bytes per second, and disk I/O is shown as read/write in bytes per second. The metrics JSONL file is pruned to 7 days with a 70,000-line safety cap. SSH commands are explicitly run through `/bin/sh -c`, so the remote user's login shell may be fish or another shell. If no samples appear after 10-20 seconds, use the dashboard's `Restart` button and inspect `Collector diagnostics`. - -The SSH client uses your local `known_hosts` and rejects unknown host keys. Connect once manually first: +The SSH client rejects unknown host keys. Connect manually once first: ```bash ssh user@host ``` -## Extending jobs - -Add templates in `src/media_library_viewer/jobs.py`: - -```python -JOB_TEMPLATES["my_job"] = JobTemplate( - name="My job", - description="What it does.", - command_template="my-command --input {path}", - destructive=False, -) -``` - -Template variables are shell-quoted before insertion. For destructive jobs, add confirmation UI before executing. - -## Development checks +## Development ```bash -PYTHONPATH=src python -m py_compile app.py src/media_library_viewer/*.py src/media_library_viewer/clients/*.py -ruff check . -pytest +# Backend +cd backend && PYTHONPATH=src python -m py_compile src/media_library_viewer_api/main.py + +# Frontend +cd frontend && npx tsc --noEmit && npm run build ``` ## Notes -- Prefer Jellyfin API for library metadata. -- Enter the Jellyfin server root URL, for example `https://jellyfin.example.com`, not `https://jellyfin.example.com/web`. The client strips a trailing `/web` defensively. -- Prefer SSH/ffprobe for authoritative disk-level media metadata such as actual bitrate, color transfer, HDR metadata, audio channels, subtitles, and container details. -- Keep cleanup/transcode jobs explicit and template-based; avoid free-form command execution in the UI unless this app is only used locally by trusted users. +- Jellyfin server root URL required (not `/web`). The client strips trailing `/web` defensively. +- SSH commands run through `/bin/sh -c` regardless of remote login shell. +- Job templates are shell-quoted. Add new templates in `backend/src/media_library_viewer_api/jobs.py`. +- Monitoring collector uses JSONL in `/tmp`, pruned to 7 days / 70k lines. diff --git a/app.py b/archive/app.py similarity index 100% rename from app.py rename to archive/app.py diff --git a/pyproject.toml b/archive/pyproject.toml similarity index 100% rename from pyproject.toml rename to archive/pyproject.toml diff --git a/requirements.txt b/archive/requirements.txt similarity index 100% rename from requirements.txt rename to archive/requirements.txt diff --git a/src/media_library_viewer/__init__.py b/archive/src/media_library_viewer/__init__.py similarity index 100% rename from src/media_library_viewer/__init__.py rename to archive/src/media_library_viewer/__init__.py diff --git a/src/media_library_viewer/app.py b/archive/src/media_library_viewer/app.py similarity index 100% rename from src/media_library_viewer/app.py rename to archive/src/media_library_viewer/app.py diff --git a/src/media_library_viewer/clients/__init__.py b/archive/src/media_library_viewer/clients/__init__.py similarity index 100% rename from src/media_library_viewer/clients/__init__.py rename to archive/src/media_library_viewer/clients/__init__.py diff --git a/backend/clients/jellyfin.py b/archive/src/media_library_viewer/clients/jellyfin.py similarity index 100% rename from backend/clients/jellyfin.py rename to archive/src/media_library_viewer/clients/jellyfin.py diff --git a/src/media_library_viewer/clients/resources.py b/archive/src/media_library_viewer/clients/resources.py similarity index 100% rename from src/media_library_viewer/clients/resources.py rename to archive/src/media_library_viewer/clients/resources.py diff --git a/backend/clients/ssh.py b/archive/src/media_library_viewer/clients/ssh.py similarity index 100% rename from backend/clients/ssh.py rename to archive/src/media_library_viewer/clients/ssh.py diff --git a/src/media_library_viewer/config.py b/archive/src/media_library_viewer/config.py similarity index 100% rename from src/media_library_viewer/config.py rename to archive/src/media_library_viewer/config.py diff --git a/src/media_library_viewer/domain/__init__.py b/archive/src/media_library_viewer/domain/__init__.py similarity index 100% rename from src/media_library_viewer/domain/__init__.py rename to archive/src/media_library_viewer/domain/__init__.py diff --git a/src/media_library_viewer/domain/media.py b/archive/src/media_library_viewer/domain/media.py similarity index 100% rename from src/media_library_viewer/domain/media.py rename to archive/src/media_library_viewer/domain/media.py diff --git a/src/media_library_viewer/jobs.py b/archive/src/media_library_viewer/jobs.py similarity index 100% rename from src/media_library_viewer/jobs.py rename to archive/src/media_library_viewer/jobs.py diff --git a/src/media_library_viewer/services/__init__.py b/archive/src/media_library_viewer/services/__init__.py similarity index 100% rename from src/media_library_viewer/services/__init__.py rename to archive/src/media_library_viewer/services/__init__.py diff --git a/src/media_library_viewer/services/media_index.py b/archive/src/media_library_viewer/services/media_index.py similarity index 100% rename from src/media_library_viewer/services/media_index.py rename to archive/src/media_library_viewer/services/media_index.py diff --git a/src/media_library_viewer/ui/__init__.py b/archive/src/media_library_viewer/ui/__init__.py similarity index 100% rename from src/media_library_viewer/ui/__init__.py rename to archive/src/media_library_viewer/ui/__init__.py diff --git a/src/media_library_viewer/ui/dashboard.py b/archive/src/media_library_viewer/ui/dashboard.py similarity index 100% rename from src/media_library_viewer/ui/dashboard.py rename to archive/src/media_library_viewer/ui/dashboard.py diff --git a/src/media_library_viewer/ui/file_browser.py b/archive/src/media_library_viewer/ui/file_browser.py similarity index 100% rename from src/media_library_viewer/ui/file_browser.py rename to archive/src/media_library_viewer/ui/file_browser.py diff --git a/src/media_library_viewer/ui/media.py b/archive/src/media_library_viewer/ui/media.py similarity index 100% rename from src/media_library_viewer/ui/media.py rename to archive/src/media_library_viewer/ui/media.py diff --git a/src/media_library_viewer/ui/preview.py b/archive/src/media_library_viewer/ui/preview.py similarity index 100% rename from src/media_library_viewer/ui/preview.py rename to archive/src/media_library_viewer/ui/preview.py diff --git a/backend/utils.py b/archive/src/media_library_viewer/utils.py similarity index 100% rename from backend/utils.py rename to archive/src/media_library_viewer/utils.py diff --git a/tests/.gitkeep b/archive/tests/.gitkeep similarity index 100% rename from tests/.gitkeep rename to archive/tests/.gitkeep diff --git a/backend/README.md b/backend/README.md index ef81a6e..a5be669 100644 --- a/backend/README.md +++ b/backend/README.md @@ -2,6 +2,38 @@ FastAPI backend serving the REST API for Jellyfin media browsing, SSH file inspection, and server monitoring. +## Project structure + +``` +backend/ +├── pyproject.toml +├── README.md +├── src/ +│ └── media_library_viewer_api/ +│ ├── __init__.py +│ ├── main.py # FastAPI app entrypoint +│ ├── config.py # pydantic-settings config +│ ├── dependencies.py # Dependency injection +│ ├── path_utils.py # Jellyfin→SSH path resolution +│ ├── jobs.py # Job templates +│ ├── utils.py # Formatting helpers +│ ├── routers/ +│ │ ├── dashboard.py +│ │ ├── monitoring.py +│ │ ├── media.py +│ │ ├── files.py +│ │ └── jobs.py +│ ├── clients/ +│ │ ├── jellyfin.py +│ │ ├── resources.py +│ │ └── ssh.py +│ ├── domain/ +│ │ └── media.py +│ └── services/ +│ └── media_index.py +└── tests/ +``` + ## Setup ```bash @@ -11,12 +43,6 @@ source .venv/bin/activate pip install -e '.[dev]' ``` -Or install dependencies directly: - -```bash -pip install fastapi uvicorn[standard] pydantic-settings paramiko requests python-dotenv pandas -``` - ## Configuration Create a `.env` file in the project root (or set environment variables): @@ -40,7 +66,13 @@ REMOTE_PATH_PREFIX= ```bash cd backend -uvicorn main:app --reload --port 8000 +uvicorn media_library_viewer_api.main:app --reload --port 8000 +``` + +Or with PYTHONPATH if not installed: + +```bash +PYTHONPATH=src uvicorn media_library_viewer_api.main:app --reload --port 8000 ``` API docs available at: http://localhost:8000/docs @@ -51,9 +83,10 @@ API docs available at: http://localhost:8000/docs - `GET /api/dashboard/libraries` — Per-library breakdown - `GET /api/dashboard/now-playing` — Active playback sessions - `GET /api/monitoring/status` — Collector status -- `GET /api/monitoring/metrics` — Resource samples +- `GET /api/monitoring/metrics` — Resource samples (last hour) - `GET /api/monitoring/disk` — Disk space - `POST /api/monitoring/start|stop|restart` — Collector controls +- `GET /api/monitoring/diagnostics` — Collector debug info - `GET /api/media/status` — Index status - `POST /api/media/build` — Rebuild index - `GET /api/media/query` — Query with filters/sort/pagination diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 3a91f28..405c06b 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -16,9 +16,21 @@ dependencies = [ [project.optional-dependencies] dev = ["httpx", "pytest", "pytest-asyncio", "ruff"] +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["src/media_library_viewer_api"] + [tool.ruff] line-length = 120 target-version = "py311" +src = ["src"] [tool.ruff.lint] select = ["E", "F", "I", "W"] + +[tool.pytest.ini_options] +testpaths = ["tests"] +pythonpath = ["src"] diff --git a/backend/__init__.py b/backend/src/media_library_viewer_api/__init__.py similarity index 100% rename from backend/__init__.py rename to backend/src/media_library_viewer_api/__init__.py diff --git a/backend/clients/__init__.py b/backend/src/media_library_viewer_api/clients/__init__.py similarity index 100% rename from backend/clients/__init__.py rename to backend/src/media_library_viewer_api/clients/__init__.py diff --git a/src/media_library_viewer/clients/jellyfin.py b/backend/src/media_library_viewer_api/clients/jellyfin.py similarity index 100% rename from src/media_library_viewer/clients/jellyfin.py rename to backend/src/media_library_viewer_api/clients/jellyfin.py diff --git a/backend/clients/resources.py b/backend/src/media_library_viewer_api/clients/resources.py similarity index 99% rename from backend/clients/resources.py rename to backend/src/media_library_viewer_api/clients/resources.py index ecf8c6b..b3862ea 100644 --- a/backend/clients/resources.py +++ b/backend/src/media_library_viewer_api/clients/resources.py @@ -13,7 +13,7 @@ import shlex from dataclasses import dataclass from typing import Any -from clients.ssh import RemoteSSHClient +from media_library_viewer_api.clients.ssh import RemoteSSHClient # POSIX shell script copied to the remote server by start_resource_collector(). # Keep this script bash-free because many NAS/media servers have minimal shells. diff --git a/src/media_library_viewer/clients/ssh.py b/backend/src/media_library_viewer_api/clients/ssh.py similarity index 100% rename from src/media_library_viewer/clients/ssh.py rename to backend/src/media_library_viewer_api/clients/ssh.py diff --git a/backend/config.py b/backend/src/media_library_viewer_api/config.py similarity index 100% rename from backend/config.py rename to backend/src/media_library_viewer_api/config.py diff --git a/backend/dependencies.py b/backend/src/media_library_viewer_api/dependencies.py similarity index 87% rename from backend/dependencies.py rename to backend/src/media_library_viewer_api/dependencies.py index 469ada3..e3564e7 100644 --- a/backend/dependencies.py +++ b/backend/src/media_library_viewer_api/dependencies.py @@ -8,9 +8,9 @@ from __future__ import annotations from functools import lru_cache -from clients.jellyfin import JellyfinClient -from clients.ssh import RemoteSSHClient -from config import get_settings +from media_library_viewer_api.clients.jellyfin import JellyfinClient +from media_library_viewer_api.clients.ssh import RemoteSSHClient +from media_library_viewer_api.config import get_settings @lru_cache diff --git a/backend/domain/__init__.py b/backend/src/media_library_viewer_api/domain/__init__.py similarity index 100% rename from backend/domain/__init__.py rename to backend/src/media_library_viewer_api/domain/__init__.py diff --git a/backend/domain/media.py b/backend/src/media_library_viewer_api/domain/media.py similarity index 98% rename from backend/domain/media.py rename to backend/src/media_library_viewer_api/domain/media.py index 42e3f01..d39fe35 100644 --- a/backend/domain/media.py +++ b/backend/src/media_library_viewer_api/domain/media.py @@ -11,7 +11,7 @@ from typing import Any import pandas as pd -from utils import human_size, ticks_to_minutes +from media_library_viewer_api.utils import human_size, ticks_to_minutes def first_media_source(item: dict[str, Any]) -> dict[str, Any]: diff --git a/backend/jobs.py b/backend/src/media_library_viewer_api/jobs.py similarity index 96% rename from backend/jobs.py rename to backend/src/media_library_viewer_api/jobs.py index 702194a..d9c411b 100644 --- a/backend/jobs.py +++ b/backend/src/media_library_viewer_api/jobs.py @@ -11,7 +11,7 @@ import shlex from dataclasses import dataclass from typing import Mapping -from clients.ssh import CommandResult, RemoteSSHClient +from media_library_viewer_api.clients.ssh import CommandResult, RemoteSSHClient @dataclass(frozen=True) diff --git a/backend/main.py b/backend/src/media_library_viewer_api/main.py similarity index 93% rename from backend/main.py rename to backend/src/media_library_viewer_api/main.py index eb358bf..7d88d7d 100644 --- a/backend/main.py +++ b/backend/src/media_library_viewer_api/main.py @@ -7,7 +7,7 @@ from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware -from routers import dashboard, monitoring, media, files, jobs +from media_library_viewer_api.routers import dashboard, monitoring, media, files, jobs @asynccontextmanager diff --git a/backend/path_utils.py b/backend/src/media_library_viewer_api/path_utils.py similarity index 100% rename from backend/path_utils.py rename to backend/src/media_library_viewer_api/path_utils.py diff --git a/backend/routers/__init__.py b/backend/src/media_library_viewer_api/routers/__init__.py similarity index 100% rename from backend/routers/__init__.py rename to backend/src/media_library_viewer_api/routers/__init__.py diff --git a/backend/routers/dashboard.py b/backend/src/media_library_viewer_api/routers/dashboard.py similarity index 93% rename from backend/routers/dashboard.py rename to backend/src/media_library_viewer_api/routers/dashboard.py index ba4600b..17c038f 100644 --- a/backend/routers/dashboard.py +++ b/backend/src/media_library_viewer_api/routers/dashboard.py @@ -6,8 +6,8 @@ from typing import Any from fastapi import APIRouter, Depends -from dependencies import get_jellyfin_client, get_user_id -from clients.jellyfin import JellyfinClient +from media_library_viewer_api.dependencies import get_jellyfin_client, get_user_id +from media_library_viewer_api.clients.jellyfin import JellyfinClient router = APIRouter(prefix="/api/dashboard", tags=["dashboard"]) diff --git a/backend/routers/files.py b/backend/src/media_library_viewer_api/routers/files.py similarity index 88% rename from backend/routers/files.py rename to backend/src/media_library_viewer_api/routers/files.py index 2da03ff..d87f254 100644 --- a/backend/routers/files.py +++ b/backend/src/media_library_viewer_api/routers/files.py @@ -7,10 +7,10 @@ from typing import Any from fastapi import APIRouter, Depends, Query, HTTPException -from dependencies import get_ssh_client -from clients.ssh import RemoteSSHClient -from config import get_settings -from path_utils import resolve_remote_media_path +from media_library_viewer_api.dependencies import get_ssh_client +from media_library_viewer_api.clients.ssh import RemoteSSHClient +from media_library_viewer_api.config import get_settings +from media_library_viewer_api.path_utils import resolve_remote_media_path router = APIRouter(prefix="/api/files", tags=["files"]) diff --git a/backend/routers/jobs.py b/backend/src/media_library_viewer_api/routers/jobs.py similarity index 86% rename from backend/routers/jobs.py rename to backend/src/media_library_viewer_api/routers/jobs.py index abe0a15..1be669c 100644 --- a/backend/routers/jobs.py +++ b/backend/src/media_library_viewer_api/routers/jobs.py @@ -7,9 +7,9 @@ from typing import Any from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel -from dependencies import get_ssh_client -from clients.ssh import RemoteSSHClient -from jobs import JOB_TEMPLATES, run_job +from media_library_viewer_api.dependencies import get_ssh_client +from media_library_viewer_api.clients.ssh import RemoteSSHClient +from media_library_viewer_api.jobs import JOB_TEMPLATES, run_job router = APIRouter(prefix="/api/jobs", tags=["jobs"]) diff --git a/backend/routers/media.py b/backend/src/media_library_viewer_api/routers/media.py similarity index 91% rename from backend/routers/media.py rename to backend/src/media_library_viewer_api/routers/media.py index 61a74dc..1262cfc 100644 --- a/backend/routers/media.py +++ b/backend/src/media_library_viewer_api/routers/media.py @@ -6,9 +6,9 @@ from typing import Any from fastapi import APIRouter, Depends, Query -from dependencies import get_jellyfin_client, get_user_id -from clients.jellyfin import JellyfinClient -from services.media_index import MediaIndex, build_media_index +from media_library_viewer_api.dependencies import get_jellyfin_client, get_user_id +from media_library_viewer_api.clients.jellyfin import JellyfinClient +from media_library_viewer_api.services.media_index import MediaIndex, build_media_index router = APIRouter(prefix="/api/media", tags=["media"]) diff --git a/backend/routers/monitoring.py b/backend/src/media_library_viewer_api/routers/monitoring.py similarity index 90% rename from backend/routers/monitoring.py rename to backend/src/media_library_viewer_api/routers/monitoring.py index a64bf34..97fa687 100644 --- a/backend/routers/monitoring.py +++ b/backend/src/media_library_viewer_api/routers/monitoring.py @@ -7,9 +7,9 @@ from typing import Any from fastapi import APIRouter, Depends -from dependencies import get_ssh_client -from clients.ssh import RemoteSSHClient -from clients.resources import ( +from media_library_viewer_api.dependencies import get_ssh_client +from media_library_viewer_api.clients.ssh import RemoteSSHClient +from media_library_viewer_api.clients.resources import ( disk_space, read_resource_metrics, resource_collector_debug_info, @@ -18,7 +18,7 @@ from clients.resources import ( start_resource_collector, stop_resource_collector, ) -from config import get_settings +from media_library_viewer_api.config import get_settings router = APIRouter(prefix="/api/monitoring", tags=["monitoring"]) diff --git a/backend/services/__init__.py b/backend/src/media_library_viewer_api/services/__init__.py similarity index 100% rename from backend/services/__init__.py rename to backend/src/media_library_viewer_api/services/__init__.py diff --git a/backend/services/media_index.py b/backend/src/media_library_viewer_api/services/media_index.py similarity index 98% rename from backend/services/media_index.py rename to backend/src/media_library_viewer_api/services/media_index.py index b238e02..75319e5 100644 --- a/backend/services/media_index.py +++ b/backend/src/media_library_viewer_api/services/media_index.py @@ -13,8 +13,8 @@ from dataclasses import dataclass from pathlib import Path from typing import Any, Iterable -from clients.jellyfin import JellyfinClient -from domain.media import display_media_row, normalize_media_item +from media_library_viewer_api.clients.jellyfin import JellyfinClient +from media_library_viewer_api.domain.media import display_media_row, normalize_media_item # Local generated database. It is ignored by git and can be rebuilt from # Jellyfin metadata whenever needed. diff --git a/src/media_library_viewer/utils.py b/backend/src/media_library_viewer_api/utils.py similarity index 100% rename from src/media_library_viewer/utils.py rename to backend/src/media_library_viewer_api/utils.py diff --git a/backend/tests/__init__.py b/backend/tests/__init__.py new file mode 100644 index 0000000..e69de29