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