"""Postgres connection pool + small query helpers for the yaris-obd remote server.""" import os from contextlib import contextmanager from psycopg_pool import ConnectionPool _POOL = None def _dsn(): return ( f"host={os.environ.get('POSTGRES_HOST', 'db')} " f"port={os.environ.get('POSTGRES_PORT', '5432')} " f"dbname={os.environ.get('POSTGRES_DB', 'yarisobd')} " f"user={os.environ.get('POSTGRES_USER', 'yarisobd')} " f"password={os.environ.get('POSTGRES_PASSWORD', '')}" ) def get_pool(): global _POOL if _POOL is None: _POOL = ConnectionPool(conninfo=_dsn(), min_size=1, max_size=5, open=True) return _POOL @contextmanager def get_cursor(commit=False): """Yield a cursor from the pool, optionally committing on clean exit.""" pool = get_pool() with pool.connection() as conn: with conn.cursor() as cur: yield cur if commit: conn.commit() def get_device_by_name(name): with get_cursor() as cur: cur.execute( "SELECT id, name, api_key_hash FROM devices WHERE name = %s", (name,), ) row = cur.fetchone() if row is None: return None return {"id": row[0], "name": row[1], "api_key_hash": row[2]} def touch_device_last_seen(device_id): with get_cursor(commit=True) as cur: cur.execute( "UPDATE devices SET last_seen_at = now() WHERE id = %s", (device_id,), ) def insert_tracking_points(device_id, points): """Bulk-insert tracking points, skipping any that already exist (device_id, recorded_at) so a client can safely re-send a batch after a partial-failure retry without creating duplicates.""" if not points: return 0 with get_cursor(commit=True) as cur: cur.executemany( """ INSERT INTO tracking_points (device_id, vehicle_name, driver_name, recorded_at, gps_lat, gps_lon, speed, odometer) VALUES (%(device_id)s, %(vehicle_name)s, %(driver_name)s, %(recorded_at)s, %(gps_lat)s, %(gps_lon)s, %(speed)s, %(odometer)s) ON CONFLICT (device_id, recorded_at) DO NOTHING """, [ { "device_id": device_id, "vehicle_name": p.get("vehicle_name"), "driver_name": p.get("driver_name"), "recorded_at": p["recorded_at"], "gps_lat": p.get("gps_lat"), "gps_lon": p.get("gps_lon"), "speed": p.get("speed"), "odometer": p.get("odometer"), } for p in points ], ) return len(points) def dashcam_file_exists(device_id, relative_path, sha256): with get_cursor() as cur: cur.execute( "SELECT 1 FROM dashcam_files WHERE device_id = %s AND relative_path = %s AND sha256 = %s", (device_id, relative_path, sha256), ) return cur.fetchone() is not None def insert_dashcam_file(device_id, relative_path, size_bytes, sha256, storage_path): with get_cursor(commit=True) as cur: cur.execute( """ INSERT INTO dashcam_files (device_id, relative_path, size_bytes, sha256, storage_path) VALUES (%s, %s, %s, %s, %s) ON CONFLICT (device_id, relative_path, sha256) DO NOTHING """, (device_id, relative_path, size_bytes, sha256, storage_path), )