diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6b244df --- /dev/null +++ b/.gitignore @@ -0,0 +1,42 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# Virtual environments +venv/ +ENV/ +.env + +# IDE specific files +.idea/ +.vscode/ +*.swp +*.swo + +# Docker +.dockerignore + +# Application specific +docker/midtownplaydio/src/static/ + +# OS specific +.DS_Store +Thumbs.db \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..0ffd9f1 --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +# MidTownPlaydio + +A progressive web app (PWA) to stream Midtown Radio KW in a mobile-friendly interface. + +## Features + +- Responsive web interface +- Progressive Web App (PWA) capabilities +- Offline caching +- Mobile-friendly design + +## Development + +### Prerequisites + +- Docker and Docker Compose + +### Running Locally + +```bash +# Clone the repository +git clone https://git.nixc.us/colin/midtownplaydio.git +cd midtownplaydio + +# Start the development server +docker-compose up +``` + +The app will be available at http://localhost:3000 + +### Directory Structure + +``` +├── docker/ +│ └── midtownplaydio/ +│ ├── src/ # Source code +│ ├── Dockerfile # Development/staging Dockerfile +│ └── Dockerfile.production # Production Dockerfile +├── docker-compose.yml # Local development setup +├── docker-compose.staging.yml # Staging deployment +├── docker-compose.production.yml # Production deployment +├── stack.staging.yml # Docker Swarm stack for staging +├── stack.production.yml # Docker Swarm stack for production +└── .woodpecker.yml # CI/CD configuration +``` + +## Deployment + +The application is set up for automatic deployment through Woodpecker CI. + +- Push to `main` branch deploys to staging environment +- Cron job or manual promotion deploys to production + +## License + +Copyright (c) 2023 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..fe2ad6c --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,14 @@ +services: + fancy-qr: + build: + context: ./docker/fancy-qr + dockerfile: Dockerfile + ports: + - "3000:3000" + volumes: + - ./docker/fancy-qr/src:/app/web + - ./uploads:/app/web/uploads + - ./output:/app/web/outputs + environment: + - NODE_ENV=development + restart: unless-stopped \ No newline at end of file diff --git a/docker/midtownplaydio/Dockerfile b/docker/midtownplaydio/Dockerfile new file mode 100644 index 0000000..f156119 --- /dev/null +++ b/docker/midtownplaydio/Dockerfile @@ -0,0 +1,31 @@ +FROM python:3.11-slim + +WORKDIR /app + +# Copy requirements first for better caching +COPY src/requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Install development tools +RUN pip install --no-cache-dir pytest pytest-cov black isort + +# Copy the rest of the application +COPY src/ . + +# Create directory for static files +RUN mkdir -p /app/static + +# Create non-root user for security +RUN adduser --disabled-password --gecos '' appuser +RUN chown -R appuser:appuser /app +USER appuser + +# Expose the port the app runs on +EXPOSE 3000 + +# Health check +HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:3000/health || exit 1 + +# Start with uvicorn for hot reloading +CMD uvicorn app:app --host 0.0.0.0 --port 3000 --reload \ No newline at end of file diff --git a/docker/midtownplaydio/Dockerfile.production b/docker/midtownplaydio/Dockerfile.production new file mode 100644 index 0000000..f482914 --- /dev/null +++ b/docker/midtownplaydio/Dockerfile.production @@ -0,0 +1,28 @@ +FROM python:3.11-slim + +WORKDIR /app + +# Copy requirements first for better caching +COPY src/requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Copy the rest of the application +COPY src/ . + +# Create directory for static files +RUN mkdir -p /app/static + +# Create non-root user for security +RUN adduser --disabled-password --gecos '' appuser +RUN chown -R appuser:appuser /app +USER appuser + +# Expose the port the app runs on +EXPOSE 3000 + +# Health check +HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:3000/health || exit 1 + +# Run the application with Gunicorn +CMD gunicorn -w 4 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:3000 app:app \ No newline at end of file diff --git a/docker/midtownplaydio/src/app.py b/docker/midtownplaydio/src/app.py new file mode 100644 index 0000000..5630064 --- /dev/null +++ b/docker/midtownplaydio/src/app.py @@ -0,0 +1,161 @@ +from fastapi import FastAPI, Request +from fastapi.responses import HTMLResponse, JSONResponse, FileResponse +from fastapi.staticfiles import StaticFiles +import os +import json + +app = FastAPI(title="MidTownPlaydio") + +# Directory to store static files +STATIC_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "static") +os.makedirs(STATIC_DIR, exist_ok=True) + +# Generate index.html +def generate_index_html(): + with open(os.path.join(STATIC_DIR, 'index.html'), 'w') as f: + f.write(''' + + +
+