From 9754731068fc1301ee3b8833dfd59dd5f623db1a Mon Sep 17 00:00:00 2001 From: Dilan Gilluly Date: Sat, 27 Sep 2025 23:09:19 -0400 Subject: [PATCH] nightly - 2025-09-27 --- README.md | 8 ++++ api/main.py | 4 +- api/repos/prefixes.py | 12 +++-- api/routers/backuprestore.py | 40 +++++++++++++++++ .../src/routes/api/backuprestore/+server.js | 45 +++++++++++++++++++ .../src/routes/drawing/[prefix]/+page.svelte | 3 ++ .../src/routes/tickets/[prefix]/+page.svelte | 2 +- 7 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 api/routers/backuprestore.py create mode 100644 webapp/src/routes/api/backuprestore/+server.js diff --git a/README.md b/README.md index 851b6d3..eeb1a81 100644 --- a/README.md +++ b/README.md @@ -21,11 +21,19 @@ On the main menu press "Alt (or Option) + a" to toggle admin mode. Then click Pr ## Status Ticket Form: **functional** + Basket Form: **functional** + Drawing Form: **functional** +*** + By Name Report: **functional** + Basket ID Report: **functional** + Ticket Count Report: **not started** +*** + Deployment System: **not started** | Likely will Dockerize it first, then will move to possible Electron or Tauri option for desktop application. \ No newline at end of file diff --git a/api/main.py b/api/main.py index fee52f1..2732bc3 100644 --- a/api/main.py +++ b/api/main.py @@ -8,6 +8,7 @@ from routers.tickets import ticket_router from routers.baskets import basket_router from routers.combined import combined_router from routers.reports import report_router +from routers.backuprestore import backup_router if argv[1] == "run": app = FastAPI(title="TAM3 API Server", docs_url=None, redoc_url=None) @@ -18,4 +19,5 @@ app.include_router(prefix_router) app.include_router(ticket_router) app.include_router(basket_router) app.include_router(combined_router) -app.include_router(report_router) \ No newline at end of file +app.include_router(report_router) +app.include_router(backup_router) \ No newline at end of file diff --git a/api/repos/prefixes.py b/api/repos/prefixes.py index b1efed2..c20fc7e 100644 --- a/api/repos/prefixes.py +++ b/api/repos/prefixes.py @@ -26,12 +26,16 @@ class PrefixRepo(Repo): return Prefix(*result) def add_one(self, prefix: Prefix) -> str: - self.cur.execute( - "REPLACE INTO prefixes VALUES (%s, %s, %s)", - (prefix.name, prefix.color, prefix.weight), - ) + self.cur.execute("REPLACE INTO prefixes VALUES (%s, %s, %s)", (prefix.name, prefix.color, prefix.weight)) self.conn.commit() return "Prefix inserted successfully." + + def add_list(self, prefix_list: list[Prefix]): + for prefix in prefix_list: + self.cur.execute("REPLACE INTO prefixes VALUES (%s, %s, %s)", (prefix.name, prefix.color, prefix.weight)) + self.conn.commit() + return "Prefixes inserted successfully." + def del_one(self, prefix_name: str) -> str: self.cur.execute("DELETE FROM prefixes WHERE name = %s", (prefix_name,)) diff --git a/api/routers/backuprestore.py b/api/routers/backuprestore.py new file mode 100644 index 0000000..ee19a11 --- /dev/null +++ b/api/routers/backuprestore.py @@ -0,0 +1,40 @@ +from fastapi import APIRouter, Body +from dataclasses import dataclass, field +from typing import List +from exceptions import bad_key + +from repos.api_keys import ApiKeyRepo +from repos.prefixes import Prefix, PrefixRepo +from repos.tickets import Ticket, TicketRepo +from repos.baskets import Basket, BasketRepo +from repos.template import Repo + +@dataclass +class BackupFile: + prefixes: List[Prefix] = field(default_factory=list) + tickets: List[Ticket] = field(default_factory=list) + baskets: List[Basket] = field(default_factory=list) + +class BackupFileRepo: + def get_file(self): + new_file = BackupFile(prefixes=PrefixRepo().get_all(), tickets=TicketRepo().get_all(), baskets=BasketRepo().get_all()) + return new_file + def post_file(self, uploaded_file: BackupFile) -> str: + PrefixRepo().add_list(uploaded_file.prefixes) + TicketRepo().post_list(uploaded_file.tickets) + BasketRepo().post_list(uploaded_file.baskets) + return "File posted successfully." + +backup_router = APIRouter(prefix="/api/backuprestore") + +@backup_router.get("/") +def get_backup_file(api_key: str): + if not ApiKeyRepo().check_api(api_key): + raise bad_key + return BackupFileRepo().get_file() + +@backup_router.post("/") +def post_backup_file(api_key: str, uploaded_file: BackupFile): + if not ApiKeyRepo().check_api(api_key): + raise bad_key + return {"detail": BackupFileRepo().post_file(uploaded_file)} \ No newline at end of file diff --git a/webapp/src/routes/api/backuprestore/+server.js b/webapp/src/routes/api/backuprestore/+server.js new file mode 100644 index 0000000..075d572 --- /dev/null +++ b/webapp/src/routes/api/backuprestore/+server.js @@ -0,0 +1,45 @@ +import { env } from "$env/dynamic/private"; +import { db } from "$lib/server/db"; +import { prefixes, tickets, baskets } from "$lib/server/db/schema"; + +export async function GET() { + if (env.TAM3_REMOTE) { + const res = await fetch(`${env.TAM3_REMOTE}/api/backuprestore/?api_key=${env.TAM3_REMOTE_KEY}`); + if (!res.ok) { + return new Response(JSON.stringify({detail: "Unable to get backup file"}), {status: res.status, statusText: res.statusText}) + }; + const data = await res.json(); + return new Response(JSON.stringify(data), {status: 200, statusText: "Fetched backup file successfully."}) + } else { + const p_data = await db.select().from(prefixes); + const t_data = await db.select().from(tickets); + const b_data = await db.select().from(baskets); + return new Response(JSON.stringify({prefixes: p_data, tickets: t_data, baskets: b_data}), {status: 200, statusText: "Loaded backup file successfully."}) + } +} + +export async function POST({ request }) { + const req = await request.json(); + for (let prefix of req.prefixes) { + await db.insert(prefixes).values({name: prefix.name, color: prefix.color, weight: prefix.weight}).onConflictDoUpdate({target: prefixes.name, set: {color: prefix.color, weight: prefix.weight}}); + }; + for (let ticket of req.tickets) { + await db.insert(tickets).values({prefix: ticket.prefix, t_id: ticket.t_id, first_name: ticket.first_name, last_name: ticket.last_name, phone_number: ticket.phone_number, preference: ticket.preference}) + .onConflictDoUpdate({target: [tickets.prefix, tickets.t_id], set: {first_name: ticket.first_name, last_name: ticket.last_name, phone_number: ticket.phone_number, preference: ticket.preference}}); + }; + for (let basket of req.baskets) { + await db.insert(baskets).values({prefix: basket.prefix, b_id: basket.b_id, description: basket.description, donors: basket.donors, winning_ticket: basket.winning_ticket}) + .onConflictDoUpdate({target: [baskets.prefix, baskets.b_id], set: {description: basket.description, donors: basket.donors, winning_ticket: basket.winning_ticket}}) + }; + if (env.TAM3_REMOTE) { + const res = await fetch(`${env.TAM3_REMOTE}/api/backuprestore/?api_key=${env.TAM3_REMOTE_KEY}`, { + body: JSON.stringify(req), + method: 'POST', + headers: {'Content-Type': 'application/json'} + }); + if (!res.ok) { + return new Response(JSON.stringify({detail: "Error posting file."}), {status: res.status, statusText: "Error posting file."}) + } + } + return new Response(JSON.stringify({detail: "File uploaded successfully"}), {status: 200, statusText: "File uploaded successfully."}) +} \ No newline at end of file diff --git a/webapp/src/routes/drawing/[prefix]/+page.svelte b/webapp/src/routes/drawing/[prefix]/+page.svelte index 002cee0..d8f7286 100644 --- a/webapp/src/routes/drawing/[prefix]/+page.svelte +++ b/webapp/src/routes/drawing/[prefix]/+page.svelte @@ -18,6 +18,9 @@ const functions = { refreshPage: async () => { + if (current_drawings.length > 0) { + functions.saveAll() + } const res = await fetch(`/api/combined/${prefix.name}/${pagerForm.id_from}/${pagerForm.id_to}`); if (res.ok) { const data = await res.json(); diff --git a/webapp/src/routes/tickets/[prefix]/+page.svelte b/webapp/src/routes/tickets/[prefix]/+page.svelte index 161d888..b66d00b 100644 --- a/webapp/src/routes/tickets/[prefix]/+page.svelte +++ b/webapp/src/routes/tickets/[prefix]/+page.svelte @@ -93,7 +93,7 @@ }; if (browser) { - document.title = `${prefix.name} Ticket Entry` + document.title = `${prefix.name} Ticket Entry`; }