Move frontend to subdirectory and add backend
|
@ -0,0 +1 @@
|
||||||
|
__pycache__
|
|
@ -0,0 +1,155 @@
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
import psycopg2
|
||||||
|
from fastapi import FastAPI, HTTPException
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from psycopg2.extras import RealDictCursor
|
||||||
|
from pydantic import BaseModel, validator
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class CountryBands(BaseModel):
|
||||||
|
uuid: str
|
||||||
|
country: str
|
||||||
|
bands: List[str]
|
||||||
|
|
||||||
|
@validator("uuid")
|
||||||
|
def is_uuid(cls, v):
|
||||||
|
assert re.match(r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", v)
|
||||||
|
return v
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
db = psycopg2.connect(dbname="internationale")
|
||||||
|
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"],
|
||||||
|
allow_credentials=False,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.post("/update")
|
||||||
|
def update_country(cb: CountryBands):
|
||||||
|
try:
|
||||||
|
with db.cursor(cursor_factory=RealDictCursor) as csr:
|
||||||
|
csr.execute(
|
||||||
|
"select * from entries where uuid = %s and country = %s",
|
||||||
|
(cb.uuid, cb.country))
|
||||||
|
bands = [r["band"] for r in csr]
|
||||||
|
for b in cb.bands:
|
||||||
|
if b in bands:
|
||||||
|
bands.remove(b)
|
||||||
|
else:
|
||||||
|
csr.execute(
|
||||||
|
"select * from users where uuid = %s",
|
||||||
|
(cb.uuid,))
|
||||||
|
if not csr.fetchall():
|
||||||
|
csr.execute(
|
||||||
|
"insert into users(uuid, public_id) values(%s, %s)",
|
||||||
|
(cb.uuid, cb.uuid[24:],))
|
||||||
|
csr.execute(
|
||||||
|
"insert into entries(uuid, country, band) values(%s, %s, %s)",
|
||||||
|
(cb.uuid, cb.country, b))
|
||||||
|
for b in bands:
|
||||||
|
csr.execute(
|
||||||
|
"""
|
||||||
|
delete from entries
|
||||||
|
where uuid = %s and country = %s and band = %s
|
||||||
|
""",
|
||||||
|
(cb.uuid, cb.country, b))
|
||||||
|
db.commit()
|
||||||
|
csr.execute(
|
||||||
|
"select count(distinct band) from entries where country = %s",
|
||||||
|
(cb.country,))
|
||||||
|
count = csr.fetchone()["count"]
|
||||||
|
return count
|
||||||
|
except Exception as e:
|
||||||
|
log.exception("caught error")
|
||||||
|
db.rollback()
|
||||||
|
return HTTPException(500)
|
||||||
|
|
||||||
|
@app.get("/countries/{lang}")
|
||||||
|
def get_countries(lang: str):
|
||||||
|
try:
|
||||||
|
with db.cursor(cursor_factory=RealDictCursor) as csr:
|
||||||
|
csr.execute(
|
||||||
|
"""
|
||||||
|
select * from countries join country_names using (code)
|
||||||
|
where lang = %s
|
||||||
|
order by population desc
|
||||||
|
""",
|
||||||
|
(lang,))
|
||||||
|
return csr.fetchall()
|
||||||
|
except Exception as e:
|
||||||
|
log.exception("caught error")
|
||||||
|
db.rollback()
|
||||||
|
return HTTPException(500)
|
||||||
|
|
||||||
|
@app.get("/stats/{id}/{lang}")
|
||||||
|
def get_stats(id: str, lang: str):
|
||||||
|
try:
|
||||||
|
with db.cursor(cursor_factory=RealDictCursor) as csr:
|
||||||
|
csr.execute(
|
||||||
|
"""
|
||||||
|
select country, name,
|
||||||
|
band,
|
||||||
|
count(*) as total,
|
||||||
|
count(*) filter(where public_id = %s) as by_user
|
||||||
|
from entries e
|
||||||
|
join countries c on c.code = e.country
|
||||||
|
join country_names cn on e.country = cn.code and cn.lang = %s
|
||||||
|
join users u using (uuid)
|
||||||
|
group by country, name, population, band
|
||||||
|
order by population desc, name, band
|
||||||
|
""",
|
||||||
|
(id, lang,))
|
||||||
|
country_stats = []
|
||||||
|
for r in csr:
|
||||||
|
if not country_stats or country_stats[-1]["code"] != r["country"]:
|
||||||
|
country_stats.append({"code": r["country"],
|
||||||
|
"name": r["name"],
|
||||||
|
"bands": []})
|
||||||
|
country_stats[-1]["bands"].append({"name": r["band"],
|
||||||
|
"total_count": r["total"],
|
||||||
|
"by_user": bool(r["by_user"])})
|
||||||
|
|
||||||
|
stats = { "countries": country_stats }
|
||||||
|
csr.execute(
|
||||||
|
"""
|
||||||
|
with a as (
|
||||||
|
select public_id, count(distinct(country)) as c
|
||||||
|
from entries join users using (uuid)
|
||||||
|
group by 1)
|
||||||
|
select public_id, c, rank() over (order by c desc) from a;
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
for r in csr:
|
||||||
|
if r["public_id"] == id:
|
||||||
|
stats["country_rank"] = r["rank"]
|
||||||
|
stats["country_count"] = r["c"]
|
||||||
|
stats["user_count"] = csr.rowcount
|
||||||
|
|
||||||
|
csr.execute(
|
||||||
|
"""
|
||||||
|
with a as (
|
||||||
|
select public_id, count(*) as c
|
||||||
|
from entries join users using (uuid)
|
||||||
|
group by 1)
|
||||||
|
select public_id, c, rank() over (order by c desc) from a;
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
for r in csr:
|
||||||
|
if r["public_id"] == id:
|
||||||
|
stats["country_rank"] = r["rank"]
|
||||||
|
stats["country_count"] = r["c"]
|
||||||
|
|
||||||
|
return stats
|
||||||
|
except Exception as e:
|
||||||
|
log.exception("caught error")
|
||||||
|
db.rollback()
|
||||||
|
return HTTPException(500)
|
||||||
|
|
||||||
|
# vim: tw=99
|
|
@ -0,0 +1,3 @@
|
||||||
|
fastapi
|
||||||
|
psycopg2
|
||||||
|
uvicorn
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 348 KiB After Width: | Height: | Size: 348 KiB |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
Before Width: | Height: | Size: 362 KiB After Width: | Height: | Size: 362 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |