mapdash/main.py

105 lines
3.4 KiB
Python

import os
import httpx
from fastapi import FastAPI, Query
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
CH_HOST = os.environ.get("CLICKHOUSE_HOST", "ClickHouse")
CH_PORT = os.environ.get("CLICKHOUSE_PORT", "8123")
CH_USER = os.environ.get("CLICKHOUSE_USER", "default")
CH_PASSWORD = os.environ.get("CLICKHOUSE_PASSWORD", "1234567890")
CH_URL = f"http://{CH_HOST}:{CH_PORT}/"
app = FastAPI()
FILTER_SQL = """
char_length(city) > 0
AND (length({cities:Array(String)}) = 0 OR city IN {cities:Array(String)})
AND (length({dimensions:Array(String)}) = 0 OR dimension IN {dimensions:Array(String)})
AND (
{search:String} = ''
OR positionCaseInsensitive(address, {search:String}) > 0
OR positionCaseInsensitive(board_id, {search:String}) > 0
)
"""
async def ch_query(sql: str, params: dict):
form = {"query": sql, "default_format": "JSONEachRow"}
for k, v in params.items():
if isinstance(v, list):
form[f"param_{k}"] = "[" + ",".join("'" + str(x).replace("'", "\\'") + "'" for x in v) + "]"
else:
form[f"param_{k}"] = str(v)
async with httpx.AsyncClient(timeout=15) as client:
r = await client.post(CH_URL, params=form, auth=(CH_USER, CH_PASSWORD))
r.raise_for_status()
text = r.text.strip()
if not text:
return []
import json
return [json.loads(line) for line in text.splitlines()]
def parse_filters(city: str, dimension: str, search: str):
cities = [c for c in city.split(",") if c] if city else []
dimensions = [d for d in dimension.split(",") if d] if dimension else []
return {"cities": cities, "dimensions": dimensions, "search": search or ""}
@app.get("/api/meta")
async def meta():
cities = await ch_query(
"SELECT DISTINCT city FROM default.pf_board WHERE char_length(city) > 0 ORDER BY city",
{},
)
dimensions = await ch_query(
"SELECT DISTINCT dimension FROM default.pf_board WHERE char_length(dimension) > 0 ORDER BY dimension",
{},
)
return {
"cities": [r["city"] for r in cities],
"dimensions": [r["dimension"] for r in dimensions],
}
@app.get("/api/boards")
async def boards(city: str = "", dimension: str = "", search: str = ""):
params = parse_filters(city, dimension, search)
sql = f"""
SELECT
board_id,
argMax(address, last_activity) AS board_address,
argMax(city, last_activity) AS board_city,
argMax(dimension, last_activity) AS board_dimension
FROM default.pf_board
WHERE {FILTER_SQL}
GROUP BY board_id
ORDER BY board_address
"""
rows = await ch_query(sql, params)
return rows
@app.get("/api/bookings")
async def bookings(city: str = "", dimension: str = "", search: str = ""):
params = parse_filters(city, dimension, search)
sql = f"""
SELECT
board_id,
toString(task_id) AS task_id,
task_status,
toString(start_date) AS start_date,
toString(end_date) AS end_date,
brand,
company_name
FROM default.pf_board
WHERE {FILTER_SQL}
ORDER BY board_id, start_date
"""
rows = await ch_query(sql, params)
return rows
app.mount("/", StaticFiles(directory="static", html=True), name="static")