diff --git a/README.md b/README.md index f970168..851b6d3 100644 --- a/README.md +++ b/README.md @@ -16,4 +16,16 @@ After cloning, cd'ing into webapp, and running `npm install` followed by `npm ru That is making prefixes. -On the main menu press "Alt (or Option) + a" to toggle admin mode. Then click Prefix editor to open the form to edit prefixes. \ No newline at end of file +On the main menu press "Alt (or Option) + a" to toggle admin mode. Then click Prefix editor to open the form to edit prefixes. + +## 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 646f243..fee52f1 100644 --- a/api/main.py +++ b/api/main.py @@ -7,6 +7,7 @@ from routers.prefixes import prefix_router from routers.tickets import ticket_router from routers.baskets import basket_router from routers.combined import combined_router +from routers.reports import report_router if argv[1] == "run": app = FastAPI(title="TAM3 API Server", docs_url=None, redoc_url=None) @@ -16,4 +17,5 @@ else: app.include_router(prefix_router) app.include_router(ticket_router) app.include_router(basket_router) -app.include_router(combined_router) \ No newline at end of file +app.include_router(combined_router) +app.include_router(report_router) \ No newline at end of file diff --git a/api/repos/reports.py b/api/repos/reports.py new file mode 100644 index 0000000..2786a8b --- /dev/null +++ b/api/repos/reports.py @@ -0,0 +1,22 @@ +from dataclasses import dataclass +from .template import Repo + +@dataclass +class ReportItem: + prefix: str + winner_name: str + phone_number: str | None + preference: str + b_id: int + winning_ticket: int + description: str + +class ReportRepo(Repo): + def get_byname(self, prefix: str) -> list[ReportItem]: + self.cur.execute("SELECT * FROM report WHERE prefix = %s", (prefix,)) + results = self.cur.fetchall() + return [ReportItem(*r) for r in results] + def get_bybasket(self, prefix: str) -> list[ReportItem]: + self.cur.execute("SELECT * FROM report WHERE prefix = %s ORDER BY b_id ASC", (prefix,)) + results = self.cur.fetchall() + return [ReportItem(*r) for r in results] \ No newline at end of file diff --git a/api/routers/reports.py b/api/routers/reports.py new file mode 100644 index 0000000..98e20d4 --- /dev/null +++ b/api/routers/reports.py @@ -0,0 +1,18 @@ +from fastapi import APIRouter +from repos.reports import ReportItem, ReportRepo +from repos.api_keys import ApiKeyRepo +from exceptions import bad_key + +report_router = APIRouter(prefix="/api/reports") + +@report_router.get("/byname/{prefix}/") +def get_report_byname(api_key: str, prefix: str): + if not ApiKeyRepo().check_api(api_key): + raise bad_key + return ReportRepo().get_byname(prefix) + +@report_router.get("/bybasket/{prefix}/") +def get_report_bybasket(api_key: str, prefix: str): + if not ApiKeyRepo().check_api(api_key): + raise bad_key + return ReportRepo().get_bybasket(prefix) \ No newline at end of file diff --git a/db/schema.sql b/db/schema.sql index 79ba527..a6bb6d3 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -12,19 +12,19 @@ CREATE TABLE IF NOT EXISTS prefixes ( CREATE TABLE IF NOT EXISTS tickets ( `prefix` VARCHAR(255), `t_id` INT, - `first_name` VARCHAR(255), - `last_name` VARCHAR(255), - `phone_number` VARCHAR(255), - `preference` VARCHAR(20), + `first_name` VARCHAR(255) DEFAULT "", + `last_name` VARCHAR(255) DEFAULT "", + `phone_number` VARCHAR(255) DEFAULT "", + `preference` VARCHAR(20) DEFAULT "CALL", PRIMARY KEY (`prefix`, `t_id`) ); CREATE TABLE IF NOT EXISTS baskets ( `prefix` VARCHAR(255), `b_id` INT, - `description` VARCHAR(255), - `donors` VARCHAR(255), - `winning_ticket` INT, + `description` VARCHAR(255) DEFAULT "", + `donors` VARCHAR(255) DEFAULT "", + `winning_ticket` INT DEFAULT 0, PRIMARY KEY (`prefix`, `b_id`) ); diff --git a/testdata/tam3_name.py b/testdata/tam3_name.py new file mode 100644 index 0000000..bd795a1 --- /dev/null +++ b/testdata/tam3_name.py @@ -0,0 +1,27 @@ +from sys import argv +from names import get_full_name +import random as r + +def gen_phone_number(): + def d0(): + return r.randint(0, 1) + def d(): + return r.randint(0, 9) + return f"{d0()}{d()}{d()}-{d()}{d()}{d()}-{d()}{d()}{d()}{d()}" + +class Person: + def __init__(self): + self.name = get_full_name() + self.phone_number = gen_phone_number() + self.preference = r.choice(("TEXT", "TEXT", "TEXT", "CALL")) + def __repr__(self): + return f"{self.name} {self.phone_number} {self.preference}" + +how_many = input("Insert how many random people to generate: ") +try: + how_many = int(how_many) +except: + print("Try entering an integer next time.") + +for i in range(0, int(how_many)): + print(Person()) \ No newline at end of file diff --git a/testdata/tam3_ticket_num.py b/testdata/tam3_ticket_num.py new file mode 100644 index 0000000..f651815 --- /dev/null +++ b/testdata/tam3_ticket_num.py @@ -0,0 +1,20 @@ +import random as r + +how_many = input("Enter how many numbers you want to generate: ") +try: + how_many = int(how_many) +except: + print("Enter an integer next time.") + quit(1) + +ticket_range = input("Enter the start and end numbers of the random range you want to use, separated by a hyphen: ") + +ticket_range = ticket_range.split("-") +try: + ticket_range = [int(i) for i in ticket_range] +except: + print("Invalid range entered. Needs to be something like \"1-20\"") + quit(1) + +for i in range(0, how_many): + print(r.randint(ticket_range[0], ticket_range[1])) \ No newline at end of file diff --git a/webapp/src/lib/server/db/schema.js b/webapp/src/lib/server/db/schema.js index 1d8b8cb..38de75c 100644 --- a/webapp/src/lib/server/db/schema.js +++ b/webapp/src/lib/server/db/schema.js @@ -10,10 +10,10 @@ export const prefixes = sqliteTable('prefixes', { export const tickets = sqliteTable('tickets', { prefix: text('prefix'), t_id: integer('t_id'), - first_name: text('first_name'), - last_name: text('last_name'), - phone_number: text('phone_number'), - preference: text('preference') + first_name: text('first_name').default(''), + last_name: text('last_name').default(''), + phone_number: text('phone_number').default(''), + preference: text('preference').default('CALL') }, (table) => [ primaryKey({columns: [table.prefix, table.t_id]}) ]); @@ -21,8 +21,8 @@ export const tickets = sqliteTable('tickets', { export const baskets = sqliteTable('baskets', { prefix: text('prefix'), b_id: integer('b_id'), - description: text('description'), - donors: text('donors'), + description: text('description').default(''), + donors: text('donors').default(''), winning_ticket: integer('winning_ticket').default(0) }, (table) => [ primaryKey({columns: [table.prefix, table.b_id]}) diff --git a/webapp/src/routes/+page.js b/webapp/src/routes/+page.js index 44022f6..05a53d5 100644 --- a/webapp/src/routes/+page.js +++ b/webapp/src/routes/+page.js @@ -1,5 +1,5 @@ export async function load({ fetch }) { - const res = await fetch('/api/prefixes/'); + const res = await fetch('/api/prefixes'); if (res.ok) { const data = await res.json(); return { prefixes: data, status: "Prefixes fetched successfully." } diff --git a/webapp/src/routes/+page.svelte b/webapp/src/routes/+page.svelte index d9708e0..e629a2e 100644 --- a/webapp/src/routes/+page.svelte +++ b/webapp/src/routes/+page.svelte @@ -47,8 +47,8 @@

