nightly - 2025-09-20

This commit is contained in:
2025-09-21 00:09:19 -04:00
parent cbc20a2897
commit b5afe0ef48
20 changed files with 323 additions and 5 deletions

2
api/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
__pycache__
*/__pycache__

7
api/data/config.ini Normal file
View File

@@ -0,0 +1,7 @@
[db]
host = localhost
port = 3306
user = tam3
password = tam3
database = tam3

10
api/db.py Normal file
View File

@@ -0,0 +1,10 @@
from settings import read_config
from mysql.connector import connect
def session():
config = read_config()
conn = connect(**config["db"])
cur = conn.cursor()
return conn, cur

9
api/exceptions.py Normal file
View File

@@ -0,0 +1,9 @@
from fastapi import HTTPException, status
bad_key = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, detail="API Key is bad, very bad."
)
not_found = HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Resource not found."
)

48
api/key.py Executable file
View File

@@ -0,0 +1,48 @@
#!/bin/env python3
import string
from sys import argv
from repos import ApiKeyRepo
rdm_str = string.ascii_lowercase + string.digits
def generate():
if len(argv) < 3:
print("Please put name after the generate verb.")
quit()
new_key = ApiKeyRepo().create_api(argv[2])
print(new_key)
def list_keys():
result_keys = ApiKeyRepo().get_all()
for key in result_keys:
print(f"pc_name: {key.pc_name}")
print(key.api_key)
print("\n")
def delete_key():
if len(argv) < 3:
print("Please put api key to delete after the delete verb.")
quit()
del_status = ApiKeyRepo().delete(argv[2])
print(del_status)
if len(argv) < 2:
print("Please put action after api.py such as generate, list, or remove.")
quit()
else:
action = argv[1]
match action:
case "generate":
generate()
case "list":
list_keys()
case "delete":
delete_key()

View File

@@ -0,0 +1,13 @@
#!/bin/env python3
from fastapi import FastAPI
from sys import argv
from routers.prefixes import prefix_router
if argv[1] == "run":
app = FastAPI(title="TAM3 API Server", docs_url=None, redoc_url=None)
else:
app = FastAPI(title="TAM3 API Server")
app.include_router(prefix_router)

1
api/repos/__init__.py Normal file
View File

@@ -0,0 +1 @@
from .api_keys import ApiKey, ApiKeyRepo

43
api/repos/api_keys.py Normal file
View File

@@ -0,0 +1,43 @@
import random as r
import string
from dataclasses import dataclass
from .template import Repo
rdm_set = string.ascii_lowercase + string.digits
@dataclass
class ApiKey:
api_key: str
pc_name: str = ""
class ApiKeyRepo(Repo):
def check_api(self, api_key: str) -> bool:
self.cur.execute("SELECT * FROM api_keys WHERE api_key = %s", (api_key,))
result = self.cur.fetchone()
if result:
return True
else:
return False
def create_api(self, name: str) -> str:
while True:
new_key = "".join(r.choice(rdm_set) for i in range(16))
if not self.check_api(new_key):
break
self.cur.execute("INSERT INTO api_keys VALUES (%s, %s)", (new_key, name))
self.conn.commit()
return new_key
def get_all(self) -> list[ApiKey]:
self.cur.execute("SELECT * FROM api_keys")
results = self.cur.fetchall()
if not results:
return []
return [ApiKey(*r) for r in results]
def delete(self, api_key: str) -> str:
self.cur.execute("DELETE FROM api_keys WHERE api_key = %s", (api_key,))
self.conn.commit()
return "Key deleted successfully."

39
api/repos/prefixes.py Normal file
View File

@@ -0,0 +1,39 @@
from .template import Repo
from dataclasses import dataclass
from exceptions import not_found
@dataclass
class Prefix:
name: str
color: str = ""
weight: int = 0
class PrefixRepo(Repo):
def get_all(self) -> list[Prefix]:
self.cur.execute("SELECT * FROM prefixes ORDER BY weight, name")
results = self.cur.fetchall()
if not results:
return []
return [Prefix(*r) for r in results]
def get_one(self, prefix_name: str):
self.cur.execute("SELECT * FROM prefixes WHERE name = %s", (prefix_name,))
result = self.cur.fetchone()
if not result:
raise not_found
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.conn.commit()
return "Prefix inserted successfully."
def del_one(self, prefix_name: str) -> str:
self.cur.execute("DELETE FROM prefixes WHERE name = %s", (prefix_name,))
self.conn.commit()
return "Prefix deleted successfully."

6
api/repos/template.py Normal file
View File

