first commit
This commit is contained in:
@@ -0,0 +1,395 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user