refactor: split backend route domains
This commit is contained in:
140
backend/app/member_routes.py
Normal file
140
backend/app/member_routes.py
Normal file
@@ -0,0 +1,140 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Callable
|
||||
|
||||
from fastapi import FastAPI, File, HTTPException, UploadFile
|
||||
|
||||
|
||||
def register_member_routes(
|
||||
app: FastAPI,
|
||||
*,
|
||||
get_conn,
|
||||
member_payload_cls,
|
||||
member_bulk_payload_cls,
|
||||
parse_as_of: Callable[[str | None], datetime | None],
|
||||
fetch_members: Callable[[], list[dict[str, object]]],
|
||||
fetch_members_as_of: Callable[[object, datetime], list[dict[str, object]]],
|
||||
build_member_compare_items: Callable[[list[dict[str, object]], list[dict[str, object]]], list[dict[str, object]]],
|
||||
serialize_member_payload: Callable[[object, int], tuple[object, ...]],
|
||||
sync_auth_users_from_members: Callable[[object], None],
|
||||
create_history_revision: Callable[[object, str, str], int],
|
||||
sync_member_versions: Callable[[object, list[int], str, int], None],
|
||||
sync_seat_assignment_versions: Callable[[object, list[int], str, int], None],
|
||||
replace_members: Callable[[list[object]], list[dict[str, object]]],
|
||||
parse_import_rows: Callable[[UploadFile, bytes], list[object]],
|
||||
) -> None:
|
||||
@app.get("/api/members")
|
||||
def list_members(as_of: str | None = None) -> dict[str, list[dict[str, object]]]:
|
||||
parsed_as_of = parse_as_of(as_of)
|
||||
if parsed_as_of is None:
|
||||
return {"items": fetch_members()}
|
||||
with get_conn() as conn:
|
||||
with conn.cursor() as cur:
|
||||
return {"items": fetch_members_as_of(cur, parsed_as_of)}
|
||||
|
||||
@app.get("/api/history/members/compare")
|
||||
def compare_members_history(from_date: str, to_date: str) -> dict[str, list[dict[str, object]]]:
|
||||
parsed_from = parse_as_of(from_date)
|
||||
parsed_to = parse_as_of(to_date)
|
||||
if parsed_from is None or parsed_to is None:
|
||||
raise HTTPException(status_code=400, detail="from_date and to_date are required.")
|
||||
with get_conn() as conn:
|
||||
with conn.cursor() as cur:
|
||||
from_items = fetch_members_as_of(cur, parsed_from)
|
||||
to_items = fetch_members_as_of(cur, parsed_to)
|
||||
return {"items": build_member_compare_items(from_items, to_items)}
|
||||
|
||||
@app.post("/api/members")
|
||||
def create_member(payload: member_payload_cls) -> dict[str, object]:
|
||||
with get_conn() as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("SELECT COALESCE(MAX(sort_order), -1) + 1 AS next_order FROM members")
|
||||
next_order = int(cur.fetchone()["next_order"])
|
||||
cur.execute(
|
||||
"""
|
||||
INSERT INTO members (
|
||||
name, employee_id, company, rank, role, department, grp, division, team, cell,
|
||||
work_status, work_time, phone, email, seat_label, photo_url, sort_order
|
||||
)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||
RETURNING id, name, employee_id, company, rank, role, department, grp, division, team, cell,
|
||||
work_status, work_time, phone, email, seat_label, photo_url,
|
||||
sort_order, created_at, updated_at
|
||||
""",
|
||||
serialize_member_payload(payload, payload.sort_order if payload.sort_order is not None else next_order),
|
||||
)
|
||||
member = cur.fetchone()
|
||||
sync_auth_users_from_members(cur)
|
||||
revision_no = create_history_revision(cur, "member-create", f"Member created id={int(member['id'])}")
|
||||
sync_member_versions(cur, [int(member["id"])], "member-create", revision_no)
|
||||
conn.commit()
|
||||
return {"item": member}
|
||||
|
||||
@app.put("/api/members/bulk-sync")
|
||||
def bulk_sync_members(payload: member_bulk_payload_cls) -> dict[str, list[dict[str, object]]]:
|
||||
return {"items": replace_members(payload.items)}
|
||||
|
||||
@app.put("/api/members/{member_id}")
|
||||
def update_member(member_id: int, payload: member_payload_cls) -> dict[str, object]:
|
||||
with get_conn() as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
UPDATE members
|
||||
SET name = %s,
|
||||
employee_id = %s,
|
||||
company = %s,
|
||||
rank = %s,
|
||||
role = %s,
|
||||
department = %s,
|
||||
grp = %s,
|
||||
division = %s,
|
||||
team = %s,
|
||||
cell = %s,
|
||||
work_status = %s,
|
||||
work_time = %s,
|
||||
phone = %s,
|
||||
email = %s,
|
||||
seat_label = %s,
|
||||
photo_url = %s,
|
||||
sort_order = COALESCE(%s, sort_order),
|
||||
updated_at = NOW()
|
||||
WHERE id = %s
|
||||
RETURNING id, name, employee_id, company, rank, role, department, grp, division, team, cell,
|
||||
work_status, work_time, phone, email, seat_label, photo_url,
|
||||
sort_order, created_at, updated_at
|
||||
""",
|
||||
(*serialize_member_payload(payload, payload.sort_order or 0)[:-1], payload.sort_order, member_id),
|
||||
)
|
||||
member = cur.fetchone()
|
||||
if member is None:
|
||||
raise HTTPException(status_code=404, detail="Member not found.")
|
||||
sync_auth_users_from_members(cur)
|
||||
revision_no = create_history_revision(cur, "member-update", f"Member updated id={member_id}")
|
||||
sync_member_versions(cur, [member_id], "member-update", revision_no)
|
||||
sync_seat_assignment_versions(cur, [member_id], "member-update", revision_no)
|
||||
conn.commit()
|
||||
return {"item": member}
|
||||
|
||||
@app.delete("/api/members/{member_id}")
|
||||
def delete_member(member_id: int) -> dict[str, bool]:
|
||||
with get_conn() as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("DELETE FROM members WHERE id = %s", (member_id,))
|
||||
deleted = cur.rowcount > 0
|
||||
if deleted:
|
||||
sync_auth_users_from_members(cur)
|
||||
revision_no = create_history_revision(cur, "member-delete", f"Member deleted id={member_id}")
|
||||
sync_member_versions(cur, [member_id], "member-delete", revision_no)
|
||||
sync_seat_assignment_versions(cur, [member_id], "member-delete", revision_no)
|
||||
conn.commit()
|
||||
if not deleted:
|
||||
raise HTTPException(status_code=404, detail="Member not found.")
|
||||
return {"ok": True}
|
||||
|
||||
@app.post("/api/members/import")
|
||||
async def import_members(file: UploadFile = File(...)) -> dict[str, list[dict[str, object]]]:
|
||||
content = await file.read()
|
||||
items = parse_import_rows(file, content)
|
||||
return {"items": replace_members(items)}
|
||||
Reference in New Issue
Block a user