nightly - 2025-09-20
This commit is contained in:
2
api/.gitignore
vendored
Normal file
2
api/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
__pycache__
|
||||
*/__pycache__
|
||||
7
api/data/config.ini
Normal file
7
api/data/config.ini
Normal file
@@ -0,0 +1,7 @@
|
||||
[db]
|
||||
host = localhost
|
||||
port = 3306
|
||||
user = tam3
|
||||
password = tam3
|
||||
database = tam3
|
||||
|
||||
10
api/db.py
Normal file
10
api/db.py
Normal 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
9
api/exceptions.py
Normal 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
48
api/key.py
Executable 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()
|
||||
|
||||
13
api/main.py
13
api/main.py
@@ -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
1
api/repos/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .api_keys import ApiKey, ApiKeyRepo
|
||||
43
api/repos/api_keys.py
Normal file
43
api/repos/api_keys.py
Normal 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
39
api/repos/prefixes.py
Normal 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
6
api/repos/template.py
Normal 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
0
api/routers/__init__.py
Normal file
26
api/routers/prefixes.py
Normal file
26
api/routers/prefixes.py
Normal 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
25
api/settings.py
Normal 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
26
db/compose.yml
Normal 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
40
db/schema.sql
Normal 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;
|
||||
1
webapp/src/hooks.server.js
Normal file
1
webapp/src/hooks.server.js
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
1
webapp/src/lib/css/main.css
Normal file
1
webapp/src/lib/css/main.css
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
0
webapp/src/routes/api/prefixes/+server.svelte
Normal file
0
webapp/src/routes/api/prefixes/+server.svelte
Normal file
Reference in New Issue
Block a user