oedb-backend/oedb/resources/db_dump.py
2025-09-26 15:08:33 +02:00

193 lines
No EOL
6.8 KiB
Python

"""
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()