Skip to content

Commit

Permalink
Merge pull request #42 from nandyalu/dev
Browse files Browse the repository at this point in the history
Release with many bug fixes and some changes
  • Loading branch information
nandyalu authored Sep 22, 2024
2 parents c254731 + 97e109d commit fd42601
Show file tree
Hide file tree
Showing 79 changed files with 4,043 additions and 1,103 deletions.
2 changes: 1 addition & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - &&\
apt-get install -y nodejs

# Install specific version of npm
RUN npm install -g npm@10.8.1
RUN npm install -g npm@10.8.3

# Install specific version of Angular CLI
RUN npm install -g @angular/cli@17.3.6
Expand Down
18 changes: 9 additions & 9 deletions .devcontainer/dev-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@
mkdocs-material==9.5.32

# Backend
aiohttp==3.10.2
aiohttp==3.10.5
aiofiles==24.1.0
alembic==1.13.2
apscheduler==3.10.4
async-lru==2.0.4
fastapi[standard]==0.112.0
fastapi[standard]==0.115.0
pillow==10.4.0
sqlmodel==0.0.21
yt_dlp==2024.8.6
sqlmodel==0.0.22
yt-dlp==2024.8.6

# Testing
aioresponses==0.7.6
hypothesis==6.108.5
schemathesis==3.33.3
pytest==8.1.1
pytest-asyncio==0.23.6
pytest-cov==4.1.0
hypothesis==6.112.1
schemathesis==3.36.0
pytest==8.3.3
pytest-asyncio==0.24.0
pytest-cov==5.0.0
2 changes: 1 addition & 1 deletion .devcontainer/dev-start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ echo "Setting TimeZone to $TZ"
echo $TZ > /etc/timezone && ln -snf /usr/share/zoneinfo/$TZ /etc/localtime

# Create data folder for storing database and other config files
mkdir -p /data/logs && chown -R vscode:vscode /data
mkdir -p /config/logs && chown -R vscode:vscode /config

# Run Alembic migrations
echo "Running Alembic migrations"
Expand Down
6 changes: 3 additions & 3 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

// Mount appdata folder for persistent storage
"mounts": [
"source=/var/appdata/trailarr-dev,target=/data,type=bind,consistency=cached",
"source=/var/appdata/trailarr-dev,target=/config,type=bind,consistency=cached",
"source=/media/all/Media,target=/media,type=bind,consistency=cached"
],

Expand All @@ -24,10 +24,10 @@

"containerEnv": {
"PYTHONPATH": "${containerWorkspaceFolder}/backend",
"DEBUG": "False",
"LOG_LEVEL": "Info",
"TESTING": "False",
"APP_PORT": "7888",
"APP_DATA_DIR": "/data"
"APP_DATA_DIR": "/config"
},

