Files
2025-07-07 01:44:12 +02:00

396 lines
11 KiB
Go

package handlers
import (
"fmt"
"log"
"net/http"
"strconv"
"strings"
"time"
"tankstopp/internal/currency"
"tankstopp/internal/models"
"tankstopp/internal/views/pages"
"github.com/gorilla/mux"
)
// AddFuelStopHandler handles adding new fuel stops
func (h *Handler) AddFuelStopHandler(w http.ResponseWriter, r *http.Request) {
userID, _ := h.getCurrentUser(r)
if userID == 0 {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
switch r.Method {
case "GET":
// Get user for default currency
user, err := h.db.GetUserByID(userID)
if err != nil {
log.Printf("Error getting user: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
// Get vehicles for the user
vehicles, err := h.db.GetVehicles(userID)
if err != nil {
log.Printf("Error getting vehicles: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
// Render add fuel stop form using templ
currencies := currency.SupportedCurrencies()
component := pages.AddFuelStopPage(user, user.Username, vehicles, currencies)
w.Header().Set("Content-Type", "text/html")
err = component.Render(r.Context(), w)
if err != nil {
log.Printf("Error rendering template: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
}
case "POST":
h.handleAddFuelStop(w, r, userID)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
// handleAddFuelStop processes the form submission for adding fuel stops
func (h *Handler) handleAddFuelStop(w http.ResponseWriter, r *http.Request, userID uint) {
err := r.ParseForm()
if err != nil {
log.Printf("Error parsing form: %v", err)
http.Error(w, "Bad request", http.StatusBadRequest)
return
}
// Parse form data
form := models.FuelStopForm{
Date: strings.TrimSpace(r.FormValue("date")),
VehicleID: parseUint(r.FormValue("vehicle_id")),
StationName: strings.TrimSpace(r.FormValue("station_name")),
Location: strings.TrimSpace(r.FormValue("location")),
FuelType: r.FormValue("fuel_type"),
Liters: parseFloat(r.FormValue("amount")),
PricePerL: parseFloat(r.FormValue("price_per_liter")),
TotalPrice: parseFloat(r.FormValue("total_cost")),
Currency: r.FormValue("currency"),
Odometer: parseInt(r.FormValue("odometer")),
TripLength: parseFloat(r.FormValue("trip_length")),
Notes: r.FormValue("notes"),
}
// Validate form
if err := h.validateFuelStopForm(&form); err != nil {
log.Printf("Validation error: %v", err)
http.Redirect(w, r, "/add?error="+err.Error(), http.StatusSeeOther)
return
}
// Convert form to fuel stop
fuelStop, err := form.ToFuelStop(userID)
if err != nil {
log.Printf("Error converting form to fuel stop: %v", err)
http.Redirect(w, r, "/add?error=Invalid+date+format", http.StatusSeeOther)
return
}
// Use station name as location if location is empty
if fuelStop.Location == "" {
fuelStop.Location = fuelStop.StationName
}
// Save to database
err = h.db.CreateFuelStop(fuelStop)
if err != nil {
log.Printf("Error creating fuel stop: %v", err)
http.Redirect(w, r, "/add?error=Failed+to+save+fuel+stop", http.StatusSeeOther)
return
}
// Redirect to dashboard with success message
http.Redirect(w, r, "/dashboard?success=Fuel+stop+added+successfully", http.StatusSeeOther)
}
// EditFuelStopHandler handles editing existing fuel stops
func (h *Handler) EditFuelStopHandler(w http.ResponseWriter, r *http.Request) {
userID, _ := h.getCurrentUser(r)
if userID == 0 {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
// Get the user object
user, err := h.db.GetUserByID(userID)
if err != nil {
log.Printf("Error getting user: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
vars := mux.Vars(r)
idInt, err := strconv.Atoi(vars["id"])
if err != nil {
http.Error(w, "Invalid ID", http.StatusBadRequest)
return
}
id := uint(idInt)
switch r.Method {
case "GET":
stop, err := h.db.GetFuelStopByID(id, userID)
if err != nil {
log.Printf("Error getting fuel stop: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
if stop == nil {
http.Error(w, "Fuel stop not found", http.StatusNotFound)
return
}
// Get vehicles for the user
vehicles, err := h.db.GetVehicles(userID)
if err != nil {
log.Printf("Error getting vehicles: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
// Render edit fuel stop form using templ
currencies := currency.SupportedCurrencies()
component := pages.EditFuelStopPage(user, user.Username, stop, vehicles, currencies)
w.Header().Set("Content-Type", "text/html")
err = component.Render(r.Context(), w)
if err != nil {
log.Printf("Error rendering template: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
}
case "POST":
h.handleEditFuelStop(w, r, id, userID)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
// handleEditFuelStop processes the form submission for editing fuel stops
func (h *Handler) handleEditFuelStop(w http.ResponseWriter, r *http.Request, id, userID uint) {
err := r.ParseForm()
if err != nil {
log.Printf("Error parsing form: %v", err)
http.Error(w, "Bad request", http.StatusBadRequest)
return
}
// Get existing fuel stop
existingStop, err := h.db.GetFuelStopByID(id, userID)
if err != nil {
log.Printf("Error getting fuel stop: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
if existingStop == nil {
http.Error(w, "Fuel stop not found", http.StatusNotFound)
return
}
// Parse form data
form := models.FuelStopForm{
Date: strings.TrimSpace(r.FormValue("date")),
VehicleID: parseUint(r.FormValue("vehicle_id")),
StationName: strings.TrimSpace(r.FormValue("station_name")),
Location: strings.TrimSpace(r.FormValue("location")),
FuelType: r.FormValue("fuel_type"),
Liters: parseFloat(r.FormValue("amount")),
PricePerL: parseFloat(r.FormValue("price_per_liter")),
TotalPrice: parseFloat(r.FormValue("total_cost")),
Currency: r.FormValue("currency"),
Odometer: parseInt(r.FormValue("odometer")),
TripLength: parseFloat(r.FormValue("trip_length")),
Notes: r.FormValue("notes"),
}
// Validate form
if err := h.validateFuelStopForm(&form); err != nil {
log.Printf("Validation error: %v", err)
http.Redirect(w, r, fmt.Sprintf("/edit/%d?error=%s", id, err.Error()), http.StatusSeeOther)
return
}
// Convert form to fuel stop
updatedStop, err := form.ToFuelStop(userID)
if err != nil {
log.Printf("Error converting form to fuel stop: %v", err)
http.Redirect(w, r, fmt.Sprintf("/edit/%d?error=Invalid+date+format", id), http.StatusSeeOther)
return
}
// Set the ID to update existing record
updatedStop.ID = id
// Use station name as location if location is empty
if updatedStop.Location == "" {
updatedStop.Location = updatedStop.StationName
}
// Update in database
err = h.db.UpdateFuelStop(updatedStop)
if err != nil {
log.Printf("Error updating fuel stop: %v", err)
http.Redirect(w, r, fmt.Sprintf("/edit/%d?error=Failed+to+update+fuel+stop", id), http.StatusSeeOther)
return
}
// Redirect to dashboard with success message
http.Redirect(w, r, "/dashboard?success=Fuel+stop+updated+successfully", http.StatusSeeOther)
}
// DeleteFuelStopHandler handles deleting fuel stops
func (h *Handler) DeleteFuelStopHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
userID, _ := h.getCurrentUser(r)
if userID == 0 {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
vars := mux.Vars(r)
idInt, err := strconv.Atoi(vars["id"])
if err != nil {
http.Error(w, "Invalid ID", http.StatusBadRequest)
return
}
id := uint(idInt)
// Verify fuel stop exists and belongs to user
fuelStop, err := h.db.GetFuelStopByID(id, userID)
if err != nil {
log.Printf("Error getting fuel stop: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
if fuelStop == nil {
http.Error(w, "Fuel stop not found", http.StatusNotFound)
return
}
// Delete fuel stop
err = h.db.DeleteFuelStop(id, userID)
if err != nil {
log.Printf("Error deleting fuel stop: %v", err)
http.Redirect(w, r, "/dashboard?error=Failed+to+delete+fuel+stop", http.StatusSeeOther)
return
}
// Redirect to dashboard with success message
http.Redirect(w, r, "/dashboard?success=Fuel+stop+deleted+successfully", http.StatusSeeOther)
}
// validateFuelStopForm validates the fuel stop form data
func (h *Handler) validateFuelStopForm(form *models.FuelStopForm) error {
if form.Date == "" {
return fmt.Errorf("Date is required")
}
// Validate date format
_, err := time.Parse("2006-01-02", form.Date)
if err != nil {
return fmt.Errorf("Invalid date format")
}
if form.VehicleID == 0 {
return fmt.Errorf("Vehicle is required")
}
if form.StationName == "" && form.Location == "" {
return fmt.Errorf("Station name or location is required")
}
if form.FuelType == "" {
return fmt.Errorf("Fuel type is required")
}
if form.Liters <= 0 {
return fmt.Errorf("Amount must be greater than 0")
}
if form.PricePerL <= 0 {
return fmt.Errorf("Price per liter must be greater than 0")
}
if form.TotalPrice <= 0 {
return fmt.Errorf("Total price must be greater than 0")
}
if form.Currency != "" && !currency.IsValidCurrency(form.Currency) {
return fmt.Errorf("Invalid currency")
}
if form.Odometer < 0 {
return fmt.Errorf("Odometer reading cannot be negative")
}
if form.TripLength < 0 {
return fmt.Errorf("Trip length cannot be negative")
}
if form.TripLength > 2000 {
return fmt.Errorf("Trip length cannot exceed 2000 km")
}
// Validate consumption if both trip length and amount are provided
if form.TripLength > 0 && form.Liters > 0 {
consumption := (form.Liters / form.TripLength) * 100
if consumption > 50 {
return fmt.Errorf("Fuel consumption %.1f L/100km seems unrealistic. Please check trip length and amount", consumption)
}
if consumption < 1 {
return fmt.Errorf("Fuel consumption %.1f L/100km seems too low. Please check trip length and amount", consumption)
}
}
return nil
}
// Helper functions for parsing form values
func parseFloat(s string) float64 {
if s == "" {
return 0
}
f, err := strconv.ParseFloat(s, 64)
if err != nil {
return 0
}
return f
}
func parseInt(s string) int {
if s == "" {
return 0
}
i, err := strconv.Atoi(s)
if err != nil {
return 0
}
return i
}
func parseUint(s string) uint {
if s == "" {
return 0
}
i, err := strconv.ParseUint(s, 10, 32)
if err != nil {
return 0
}
return uint(i)
}