Files
reservierungssystem/backend/internal/handlers/handlers_new.go
T

774 lines
20 KiB
Go

package api
import (
"encoding/json"
"net/http"
"strconv"
"strings"
"time"
"reservation-system/backend/internal/auth"
"reservation-system/backend/internal/db"
"reservation-system/backend/internal/models"
"golang.org/x/crypto/bcrypt"
)
type Handler struct {
db *db.DB
}
func NewHandler(database *db.DB) *Handler {
return &Handler{db: database}
}
func (h *Handler) RegisterRoutes(mux *http.ServeMux) {
// Auth
mux.HandleFunc("/api/auth/login", h.handleLogin)
mux.HandleFunc("/api/auth/refresh", h.handleRefresh)
// Rooms (protected)
mux.HandleFunc("/api/rooms", h.authMiddleware(h.handleRooms))
mux.HandleFunc("/api/rooms/", h.authMiddleware(h.handleRoomDetail))
// Tables (protected)
mux.HandleFunc("/api/tables", h.authMiddleware(h.handleTables))
mux.HandleFunc("/api/tables/", h.authMiddleware(h.handleTableDetail))
mux.HandleFunc("/api/tables/merge", h.authMiddleware(h.handleMergeTables))
// NEW: Room tables endpoint
mux.HandleFunc("/api/rooms/", h.authMiddleware(h.handleRoomTables))
// Reservations (protected)
mux.HandleFunc("/api/reservations", h.authMiddleware(h.handleReservations))
mux.HandleFunc("/api/reservations/", h.authMiddleware(h.handleReservationDetail))
mux.HandleFunc("/api/reservations/date/", h.authMiddleware(h.handleReservationsByDate))
// NEW: Room Bookings
mux.HandleFunc("/api/room-bookings", h.authMiddleware(h.handleRoomBookings))
mux.HandleFunc("/api/room-bookings/", h.authMiddleware(h.handleRoomBookingDetail))
// Availability
mux.HandleFunc("/api/availability", h.authMiddleware(h.handleAvailability))
mux.HandleFunc("/api/availability/room", h.authMiddleware(h.handleRoomAvailability))
// Email config
mux.HandleFunc("/api/email-config", h.authMiddleware(h.handleEmailConfig))
// Dashboard stats
mux.HandleFunc("/api/dashboard", h.authMiddleware(h.handleDashboard))
}
func (h *Handler) handleLogin(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
var req struct {
Username string `json:"username"`
Password string `json:"password"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
return
}
user, err := h.db.GetUserByUsername(req.Username)
if err != nil {
http.Error(w, "Server error", http.StatusInternalServerError)
return
}
if user == nil || bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password)) != nil {
http.Error(w, "Invalid credentials", http.StatusUnauthorized)
return
}
token, err := auth.GenerateToken(user.Username, user.IsAdmin)
if err != nil {
http.Error(w, "Server error", http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(map[string]string{
"token": token,
"user": user.Username,
})
}
func (h *Handler) handleRefresh(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
http.Error(w, "Missing token", http.StatusUnauthorized)
return
}
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
claims, err := auth.ValidateToken(tokenString)
if err != nil {
http.Error(w, "Invalid token", http.StatusUnauthorized)
return
}
newToken, err := auth.GenerateToken(claims.Username, claims.IsAdmin)
if err != nil {
http.Error(w, "Server error", http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(map[string]string{
"token": newToken,
})
}
func (h *Handler) authMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
http.Error(w, "Missing token", http.StatusUnauthorized)
return
}
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
if _, err := auth.ValidateToken(tokenString); err != nil {
http.Error(w, "Invalid token", http.StatusUnauthorized)
return
}
next(w, r)
}
}
// Rooms Handler
func (h *Handler) handleRooms(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
rooms, err := h.db.GetRooms()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(rooms)
case http.MethodPost:
var room models.Room
if err := json.NewDecoder(r.Body).Decode(&room); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
return
}
if room.Name == "" {
http.Error(w, "Name required", http.StatusBadRequest)
return
}
if err := h.db.CreateRoom(&room); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(room)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
func (h *Handler) handleRoomDetail(w http.ResponseWriter, r *http.Request) {
// Check if it's /api/rooms/:id/tables
path := r.URL.Path
if strings.HasSuffix(path, "/tables") {
h.handleRoomTables(w, r)
return
}
idStr := strings.TrimPrefix(path, "/api/rooms/")
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "Invalid ID", http.StatusBadRequest)
return
}
switch r.Method {
case http.MethodGet:
room, err := h.db.GetRoom(id)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if room == nil {
http.Error(w, "Not found", http.StatusNotFound)
return
}
json.NewEncoder(w).Encode(room)
case http.MethodPut:
var room models.Room
if err := json.NewDecoder(r.Body).Decode(&room); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
return
}
room.ID = id
if err := h.db.UpdateRoom(&room); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(room)
case http.MethodDelete:
if err := h.db.DeleteRoom(id); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusNoContent)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
// NEW: Room Tables Handler - GET /api/rooms/:id/tables
func (h *Handler) handleRoomTables(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet && r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
path := r.URL.Path
idStr := strings.TrimSuffix(strings.TrimPrefix(path, "/api/rooms/"), "/tables")
roomID, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "Invalid room ID", http.StatusBadRequest)
return
}
if r.Method == http.MethodGet {
tables, err := h.db.GetTablesByRoom(roomID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(tables)
} else {
// POST - create table for room
var table models.Table
if err := json.NewDecoder(r.Body).Decode(&table); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
return
}
table.RoomID = roomID
if table.MaxGuests == 0 {
http.Error(w, "Max guests required", http.StatusBadRequest)
return
}
if err := h.db.CreateTable(&table); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(table)
}
}
// Tables Handler
func (h *Handler) handleTables(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
roomID := r.URL.Query().Get("room_id")
var tables []models.Table
var err error
if roomID != "" {
rid, _ := strconv.Atoi(roomID)
tables, err = h.db.GetTablesByRoom(rid)
} else {
tables, err = h.db.GetAllTables()
}
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(tables)
case http.MethodPost:
var table models.Table
if err := json.NewDecoder(r.Body).Decode(&table); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
return
}
if table.MaxGuests == 0 {
http.Error(w, "Max guests required", http.StatusBadRequest)
return
}
if err := h.db.CreateTable(&table); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(table)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
func (h *Handler) handleTableDetail(w http.ResponseWriter, r *http.Request) {
idStr := strings.TrimPrefix(r.URL.Path, "/api/tables/")
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "Invalid ID", http.StatusBadRequest)
return
}
switch r.Method {
case http.MethodGet:
table, err := h.db.GetTable(id)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if table == nil {
http.Error(w, "Not found", http.StatusNotFound)
return
}
json.NewEncoder(w).Encode(table)
case http.MethodPut:
var table models.Table
if err := json.NewDecoder(r.Body).Decode(&table); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
return
}
table.ID = id
if err := h.db.UpdateTable(&table); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(table)
case http.MethodDelete:
if err := h.db.DeleteTable(id); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusNoContent)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
// Reservations Handler
func (h *Handler) handleReservations(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
date := r.URL.Query().Get("date")
if date == "" {
date = time.Now().Format("2006-01-02")
}
reservations, err := h.db.GetReservationsByDate(date)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(reservations)
case http.MethodPost:
var res models.Reservation
if err := json.NewDecoder(r.Body).Decode(&res); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
return
}
// Check availability
available, err := h.db.CheckAvailability(res.TableID, res.Date, res.TimeFrom, res.TimeTo)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if !available {
http.Error(w, "Table not available at this time", http.StatusConflict)
return
}
// Check table capacity
table, err := h.db.GetTable(res.TableID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if table != nil && res.Guests > table.MaxGuests {
http.Error(w, "Guest count exceeds table capacity", http.StatusBadRequest)
return
}
if res.Source == "" {
res.Source = "manual"
}
if err := h.db.CreateReservation(&res); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(res)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
func (h *Handler) handleReservationDetail(w http.ResponseWriter, r *http.Request) {
idStr := strings.TrimPrefix(r.URL.Path, "/api/reservations/")
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "Invalid ID", http.StatusBadRequest)
return
}
switch r.Method {
case http.MethodGet:
res, err := h.db.GetReservation(id)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if res == nil {
http.Error(w, "Not found", http.StatusNotFound)
return
}
json.NewEncoder(w).Encode(res)
case http.MethodPut:
var res models.Reservation
if err := json.NewDecoder(r.Body).Decode(&res); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
return
}
res.ID = id
if err := h.db.UpdateReservation(&res); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(res)
case http.MethodDelete:
if err := h.db.DeleteReservation(id); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusNoContent)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
func (h *Handler) handleReservationsByDate(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
date := strings.TrimPrefix(r.URL.Path, "/api/reservations/date/")
if date == "" {
http.Error(w, "Date required", http.StatusBadRequest)
return
}
reservations, err := h.db.GetReservationsByDate(date)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(reservations)
}
// NEW: Room Bookings Handlers
func (h *Handler) handleRoomBookings(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
roomID := r.URL.Query().Get("room_id")
date := r.URL.Query().Get("date")
var bookings []models.RoomBooking
var err error
if roomID != "" {
rid, _ := strconv.Atoi(roomID)
bookings, err = h.db.GetRoomBookingsByRoom(rid)
} else if date != "" {
bookings, err = h.db.GetRoomBookingsByDate(date)
} else {
// Get all bookings for today
bookings, err = h.db.GetRoomBookingsByDate(time.Now().Format("2006-01-02"))
}
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(bookings)
case http.MethodPost:
var rb models.RoomBooking
if err := json.NewDecoder(r.Body).Decode(&rb); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
return
}
// Check room availability
available, err := h.db.CheckRoomAvailability(rb.RoomID, rb.Date, rb.TimeFrom, rb.TimeTo)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if !available {
http.Error(w, "Room not available at this time", http.StatusConflict)
return
}
// Check room capacity
room, err := h.db.GetRoom(rb.RoomID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if room != nil && rb.Guests > room.Capacity {
http.Error(w, "Guest count exceeds room capacity", http.StatusBadRequest)
return
}
if rb.Status == "" {
rb.Status = "confirmed"
}
if err := h.db.CreateRoomBooking(&rb); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(rb)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
func (h *Handler) handleRoomBookingDetail(w http.ResponseWriter, r *http.Request) {
idStr := strings.TrimPrefix(r.URL.Path, "/api/room-bookings/")
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "Invalid ID", http.StatusBadRequest)
return
}
switch r.Method {
case http.MethodGet:
rb, err := h.db.GetRoomBooking(id)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if rb == nil {
http.Error(w, "Not found", http.StatusNotFound)
return
}
json.NewEncoder(w).Encode(rb)
case http.MethodPut:
var rb models.RoomBooking
if err := json.NewDecoder(r.Body).Decode(&rb); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
return
}
rb.ID = id
if err := h.db.UpdateRoomBooking(&rb); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(rb)
case http.MethodDelete:
if err := h.db.DeleteRoomBooking(id); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusNoContent)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
// Availability Handler
func (h *Handler) handleAvailability(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
date := r.URL.Query().Get("date")
if date == "" {
date = time.Now().Format("2006-01-02")
}
tables, err := h.db.GetAllTables()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
var availability []map[string]interface{}
for _, table := range tables {
reservations, _ := h.db.GetReservationsByTable(table.ID, date)
availability = append(availability, map[string]interface{}{
"table_id": table.ID,
"table_name": table.RoomName + " - Tisch " + strconv.Itoa(table.ID),
"max_guests": table.MaxGuests,
"reservations": reservations,
})
}
json.NewEncoder(w).Encode(availability)
}
// NEW: Room Availability Handler
func (h *Handler) handleRoomAvailability(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
date := r.URL.Query().Get("date")
if date == "" {
date = time.Now().Format("2006-01-02")
}
rooms, err := h.db.GetRooms()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
var availability []map[string]interface{}
for _, room := range rooms {
bookings, _ := h.db.GetRoomBookingsByRoom(room.ID)
availability = append(availability, map[string]interface{}{
"room_id": room.ID,
"room_name": room.Name,
"capacity": room.Capacity,
"bookings": bookings,
})
}
json.NewEncoder(w).Encode(availability)
}
func (h *Handler) handleMergeTables(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
var req struct {
ParentTableID int `json:"parent_table_id"`
ChildTableID int `json:"child_table_id"`
MergedName string `json:"merged_name"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
return
}
parentTable, err := h.db.GetTable(req.ParentTableID)
if err != nil || parentTable == nil {
http.Error(w, "Parent table not found", http.StatusBadRequest)
return
}
childTable, err := h.db.GetTable(req.ChildTableID)
if err != nil || childTable == nil {
http.Error(w, "Child table not found", http.StatusBadRequest)
return
}
if parentTable.RoomID != childTable.RoomID {
http.Error(w, "Tables must be in the same room", http.StatusBadRequest)
return
}
merge := models.TableMerge{
ParentTableID: req.ParentTableID,
ChildTableID: req.ChildTableID,
MergedName: req.MergedName,
Active: true,
}
if err := h.db.CreateTableMerge(&merge); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(merge)
}
func (h *Handler) handleEmailConfig(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
cfg, err := h.db.GetEmailConfig()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if cfg != nil {
cfg.Password = "***"
}
json.NewEncoder(w).Encode(cfg)
case http.MethodPost, http.MethodPut:
var cfg models.EmailConfig
if err := json.NewDecoder(r.Body).Decode(&cfg); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
return
}
if err := h.db.SaveEmailConfig(&cfg); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(cfg)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
func (h *Handler) handleDashboard(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
today := time.Now().Format("2006-01-02")
totalGuests, _ := h.db.GetTotalGuestsForDate(today)
reservations, _ := h.db.GetReservationsByDate(today)
roomBookings, _ := h.db.GetRoomBookingsByDate(today)
dashboard := map[string]interface{}{
"today": today,
"total_guests": totalGuests,
"reservation_count": len(reservations),
"room_booking_count": len(roomBookings),
"reservations": reservations,
"room_bookings": roomBookings,
}
json.NewEncoder(w).Encode(dashboard)
}