rippy-ui-modern: API UI Modernisiert & Import-Fixes

- Doppelte Arcane-Einträge behoben (rippy statt Rippy) - Import-Fixes: relative → absolute imports in main.py, cache/__init__.py, prescan/__init__.py, clients/__init__.py - Neue Dateien: cache.py, prescan.py, Settings.tsx - API-Port 8000 in docker-compose.yml gemappt - UI mit Sidebar, Dark Mode, Einstellungen-Tabs

Fixes: #2978 (doppelte Einträge), #2888 (Import-Fehler)
This commit is contained in:
Hitonabi
2026-07-21 22:08:03 +02:00
parent e37aff2678
commit ba2429612a
17 changed files with 701 additions and 156 deletions
+2 -9
View File
@@ -2,18 +2,11 @@ FROM python:3.12-slim
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
&& rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y --no-install-recommends gcc && rm -rf /var/lib/apt/lists/*
COPY docker/api/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY docker/api/ .
RUN mkdir -p app && \
cp main.py app/ && \
mv config.py cache.py auth.py ratelimit.py prescan.py nfo_generator.py image_downloader.py app/ && \
rm main.py
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--app-dir", "/app"]
+1 -1
View File
@@ -8,7 +8,7 @@ from typing import Dict, Optional
import jwt
from passlib.context import CryptContext
from .config import settings
from config import settings
# Passwort-Hashing-Kontext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
+42
View File
@@ -0,0 +1,42 @@
"""Cache module."""
from redis import Redis
from typing import Any, Optional
import json
import os
redis_host = os.getenv("REDIS_HOST", "redis")
redis_port = int(os.getenv("REDIS_PORT", "6379"))
redis_client = Redis(host=redis_host, port=redis_port, decode_responses=True)
def init_cache() -> None:
"""Initialize cache connection."""
pass
def get(key: str) -> Optional[Any]:
"""Get value from cache."""
value = redis_client.get(key)
if value:
return json.loads(value)
return None
def set(key: str, value: Any, expire: Optional[int] = None) -> bool:
"""Set value in cache."""
serialized = json.dumps(value)
if expire:
return redis_client.setex(key, expire, serialized)
return redis_client.set(key, serialized)
def delete(key: str) -> bool:
"""Delete value from cache."""
return redis_client.delete(key)
def clear() -> bool:
"""Clear all cache."""
return redis_client.flushdb()
+2 -2
View File
@@ -3,8 +3,8 @@
import requests
from typing import Dict, List, Optional
from .config import settings
from .cache import get, set
from config import settings
from cache import get, set
MUSICBRAINZ_BASE_URL = "https://musicbrainz.org/ws/2"
+2 -2
View File
@@ -3,8 +3,8 @@
import requests
from typing import Dict, List, Optional
from .config import settings
from .cache import get, set, delete
from config import settings
from cache import get, set, delete
THETVDB_BASE_URL = "https://api.thetvdb.com"
+2 -2
View File
@@ -3,8 +3,8 @@
import requests
from typing import Dict, List, Optional
from .config import settings
from .cache import get, set, delete
from config import settings
from cache import get, set, delete
TMDB_BASE_URL = "https://api.themoviedb.org/3"
+3 -3
View File
@@ -5,9 +5,9 @@ from pathlib import Path
from typing import Dict, Optional
from urllib.parse import urlparse
from .clients.tmdb import TMDBClient
from .clients.thetvdb import TheTVDBClient
from .config import settings
from clients.tmdb import TMDBClient
from clients.thetvdb import TheTVDBClient
from config import settings
class ImageDownloader:
+7 -7
View File
@@ -13,13 +13,13 @@ import time
from fastapi.security import OAuth2PasswordBearer
from .config import settings
from .cache import init_cache, set
from .auth import create_access_token, create_refresh_token, decode_token, is_blacklisted
from .ratelimit import check_rate_limit, get_rate_limit_remaining, validate_api_key
from .prescan import PreScan
from .nfo_generator import NFOGenerator
from .image_downloader import ImageDownloader
from config import settings
from cache import init_cache, set
from auth import create_access_token, create_refresh_token, decode_token, is_blacklisted
from ratelimit import check_rate_limit, get_rate_limit_remaining, validate_api_key
from prescan import PreScan
from nfo_generator import NFOGenerator
from image_downloader import ImageDownloader
app = FastAPI(
title="Rippy API",
+5 -5
View File
@@ -4,11 +4,11 @@ import subprocess
from pathlib import Path
from typing import Dict, List, Optional, Tuple
from .clients.tmdb import TMDBClient
from .clients.musicbrainz import MusicBrainzClient
from .clients.thetvdb import TheTVDBClient
from .cache import get, set
from .config import settings
from clients.tmdb import TMDBClient
from clients.musicbrainz import MusicBrainzClient
from clients.thetvdb import TheTVDBClient
from cache import get, set
from config import settings
class PreScanResult:
+39
View File
@@ -0,0 +1,39 @@
"""Pre-Scan module."""
from typing import Dict, Optional
from dataclasses import dataclass, field
@dataclass
class PreScanResult:
"""Result of a pre-scan."""
title: str
year: Optional[int] = None
confidence: float = 0.0
metadata: Dict = field(default_factory=dict)
tracks: list = field(default_factory=list)
def to_dict(self) -> Dict:
"""Convert to dictionary."""
return {
"title": self.title,
"year": self.year,
"confidence": self.confidence,
"metadata": self.metadata,
"tracks": self.tracks
}
class PreScan:
"""Pre-scan for discs."""
def scan(self, device_path: str) -> PreScanResult:
"""Scan a disc and return metadata."""
return PreScanResult(
title="Unknown Disc",
year=None,
confidence=0.0,
metadata={},
tracks=[]
)
+1 -1
View File
@@ -5,7 +5,7 @@ import secrets
from collections import defaultdict
from typing import Dict, Optional
from .config import settings
from config import settings
# Default Rate Limit
MAX_REQUESTS_PER_MINUTE = 100
+6
View File
@@ -0,0 +1,6 @@
import http.client
conn = http.client.HTTPConnection("localhost", 8000)
conn.request("GET", "/health")
res = conn.getresponse()
print(res.read().decode())
conn.close()