# Eurovision 25 Homereview Backend ## Description This project provides the backend API for the "Eurovision 25 Homereview" application. It allows a small, known group of users, organized into households/groups, to collaboratively review and score Eurovision Song Contest entries during the live show. Users submit numerical scores (1-100) for stage show, wardrobe, and song quality, along with optional text comments. Users can update their scores until the contest is marked as finished by an admin. Afterward, users can view aggregated results within their group or across all participants, including basic statistics. This backend is built with FastAPI, SQLModel, and SQLite, focusing on core functionality for a fun, trusted-group experience. ## Key Features * **User Authentication & Management:** Secure login (JWT), Admin management of users (add, view, update/deactivate). * **Group Management:** Associates users with specific households/groups. * **Contest Control:** Admin can toggle contest status (active for reviews vs. inactive for results). * **Song Data:** Serves information about the participating songs. * **Review Submission & Updates:** Allows users to submit and update numerical scores (1-100 per category) and optional text reviews per song while the contest is active. * **Results Viewing:** Displays aggregated results (group or all) and basic statistics after the contest concludes. ## Technology Stack * **Framework:** FastAPI * **Database:** SQLite * **ORM / Data Validation:** SQLModel * **Python Version:** Python 3.12+ * **Package Manager:** uv * **ASGI Server:** Uvicorn * **Authentication:** JWT (`python-jose` or similar library recommended) * **Password Hashing:** `passlib` (using `bcrypt` or similar) recommended ## Project Structure ``` . ├── assets/ # Development assets (e.g., notes, mockups) - Not used by the running app ├── docs/ # Documentation (e.g., database_schema.md) │ └── database_schema.md ├── src/ # ALL application source code and runtime data │ ├── data/ # Data storage (e.g., SQLite database file) │ ├── routes/ # API endpoint definitions (routers) │ │ ├── auth.py │ │ ├── admin.py │ │ ├── users.py │ │ ├── songs.py │ │ ├── reviews.py │ │ └── results.py │ ├── models/ # SQLModel definitions │ ├── schemas/ # Pydantic schemas (if distinct from models) │ ├── static/ # Static files served directly by the web server/FastAPI │ │ └── avatars/ # User avatar images (e.g., 0.png, 1.png) │ └── app.py # FastAPI application instance and setup ├── .env.example # Example environment variables (if used) ├── .gitignore ├── pyproject.toml # Project metadata and dependencies for uv ├── LICENSE └── README.md ``` *(Note: Router filenames and static directory structure are suggestions)* ## Prerequisites * Python 3.12 or higher * [uv](https://github.com/astral-sh/uv) package manager (`pip install uv`) * Git ## Setup and Installation 1. **Clone the repository:** ```bash git clone cd eurovision-25-homereview-backend ``` 2. **Install dependencies using uv:** *(Dependencies are defined in `pyproject.toml`)* ```bash # Create and activate a virtual environment (recommended) # python -m venv .venv # source .venv/bin/activate # On Linux/macOS # .\.venv\Scripts\activate # On Windows # Install dependencies into the active virtual environment uv sync ``` 3. **Configuration:** * If using environment variables (e.g., for `SECRET_KEY` for JWT), copy `.env.example` to `.env` (in the project root) and fill in the required values. ## Running the Application Locally 1. **Static Files Setup (Development):** * Ensure the static files directory exists (e.g., `src/static/avatars/`) and contains the necessary avatar images (`0.jpg`, `1.jpg`, etc.). * In `src/app.py`, you likely need to mount the static directory using FastAPI's `StaticFiles`. Example: ```python # Inside src/app.py from fastapi.staticfiles import StaticFiles # ... other FastAPI setup ... app.mount("/static", StaticFiles(directory="src/static"), name="static") ``` *(Adjust the path `/static` and directory `"src/static"` as needed)* 2. **Start the development server:** *Ensure you are in the project's root directory.* ```bash # Assuming your FastAPI app instance is named 'app' in 'src/app.py' uvicorn src.app:app --reload ``` *(The command `src.app:app` correctly points to the `app` object inside `src/app.py`)* 3. **Access the API:** The API will typically be available at `http://127.0.0.1:8000`. Static files will be available under the path you mounted (e.g., `http://127.0.0.1:8000/static/avatars/1.jpg`). ## API Documentation & Endpoints FastAPI provides automatic interactive API documentation where you can explore and test the endpoints below: * **Swagger UI:** [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs) * **ReDoc:** [http://127.0.0.1:8000/redoc](http://127.0.0.1:8000/redoc) ### Authentication (`/auth`) * `POST /token` * **Action:** User login. Accepts `username` and `password`. * **Auth:** None required. * **Response:** Access Token (JWT). ### Users (`/users`) * `GET /users/me` * **Action:** Get details of the currently authenticated user. * **Auth:** Required (User). * **Response:** Current user's details (ID, username, group info, **avatar_id**). * **Note:** Clients should construct the avatar image URL using the `avatar_id` and the static path (e.g., `/static/avatars/{avatar_id}.jpg`). ### Contest/Songs (`/songs`, `/contest`) * `GET /songs/` * **Action:** Get the list of participating songs for the contest. * **Auth:** Required (User). * **Response:** List of song objects. * `GET /contest/status` * **Action:** Check if the contest is active (reviews allowed) or inactive (results available). * **Auth:** Required (User). * **Response:** `{ "is_active": true/false }`. ### Reviews (`/reviews`) * `POST /reviews/` * **Action:** Submit **or Update** a review for a song by the current user. Requires `song_id`, `score_song`, `score_show`, `score_wardrobe`, optional `text_review`. * **Auth:** Required (User). * **Notes:** Only allowed when the contest is active. Updates existing review if one exists for the user/song pair. * `GET /reviews/me` * **Action:** Get all reviews submitted by the current user. * **Auth:** Required (User). * **Response:** List of the user's review objects. ### Results (`/results`) * `GET /results/` * **Action:** Get aggregated song results. Use query param `?scope=group` (default) or `?scope=all`. * **Auth:** Required (User). * **Notes:** Only available when the contest is inactive. * **Response:** List of songs with aggregated scores based on scope. * `GET /results/stats` * **Action:** Get overall statistics (best song, wardrobe, etc.). Use query param `?scope=group` (default) or `?scope=all`. * **Auth:** Required (User). * **Notes:** Only available when the contest is inactive. * **Response:** Statistics object based on scope. ### Admin (`/admin`) * `PATCH /admin/contest/status` * **Action:** Set the contest status (active/inactive). Requires `{"is_active": boolean}`. * **Auth:** Required (Admin). * `POST /admin/users` * **Action:** Add a new user. Requires `username`, `password`, `group_id`. * **Auth:** Required (Admin). * `GET /admin/users` * **Action:** List users (optional query filters: `group_id`, `is_active`). * **Auth:** Required (Admin). * `GET /admin/users/{user_id}` * **Action:** Get details for a specific user. * **Auth:** Required (Admin). * `PATCH /admin/users/{user_id}` * **Action:** Update a user (optional: `group_id`, `password`, `is_active`). Use `is_active: false` to deactivate. * **Auth:** Required (Admin). ## Database * This application uses **SQLite** for data storage. * The database file (e.g., `database.sqlite`) is configured to be stored within the `src/data/` directory. * The application should create this file automatically on first run if it doesn't exist (depending on your SQLModel/database connection setup). You might need to ensure the `src/data/` directory exists or is created by the application. * **Database Migrations:** Currently, database migrations (like Alembic) are **not** implemented. Schema changes require manual database adjustments or deletion/recreation of the file in `src/data/`. * **Schema Documentation:** A high-level overview of the database schema can be found in `docs/database_schema.md`. ## Security Considerations * **Authentication:** Uses JWT Bearer tokens obtained via the `/token` endpoint. Most endpoints require a valid token. * **Authorization:** Admin endpoints require specific user privileges (implementation details depend on your user model/logic). * **Passwords:** User passwords should be securely hashed using `passlib` before storage. * **Scope:** This application is designed for a small group of known and trusted users. Features like self-registration are omitted. **Do not expose this backend directly to the public internet without further security hardening.** ## Static Assets (Avatars) * User avatars (e.g., `0.jpg`, `1.jpg`) are served as static files rather than through a dedicated API endpoint. * These files should be placed in a directory accessible to the web server (e.g., `src/static/avatars/`). * The web server (Nginx, Apache, or FastAPI's `StaticFiles` for development) must be configured to serve files from this directory under a specific URL path (e.g., `/static/avatars/`). * Clients are responsible for constructing the full URL to an avatar image using the `avatar_id` returned by the API and the configured static path. ## Testing Automated tests are not implemented at this time. Manual testing via the API documentation UI and beta testing by the user group are the primary methods of verification. ## Deployment * **Containerization:** The intended deployment method is containerization using **Podman** on a private home server. A `Containerfile`/`Dockerfile` should be placed in the project root. * **Static Files:** The production deployment **must** include configuring the web server (like Nginx running as a reverse proxy or alongside) to efficiently serve the static files (avatars) from their directory (e.g., `/var/www/html/static/avatars` or similar) under the expected URL path (e.g., `/static/avatars/`). This is more performant than serving via Python in production. * **Source Code:** The container build process needs to ensure the `src` directory contents are correctly copied into the container image. ## TODOs * [ ] Replace password hashing with passlib * [ ] Switch to PostgreSQL from DuckDB * [x] Implement more comprehensive logging * [ ] Implement user disabling functionality * [x] Implement user profile update functionality (name, email, password, avatar, etc.) ## Contributing This is primarily a personal project. Contributions are generally not expected. If you find a bug, feel free to open an issue in the repository. ## License This project is licensed under the **MIT License**. See the `LICENSE` file for details.