API reports and search

This commit is contained in:
2026-05-06 17:44:16 -04:00
parent b9defa7a2e
commit f62237dc3f
10 changed files with 177 additions and 3 deletions
+5
View File
@@ -59,6 +59,11 @@ def init_db():
UNION ALL UNION ALL
SELECT 'Total', COUNT(DISTINCT(CONCAT(first_name, last_name, phone_number))), COUNT(*) SELECT 'Total', COUNT(DISTINCT(CONCAT(first_name, last_name, phone_number))), COUNT(*)
FROM tickets""") FROM tickets""")
cur.execute("""CREATE VIEW IF NOT EXISTS v_donors AS
SELECT bd.b_prefix, bd.b_id, bd.d_id, d.donor_name, d.donor_business, b.description
FROM r_basket_donor bd LEFT JOIN donors d ON bd.d_id = d.donor_id
LEFT JOIN baskets b ON bd.b_prefix = b.prefix AND bd.b_id = b.basket_id
ORDER BY bd.b_prefix, bd.b_id""")
if config["mode"] != "prod": if config["mode"] != "prod":
cur.execute("""REPLACE INTO auth VALUES ('2RO2T7GET9S7X64JUFN67OAV', 'Testing')""") cur.execute("""REPLACE INTO auth VALUES ('2RO2T7GET9S7X64JUFN67OAV', 'Testing')""")
conn.commit() conn.commit()
+9
View File
@@ -40,6 +40,15 @@ class BasketDonorRel:
b_id: int = 0 b_id: int = 0
d_id: int = 0 d_id: int = 0
@dataclass
class BasketDonorView:
b_prefix: str = ""
b_id: int = 0
d_id: int = 0
donor_name: str = ""
donor_business: str = ""
description: str = ""
@dataclass @dataclass
class WinnerByBasket: class WinnerByBasket:
prefix: str = "" prefix: str = ""
+39 -1
View File
@@ -1,5 +1,5 @@
from ..core.models import RepoTemplate from ..core.models import RepoTemplate
from .models import Prefix, Ticket, Count, Basket, WinnerByBasket from .models import Prefix, Ticket, Count, Basket, WinnerByBasket, Donor, BasketDonorView, BasketDonorRel
class PrefixRepo(RepoTemplate): class PrefixRepo(RepoTemplate):
@@ -156,3 +156,41 @@ class DrawingRepo(RepoTemplate):
winning_ticket = EXCLUDED.winning_ticket""", (b.prefix, b.basket_id, b.winning_ticket)) winning_ticket = EXCLUDED.winning_ticket""", (b.prefix, b.basket_id, b.winning_ticket))
self.conn.commit() self.conn.commit()
return {"detail": "Winners posted successfully."} return {"detail": "Winners posted successfully."}
class DonorRepo(RepoTemplate):
"""Repo that controls Donors."""
def get_all_donors(self):
"""Gets all donors."""
self.cur.execute("SELECT * FROM donors")
results = self.cur.fetchall()
return [Donor(*r) for r in results]
def get_donor_relations(self, prefix: str, basket_id: int):
"""Get a list of donors for one basket."""
self.cur.execute("SELECT * FROM v_donors WHERE b_prefix = ? AND b_id = ?", (prefix, basket_id))
results = self.cur.fetchall()
return [BasketDonorView(*r) for r in results]
def post_donors(self, ds: list[Donor]):
"""Posts donors"""
rtn_lst = []
for d in ds:
self.cur.execute("INSERT INTO donors (donor_name, donor_business) VALUES (?, ?) RETURNING *", (d.donor_name, d.donor_business))
rtn_lst.append(Donor(*self.cur.fetchone()))
self.conn.commit()
return rtn_lst
def post_donor_relation(self, drs: list[BasketDonorRel]):
"""Post donor relations."""
for dr in drs:
self.cur.execute("""INSERT INTO r_basket_donor VALUES (?, ?, ?) ON CONFLICT (b_prefix, b_id, d_id)
DO NOTHING""", (dr.b_prefix, dr.b_id, dr.d_id))
self.conn.commit()
return {"detail": "Relations posted successfully."}
def del_donor_relation(self, r: BasketDonorRel):
"""Delete donor relation."""
self.cur.execute("DELETE FROM r_basket_donor WHERE b_prefix = ? AND b_id = ? AND d_id = ?", (r.b_prefix, r.b_id, r.d_id))
self.conn.commit()
return {"detail": "Relation deleted successfully."}
+42 -2
View File
@@ -1,8 +1,15 @@
from fastapi import APIRouter, Header, HTTPException, status from fastapi import APIRouter, Header, HTTPException, status
from ..core.auth import AuthRepo from ..core.auth import AuthRepo
from .models import Basket, Prefix, Ticket from .models import Basket, BasketDonorRel, Donor, Prefix, Ticket
from .repos import BasketRepo, CountsRepo, DrawingRepo, PrefixRepo, TicketRepo from .repos import (
BasketRepo,
CountsRepo,
DonorRepo,
DrawingRepo,
PrefixRepo,
TicketRepo,
)
prefix_router = APIRouter(prefix="/api/prefix") prefix_router = APIRouter(prefix="/api/prefix")
@@ -164,3 +171,36 @@ def get_drawing_scope(prefix: str, s_id: str, tam_auth_key: str = Header("")):
def post_winners(bs: list[Basket], tam_auth_key: str = Header("")): def post_winners(bs: list[Basket], tam_auth_key: str = Header("")):
AuthRepo().verify_key(tam_auth_key) AuthRepo().verify_key(tam_auth_key)
return DrawingRepo().post_winners(bs) return DrawingRepo().post_winners(bs)
donor_router = APIRouter(prefix="/api/donors")
@donor_router.get("")
def get_all_donors(tam_auth_key: str = Header("")):
AuthRepo().verify_key(tam_auth_key)
return DonorRepo().get_all_donors()
@donor_router.get("/b/{prefix}/{b_id}")
def get_basket_donors(prefix: str, b_id: int, tam_auth_key: str = Header("")):
AuthRepo().verify_key(tam_auth_key)
return DonorRepo().get_donor_relations(prefix, b_id)
@donor_router.post("/new")
def post_donors(ds: list[Donor], tam_auth_key: str = Header("")):
AuthRepo().verify_key(tam_auth_key)
return DonorRepo().post_donors(ds)
@donor_router.post("/rel")
def post_donor_relations(drs: list[BasketDonorRel], tam_auth_key: str = Header("")):
AuthRepo().verify_key(tam_auth_key)
return DonorRepo().post_donor_relation(drs)
@donor_router.delete("/rel")
def delete_donor_relation(r: BasketDonorRel, tam_auth_key: str = Header("")):
AuthRepo().verify_key(tam_auth_key)
return DonorRepo().del_donor_relation(r)
+23
View File
@@ -0,0 +1,23 @@
from ..core.models import RepoTemplate
from ..data.models import WinnerByBasket, WinnerByName, BasketDonorView
class ReportsRepo(RepoTemplate):
"""Repo that controls all report functions."""
def get_winners_by_basket(self, prefix: str):
"""Gets prefix winners by basket."""
self.cur.execute("SELECT * FROM winners_by_basket WHERE prefix = ?", (prefix,))
results = self.cur.fetchall()
return [WinnerByBasket(*r) for r in results]
def get_winners_by_name(self, prefix: str):
"""Gets prefix winners by name."""
self.cur.execute("SELECT * FROM winners_by_name WHERE prefix = ?", (prefix,))
results = self.cur.fetchall()
return [WinnerByName(*r) for r in results]
def get_donor_contributions(self):
"""Gets all donor contributions."""
self.cur.execute("SELECT * FROM v_donors")
results = self.cur.fetchall()
return [BasketDonorView(*r) for r in results]
+20
View File
@@ -0,0 +1,20 @@
from fastapi import APIRouter, Header
from .repos import ReportsRepo
from ..core.auth import AuthRepo
reports_router = APIRouter(prefix="/api/reports")
@reports_router.get("/winners/bybasket/{prefix}")
def get_winners_by_basket(prefix: str, tam_auth_key: str = Header("")):
AuthRepo().verify_key(tam_auth_key)
return ReportsRepo().get_winners_by_basket(prefix)
@reports_router.get("/winners/byname/{prefix}")
def get_winners_by_name(prefix: str, tam_auth_key: str = Header("")):
AuthRepo().verify_key(tam_auth_key)
return ReportsRepo().get_winners_by_name(prefix)
@reports_router.get("/donors/contrib")
def get_donor_contributions(tam_auth_key: str = Header("")):
AuthRepo().verify_key(tam_auth_key)
return ReportsRepo().get_donor_contributions()
+5
View File
@@ -2,6 +2,8 @@ from fastapi import FastAPI
from .core.auth import auth_router from .core.auth import auth_router
from .data import routers from .data import routers
from .reports import routers as report_routers
from .search import routers as search_routers
def append_routers(app: FastAPI): def append_routers(app: FastAPI):
app.include_router(auth_router) app.include_router(auth_router)
@@ -10,3 +12,6 @@ def append_routers(app: FastAPI):
app.include_router(routers.counts_router) app.include_router(routers.counts_router)
app.include_router(routers.basket_router) app.include_router(routers.basket_router)
app.include_router(routers.drawing_router) app.include_router(routers.drawing_router)
app.include_router(routers.donor_router)
app.include_router(report_routers.reports_router)
app.include_router(search_routers.search_router)
+19
View File
@@ -0,0 +1,19 @@
from ..core.models import RepoTemplate
from ..data.models import Ticket
class SearchRepo(RepoTemplate):
"""Repo that controls search."""
def search_tickets(
self, first_name: str = "", last_name: str = "", phone_number: str = ""
):
"""Searches tickets for a specific name."""
self.cur.execute(
"""SELECT * FROM tickets
WHERE first_name LIKE ? AND last_name LIKE ? AND phone_number LIKE ?
ORDER BY prefix, ticket_id""",
(f"%{first_name}%", f"%{last_name}%", f"%{phone_number}%"),
)
results = self.cur.fetchall()
return [Ticket(*r) for r in results]
+12
View File
@@ -0,0 +1,12 @@
from fastapi import APIRouter, Header
from .repo import SearchRepo
from ..core.auth import AuthRepo
search_router = APIRouter(prefix="/api/search")
@search_router.get("/tickets")
def search_tickets(
first_name: str = "", last_name: str = "", phone_number: str = "", tam_auth_key: str = Header("")
):
AuthRepo().verify_key(tam_auth_key)
return SearchRepo().search_tickets(first_name, last_name, phone_number)
+3
View File
@@ -0,0 +1,3 @@
localhost:8443 {
reverse_proxy localhost:8000
}