Add PostgreSQL analytics dashboard prototype and realistic sample data
This commit is contained in:
62
mockups/server_pg.py
Normal file
62
mockups/server_pg.py
Normal file
@@ -0,0 +1,62 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user