19
.env
Normal file
19
.env
Normal 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
67
.pre-commit-config.yaml
Normal 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
33
Dockerfile.backend
Normal 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
39
Dockerfile.frontend
Normal 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
170
README.md
Normal 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
78
backend/pyproject.toml
Normal 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"]
|
||||
34
backend/pyrightconfig.json
Normal file
34
backend/pyrightconfig.json
Normal 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
37
compose.dev.yml
Normal 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
44
compose.yml
Normal 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
38
frontend/nginx.conf
Normal 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
10
renovate.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": ["config:recommended"],
|
||||
"packageRules": [
|
||||
{
|
||||
"matchUpdateTypes": ["minor", "patch", "pin", "digest"],
|
||||
"automerge": true
|
||||
}
|
||||
],
|
||||
"osvVulnerabilityAlerts": true
|
||||
}
|
||||
1
secrets/postgres_password
Normal file
1
secrets/postgres_password
Normal file
@@ -0,0 +1 @@
|
||||
plex_password
|
||||
Reference in New Issue
Block a user