Reports:

- By Name - By Basket ID + By Name + By Basket ID
{#if admin_mode}

Admin Mode:

diff --git a/webapp/src/routes/api/baskets/[prefix]/[b_from]/[b_to]/+server.js b/webapp/src/routes/api/baskets/[prefix]/[b_from]/[b_to]/+server.js index 4e85fe3..d10a07b 100644 --- a/webapp/src/routes/api/baskets/[prefix]/[b_from]/[b_to]/+server.js +++ b/webapp/src/routes/api/baskets/[prefix]/[b_from]/[b_to]/+server.js @@ -6,7 +6,7 @@ import { eq, and } from "drizzle-orm"; export async function GET({ params }) { let n_b_from = parseInt(params.b_from), n_b_to = parseInt(params.b_to); if (env.TAM3_REMOTE) { - const res = await fetch(`${env.TAM3_REMOTE}/api/baskets/${n_b_from}/${n_b_to}/?api_key=${env.TAM3_REMOTE_KEY}`); + const res = await fetch(`${env.TAM3_REMOTE}/api/baskets/${params.prefix}/${n_b_from}/${n_b_to}/?api_key=${env.TAM3_REMOTE_KEY}`); if (!res.ok) { return new Response(JSON.stringify({detail: "Unable to fetch baskets"}), {status: res.status, statusText: res.statusText}); }; diff --git a/webapp/src/routes/api/prefixes/[name]/+server.js b/webapp/src/routes/api/prefixes/[name]/+server.js index 8eebe5f..eb1b7f9 100644 --- a/webapp/src/routes/api/prefixes/[name]/+server.js +++ b/webapp/src/routes/api/prefixes/[name]/+server.js @@ -6,7 +6,7 @@ import { env } from "$env/dynamic/private"; export async function GET({ params }) { let { name } = params; if (env.TAM3_REMOTE) { - const res = await fetch(`${env.TAM3_REMOTE}/prefixes/${name}/?api_key=${env.TAM3_REMOTE_KEY}`); + const res = await fetch(`${env.TAM3_REMOTE}/api/prefixes/${name}/?api_key=${env.TAM3_REMOTE_KEY}`); if (!res.ok) { return new Response(JSON.stringify({status: "Issue getting prefix."}), {status: res.status, statusText: res.statusText}); } @@ -26,7 +26,7 @@ export async function DELETE({ params }) { let { name } = params; await db.delete(prefixes).where(eq(prefixes.name, name)) if (env.TAM3_REMOTE) { - const res = await fetch(`${env.TAM3_REMOTE}/prefixes/?api_key=${env.TAM3_REMOTE_KEY}&prefix_name=${name}`); + const res = await fetch(`${env.TAM3_REMOTE}/api/prefixes/?api_key=${env.TAM3_REMOTE_KEY}&prefix_name=${name}`); if (!res.ok) { return new Response(JSON.stringify({status: "Issue deleting prefix."}), {status: res.status, statusText: res.statusText}); } else { diff --git a/webapp/src/routes/api/reports/bybasket/[prefix]/+server.js b/webapp/src/routes/api/reports/bybasket/[prefix]/+server.js new file mode 100644 index 0000000..d545faf --- /dev/null +++ b/webapp/src/routes/api/reports/bybasket/[prefix]/+server.js @@ -0,0 +1,18 @@ +import { env } from "process"; +import { db } from "$lib/server/db"; +import { report } from "$lib/server/db/schema"; +import { eq } from "drizzle-orm"; + +export async function GET({ params }) { + if (env.TAM3_REMOTE) { + const res = await fetch(`${env.TAM3_REMOTE}/api/reports/bybasket/${params.prefix}/?api_key=${env.TAM3_REMOTE_KEY}`); + if (!res.ok) { + return new Response(JSON.stringify({detail: "Unable to fetch report."}), {status: res.status, statusText: res.statusText}) + } + const data = await res.json(); + return new Response(JSON.stringify(data), {status: 200, statusText: "Fetched report successfully."}) + } else { + const data = await db.select().from(report).where(eq(report.prefix, params.prefix)).orderBy(report.b_id); + return new Response(JSON.stringify(data), {status: 200, statusText: "Loaded report successfully."}) + } +} \ No newline at end of file diff --git a/webapp/src/routes/api/reports/byname/[prefix]/+server.js b/webapp/src/routes/api/reports/byname/[prefix]/+server.js new file mode 100644 index 0000000..5157995 --- /dev/null +++ b/webapp/src/routes/api/reports/byname/[prefix]/+server.js @@ -0,0 +1,18 @@ +import { env } from "process"; +import { db } from "$lib/server/db"; +import { report } from "$lib/server/db/schema"; +import { eq } from "drizzle-orm"; + +export async function GET({ params }) { + if (env.TAM3_REMOTE) { + const res = await fetch(`${env.TAM3_REMOTE}/api/reports/byname/${params.prefix}/?api_key=${env.TAM3_REMOTE_KEY}`); + if (!res.ok) { + return new Response(JSON.stringify({detail: "Unable to fetch report."}), {status: res.status, statusText: res.statusText}) + } + const data = await res.json(); + return new Response(JSON.stringify(data), {status: 200, statusText: "Fetched report successfully."}) + } else { + const data = await db.select().from(report).where(eq(report.prefix, params.prefix)); + return new Response(JSON.stringify(data), {status: 200, statusText: "Loaded report successfully."}) + } +} \ No newline at end of file diff --git a/webapp/src/routes/api/tickets/[prefix]/[t_id]/+server.js b/webapp/src/routes/api/tickets/[prefix]/[t_id]/+server.js index 05bf0ea..218345a 100644 --- a/webapp/src/routes/api/tickets/[prefix]/[t_id]/+server.js +++ b/webapp/src/routes/api/tickets/[prefix]/[t_id]/+server.js @@ -5,7 +5,7 @@ import { eq, and } from "drizzle-orm"; export async function GET({ params }) { if (env.TAM3_REMOTE) { - const res = await fetch(`${env.TAM3_REMOTE}/api/tickets/${params.prefix}/${params.t_id}/?api_key=${env.TAM3_REMOTE}`); + const res = await fetch(`${env.TAM3_REMOTE}/api/tickets/${params.prefix}/${params.t_id}/?api_key=${env.TAM3_REMOTE_KEY}`); if (!res.ok) { return new Response(JSON.stringify({detail: "Unable to fetch ticket."}), {status: res.status, statusText: res.statusText}) } diff --git a/webapp/src/routes/baskets/[prefix]/+page.svelte b/webapp/src/routes/baskets/[prefix]/+page.svelte index 5450ae8..d839aa0 100644 --- a/webapp/src/routes/baskets/[prefix]/+page.svelte +++ b/webapp/src/routes/baskets/[prefix]/+page.svelte @@ -44,7 +44,7 @@ duplicateDown: () => { const next_idx = current_idx + 1; if (current_baskets[next_idx]) { - current_baskets[next_idx] = {...current_baskets[current_idx], b_id: current_baskets[next_idx].b_id, changed: true}; + current_baskets[next_idx] = {...current_baskets[current_idx], b_id: current_baskets[next_idx].b_id, winning_ticket: current_baskets[next_idx].winning_ticket, changed: true}; changeFocus(next_idx); } else { changeFocus(next_idx); @@ -53,7 +53,7 @@ duplicateUp: () => { const prev_idx = current_idx - 1; if (prev_idx >= 0) { - current_baskets[prev_idx] = {...current_baskets[current_idx], b_id: current_baskets[prev_idx].b_id, changed: true}; + current_baskets[prev_idx] = {...current_baskets[current_idx], b_id: current_baskets[prev_idx].b_id, winning_ticket: current_baskets[prev_idx].winning_ticket, changed: true}; changeFocus(prev_idx); } else { changeFocus(prev_idx); diff --git a/webapp/src/routes/reports/bybasket/[prefix]/+page.js b/webapp/src/routes/reports/bybasket/[prefix]/+page.js new file mode 100644 index 0000000..40b5754 --- /dev/null +++ b/webapp/src/routes/reports/bybasket/[prefix]/+page.js @@ -0,0 +1,7 @@ +export async function load({ fetch, params }) { + let res = await fetch(`/api/prefixes/${params.prefix}`); + const prefix_data = await res.json(); + res = await fetch(`/api/reports/bybasket/${params.prefix}`); + const report_data = await res.json() + return {prefix: prefix_data, report: report_data} +} \ No newline at end of file diff --git a/webapp/src/routes/reports/bybasket/[prefix]/+page.svelte b/webapp/src/routes/reports/bybasket/[prefix]/+page.svelte new file mode 100644 index 0000000..f215417 --- /dev/null +++ b/webapp/src/routes/reports/bybasket/[prefix]/+page.svelte @@ -0,0 +1,95 @@ + + +
+
+
+ + + +
+
+ +
+
+
+ + + + + + + + + + + + + + + {#each show_data as report_entry} + + + + + + + + {/each} + + + + + + + +

{prefix.name} - Report - {report_subject}

Basket IDDescriptionTicket #Winner NamePhone Number
{report_entry.b_id}{report_entry.description}{report_entry.winning_ticket}{report_entry.winner_name}{report_entry.phone_number}
{env.PUBLIC_TAM3_VENUE || ""}TAM3 by Dilan Gilluly
+ + \ No newline at end of file diff --git a/webapp/src/routes/reports/byname/[prefix]/+page.js b/webapp/src/routes/reports/byname/[prefix]/+page.js new file mode 100644 index 0000000..8bfff47 --- /dev/null +++ b/webapp/src/routes/reports/byname/[prefix]/+page.js @@ -0,0 +1,7 @@ +export async function load({ fetch, params }) { + let res = await fetch(`/api/prefixes/${params.prefix}`); + const prefix_data = await res.json(); + res = await fetch(`/api/reports/byname/${params.prefix}`); + const report_data = await res.json() + return {prefix: prefix_data, report: report_data} +} \ No newline at end of file diff --git a/webapp/src/routes/reports/byname/[prefix]/+page.svelte b/webapp/src/routes/reports/byname/[prefix]/+page.svelte new file mode 100644 index 0000000..8dda445 --- /dev/null +++ b/webapp/src/routes/reports/byname/[prefix]/+page.svelte @@ -0,0 +1,95 @@ + + +
+
+
+ + + +
+
+ +
+
+
+ + + + + + + + + + + + + + + {#each show_data as report_entry} + + + + + + + + {/each} + + + + + + + +

{prefix.name} - Report - {report_subject}

Winner NamePhone NumberBasket IDTicket #Description
{report_entry.winner_name}{report_entry.phone_number}{report_entry.b_id}{report_entry.winning_ticket}{report_entry.description}
{env.PUBLIC_TAM3_VENUE || ""}TAM3 by Dilan Gilluly
+ + \ No newline at end of file