@@ -0,0 +1,6 @@
from db import session
class Repo:
def __init__(self):
self.conn, self.cur = session()

0
api/routers/__init__.py Normal file
View File

26
api/routers/prefixes.py Normal file
View File

@@ -0,0 +1,26 @@
from fastapi import APIRouter
from repos.prefixes import Prefix, PrefixRepo
prefix_router = APIRouter(prefix="/api/prefixes")
@prefix_router.get("/")
def get_all_prefixes():
return PrefixRepo().get_all()
@prefix_router.get("/{prefix_name}/")
def get_one_prefix(prefix_name: str):
return PrefixRepo().get_one(prefix_name)
@prefix_router.post("/")
def post_one_prefix(p: Prefix):
rep_detail = PrefixRepo().add_one(p)
return {"detail": rep_detail}
@prefix_router.delete("/")
def del_one_prefix(prefix_name: str):
rep_detail = PrefixRepo().del_one(prefix_name)
return {"detail": rep_detail}

25
api/settings.py Normal file
View File

@@ -0,0 +1,25 @@
import os
from pathlib import Path
from configparser import ConfigParser
data_path = Path(os.getenv("TAM3_DATA_PATH", "data"))
data_path.mkdir(exist_ok=True)
def read_config():
config = ConfigParser()
config_path = data_path / "config.ini"
if config_path.is_file():
config.read(config_path)
return config
else:
config["db"] = {
"host": os.getenv("TAM3_DB_HOST", "localhost"),
"port": os.getenv("TAM3_DB_PORT", "3306"),
"user": os.getenv("TAM3_DB_USER", "tam3"),
"password": os.getenv("TAM3_DB_PASSWD", "tam3"),
"database": os.getenv("TAM3_DB_DATABASE", "tam3"),
}
with open(config_path, "w") as f:
config.write(f)
return config

26
db/compose.yml Normal file
View File

@@ -0,0 +1,26 @@
services:
db:
image: mariadb:lts
restart: always
hostname: mariadb
container_name: mariadb
environment:
MARIADB_ROOT_PASSWORD: dbob16
MARIADB_DATABASE: tam3
MARIADB_USER: tam3
MARIADB_PASSWORD: tam3
volumes:
- "tam3-db:/var/lib/mysql"
- "./schema.sql:/docker-entrypoint-initdb.d/schema.sql:ro"
ports:
- 127.0.0.1:3306:3306
adminer:
image: adminer
restart: always
hostname: adminer
container_name: adminer
ports:
- 127.0.0.1:8080:8080
volumes:
tam3-db:

40
db/schema.sql Normal file
View File

@@ -0,0 +1,40 @@
CREATE TABLE IF NOT EXISTS api_keys (
`api_key` VARCHAR(255),
`pc_name` VARCHAR(255)
);
CREATE TABLE IF NOT EXISTS prefixes (
`name` VARCHAR(255),
`color` VARCHAR(255),
`weight` INT DEFAULT 0
);
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),
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
);
CREATE VIEW IF NOT EXISTS combined AS
SELECT b.prefix, b.b_id, b.winning_ticket, CONCAT(t.last_name, ", ", t.first_name) AS winner
FROM baskets b LEFT JOIN tickets t
ON b.prefix = t.prefix AND b.winning_ticket = t.t_id
ORDER BY b.prefix, b.b_id;
CREATE VIEW IF NOT EXISTS report AS
SELECT b.prefix, CONCAT(t.last_name, ", ", t.first_name) AS winner_name, t.phone_number, t.preference, b.b_id, b.winning_ticket, b.description
FROM baskets b LEFT JOIN tickets t
ON b.prefix = t.prefix AND b.winning_ticket = t.t_id
ORDER BY b.prefix, winner_name, t.phone_number, t.preference, b.b_id;

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -1,5 +1,6 @@
<script>
import favicon from '$lib/assets/favicon.svg';
import favicon from "$lib/assets/favicon.svg";
import "$lib/css/main.css";
let { children } = $props();
</script>

View File

@@ -1,2 +1,22 @@
<h1>Welcome to SvelteKit</h1>
<p>Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the documentation</p>
<script>
let prefix_name = $state("");
</script>
<div class="main-menu">
<h1>TAM3 - Main Menu</h1>
<div class="prefix-selector">
<select bind:value={prefix_name}></select>
</div>
<div class="form-buttons">
<a href="/tickets/" class="button">Tickets</a>
<a href="/baskets/" class="button">Baskets</a>
<a href="/drawing/" class="button">Drawing</a>
</div>
<button>Test</button>
</div>
<style>
.prefix-selector select {
width: 100%;
}
</style>