Initial commit.

Signed-off-by: Cliff Hill <xlorep@darkhelm.org>
This commit is contained in:
2025-10-18 09:14:10 -04:00
commit 9c4a8d96bc
12 changed files with 570 additions and 0 deletions

19
.env Normal file
View File

@@ -0,0 +1,19 @@
# Database Configuration
POSTGRES_DB=plex_playlist
POSTGRES_USER=plex_user
DATABASE_NAME=plex_playlist
DATABASE_HOST=database
DATABASE_PORT=5432
# Application Configuration
ENVIRONMENT=production
BACKEND_PORT=8000
FRONTEND_PORT=80
# Development Overrides
DEV_DATABASE_PORT=5433
DEV_BACKEND_PORT=8001
DEV_FRONTEND_PORT=5173
# Node Environment
NODE_ENV=production

67
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,67 @@
repos:
# General hooks for all files
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-merge-conflict
- id: check-added-large-files
- id: check-yaml
- id: check-json
- id: check-toml
- id: mixed-line-ending
# TOML linting
- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
rev: v2.14.0
hooks:
- id: pretty-format-toml
args: [--autofix]
# Python backend linting and formatting with ruff
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.9
hooks:
# Linter
- id: ruff
args: [--fix]
files: ^backend/
# Formatter
- id: ruff-format
files: ^backend/
# Python type checking with pyright
- repo: https://github.com/RobertCraigie/pyright-python
rev: v1.1.384
hooks:
- id: pyright
files: ^backend/
additional_dependencies: [fastapi, uvicorn, sqlalchemy, pydantic]
# Frontend linting and formatting
- repo: local
hooks:
# ESLint
- id: eslint
name: eslint
entry: bash -c 'cd frontend && npm run lint:fix'
language: system
files: ^frontend/.*\.(js|ts|vue)$
pass_filenames: false
# Prettier
- id: prettier
name: prettier
entry: bash -c 'cd frontend && npm run format'
language: system
files: ^frontend/.*\.(js|ts|vue|json|css|scss|md)$
pass_filenames: false
# TypeScript type checking
- id: typescript-check
name: typescript-check
entry: bash -c 'cd frontend && npm run type-check'
language: system
files: ^frontend/.*\.(ts|vue)$
pass_filenames: false

33
Dockerfile.backend Normal file
View File

