package handlers import ( "database/sql" "encoding/json" "log" "net/http" "time" "github.com/go-chi/chi/v5" "github.com/golang-jwt/jwt/v5" ) // GET current finalize time func (app *App) HandleGetFinalizeTime(w http.ResponseWriter, r *http.Request) { var t string err := app.DB.QueryRow("SELECT value FROM settings WHERE key = 'finalize_time'").Scan(&t) if err == sql.ErrNoRows { t = "10:30" // default if not set } writeJSON(w, map[string]string{"time": t}) } // Handle User Registration func (app *App) HandleRegister(w http.ResponseWriter, r *http.Request) { var req registerRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid request", http.StatusBadRequest) return } if req.Username == "" || req.Password == "" { http.Error(w, "username and password required", http.StatusBadRequest) return } res, err := app.DB.Exec( "INSERT INTO users (username, password, role) VALUES (?, ?, ?)", req.Username, req.Password, 100, ) if err != nil { http.Error(w, "username already exists", http.StatusBadRequest) return } id, _ := res.LastInsertId() token, err := generateToken(int(id), req.Username) if err != nil { http.Error(w, "could not generate token", http.StatusInternalServerError) return } writeJSON(w, map[string]string{ "token": token, "username": req.Username, }) } // Handle User Login func (app *App) HandleLogin(w http.ResponseWriter, r *http.Request) { var req loginRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid request", http.StatusBadRequest) return } var id int var storedPassword string err := app.DB.QueryRow("SELECT id, password FROM users WHERE username = ?", req.Username).Scan(&id, &storedPassword) if err != nil { http.Error(w, "invalid username or password", http.StatusUnauthorized) return } if storedPassword != req.Password { http.Error(w, "invalid username or password", http.StatusUnauthorized) return } token, err := generateToken(id, req.Username) if err != nil { http.Error(w, "could not generate token", http.StatusInternalServerError) return } writeJSON(w, map[string]string{ "token": token, "username": req.Username, }) } // Get Menu Options from DB func (app *App) HandleOptions(w http.ResponseWriter, r *http.Request) { rows, err := app.DB.Query("SELECT category, name FROM menu_items ORDER BY id ASC") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } defer rows.Close() valasztek := Valasztek{ Levesek: []Leves{}, Foetelek: []Foetel{}, Koretek: []Koret{}, } for rows.Next() { var category, name string if err := rows.Scan(&category, &name); err == nil { switch category { case "soup": valasztek.Levesek = append(valasztek.Levesek, name) case "main": valasztek.Foetelek = append(valasztek.Foetelek, name) case "side": valasztek.Koretek = append(valasztek.Koretek, name) } } } writeJSON(w, valasztek) } // Handle User Saved Selection func (app *App) HandleSaveSelection(w http.ResponseWriter, r *http.Request) { tokenStr := r.Header.Get("Authorization") if tokenStr == "" { http.Error(w, "missing token", http.StatusUnauthorized) return } // Strip "Bearer " prefix if len(tokenStr) > 7 && tokenStr[:7] == "Bearer " { tokenStr = tokenStr[7:] } token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) { return jwtSecret, nil }) if err != nil || !token.Valid { http.Error(w, "invalid token", http.StatusUnauthorized) return } claims := token.Claims.(jwt.MapClaims) userID := int(claims["user_id"].(float64)) var req selectionRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid request", http.StatusBadRequest) return } now := time.Now().Format(time.RFC3339) // Insert or update selection (simple approach: delete old one, insert new) _, _ = app.DB.Exec("DELETE FROM selections WHERE user_id = ?", userID) _, err = app.DB.Exec( "INSERT INTO selections (user_id, main, side, soup, created_at) VALUES (?, ?, ?, ?, ?)", userID, req.Main, req.Side, req.Soup, now, ) if err != nil { http.Error(w, "failed to save selection", http.StatusInternalServerError) return } writeJSON(w, map[string]string{ "status": "ok", "main": req.Main, "side": req.Side, "soup": req.Soup, }) } // Handle User's selected Menu func (app *App) HandleGetSelection(w http.ResponseWriter, r *http.Request) { tokenStr := r.Header.Get("Authorization") if tokenStr == "" { http.Error(w, "missing token", http.StatusUnauthorized) return } if len(tokenStr) > 7 && tokenStr[:7] == "Bearer " { tokenStr = tokenStr[7:] } token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) { return jwtSecret, nil }) if err != nil || !token.Valid { http.Error(w, "invalid token", http.StatusUnauthorized) return } claims := token.Claims.(jwt.MapClaims) userID := int(claims["user_id"].(float64)) row := app.DB.QueryRow("SELECT main, side, soup, created_at FROM selections WHERE user_id = ?", userID) var main, side, soup, createdAt string err = row.Scan(&main, &side, &soup, &createdAt) if err == sql.ErrNoRows { writeJSON(w, map[string]string{"status": "none"}) return } else if err != nil { http.Error(w, "db error", http.StatusInternalServerError) return } writeJSON(w, map[string]string{ "main": main, "side": side, "soup": soup, "created_at": createdAt, }) } // Get all active Orders func (app *App) HandleGetOrders(w http.ResponseWriter, r *http.Request) { rows, err := app.DB.Query("SELECT id, username, soup, main, side, created_at, status FROM orders WHERE status = 'active' ORDER BY id DESC") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } defer rows.Close() var out []Order for rows.Next() { var o Order if err := rows.Scan(&o.ID, &o.Username, &o.Soup, &o.Main, &o.Side, &o.CreatedAt, &o.Status); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } out = append(out, o) } writeJSON(w, out) } // Handle new Order func (app *App) HandleAddOrder(w http.ResponseWriter, r *http.Request) { var in struct{ Soup, Main, Side string } if err := json.NewDecoder(r.Body).Decode(&in); err != nil || in.Main == "" || in.Side == "" { http.Error(w, "invalid order", http.StatusBadRequest) return } username, err := usernameFromJWT(r) if err != nil || username == "" { http.Error(w, "unauthorized", http.StatusUnauthorized) return } now := time.Now().Format(time.RFC3339) // // Step 1: mark any previous active orders for this user as history // _, _ = app.DB.Exec("UPDATE orders SET status = 'history' WHERE username = ? AND status = 'active'", username) // Step 2: insert new active order res, err := app.DB.Exec( "INSERT INTO orders (username, soup, main, side, created_at, status) VALUES (?, ?, ?, ?, ?, ?)", username, in.Soup, in.Main, in.Side, now, "active", ) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } id, _ := res.LastInsertId() ord := Order{ ID: int(id), Username: username, Soup: in.Soup, Main: in.Main, Side: in.Side, CreatedAt: now, Status: "active", } // broadcast to SSE clients if data, err := json.Marshal(ord); err == nil { log.Printf("Broadcasting active order via SSE: %+v", ord) app.Broker.broadcast <- data } writeJSON(w, ord) } // Handle Order deletion func (app *App) HandleDeleteOrder(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") username, err := usernameFromJWT(r) if err != nil || username == "" { http.Error(w, "unauthorized", http.StatusUnauthorized) return } res, err := app.DB.Exec("UPDATE orders SET status = 'history' WHERE id = ? AND username = ?", id, username) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } n, _ := res.RowsAffected() if n == 0 { http.Error(w, "order not found", http.StatusNotFound) return } // broadcast deletion (tell clients to refresh) msg := map[string]any{"id": id, "status": "history", "event": "deleted"} if data, err := json.Marshal(msg); err == nil { app.Broker.broadcast <- data } writeJSON(w, map[string]string{"status": "archived"}) } // Send Orders over SSE to client func (app *App) HandleOrdersStream(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/event-stream") w.Header().Set("Cache-Control", "no-cache") w.Header().Set("Connection", "keep-alive") w.Header().Del("Content-Encoding") w.Header().Set("X-Accel-Buffering", "no") flusher, ok := w.(http.Flusher) if !ok { http.Error(w, "stream unsupported", http.StatusInternalServerError) return } ch := make(chan []byte, 1) app.Broker.add <- ch defer func() { app.Broker.remove <- ch }() // open the stream w.Write([]byte(":ok\n\n")) flusher.Flush() ticker := time.NewTicker(15 * time.Second) defer ticker.Stop() for { select { case <-r.Context().Done(): log.Println("SSE client disconnected") return case msg := <-ch: log.Printf("SSE send: %s", string(msg)) w.Write([]byte("data: ")) w.Write(msg) w.Write([]byte("\n\n")) flusher.Flush() case <-ticker.C: // log.Println("SSE heartbeat -> :ping") w.Write([]byte(":ping\n\n")) flusher.Flush() } } } // Get User's information and Role func (app *App) HandleWhoAmI(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 } writeJSON(w, map[string]any{ "username": username, "role": role, }) }