from db import get_connection def create_match_clock_table() -> None: query = """ CREATE TABLE IF NOT EXISTS match_clocks ( match_id BIGINT PRIMARY KEY REFERENCES matches(id) ON DELETE CASCADE, current_period VARCHAR(10) NOT NULL DEFAULT '1H', timer_running BOOLEAN NOT NULL DEFAULT FALSE, accumulated_seconds INTEGER NOT NULL DEFAULT 0, period_started_at TIMESTAMP NULL, updated_at TIMESTAMP NOT NULL DEFAULT NOW() ); """ conn = get_connection() try: with conn.cursor() as cur: cur.execute(query) conn.commit() finally: conn.close() def ensure_match_clock(match_id: int) -> None: query = """ INSERT INTO match_clocks (match_id, current_period, timer_running, accumulated_seconds, period_started_at, updated_at) VALUES (%s, '1H', FALSE, 0, NULL, NOW()) ON CONFLICT (match_id) DO NOTHING; """ conn = get_connection() try: with conn.cursor() as cur: cur.execute(query, (match_id,)) conn.commit() finally: conn.close() def get_match_clock(match_id: int) -> dict: ensure_match_clock(match_id) query = """ SELECT match_id, current_period, timer_running, accumulated_seconds, period_started_at, CASE WHEN timer_running = TRUE AND period_started_at IS NOT NULL THEN accumulated_seconds + FLOOR(EXTRACT(EPOCH FROM (NOW() - period_started_at)))::INT ELSE accumulated_seconds END AS current_seconds FROM match_clocks WHERE match_id = %s; """ conn = get_connection() try: with conn.cursor() as cur: cur.execute(query, (match_id,)) row = cur.fetchone() finally: conn.close() return { "match_id": row[0], "current_period": row[1], "timer_running": bool(row[2]), "accumulated_seconds": row[3] or 0, "period_started_at": row[4].isoformat() if row[4] else None, "current_seconds": row[5] or 0, } def update_match_clock(match_id: int, action: str, seconds: int | None = None) -> dict: ensure_match_clock(match_id) conn = get_connection() try: with conn.cursor() as cur: if action == "start_1h": cur.execute( """ UPDATE match_clocks SET current_period = '1H', timer_running = TRUE, accumulated_seconds = 0, period_started_at = NOW(), updated_at = NOW() WHERE match_id = %s; """, (match_id,), ) elif action == "pause": cur.execute( """ UPDATE match_clocks SET accumulated_seconds = CASE WHEN timer_running = TRUE AND period_started_at IS NOT NULL THEN accumulated_seconds + FLOOR(EXTRACT(EPOCH FROM (NOW() - period_started_at)))::INT ELSE accumulated_seconds END, timer_running = FALSE, period_started_at = NULL, updated_at = NOW() WHERE match_id = %s; """, (match_id,), ) elif action == "halftime": cur.execute( """ UPDATE match_clocks SET accumulated_seconds = CASE WHEN timer_running = TRUE AND period_started_at IS NOT NULL THEN accumulated_seconds + FLOOR(EXTRACT(EPOCH FROM (NOW() - period_started_at)))::INT ELSE accumulated_seconds END, current_period = 'HT', timer_running = FALSE, period_started_at = NULL, updated_at = NOW() WHERE match_id = %s; """, (match_id,), ) elif action == "start_2h": cur.execute( """ UPDATE match_clocks SET current_period = '2H', timer_running = TRUE, accumulated_seconds = CASE WHEN accumulated_seconds < 2700 THEN 2700 ELSE accumulated_seconds END, period_started_at = NOW(), updated_at = NOW() WHERE match_id = %s; """, (match_id,), ) elif action == "finish": cur.execute( """ UPDATE match_clocks SET accumulated_seconds = CASE WHEN timer_running = TRUE AND period_started_at IS NOT NULL THEN accumulated_seconds + FLOOR(EXTRACT(EPOCH FROM (NOW() - period_started_at)))::INT ELSE accumulated_seconds END, current_period = 'FT', timer_running = FALSE, period_started_at = NULL, updated_at = NOW() WHERE match_id = %s; """, (match_id,), ) elif action == "set_time": if seconds is None or not isinstance(seconds, int) or seconds < 0: raise ValueError("invalid_seconds") cur.execute( """ UPDATE match_clocks SET accumulated_seconds = %s, period_started_at = CASE WHEN timer_running THEN NOW() ELSE NULL END, updated_at = NOW() WHERE match_id = %s; """, (seconds, match_id), ) else: raise ValueError("invalid_clock_action") conn.commit() except Exception: conn.rollback() raise finally: conn.close() return get_match_clock(match_id)