"customizations": {
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
TZ="America/New_York" \
APP_NAME="Trailarr" \
APP_PORT=7889 \
APP_DATA_DIR="/data" \
APP_DATA_DIR="/config" \
PUID=1000 \
PGID=1000 \
APP_VERSION=${APP_VERSION}
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

[![Python](https://img.shields.io/badge/python-3.12-3670A0?style=flat&logo=python)](https://www.python.org/)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![FastAPI](https://img.shields.io/badge/FastAPI-0.112.0-009688.svg?style=flat&logo=FastAPI)](https://fastapi.tiangolo.com)
[![FastAPI](https://img.shields.io/badge/FastAPI-0.115.0-009688.svg?style=flat&logo=FastAPI)](https://fastapi.tiangolo.com)
[![Angular](https://img.shields.io/badge/angular-17.3.6-%23DD0031.svg?style=flat&logo=angular)](https://angular.dev/)
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://github.com/nandyalu/trailarr)

Expand Down Expand Up @@ -43,7 +43,7 @@ Documentation: [https://nandyalu.github.io/trailarr](https://nandyalu.github.io/

## Installation

See the [Installation](https://nandyalu.github.io/trailarr/install/install/) guide for detailed instructions on how to install Trailarr.
See the [Installation](https://nandyalu.github.io/trailarr/install/) guide for detailed instructions on how to install Trailarr.

## Setup

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""Add Media Status
Revision ID: 1cc1dd5dbe9f
Revises: 34f937f1d7b9
Create Date: 2024-09-17 13:27:49.217863
"""

from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa
from app_logger import ModuleLogger


# revision identifiers, used by Alembic.
revision: str = "1cc1dd5dbe9f"
down_revision: Union[str, None] = "34f937f1d7b9"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None

logging = ModuleLogger("AlembicMigrations")


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("media", schema=None) as batch_op:
batch_op.add_column(
sa.Column(
"status",
sa.Enum(
"DOWNLOADED",
"DOWNLOADING",
"MISSING",
"MONITORED",
name="monitorstatus",
),
server_default="MISSING",
nullable=False,
)
)

# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("media", schema=None) as batch_op:
batch_op.drop_column("status")

# ### end Alembic commands ###
30 changes: 28 additions & 2 deletions backend/api/v1/authentication.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,35 @@
import secrets
from typing import Annotated
from fastapi import Cookie, Depends, HTTPException
from fastapi.security import APIKeyHeader, APIKeyQuery
from fastapi import Cookie, Depends, HTTPException, status
from fastapi.security import APIKeyHeader, APIKeyQuery, HTTPBasic, HTTPBasicCredentials

from config.settings import app_settings

# Dependency to validate HHTP Basic Authentication in frontend
browser_security = HTTPBasic()


def validate_login(
credentials: Annotated[HTTPBasicCredentials, Depends(browser_security)],
):
current_username_bytes = credentials.username.encode("utf8")
correct_username_bytes = b"admin"
is_correct_username = secrets.compare_digest(
current_username_bytes, correct_username_bytes
)
current_password_bytes = credentials.password.encode("utf8")
correct_password_bytes = b"trailarr"
is_correct_password = secrets.compare_digest(
current_password_bytes, correct_password_bytes
)
if not (is_correct_username and is_correct_password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Basic"},
)
return True


# Dependency to validate the API key provided in the query or header
header_scheme = APIKeyHeader(name="X-API-KEY", auto_error=False)
Expand Down
6 changes: 5 additions & 1 deletion backend/api/v1/connections.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ async def get_connections() -> list[ConnectionRead]:
async def create_connection(connection: ConnectionCreate) -> str:
db_handler = ConnectionDatabaseManager()
try:
result = await db_handler.create(connection)
result, connection_id = await db_handler.create(connection)
await refresh_connection(connection_id)
except Exception as e:
await websockets.ws_manager.broadcast("Failed to add Connection!", "Error")
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
Expand Down Expand Up @@ -80,7 +81,10 @@ async def get_connection(connection_id: int) -> ConnectionRead:
async def update_connection(connection_id: int, connection: ConnectionUpdate) -> str:
db_handler = ConnectionDatabaseManager()
try:
# Update the connection in the database
await db_handler.update(connection_id, connection)
# Refresh data from API for the connection
await refresh_connection(connection_id)
except Exception as e:
await websockets.ws_manager.broadcast("Failed to update Connection!", "Error")
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e))
Expand Down
4 changes: 3 additions & 1 deletion backend/api/v1/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,13 @@ class Settings(BaseModel):
version: str
server_start_time: str
timezone: str
debug: bool
log_level: str
monitor_enabled: bool
monitor_interval: int
trailer_folder_movie: bool
trailer_folder_series: bool
trailer_resolution: int
trailer_file_name: str
trailer_file_format: str
trailer_audio_format: str
trailer_video_format: str
Expand All @@ -40,6 +41,7 @@ class Settings(BaseModel):
trailer_remove_sponsorblocks: bool
trailer_web_optimized: bool
wait_for_media: bool
yt_cookies_path: str


class UpdateSetting(BaseModel):
Expand Down
20 changes: 17 additions & 3 deletions backend/api/v1/movies.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,21 @@
from api.v1 import websockets
from api.v1.models import ErrorResponse
from core.base.database.manager.base import MediaDatabaseManager
from core.base.database.models.media import MediaRead, MediaUpdate
from core.base.database.models.media import MediaRead, MediaUpdate, MonitorStatus
from core.files_handler import FilesHandler, FolderInfo
from core.tasks.download_trailers import download_trailer_by_id


movies_router = APIRouter(prefix="/movies", tags=["Movies"])


@movies_router.get("/all")
async def get_all_movies() -> list[MediaRead]:
db_handler = MediaDatabaseManager()
movies = db_handler.read_all(movies_only=True)
return movies


@movies_router.get("/")
async def get_recent_movies(limit: int = 30, offset: int = 0) -> list[MediaRead]:
db_handler = MediaDatabaseManager()
Expand Down Expand Up @@ -108,7 +115,14 @@ async def monitor_movie(movie_id: int, monitor: bool = True) -> str:
msg = f"Movie '{movie.title}' [{movie.id}] already has a trailer!"
await websockets.ws_manager.broadcast(msg, "Error")
return msg
movie_update = MediaUpdate(monitor=monitor)
if monitor:
monitor_status = MonitorStatus.MONITORED
else:
if movie.trailer_exists:
monitor_status = MonitorStatus.DOWNLOADED
else:
monitor_status = MonitorStatus.MISSING
movie_update = MediaUpdate(monitor=monitor, status=monitor_status)
db_handler.update(movie_id, movie_update)
if monitor:
msg = f"Movie '{movie.title}' [{movie.id}] is now monitored"
Expand Down Expand Up @@ -151,7 +165,7 @@ async def delete_movie_trailer(movie_id: int) -> str:
msg = f"Failed to delete trailer for movie '{movie.title}' [{movie.id}]"
await websockets.ws_manager.broadcast(msg, "Error")
return msg
movie_update = MediaUpdate(trailer_exists=False)
movie_update = MediaUpdate(trailer_exists=False, status=MonitorStatus.MISSING)
db_handler.update(movie_id, movie_update)
msg = f"Trailer for movie '{movie.title}' [{movie.id}] has been deleted."
logging.info(msg)
Expand Down
20 changes: 17 additions & 3 deletions backend/api/v1/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,21 @@
from api.v1 import websockets
from api.v1.models import ErrorResponse
from core.base.database.manager.base import MediaDatabaseManager
from core.base.database.models.media import MediaRead, MediaUpdate
from core.base.database.models.media import MediaRead, MediaUpdate, MonitorStatus
from core.files_handler import FilesHandler, FolderInfo
from core.tasks.download_trailers import download_trailer_by_id


series_router = APIRouter(prefix="/series", tags=["Series"])


@series_router.get("/all")
async def get_all_series() -> list[MediaRead]:
db_handler = MediaDatabaseManager()
all_series = db_handler.read_all(movies_only=False)
return all_series


@series_router.get("/")
async def get_recent_series(limit: int = 30, offset: int = 0) -> list[MediaRead]:
db_handler = MediaDatabaseManager()
Expand Down Expand Up @@ -101,7 +108,14 @@ async def monitor_series(series_id: int, monitor: bool = True) -> str:
msg = f"Series '{series.title}' [{series.id}] already has a trailer!"
await websockets.ws_manager.broadcast(msg, "Error")
return msg
series_update = MediaUpdate(monitor=monitor)
if monitor:
monitor_status = MonitorStatus.MONITORED
else:
if series.trailer_exists:
monitor_status = MonitorStatus.DOWNLOADED
else:
monitor_status = MonitorStatus.MISSING
series_update = MediaUpdate(monitor=monitor, status=monitor_status)
db_handler.update(series_id, series_update)
if monitor:
msg = f"Series '{series.title}' [{series.id}] is now monitored"
Expand Down Expand Up @@ -146,7 +160,7 @@ async def delete_series_trailer(series_id: int) -> str:
msg = f"Failed to delete trailer for series '{series.title}' [{series.id}]"
await websockets.ws_manager.broadcast(msg, "Error")
return msg
series_update = MediaUpdate(trailer_exists=False)
series_update = MediaUpdate(trailer_exists=False, status=MonitorStatus.MISSING)
db_handler.update(series_id, series_update)
msg = f"Trailer for series '{series.title}' [{series.id}] has been deleted."
logging.info(msg)
Expand Down
31 changes: 2 additions & 29 deletions backend/app_logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import pathlib
import threading

from config import app_logger_opts
from config.settings import app_settings

_is_logging_setup = False
Expand All @@ -25,34 +26,6 @@ def stop_logging(queue: multiprocessing.Queue):
queue.close()


def set_handler_level(handler_name, level: int):
"""Set the level for a specific handler."""
logger = logging.getLogger()
for handler in logger.handlers:
if handler.get_name() == handler_name:
handler.setLevel(level)
break
return


def set_logger_level() -> None:
"""Set the log level for the root logger."""
log_levels = {
"DEBUG": logging.DEBUG,
"INFO": logging.INFO,
"WARNING": logging.WARNING,
"ERROR": logging.ERROR,
"CRITICAL": logging.CRITICAL,
}
_log_level = "DEBUG" if app_settings.debug else "INFO"
level = log_levels.get(_log_level, logging.INFO)
logging.getLogger().setLevel(level)
set_handler_level("console", level)
set_handler_level("file", level)
logging.info(f"Log level set to '{_log_level}'")
return


def config_logging():
"""Setup the logging configuration using the config file.
This will setup the root logger configuration and start the queue handler listener.
Expand All @@ -70,7 +43,7 @@ def config_logging():
logging.debug(f"Logger config file not found: {config_file}")

logging.config.dictConfig(config)
set_logger_level()
app_logger_opts.set_logger_level(app_settings.log_level)
logger_thread = threading.Thread(target=handle_logs, args=(queue,))
logger_thread.daemon = True
logger_thread.start()
Expand Down
Loading

0 comments on commit fd42601

Please sign in to comment.