import json import secrets from datetime import datetime, timezone import db JSON_FIELDS = {"track_coordinates", "track_times", "parse_warnings"} def _now() -> str: return datetime.now(timezone.utc).isoformat() def _row_to_dict(row) -> dict: if row is None: return None d = dict(row) for field in JSON_FIELDS: if d.get(field): try: d[field] = json.loads(d[field]) except (TypeError, ValueError): d[field] = None return d # --- settings ----------------------------------------------------------- def get_setting(key: str, default: str | None = None) -> str | None: with db.connect() as conn: row = conn.execute("SELECT value FROM settings WHERE key = ?", (key,)).fetchone() return row["value"] if row else default def set_setting(key: str, value: str) -> None: with db.connect() as conn: conn.execute( "INSERT INTO settings (key, value) VALUES (?, ?) " "ON CONFLICT(key) DO UPDATE SET value = excluded.value", (key, value), ) def get_or_create_secret_key() -> str: existing = get_setting("secret_key") if existing: return existing new_key = secrets.token_hex(32) set_setting("secret_key", new_key) return new_key DEFAULT_SETTINGS = { "notify_backend": "ntfy", "ntfy_server_url": "https://ntfy.sh", "ntfy_topic": "", "ntfy_auth_token": "", "daily_run_time": "03:00", } def get_all_settings() -> dict: return {key: get_setting(key, default) for key, default in DEFAULT_SETTINGS.items()} # --- pending_flights ------------------------------------------------------ INSERT_FIELDS = [ "image_path", "raw_bcbp", "passenger_name", "pnr", "from_iata", "to_iata", "from_icao", "to_icao", "operating_carrier_iata", "operating_carrier_icao", "flight_number", "julian_day", "flight_date", "known_departure_time", "compartment", "seat", "process_after", "parse_warnings", "error_message", "status", ] def create_pending_flight(**fields) -> int: now = _now() cols = ["created_at", "updated_at"] vals = [now, now] for f in INSERT_FIELDS: if f in fields: v = fields[f] if f in JSON_FIELDS and v is not None: v = json.dumps(v) cols.append(f) vals.append(v) placeholders = ", ".join("?" for _ in cols) with db.connect() as conn: cur = conn.execute( f"INSERT INTO pending_flights ({', '.join(cols)}) VALUES ({placeholders})", vals, ) return cur.lastrowid def get_flight(flight_id: int) -> dict | None: with db.connect() as conn: row = conn.execute( "SELECT * FROM pending_flights WHERE id = ?", (flight_id,) ).fetchone() return _row_to_dict(row) def list_flights(statuses: list[str] | None = None) -> list[dict]: with db.connect() as conn: if statuses: placeholders = ", ".join("?" for _ in statuses) rows = conn.execute( f"SELECT * FROM pending_flights WHERE status IN ({placeholders}) " "ORDER BY created_at DESC", statuses, ).fetchall() else: rows = conn.execute( "SELECT * FROM pending_flights ORDER BY created_at DESC" ).fetchall() return [_row_to_dict(r) for r in rows] def list_due_flights(now_iso: str) -> list[dict]: with db.connect() as conn: rows = conn.execute( "SELECT * FROM pending_flights WHERE status = 'queued' AND process_after <= ?", (now_iso,), ).fetchall() return [_row_to_dict(r) for r in rows] def update_flight(flight_id: int, **fields) -> None: if not fields: return cols = [] vals = [] for k, v in fields.items(): if k in JSON_FIELDS and v is not None and not isinstance(v, str): v = json.dumps(v) cols.append(f"{k} = ?") vals.append(v) cols.append("updated_at = ?") vals.append(_now()) vals.append(flight_id) with db.connect() as conn: conn.execute( f"UPDATE pending_flights SET {', '.join(cols)} WHERE id = ?", vals )