@@ -0,0 +1,33 @@
# Backend Dockerfile for FastAPI with Python 3.13
FROM python:3.13-slim
# Set working directory
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
curl \
&& rm -rf /var/lib/apt/lists/*
# Install uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
# Create and activate virtual environment
ENV VIRTUAL_ENV=/app/.venv
RUN uv venv $VIRTUAL_ENV
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
# Copy dependency files first for better caching
COPY backend/pyproject.toml backend/uv.lock* ./
# Install dependencies
RUN uv sync --frozen
# Copy application code
COPY backend/ .
# Expose port
EXPOSE 8000
# Default command - can be overridden in compose for development
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

39
Dockerfile.frontend Normal file
View File

@@ -0,0 +1,39 @@
# Frontend Dockerfile for Vue/Vite TypeScript project
FROM node:20-alpine AS base
# Set working directory
WORKDIR /app
# Copy package files first for better caching
COPY frontend/package*.json ./
COPY frontend/yarn.lock* frontend/pnpm-lock.yaml* ./
# Install dependencies
RUN if [ -f yarn.lock ]; then yarn install; \
elif [ -f pnpm-lock.yaml ]; then npm install -g pnpm && pnpm install; \
else npm install; fi
# Copy application code
COPY frontend/ .
# Development stage
FROM base AS development
EXPOSE 5173
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]
# Production build stage
FROM base AS build
RUN if [ -f yarn.lock ]; then yarn build; \
elif [ -f pnpm-lock.yaml ]; then pnpm build; \
else npm run build; fi
# Production stage
FROM nginx:alpine AS production
# Copy built assets from build stage
COPY --from=build /app/dist /usr/share/nginx/html
# Copy nginx configuration
COPY frontend/nginx.conf /etc/nginx/nginx.conf
# Expose port
EXPOSE 80
# Start nginx
CMD ["nginx", "-g", "daemon off;"]

170
README.md Normal file
View File

@@ -0,0 +1,170 @@
# Plex Playlist Project
A full-stack application for managing Plex playlists with a FastAPI backend and Vue.js frontend.
## Architecture
- **Backend**: Python 3.13 + FastAPI + uv + ruff
- **Frontend**: TypeScript + Vue.js + Vite
- **Database**: PostgreSQL 16
- **Containerization**: Docker + Docker Compose
## Development Setup
### Prerequisites
- Docker and Docker Compose
- Git
### Running in Development Mode
1. Clone the repository and navigate to the project directory:
```bash
cd plex-playlist
```
2. Start the development environment:
```bash
docker compose -f compose.yml -f compose.dev.yml up --build
```
This will start:
- PostgreSQL database on port 5433
- FastAPI backend on port 8001 (with hot reload)
- Vue.js frontend on port 5173 (with hot reload)
### Running in Production Mode
```bash
docker compose up --build -d
```
This will start:
- PostgreSQL database on port 5432
- FastAPI backend on port 8000
- Vue.js frontend on port 80
## Project Structure
```
plex-playlist/
├── backend/ # FastAPI backend
├── frontend/ # Vue.js frontend
│ └── nginx.conf # Nginx configuration
├── Dockerfile.backend # Backend Docker image
├── Dockerfile.frontend # Frontend Docker image
├── compose.yml # Production Docker Compose
├── compose.dev.yml # Development Docker Compose override
└── README.md
```
## Environment Variables
### Backend
- `DATABASE_URL`: PostgreSQL connection string
- `ENVIRONMENT`: `development` or `production`
- `RELOAD`: Enable uvicorn auto-reload (development only)
### Frontend
- `NODE_ENV`: `development` or `production`
## Database
The PostgreSQL database is configured with:
- Database: `plex_playlist`
- User: `plex_user`
- Password: `plex_password`
## Development Workflow
1. Make changes to your code
2. The development containers will automatically reload:
- Backend: uvicorn with `--reload` flag
- Frontend: Vite dev server with hot module replacement
## API Documentation
When running, the FastAPI automatic documentation is available at:
- Development: http://localhost:8001/docs
- Production: http://localhost:8000/docs
---
# Manual Setup (if not using Docker)
## Backend Setup (FastAPI, Python 3.13, uv, ruff, pyright)
### 1. Create and activate the uv virtual environment
```sh
uv venv plex-playlist
source plex-playlist/bin/activate
```
### 2. Install dependencies
```sh
uv pip install fastapi uvicorn[standard]
uv pip install ruff
```
### 3. Install dev tools
```sh
uv pip install pyright
```
### 4. Project structure
- `app/` - FastAPI application code
- `tests/` - Test suite
- `pyrightconfig.json` - Pyright type checking config
- `ruff.toml` - Ruff linter config
### 5. Run the development server
```sh
uvicorn app.main:app --reload
```
---
## Frontend Setup (Vue 3, Vite, TypeScript)
### 1. Create the project
```sh
cd frontend
npm create vite@latest . -- --template vue-ts
npm install
```
### 2. Recommended: Enable strictest TypeScript settings
Edit `tsconfig.json` and set:
```json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
}
}
```
## 3. Run the frontend
```sh
npm run dev
```
---
See the `backend/` and `frontend/` folders for more details.

78
backend/pyproject.toml Normal file
View File

@@ -0,0 +1,78 @@
[tool.ruff]
# Exclude common directories
exclude = [
".bzr",
".direnv",
".eggs",
".git",
".git-rewrite",
".hg",
".ipynb_checkpoints",
".mypy_cache",
".nox",
".pants.d",
".pyenv",
".pytest_cache",
".pytype",
".ruff_cache",
".svn",
".tox",
".venv",
".vscode",
"__pypackages__",
"_build",
"buck-out",
"build",
"dist",
"node_modules",
"site-packages",
"venv"
]
# Set the maximum line length
line-length = 88
target-version = "py313"
[tool.ruff.format]
# Like Black, indent with spaces, rather than tabs.
indent-style = "space"
# Like Black, automatically detect the appropriate line ending.
line-ending = "auto"
# Like Black, use double quotes for strings.
quote-style = "double"
# Like Black, respect magic trailing commas.
skip-magic-trailing-comma = false
[tool.ruff.lint]
# Allow unused variables when underscore-prefixed.
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
# Allow fix for all enabled rules (when `--fix`) is provided.
fixable = ["ALL"]
# Ignore specific rules
ignore = [
"E501", # line too long (handled by line-length setting)
"B008", # do not perform function calls in argument defaults
"RUF012" # mutable class attributes should be annotated with typing.ClassVar
]
# Enable specific rule sets
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"B", # flake8-bugbear
"C4", # flake8-comprehensions
"UP", # pyupgrade
"ARG", # flake8-unused-arguments
"SIM", # flake8-simplify
"TCH", # flake8-type-checking
"PTH", # flake8-use-pathlib
"RUF" # ruff-specific rules
]
unfixable = []
[tool.ruff.lint.isort]
known-first-party = ["app"]
[tool.ruff.lint.per-file-ignores]
# Tests can use magic values, assertions, and relative imports
"tests/**/*" = ["PLR2004", "S101", "TID252"]

