diff --git a/src/app.py b/src/app.py index acd2f8c..748b2f3 100644 --- a/src/app.py +++ b/src/app.py @@ -3,7 +3,7 @@ from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles # Import routers -from routes import auth, admin, users, songs, reviews, results +from routes import auth, admin, users, songs, reviews, results, contest from lib.logger import logger from lib.db import init_db @@ -60,3 +60,4 @@ app.include_router(users.router) app.include_router(songs.router) app.include_router(reviews.router) app.include_router(results.router) +app.include_router(contest.router) diff --git a/src/lib/database.sql b/src/lib/database.sql index 99b4deb..e2182a5 100644 --- a/src/lib/database.sql +++ b/src/lib/database.sql @@ -9,6 +9,7 @@ DROP TABLE IF EXISTS Review; DROP TABLE IF EXISTS Song; DROP TABLE IF EXISTS "User"; -- Quoted because USER is a reserved keyword DROP TABLE IF EXISTS Team; +DROP TABLE IF EXISTS Contest; DROP TABLE IF EXISTS CountryCodes; DROP VIEW IF EXISTS ReviewSummary; DROP VIEW IF EXISTS ReviewSummaryByTeam; @@ -17,6 +18,7 @@ DROP SEQUENCE IF EXISTS group_id_seq; DROP SEQUENCE IF EXISTS user_id_seq; DROP SEQUENCE IF EXISTS song_id_seq; DROP SEQUENCE IF EXISTS review_id_seq; +DROP SEQUENCE IF EXISTS contest_id_seq; -- ============================================================================= -- Sequences for Primary Keys @@ -25,6 +27,23 @@ CREATE SEQUENCE group_id_seq START 1; CREATE SEQUENCE user_id_seq START 1; CREATE SEQUENCE song_id_seq START 1; CREATE SEQUENCE review_id_seq START 1; +CREATE SEQUENCE contest_id_seq START 1; + +-- ============================================================================= +-- Table: Contest +-- Stores information about Eurovision contests by year. +-- ============================================================================= +CREATE TABLE Contest ( + id INTEGER PRIMARY KEY DEFAULT nextval('contest_id_seq'), -- Use sequence for auto-increment + year INTEGER DEFAULT EXTRACT(YEAR FROM CURRENT_DATE) NOT NULL, -- The year of the Eurovision contest + is_active BOOLEAN DEFAULT TRUE NOT NULL, -- Flag indicating if this is the active contest + finals_date DATE NOT NULL, -- Date of the finals + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Timestamp when the contest was created + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- Timestamp when the contest was last updated +); + +-- Add index for faster lookups by year +CREATE INDEX idx_contest_year ON Contest (year); -- ============================================================================= -- Table: Group @@ -183,6 +202,9 @@ INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('CHE', 'Switz INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('UKR', 'Ukraine', 'Ukraina', 'Ukraina'); INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('GBR', 'United Kingdom', 'Englanti', 'Storbritannien'); + +-- TODO: Clean this next year. Its now here just to get the show running +INSERT INTO Contest (year, finals_date) VALUES (2025, '2025-05-16'); -- ============================================================================= -- Views (Optional - Not Created Here Based on Documentation Decision) -- ============================================================================= diff --git a/src/models/contest.py b/src/models/contest.py new file mode 100644 index 0000000..23304a4 --- /dev/null +++ b/src/models/contest.py @@ -0,0 +1,25 @@ +from pydantic import BaseModel, Field +from datetime import date, datetime + + +class Contest(BaseModel): + id: int + year: int = Field( + default_factory=lambda: datetime.now().year, + description="The year of the contest", + examples=[2025], + ) + is_active: bool = Field( + default=True, description="Is this the active contest?", examples=[True, False] + ) + finals_date: date = Field( + description="The date of the finals", examples=["2025-05-15"] + ) + created_at: datetime = Field( + description="The date and time when the contest was created", + examples=["2025-05-15T17:40:42.000Z"], + ) + updated_at: datetime = Field( + description="The date and time when the contest was last updated", + examples=["2025-05-15T17:40:42.000Z"], + ) diff --git a/src/routes/contest.py b/src/routes/contest.py new file mode 100644 index 0000000..3bdb962 --- /dev/null +++ b/src/routes/contest.py @@ -0,0 +1,39 @@ +from fastapi import APIRouter, HTTPException + +from lib.db import get_connection +from lib.logger import logger +from models.contest import Contest +from models.msg import Message + +router = APIRouter(prefix="/contest", tags=["contest"]) + + +@router.get("/", response_model=list[Contest]) +async def list_contests(): + with get_connection() as conn: + logger.debug("Listing all contests") + df = conn.execute("SELECT * FROM Contest").fetchdf() + logger.debug(f"Contests: {df}") + contests = [Contest(**row) for row in df.to_dict(orient="records")] + return contests + + +@router.get("/{year}", response_model=Contest) +async def get_contest(year: int): + with get_connection() as conn: + df = conn.execute("SELECT * FROM Contest WHERE year = ?", (year,)).fetchdf() + if df.empty: + raise HTTPException(status_code=404, detail="Contest not found") + contest = Contest(**df.to_dict(orient="records")[0]) + return contest + + +@router.put("/", response_model=Message) +async def update_contest(year: int, is_active: bool): + with get_connection() as conn: + logger.debug(f"Updating contest {year} to active: {is_active}") + conn.execute( + "UPDATE Contest SET is_active = ? WHERE year = ?", + (is_active, year), + ) + return Message(message="Contest updated successfully")