151 lines
3.4 KiB
Go
151 lines
3.4 KiB
Go
package handlers
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type FinalizedSummary struct {
|
|
Pickup []string `json:"pickup"`
|
|
Kitchen []string `json:"kitchen"`
|
|
}
|
|
|
|
type App struct {
|
|
DB *sql.DB
|
|
Broker *Broker
|
|
FinalizeUpdate chan struct{} // notify scheduler to reload time
|
|
LastSummary *FinalizedSummary
|
|
}
|
|
|
|
func NewApp(db *sql.DB, broker *Broker) *App {
|
|
return &App{
|
|
DB: db,
|
|
Broker: broker,
|
|
FinalizeUpdate: make(chan struct{}, 1),
|
|
}
|
|
}
|
|
|
|
func (app *App) RequireLevel(maxAllowed int) func(http.Handler) http.Handler {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
username, err := usernameFromJWT(r)
|
|
if err != nil || username == "" {
|
|
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
role, err := app.getUserRole(username)
|
|
if err != nil {
|
|
http.Error(w, "db error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
if role > maxAllowed {
|
|
http.Error(w, "forbidden", http.StatusForbidden)
|
|
return
|
|
}
|
|
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|
|
|
|
func (app *App) getUserRole(username string) (int, error) {
|
|
var role int
|
|
err := app.DB.QueryRow("SELECT role FROM users WHERE username = ?", username).Scan(&role)
|
|
return role, err
|
|
}
|
|
|
|
func finalizeOrders(app *App) error {
|
|
rows, err := app.DB.Query("SELECT id, username, soup, main, side FROM orders WHERE status = 'active'")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var summary []Order
|
|
for rows.Next() {
|
|
var o Order
|
|
if err := rows.Scan(&o.ID, &o.Username, &o.Soup, &o.Main, &o.Side); err == nil {
|
|
summary = append(summary, o)
|
|
}
|
|
}
|
|
|
|
// Step 2: send summary (log, email, or message)
|
|
sendSummary(app, summary)
|
|
|
|
// Step 3: archive
|
|
_, err = app.DB.Exec("UPDATE orders SET status = 'history' WHERE status = 'active'")
|
|
|
|
// Step 4: broadcast deletions to SSE clients
|
|
for _, o := range summary {
|
|
msg := map[string]any{"id": o.ID, "status": "history", "event": "deleted"}
|
|
if data, err := json.Marshal(msg); err == nil {
|
|
app.Broker.broadcast <- data
|
|
}
|
|
time.Sleep(20 * time.Millisecond)
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func sendSummary(app *App, orders []Order) *FinalizedSummary {
|
|
// Pickup view
|
|
userMap := make(map[string][]string)
|
|
for _, o := range orders {
|
|
items := []string{}
|
|
if o.Soup != "" {
|
|
items = append(items, o.Soup)
|
|
}
|
|
items = append(items, o.Main, o.Side)
|
|
userMap[o.Username] = append(userMap[o.Username], items...)
|
|
}
|
|
pickup := []string{}
|
|
for user, items := range userMap {
|
|
pickup = append(pickup, fmt.Sprintf("%s: [%s]", user, strings.Join(items, ", ")))
|
|
}
|
|
|
|
// Kitchen view
|
|
kitchenMap := make(map[string]int)
|
|
for _, o := range orders {
|
|
if o.Soup != "" {
|
|
kitchenMap[o.Soup]++
|
|
}
|
|
kitchenMap[o.Main]++
|
|
kitchenMap[o.Side]++
|
|
}
|
|
kitchen := []string{}
|
|
for item, count := range kitchenMap {
|
|
kitchen = append(kitchen, fmt.Sprintf("%s: %d", item, count))
|
|
}
|
|
|
|
summary := &FinalizedSummary{Pickup: pickup, Kitchen: kitchen}
|
|
app.LastSummary = summary
|
|
|
|
// Log
|
|
log.Println("=== Pickup view ===")
|
|
for _, line := range pickup {
|
|
log.Println(line)
|
|
}
|
|
log.Println("=== Kitchen view ===")
|
|
for _, line := range kitchen {
|
|
log.Println(line)
|
|
}
|
|
|
|
return summary
|
|
}
|
|
|
|
func (app *App) getSetting(key, def string) string {
|
|
var v string
|
|
err := app.DB.QueryRow("SELECT value FROM settings WHERE key = ?", key).Scan(&v)
|
|
if err != nil || v == "" {
|
|
return def
|
|
}
|
|
return v
|
|
}
|