mirror of
https://github.com/maxdorninger/MediaManager.git
synced 2026-04-17 23:53:58 +02:00
@@ -1,15 +0,0 @@
|
||||
FROM mcr.microsoft.com/devcontainers/python:1-3.11-bullseye
|
||||
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
|
||||
# [Optional] If your requirements rarely change, uncomment this section to add them to the image.
|
||||
# COPY requirements.txt /tmp/pip-tmp/
|
||||
# RUN pip3 --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \
|
||||
# && rm -rf /tmp/pip-tmp
|
||||
|
||||
# [Optional] Uncomment this section to install additional OS packages.
|
||||
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||
# && apt-get -y install --no-install-recommends <your-package-list-here>
|
||||
|
||||
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||
// README at: https://github.com/devcontainers/templates/tree/main/src/postgres
|
||||
{
|
||||
"name": "Python 3 & PostgreSQL",
|
||||
"dockerComposeFile": "./docker-compose.yml",
|
||||
"service": "app",
|
||||
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
|
||||
|
||||
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||
// "features": {},
|
||||
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// This can be used to network with other containers or the host.
|
||||
// "forwardPorts": [5000, 5432],
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
"postCreateCommand": "pip install --user -r ./MediaManager/src/requirements.txt",
|
||||
|
||||
// Configure tool-specific properties.
|
||||
"customizations" : {
|
||||
"jetbrains" : {
|
||||
"settings": {
|
||||
"com.intellij:app:HttpConfigurable.use_proxy_pac": true
|
||||
},
|
||||
"backend" : "PyCharm"
|
||||
}
|
||||
},
|
||||
|
||||
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
||||
// "remoteUser": "root"
|
||||
}
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,6 +1,6 @@
|
||||
.idea
|
||||
venv
|
||||
MediaManager.iml
|
||||
MediaManager/res
|
||||
MediaManager/res/.env
|
||||
backend/res
|
||||
backend/res/.env
|
||||
docker-compose.yml
|
||||
@@ -1,60 +0,0 @@
|
||||
import os
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class DbConfig(BaseModel):
|
||||
host: str = os.getenv("DB_HOST") or "localhost"
|
||||
port: int = int(os.getenv("DB_PORT") or 5432)
|
||||
user: str = os.getenv("DB_USERNAME") or "MediaManager"
|
||||
_password: str = os.getenv("DB_PASSWORD") or "MediaManager"
|
||||
dbname: str = os.getenv("DB_NAME") or "MediaManager"
|
||||
|
||||
@property
|
||||
def password(self):
|
||||
return self._password
|
||||
|
||||
|
||||
class TmdbConfig(BaseModel):
|
||||
api_key: str = os.getenv("TMDB_API_KEY") or None
|
||||
|
||||
|
||||
class BasicConfig(BaseModel):
|
||||
storage_directory: str = os.getenv("STORAGE_FILE_PATH") or "."
|
||||
|
||||
class ProwlarrConfig(BaseModel):
|
||||
enabled: bool = bool(os.getenv("PROWLARR_ENABLED") or True)
|
||||
api_key: str = os.getenv("PROWLARR_API_KEY")
|
||||
url: str = os.getenv("PROWLARR_URL")
|
||||
|
||||
|
||||
class AuthConfig(BaseModel):
|
||||
# to get a signing key run:
|
||||
# openssl rand -hex 32
|
||||
_jwt_signing_key: str = os.getenv("JWT_SIGNING_KEY")
|
||||
jwt_signing_algorithm: str = "HS256"
|
||||
jwt_access_token_lifetime: int = int(os.getenv("JWT_ACCESS_TOKEN_LIFETIME") or 60 * 24 * 30)
|
||||
|
||||
@property
|
||||
def jwt_signing_key(self):
|
||||
return self._jwt_signing_key
|
||||
|
||||
|
||||
class QbittorrentConfig(BaseModel):
|
||||
host: str = os.getenv("QBITTORRENT_HOST") or "localhost"
|
||||
port: int = os.getenv("QBITTORRENT_PORT") or 8080
|
||||
username: str = os.getenv("QBITTORRENT_USERNAME") or "admin"
|
||||
password: str = os.getenv("QBITTORRENT_PASSWORD") or "adminadmin"
|
||||
|
||||
|
||||
class DownloadClientConfig(BaseModel):
|
||||
client: Literal['qbit'] = os.getenv("DOWNLOAD_CLIENT") or "qbit"
|
||||
|
||||
|
||||
class MachineLearningConfig(BaseModel):
|
||||
model_name: str = os.getenv("OLLAMA_MODEL_NAME") or "qwen2.5:0.5b"
|
||||
|
||||
|
||||
def get_db_config() -> DbConfig:
|
||||
return DbConfig()
|
||||
@@ -1,10 +0,0 @@
|
||||
from config import DownloadClientConfig
|
||||
from dowloadClients.qbittorrent import QbittorrentClient
|
||||
|
||||
config = DownloadClientConfig()
|
||||
|
||||
# TODO: add more elif when implementing more download clients
|
||||
if config.client == "qbit":
|
||||
client = QbittorrentClient()
|
||||
else:
|
||||
client = QbittorrentClient()
|
||||
@@ -2,16 +2,17 @@ import logging
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
import jwt
|
||||
from fastapi import Depends, HTTPException, status, APIRouter
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from jwt.exceptions import InvalidTokenError
|
||||
from pydantic import BaseModel
|
||||
|
||||
from config import AuthConfig
|
||||
from database import SessionDependency
|
||||
from auth.config import AuthConfig
|
||||
from database import DbSessionDependency
|
||||
from database.users import User
|
||||
|
||||
|
||||
# TODO: evaluate FASTAPI-Users package
|
||||
|
||||
class Token(BaseModel):
|
||||
access_token: str
|
||||
@@ -29,20 +30,20 @@ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="api/v1/token")
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
async def get_current_user(db: SessionDependency, token: str = Depends(oauth2_scheme)) -> User:
|
||||
async def get_current_user(db: DbSessionDependency, token: str = Depends(oauth2_scheme)) -> User:
|
||||
credentials_exception = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Could not validate credentials",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
config = AuthConfig()
|
||||
auth_config = AuthConfig
|
||||
log.debug("token: " + token)
|
||||
|
||||
try:
|
||||
payload = jwt.decode(token, config.jwt_signing_key, algorithms=[config.jwt_signing_algorithm])
|
||||
payload = jwt.decode(token, auth_config.jwt_signing_key, algorithms=[auth_config.jwt_signing_algorithm])
|
||||
log.debug("jwt payload: " + payload.__str__())
|
||||
user_uid: str = payload.get("sub")
|
||||
log.debug("jwt payload sub (user uid): " + user_uid)
|
||||
log.debug("jwt payload sub (USER uid): " + user_uid)
|
||||
if user_uid is None:
|
||||
raise credentials_exception
|
||||
token_data = TokenData(uid=user_uid)
|
||||
@@ -53,20 +54,20 @@ async def get_current_user(db: SessionDependency, token: str = Depends(oauth2_sc
|
||||
user: User | None = db.get(User, token_data.uid)
|
||||
|
||||
if user is None:
|
||||
log.debug("user not found")
|
||||
log.debug("USER not found")
|
||||
raise credentials_exception
|
||||
|
||||
log.debug("received user: " + user.__str__())
|
||||
log.debug("received USER: " + user.__str__())
|
||||
return user
|
||||
|
||||
|
||||
def create_access_token(data: dict, expires_delta: timedelta | None = None):
|
||||
to_encode = data.copy()
|
||||
config = AuthConfig()
|
||||
auth_config = AuthConfig
|
||||
if expires_delta:
|
||||
expire = datetime.now(timezone.utc) + expires_delta
|
||||
else:
|
||||
expire = datetime.now(timezone.utc) + timedelta(minutes=config.jwt_access_token_lifetime)
|
||||
expire = datetime.now(timezone.utc) + timedelta(minutes=auth_config.jwt_access_token_lifetime)
|
||||
to_encode.update({"exp": expire})
|
||||
encoded_jwt = jwt.encode(to_encode, config.jwt_signing_key, algorithm=config.jwt_signing_algorithm)
|
||||
encoded_jwt = jwt.encode(to_encode, auth_config.jwt_signing_key, algorithm=auth_config.jwt_signing_algorithm)
|
||||
return encoded_jwt
|
||||
13
backend/src/auth/config.py
Normal file
13
backend/src/auth/config.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class AuthConfig(BaseSettings):
|
||||
# to get a signing key run:
|
||||
# openssl rand -hex 32
|
||||
jwt_signing_key: str
|
||||
jwt_signing_algorithm: str = "HS256"
|
||||
jwt_access_token_lifetime: int = 60 * 24 * 30
|
||||
|
||||
@property
|
||||
def jwt_signing_key(self):
|
||||
return self._jwt_signing_key
|
||||
@@ -1,15 +1,12 @@
|
||||
from typing import Annotated
|
||||
|
||||
import hashlib
|
||||
|
||||
import bcrypt
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
from sqlmodel import Session, select
|
||||
from sqlmodel import select
|
||||
|
||||
import database
|
||||
from auth import create_access_token, Token, router
|
||||
from database import users, SessionDependency
|
||||
from auth import Token, create_access_token, router
|
||||
from database import DbSessionDependency
|
||||
from database.users import User
|
||||
|
||||
|
||||
@@ -24,12 +21,12 @@ def get_password_hash(password: str) -> str:
|
||||
return bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()).decode("utf-8")
|
||||
|
||||
|
||||
def authenticate_user(db: SessionDependency, email: str, password: str) -> bool | User:
|
||||
def authenticate_user(db: DbSessionDependency, email: str, password: str) -> bool | User:
|
||||
"""
|
||||
|
||||
:param email: email of the user
|
||||
:param password: password of the user
|
||||
:return: if authentication succeeds, returns the user object with added name and lastname, otherwise or if the user doesn't exist returns False
|
||||
:param email: email of the USER
|
||||
:param password: PASSWORD of the USER
|
||||
:return: if authentication succeeds, returns the USER object with added name and lastname, otherwise or if the USER doesn't exist returns False
|
||||
"""
|
||||
user: User | None = db.exec(select(User).where(User.email == email)).first()
|
||||
if not user:
|
||||
@@ -42,13 +39,13 @@ def authenticate_user(db: SessionDependency, email: str, password: str) -> bool
|
||||
@router.post("/token")
|
||||
async def login_for_access_token(
|
||||
form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
|
||||
db: SessionDependency,
|
||||
db: DbSessionDependency,
|
||||
) -> Token:
|
||||
user = authenticate_user(db,form_data.username, form_data.password)
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Incorrect email or password",
|
||||
detail="Incorrect email or PASSWORD",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
# id needs to be converted because a UUID object isn't json serializable
|
||||
5
backend/src/config.py
Normal file
5
backend/src/config.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class BasicConfig(BaseSettings):
|
||||
storage_directory: str = "."
|
||||
@@ -4,14 +4,13 @@ from typing import Annotated, Any, Generator
|
||||
from fastapi import Depends
|
||||
from sqlmodel import SQLModel, Session, create_engine
|
||||
|
||||
import config
|
||||
from config import DbConfig
|
||||
from database.config import DbConfig
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
config: DbConfig = config.get_db_config()
|
||||
config = DbConfig()
|
||||
|
||||
db_url = "postgresql+psycopg" + "://" + config.user + ":" + config.password + "@" + config.host + ":" + str(
|
||||
config.port) + "/" + config.dbname
|
||||
db_url = "postgresql+psycopg" + "://" + config.USER + ":" + config.PASSWORD + "@" + config.HOST + ":" + str(
|
||||
config.PORT) + "/" + config.DBNAME
|
||||
|
||||
engine = create_engine(db_url, echo=False)
|
||||
|
||||
@@ -24,5 +23,5 @@ def get_session() -> Generator[Session, Any, None]:
|
||||
with Session(engine) as session:
|
||||
yield session
|
||||
|
||||
SessionDependency = Annotated[Session, Depends(get_session)]
|
||||
|
||||
DbSessionDependency = Annotated[Session, Depends(get_session)]
|
||||
10
backend/src/database/config.py
Normal file
10
backend/src/database/config.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class DbConfig(BaseSettings):
|
||||
model_config = SettingsConfigDict(env_prefix='DB')
|
||||
HOST: str = "localhost"
|
||||
PORT: int = 5432
|
||||
USER: str = "MediaManager"
|
||||
PASSWORD: str = "MediaManager"
|
||||
DBNAME: str = "MediaManager"
|
||||
9
backend/src/dowloadClients/__init__.py
Normal file
9
backend/src/dowloadClients/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from dowloadClients.config import DownloadClientConfig
|
||||
from dowloadClients.qbittorrent import QbittorrentClient
|
||||
|
||||
config = DownloadClientConfig()
|
||||
|
||||
if config.download_client == "qbit":
|
||||
client = QbittorrentClient()
|
||||
else:
|
||||
raise ValueError("Unknown download client")
|
||||
5
backend/src/dowloadClients/config.py
Normal file
5
backend/src/dowloadClients/config.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class DownloadClientConfig(BaseSettings):
|
||||
download_client: str = "qbit"
|
||||
@@ -9,7 +9,6 @@ class GenericDownloadClient(object):
|
||||
raise ValueError('name cannot be None')
|
||||
self.name = name
|
||||
|
||||
# TODO: change Torrents type to SeasonTorrents|MovieTorrents
|
||||
def download(self, torrent: TorrentMixin) -> TorrentMixin:
|
||||
"""
|
||||
downloads a torrent
|
||||
@@ -1,13 +1,21 @@
|
||||
import logging
|
||||
|
||||
import qbittorrentapi
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
from config import QbittorrentConfig
|
||||
from database.torrents import Torrent
|
||||
from dowloadClients.genericDownloadClient import GenericDownloadClient
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class QbittorrentConfig(BaseSettings):
|
||||
model_config = SettingsConfigDict(env_prefix='QBITTORRENT_')
|
||||
host: str = "localhost"
|
||||
port: int = 8080
|
||||
username: str = "admin"
|
||||
|
||||
|
||||
class QbittorrentClient(GenericDownloadClient):
|
||||
DOWNLOADING_STATE = ("allocating", "downloading", "metaDL", "pausedDL", "queuedDL", "stalledDL", "checkingDL",
|
||||
"forcedDL", "moving")
|
||||
@@ -1,7 +1,7 @@
|
||||
import logging
|
||||
|
||||
import config
|
||||
from database.tv import Season
|
||||
from indexer.config import ProwlarrConfig
|
||||
from indexer.generic import GenericIndexer, IndexerQueryResult
|
||||
from indexer.prowlarr import Prowlarr
|
||||
|
||||
@@ -23,5 +23,5 @@ def search(query: str | Season) -> list[IndexerQueryResult]:
|
||||
|
||||
indexers: list[GenericIndexer] = []
|
||||
|
||||
if config.ProwlarrConfig().enabled:
|
||||
if ProwlarrConfig.enabled:
|
||||
indexers.append(Prowlarr())
|
||||
8
backend/src/indexer/config.py
Normal file
8
backend/src/indexer/config.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class ProwlarrConfig(BaseSettings):
|
||||
model_config = SettingsConfigDict(env_prefix="PROWLARR_")
|
||||
enabled: bool = True
|
||||
api_key: str
|
||||
url: str
|
||||
@@ -7,6 +7,7 @@ from pydantic import BaseModel, computed_field
|
||||
from database.torrents import QualityMixin, Torrent
|
||||
|
||||
|
||||
# TODO: use something like strategy pattern to make sorting more user customizable
|
||||
class IndexerQueryResult(BaseModel, QualityMixin):
|
||||
id: UUID = pydantic.Field(default_factory=uuid4)
|
||||
title: str
|
||||
@@ -2,8 +2,8 @@ import logging
|
||||
|
||||
import requests
|
||||
|
||||
from config import ProwlarrConfig
|
||||
from indexer import GenericIndexer, IndexerQueryResult
|
||||
from indexer.config import ProwlarrConfig
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -17,7 +17,7 @@ class Prowlarr(GenericIndexer):
|
||||
:param kwargs: Additional keyword arguments to pass to the superclass constructor.
|
||||
"""
|
||||
super().__init__(name='prowlarr')
|
||||
config = ProwlarrConfig()
|
||||
config = ProwlarrConfig
|
||||
self.api_key = config.api_key
|
||||
self.url = config.url
|
||||
|
||||
@@ -14,7 +14,7 @@ from fastapi import FastAPI
|
||||
import database.users
|
||||
import tv.router
|
||||
from auth import password
|
||||
from routers import users
|
||||
from users import routers
|
||||
|
||||
LOGGING_CONFIG = {
|
||||
"version": 1,
|
||||
@@ -44,7 +44,7 @@ dictConfig(LOGGING_CONFIG)
|
||||
|
||||
database.init_db()
|
||||
app = FastAPI(root_path="/api/v1")
|
||||
app.include_router(users.router, tags=["users"])
|
||||
app.include_router(routers.router, tags=["users"])
|
||||
app.include_router(password.router, tags=["authentication"])
|
||||
app.include_router(tv.router.router, tags=["tv"])
|
||||
|
||||
@@ -7,7 +7,7 @@ from database.tv import Show
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MetadataProvider(ABC):
|
||||
class AbstractMetadataProvider(ABC):
|
||||
storage_path = config.BasicConfig().storage_directory
|
||||
@property
|
||||
@abstractmethod
|
||||
@@ -26,6 +26,6 @@ class MetadataProvider(ABC):
|
||||
metadata_providers = {}
|
||||
|
||||
|
||||
def register_metadata_provider(metadata_provider: MetadataProvider):
|
||||
def register_metadata_provider(metadata_provider: AbstractMetadataProvider):
|
||||
log.info("Registering metadata provider:" + metadata_provider.name)
|
||||
metadata_providers[metadata_provider.name] = metadata_provider
|
||||
@@ -3,18 +3,24 @@ import mimetypes
|
||||
|
||||
import requests
|
||||
import tmdbsimple
|
||||
from pydantic_settings import BaseSettings
|
||||
from tmdbsimple import TV, TV_Seasons
|
||||
|
||||
import config
|
||||
from database.tv import Episode, Season, Show
|
||||
from metadataProvider.abstractMetaDataProvider import MetadataProvider, register_metadata_provider
|
||||
from metadataProvider.abstractMetaDataProvider import AbstractMetadataProvider, register_metadata_provider
|
||||
|
||||
config = config.TmdbConfig()
|
||||
|
||||
class TmdbConfig(BaseSettings):
|
||||
TMDB_API_KEY: str | None = None
|
||||
|
||||
|
||||
config = TmdbConfig
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TmdbMetadataProvider(MetadataProvider):
|
||||
class TmdbMetadataProvider(AbstractMetadataProvider):
|
||||
name = "tmdb"
|
||||
|
||||
def get_show_metadata(self, id: int = None) -> Show:
|
||||
"""
|
||||
|
||||
@@ -86,6 +92,6 @@ class TmdbMetadataProvider(MetadataProvider):
|
||||
tmdbsimple.API_KEY = api_key
|
||||
|
||||
|
||||
if config.api_key is not None:
|
||||
if config.TMDB_API_KEY is not None:
|
||||
log.info("Registering TMDB as metadata provider")
|
||||
register_metadata_provider(metadata_provider=TmdbMetadataProvider(config.api_key))
|
||||
register_metadata_provider(metadata_provider=TmdbMetadataProvider(config.TMDB_API_KEY))
|
||||
@@ -6,7 +6,7 @@ from typing import List
|
||||
from ollama import ChatResponse, chat
|
||||
from pydantic import BaseModel
|
||||
|
||||
import config
|
||||
from ml.config import MachineLearningConfig
|
||||
|
||||
|
||||
class NFO(BaseModel):
|
||||
@@ -22,11 +22,11 @@ def get_season(nfo: str) -> int | None:
|
||||
|
||||
for i in range(0, 5):
|
||||
responses.append(chat(
|
||||
model=config.model_name,
|
||||
model=config.ollama_model_name,
|
||||
format=NFO.model_json_schema(),
|
||||
messages=[
|
||||
{
|
||||
'role': 'user',
|
||||
'role': 'USER',
|
||||
'content':
|
||||
"Tell me which season the torrent with this description contains?" +
|
||||
" output a season number in json format, the season number is an integer" +
|
||||
@@ -54,11 +54,11 @@ def contains_season(season_number: int, string_to_analyze: str) -> bool:
|
||||
|
||||
for i in range(0, 3):
|
||||
responses.append(chat(
|
||||
model=config.model_name,
|
||||
model=config.ollama_model_name,
|
||||
format=Contains.model_json_schema(),
|
||||
messages=[
|
||||
{
|
||||
'role': 'user',
|
||||
'role': 'USER',
|
||||
'content':
|
||||
"Does this torrent contain the season " + season_number.__str__() + " ?" +
|
||||
" output a boolean json format" +
|
||||
@@ -79,5 +79,6 @@ def contains_season(season_number: int, string_to_analyze: str) -> bool:
|
||||
log.debug(f"according to AI {string_to_analyze} contains season {season_number} {most_common[0][0]}")
|
||||
return most_common[0][0]
|
||||
|
||||
config = config.MachineLearningConfig()
|
||||
|
||||
config = MachineLearningConfig
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -1,8 +1,7 @@
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from ollama import ChatResponse
|
||||
from ollama import chat
|
||||
from ollama import ChatResponse, chat
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
@@ -19,7 +18,7 @@ while start_time > datetime.now():
|
||||
format=NFO.model_json_schema()
|
||||
, messages=[
|
||||
{
|
||||
'role': 'user',
|
||||
'role': 'USER',
|
||||
'content':
|
||||
"which season does a torrent with the following NFO contain? output the season number, which is an integer in json please\n" +
|
||||
"The.Big.Bang.Theory.(2007).Season.9.S09.(1080p.BluRay.x265.HEVC.10bit.AAC.5.1.Vyndros)"
|
||||
5
backend/src/ml/config.py
Normal file
5
backend/src/ml/config.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class MachineLearningConfig(BaseSettings):
|
||||
ollama_model_name: str = "qwen2.5:0.5b"
|
||||
@@ -12,12 +12,12 @@ import auth
|
||||
import dowloadClients
|
||||
import indexer
|
||||
import metadataProvider
|
||||
from database import SessionDependency
|
||||
from database import DbSessionDependency
|
||||
from database.torrents import Torrent
|
||||
from database.tv import Season, Show
|
||||
from indexer import IndexerQueryResult
|
||||
from routers.users import Message
|
||||
from tv import log
|
||||
from users.routers import Message
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/tv",
|
||||
@@ -34,7 +34,7 @@ class ShowDetails(BaseModel):
|
||||
status.HTTP_201_CREATED: {"model": Show, "description": "Successfully created show"},
|
||||
status.HTTP_409_CONFLICT: {"model": Message, "description": "Show already exists"},
|
||||
})
|
||||
def add_show(db: SessionDependency, show_id: int, metadata_provider: str = "tmdb", version: str = ""):
|
||||
def add_show(db: DbSessionDependency, show_id: int, metadata_provider: str = "tmdb", version: str = ""):
|
||||
res = db.exec(select(Show).
|
||||
where(Show.external_id == show_id).
|
||||
where(Show.metadata_provider == metadata_provider).
|
||||
@@ -59,14 +59,14 @@ def add_show(db: SessionDependency, show_id: int, metadata_provider: str = "tmdb
|
||||
|
||||
|
||||
@router.delete("/{show_id}", status_code=status.HTTP_200_OK)
|
||||
def delete_show(db: SessionDependency, show_id: UUID):
|
||||
def delete_show(db: DbSessionDependency, show_id: UUID):
|
||||
db.delete(db.get(Show, show_id))
|
||||
db.commit()
|
||||
|
||||
|
||||
@router.patch("/{show_id}/{season_id}", status_code=status.HTTP_200_OK, dependencies=[Depends(auth.get_current_user)],
|
||||
response_model=Season)
|
||||
def add_season(db: SessionDependency, season_id: UUID):
|
||||
def add_season(db: DbSessionDependency, season_id: UUID):
|
||||
"""
|
||||
adds requested flag to a season
|
||||
"""
|
||||
@@ -81,7 +81,7 @@ def add_season(db: SessionDependency, season_id: UUID):
|
||||
|
||||
@router.delete("/{show_id}/{season_id}", status_code=status.HTTP_200_OK, dependencies=[Depends(auth.get_current_user)],
|
||||
response_model=Show)
|
||||
def delete_season(db: SessionDependency, show_id: UUID, season: int):
|
||||
def delete_season(db: DbSessionDependency, show_id: UUID, season: int):
|
||||
"""
|
||||
removes requested flag from a season
|
||||
"""
|
||||
@@ -96,7 +96,7 @@ def delete_season(db: SessionDependency, show_id: UUID, season: int):
|
||||
@router.get("/{show_id}/{season_id}/torrent", status_code=status.HTTP_200_OK, dependencies=[Depends(
|
||||
auth.get_current_user)],
|
||||
response_model=list[IndexerQueryResult])
|
||||
def get_season_torrents(db: SessionDependency, show_id: UUID, season_id: UUID):
|
||||
def get_season_torrents(db: DbSessionDependency, show_id: UUID, season_id: UUID):
|
||||
season = db.get(Season, season_id)
|
||||
|
||||
if season is None:
|
||||
@@ -121,7 +121,7 @@ def get_season_torrents(db: SessionDependency, show_id: UUID, season_id: UUID):
|
||||
|
||||
@router.post("/{show_id}/torrent", status_code=status.HTTP_200_OK, dependencies=[Depends(
|
||||
auth.get_current_user)], response_model=list[Season])
|
||||
def download_seasons_torrent(db: SessionDependency, show_id: UUID, torrent_id: UUID):
|
||||
def download_seasons_torrent(db: DbSessionDependency, show_id: UUID, torrent_id: UUID):
|
||||
"""
|
||||
downloads torrents for a show season, links the torrent for all seasons the torrent contains
|
||||
|
||||
@@ -153,7 +153,7 @@ def download_seasons_torrent(db: SessionDependency, show_id: UUID, torrent_id: U
|
||||
|
||||
@router.post("/{show_id}/{season_id}/torrent", status_code=status.HTTP_200_OK, dependencies=[Depends(
|
||||
auth.get_current_user)], response_model=list[Season])
|
||||
def delete_seasons_torrent(db: SessionDependency, show_id: UUID, season_id: UUID, torrent_id: UUID):
|
||||
def delete_seasons_torrent(db: DbSessionDependency, show_id: UUID, season_id: UUID, torrent_id: UUID):
|
||||
"""
|
||||
downloads torrents for a season, links the torrent only to the specified season
|
||||
this means that multiple torrents can contain a season but you can choose from one which the content should be
|
||||
@@ -186,13 +186,13 @@ def delete_seasons_torrent(db: SessionDependency, show_id: UUID, season_id: UUID
|
||||
|
||||
|
||||
@router.get("/", dependencies=[Depends(auth.get_current_user)], response_model=list[Show])
|
||||
def get_shows(db: SessionDependency):
|
||||
def get_shows(db: DbSessionDependency):
|
||||
""""""
|
||||
return db.exec(select(Show)).unique().fetchall()
|
||||
|
||||
|
||||
@router.get("/{show_id}", dependencies=[Depends(auth.get_current_user)], response_model=ShowDetails)
|
||||
def get_show(db: SessionDependency, show_id: UUID):
|
||||
def get_show(db: DbSessionDependency, show_id: UUID):
|
||||
"""
|
||||
|
||||
:param show_id:
|
||||
@@ -1,14 +1,13 @@
|
||||
from fastapi import APIRouter
|
||||
from fastapi import Depends
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from fastapi import APIRouter, Depends
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from starlette.responses import JSONResponse
|
||||
|
||||
from auth import get_current_user
|
||||
from auth.password import get_password_hash
|
||||
from database import SessionDependency, get_session
|
||||
from database import DbSessionDependency
|
||||
from database.users import User, UserCreate, UserPublic
|
||||
from routers import log
|
||||
from users import log
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/users",
|
||||
@@ -25,7 +24,7 @@ class Message(BaseModel):
|
||||
201: {"model": UserPublic, "description": "User created successfully"}
|
||||
})
|
||||
async def create_user(
|
||||
db: SessionDependency,
|
||||
db: DbSessionDependency,
|
||||
user: UserCreate = Depends(UserCreate),
|
||||
):
|
||||
internal_user = User(name=user.name, lastname=user.lastname, email=user.email,
|
||||
@@ -35,9 +34,9 @@ async def create_user(
|
||||
db.commit()
|
||||
except IntegrityError as e:
|
||||
log.debug(e)
|
||||
log.warning("Failed to create new user, User with this email already exists "+internal_user.model_dump().__str__())
|
||||
log.warning("Failed to create new USER, User with this email already exists " + internal_user.model_dump().__str__())
|
||||
return JSONResponse(status_code=409, content={"message": "User with this email already exists"})
|
||||
log.info("Created new user "+internal_user.email)
|
||||
log.info("Created new USER " + internal_user.email)
|
||||
return UserPublic(**internal_user.model_dump())
|
||||
|
||||
|
||||
Reference in New Issue
Block a user