View File

@@ -0,0 +1,34 @@
{
"reportMissingImports": true,
"reportMissingTypeStubs": false,
"pythonVersion": "3.13",
"pythonPlatform": "Linux",
"executionEnvironments": [
{
"root": "."
}
],
"typeCheckingMode": "strict",
"useLibraryCodeForTypes": true,
"reportGeneralTypeIssues": true,
"reportPropertyTypeMismatch": true,
"reportFunctionMemberAccess": true,
"reportMissingParameterType": true,
"reportMissingTypeArgument": true,
"reportIncompatibleMethodOverride": "error",
"reportIncompatibleVariableOverride": true,
"reportInconsistentConstructor": true,
"strictListInference": true,
"strictDictionaryInference": true,
"strictSetInference": true,
"reportCallInDefaultInitializer": true,
"reportUnnecessaryIsInstance": true,
"reportUnnecessaryCast": true,
"reportUnnecessaryComparison": true,
"reportUnnecessaryContains": true,
"reportImplicitStringConcatenation": true,
"reportUnusedCoroutine": true,
"reportPrivateUsage": "warning",
"reportConstantRedefinition": "warning",
"reportImplicitOverride": "warning"
}

37
compose.dev.yml Normal file
View File

@@ -0,0 +1,37 @@
services:
database:
ports:
- "5433:5432" # Different port to avoid conflicts with local PostgreSQL
backend:
build:
context: .
dockerfile: Dockerfile.backend
environment:
DATABASE_URL: postgresql://plex_user:plex_password@database:5432/plex_playlist
ENVIRONMENT: development
RELOAD: "true"
volumes:
- ./backend:/app
- backend_venv:/app/.venv
ports:
- "8001:8000" # Different port for development
command: ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
frontend:
build:
context: .
dockerfile: Dockerfile.frontend
target: development
volumes:
- ./frontend:/app
- frontend_node_modules:/app/node_modules
ports:
- "5173:5173" # Vite default dev port
environment:
- NODE_ENV=development
volumes:
postgres_data:
backend_venv:
frontend_node_modules:

44
compose.yml Normal file
View File

@@ -0,0 +1,44 @@
services:
database:
image: postgres:16-alpine
environment:
POSTGRES_DB: plex_playlist
POSTGRES_USER: plex_user
POSTGRES_PASSWORD: plex_password
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U plex_user -d plex_playlist"]
interval: 10s
timeout: 5s
retries: 5
backend:
build:
context: .
dockerfile: Dockerfile.backend
environment:
DATABASE_URL: postgresql://plex_user:plex_password@database:5432/plex_playlist
ENVIRONMENT: production
ports:
- "8000:8000"
depends_on:
database:
condition: service_healthy
restart: unless-stopped
frontend:
build:
context: .
dockerfile: Dockerfile.frontend
target: production
ports:
- "80:80"
depends_on:
- backend
restart: unless-stopped
volumes:
postgres_data:

38
frontend/nginx.conf Normal file
View File

@@ -0,0 +1,38 @@
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# Handle client-side routing
location / {
try_files $uri $uri/ /index.html;
}
# API proxy to backend
location /api/ {
proxy_pass http://backend:8000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Enable gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
}
}

10
renovate.json Normal file
View File

@@ -0,0 +1,10 @@
{
"extends": ["config:recommended"],
"packageRules": [
{
"matchUpdateTypes": ["minor", "patch", "pin", "digest"],
"automerge": true
}
],
"osvVulnerabilityAlerts": true
}

View File

@@ -0,0 +1 @@
plex_password