""" Database dump resource for the OpenEventDatabase. Provides endpoints to list and create database dumps. """ import os import subprocess import datetime import falcon import psycopg2.extras import json from pathlib import Path from oedb.utils.db import db_connect from oedb.utils.serialization import dumps from oedb.utils.logging import logger # Directory to store database dumps DUMPS_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../dumps')) # Ensure the dumps directory exists os.makedirs(DUMPS_DIR, exist_ok=True) class DbDumpListResource: """ Resource for listing database dumps. Handles the /db/dumps endpoint. """ def on_get(self, req, resp): """ Handle GET requests to the /db/dumps endpoint. Lists all available database dumps. Args: req: The request object. resp: The response object. """ logger.info("Processing GET request to /db/dumps") try: # Get list of dump files dump_files = [] for ext in ['sql', 'geojson']: for file_path in Path(DUMPS_DIR).glob(f'*.{ext}'): stat = file_path.stat() dump_files.append({ 'filename': file_path.name, 'path': f'/db/dumps/{file_path.name}', 'size': stat.st_size, 'created': datetime.datetime.fromtimestamp(stat.st_ctime).isoformat(), 'type': ext }) # Sort by creation time (newest first) dump_files.sort(key=lambda x: x['created'], reverse=True) resp.text = dumps({'dumps': dump_files}) resp.status = falcon.HTTP_200 logger.success("Successfully processed GET request to /db/dumps") except Exception as e: logger.error(f"Error processing GET request to /db/dumps: {e}") resp.status = falcon.HTTP_500 resp.text = dumps({"error": str(e)}) class DbDumpCreateResource: """ Resource for creating database dumps. Handles the /db/dumps/create endpoint. """ def on_post(self, req, resp): """ Handle POST requests to the /db/dumps/create endpoint. Creates a new database dump in SQL and GeoJSON formats. Args: req: The request object. resp: The response object. """ logger.info("Processing POST request to /db/dumps/create") try: # Generate timestamp for filenames timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") # Create SQL dump sql_filename = f"oedb_dump_{timestamp}.sql" sql_path = os.path.join(DUMPS_DIR, sql_filename) # Get database connection parameters from environment dbname = os.getenv("DB_NAME", "oedb") host = os.getenv("DB_HOST", "localhost") user = os.getenv("DB_USER", "postgres") password = os.getenv("POSTGRES_PASSWORD", "") # Set PGPASSWORD environment variable for pg_dump env = os.environ.copy() env["PGPASSWORD"] = password # Execute pg_dump command pg_dump_cmd = [ "pg_dump", "-h", host, "-U", user, "-d", dbname, "-f", sql_path ] logger.info(f"Creating SQL dump: {sql_filename}") subprocess.run(pg_dump_cmd, env=env, check=True) # Create GeoJSON dump geojson_filename = f"oedb_dump_{timestamp}.geojson" geojson_path = os.path.join(DUMPS_DIR, geojson_filename) # Connect to database db = db_connect() cur = db.cursor(cursor_factory=psycopg2.extras.RealDictCursor) # Query all events logger.info(f"Creating GeoJSON dump: {geojson_filename}") cur.execute("SELECT * FROM events;") rows = cur.fetchall() # Convert to GeoJSON features = [] for row in rows: # Extract geometry geom = None if row.get('events_where'): try: geom = json.loads(row['events_where']) except: pass # Create feature feature = { "type": "Feature", "geometry": geom or {"type": "Point", "coordinates": [0, 0]}, "properties": { "id": row.get('events_id'), "what": row.get('events_what'), "label": row.get('events_label'), "when": { "start": row.get('events_when', {}).get('lower', None), "stop": row.get('events_when', {}).get('upper', None) }, "tags": row.get('events_tags'), "createdate": row.get('createdate'), "lastupdate": row.get('lastupdate') } } features.append(feature) # Write GeoJSON file with open(geojson_path, 'w') as f: json.dump({ "type": "FeatureCollection", "features": features }, f) # Return information about created dumps resp.text = dumps({ "message": "Database dumps created successfully", "dumps": [ { "filename": sql_filename, "path": f"/db/dumps/{sql_filename}", "type": "sql", "size": os.path.getsize(sql_path) }, { "filename": geojson_filename, "path": f"/db/dumps/{geojson_filename}", "type": "geojson", "size": os.path.getsize(geojson_path) } ] }) resp.status = falcon.HTTP_201 logger.success("Successfully processed POST request to /db/dumps/create") except Exception as e: logger.error(f"Error processing POST request to /db/dumps/create: {e}") resp.status = falcon.HTTP_500 resp.text = dumps({"error": str(e)}) finally: if 'db' in locals() and db: cur.close() db.close() # Create resource instances db_dump_list = DbDumpListResource() db_dump_create = DbDumpCreateResource()