Files
Stat-Dashboard/mockups/server_pg.py

63 lines
2.5 KiB
Python

"""Local static asset and read-only PostgreSQL API server."""
from datetime import date, datetime
from decimal import Decimal
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
import json
import logging
import os
from pathlib import Path
from urllib.parse import parse_qs, unquote, urlparse
import psycopg
from database import connect
from sample_api import ApiError, dispatch
STATIC_ROOT=Path(__file__).resolve().parent
def encode(value):
if isinstance(value,(date,datetime)): return value.isoformat()
if isinstance(value,Decimal): return float(value)
raise TypeError(f'Unsupported JSON type: {type(value).__name__}')
class Handler(SimpleHTTPRequestHandler):
def __init__(self,*args,**kwargs):
super().__init__(*args,directory=str(STATIC_ROOT),**kwargs)
def end_headers(self):
self.send_header('Cache-Control','no-store')
super().end_headers()
def send_json(self,data,status=200):
body=json.dumps(data,default=encode,allow_nan=False).encode('utf-8')
self.send_response(status)
self.send_header('Content-Type','application/json; charset=utf-8')
self.send_header('Content-Length',str(len(body)))
self.end_headers()
self.wfile.write(body)
def do_GET(self):
parsed=urlparse(self.path)
if parsed.path.startswith('/api/'):
try: return self.send_json(dispatch(parsed.path,parse_qs(parsed.query)))
except ApiError as error: return self.send_json({'error':error.message},error.status)
except (psycopg.Error,RuntimeError):
logging.exception('Database request failed')
return self.send_json({'error':'Sample database unavailable. Start PostgreSQL and run setup-database.ps1.'},503)
if parsed.path=='/':
self.send_response(302); self.send_header('Location','/mockups/overview-mockup.html'); self.end_headers(); return
name=unquote(parsed.path.removeprefix('/mockups/'))
if not parsed.path.startswith('/mockups/') or '/' in name or '\\' in name or Path(name).suffix not in ('.html','.css','.js'):
return self.send_error(404)
self.path='/'+name
return super().do_GET()
def main():
with connect() as conn: conn.execute('SELECT 1 FROM games LIMIT 1')
port=int(os.environ.get('MOCKUP_PORT','8011'))
server=ThreadingHTTPServer(('127.0.0.1',port),Handler)
print(f'PostgreSQL mockups: http://127.0.0.1:{port}/mockups/overview-mockup.html',flush=True)
try: server.serve_forever()
except KeyboardInterrupt: server.server_close()