404 lines
11 KiB
Go
404 lines
11 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"tankstopp/internal/models"
|
|
|
|
"github.com/gorilla/mux"
|
|
)
|
|
|
|
// APIGetFuelStopsHandler returns fuel stops as JSON
|
|
func (h *Handler) APIGetFuelStopsHandler(w http.ResponseWriter, r *http.Request) {
|
|
userID, _ := h.getCurrentUser(r)
|
|
if userID == 0 {
|
|
h.writeJSONError(w, "Unauthorized", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
// Parse query parameters for filtering (not used in simplified implementation)
|
|
_ = r.URL.Query().Get("vehicle_id")
|
|
_ = r.URL.Query().Get("fuel_type")
|
|
_ = r.URL.Query().Get("date_from")
|
|
_ = r.URL.Query().Get("date_to")
|
|
limitStr := r.URL.Query().Get("limit")
|
|
offsetStr := r.URL.Query().Get("offset")
|
|
|
|
// Parse limit and offset
|
|
limit := 50 // default limit
|
|
offset := 0 // default offset
|
|
|
|
if limitStr != "" {
|
|
if l, err := strconv.Atoi(limitStr); err == nil && l > 0 && l <= 1000 {
|
|
limit = l
|
|
}
|
|
}
|
|
|
|
if offsetStr != "" {
|
|
if o, err := strconv.Atoi(offsetStr); err == nil && o >= 0 {
|
|
offset = o
|
|
}
|
|
}
|
|
|
|
// Get fuel stops (simplified - using existing method)
|
|
stops, err := h.db.GetFuelStops(userID)
|
|
if err != nil {
|
|
log.Printf("Error getting fuel stops: %v", err)
|
|
h.writeJSONError(w, "Failed to retrieve fuel stops", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Apply basic pagination
|
|
totalCount := len(stops)
|
|
end := offset + limit
|
|
if end > len(stops) {
|
|
end = len(stops)
|
|
}
|
|
if offset < len(stops) {
|
|
stops = stops[offset:end]
|
|
} else {
|
|
stops = []models.FuelStop{}
|
|
}
|
|
|
|
// Prepare response
|
|
response := struct {
|
|
Data []models.FuelStop `json:"data"`
|
|
Total int `json:"total"`
|
|
Limit int `json:"limit"`
|
|
Offset int `json:"offset"`
|
|
HasMore bool `json:"has_more"`
|
|
Pagination struct {
|
|
CurrentPage int `json:"current_page"`
|
|
TotalPages int `json:"total_pages"`
|
|
PerPage int `json:"per_page"`
|
|
} `json:"pagination"`
|
|
}{
|
|
Data: stops,
|
|
Total: totalCount,
|
|
Limit: limit,
|
|
Offset: offset,
|
|
HasMore: offset+len(stops) < totalCount,
|
|
}
|
|
|
|
// Calculate pagination
|
|
response.Pagination.PerPage = limit
|
|
response.Pagination.CurrentPage = (offset / limit) + 1
|
|
response.Pagination.TotalPages = (totalCount + limit - 1) / limit
|
|
|
|
h.writeJSONResponse(w, response, http.StatusOK)
|
|
}
|
|
|
|
// APICreateFuelStopHandler creates a new fuel stop via JSON API
|
|
func (h *Handler) APICreateFuelStopHandler(w http.ResponseWriter, r *http.Request) {
|
|
userID, _ := h.getCurrentUser(r)
|
|
if userID == 0 {
|
|
h.writeJSONError(w, "Unauthorized", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
// Parse JSON request body
|
|
var request struct {
|
|
VehicleID uint `json:"vehicle_id"`
|
|
Date string `json:"date"`
|
|
StationName string `json:"station_name"`
|
|
Location string `json:"location"`
|
|
FuelType string `json:"fuel_type"`
|
|
Liters float64 `json:"liters"`
|
|
PricePerL float64 `json:"price_per_l"`
|
|
TotalPrice float64 `json:"total_price"`
|
|
Currency string `json:"currency"`
|
|
Odometer int `json:"odometer"`
|
|
TripLength float64 `json:"trip_length"`
|
|
Notes string `json:"notes"`
|
|
}
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
|
h.writeJSONError(w, "Invalid JSON format", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Validate required fields
|
|
if err := h.validateAPIFuelStopRequest(&request); err != nil {
|
|
h.writeJSONError(w, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Parse date
|
|
date, err := time.Parse("2006-01-02", request.Date)
|
|
if err != nil {
|
|
h.writeJSONError(w, "Invalid date format. Use YYYY-MM-DD", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Get user's default currency if not provided
|
|
if request.Currency == "" {
|
|
user, err := h.db.GetUserByID(userID)
|
|
if err != nil {
|
|
log.Printf("Error getting user: %v", err)
|
|
h.writeJSONError(w, "Failed to retrieve user information", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
request.Currency = user.BaseCurrency
|
|
}
|
|
|
|
// Create fuel stop model
|
|
fuelStop := &models.FuelStop{
|
|
UserID: userID,
|
|
VehicleID: request.VehicleID,
|
|
Date: date,
|
|
StationName: request.StationName,
|
|
Location: request.Location,
|
|
FuelType: request.FuelType,
|
|
Liters: request.Liters,
|
|
PricePerL: request.PricePerL,
|
|
TotalPrice: request.TotalPrice,
|
|
Currency: request.Currency,
|
|
Odometer: request.Odometer,
|
|
TripLength: request.TripLength,
|
|
Notes: request.Notes,
|
|
}
|
|
|
|
// Use station name as location if location is empty
|
|
if fuelStop.Location == "" {
|
|
fuelStop.Location = fuelStop.StationName
|
|
}
|
|
|
|
// Save to database
|
|
err = h.db.CreateFuelStopWithValidation(fuelStop)
|
|
if err != nil {
|
|
log.Printf("Error creating fuel stop: %v", err)
|
|
h.writeJSONError(w, "Failed to create fuel stop", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Return created fuel stop
|
|
h.writeJSONResponse(w, map[string]interface{}{
|
|
"message": "Fuel stop created successfully",
|
|
"data": fuelStop,
|
|
}, http.StatusCreated)
|
|
}
|
|
|
|
// APIGetFuelStopStatsHandler returns fuel stop statistics as JSON
|
|
func (h *Handler) APIGetFuelStopStatsHandler(w http.ResponseWriter, r *http.Request) {
|
|
userID, _ := h.getCurrentUser(r)
|
|
if userID == 0 {
|
|
h.writeJSONError(w, "Unauthorized", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
// Parse query parameters (not used in simplified implementation)
|
|
_ = r.URL.Query().Get("date_from")
|
|
_ = r.URL.Query().Get("date_to")
|
|
groupBy := r.URL.Query().Get("group_by") // month, year, vehicle
|
|
|
|
// Get basic statistics
|
|
stats, err := h.db.GetFuelStopStats(userID)
|
|
if err != nil {
|
|
log.Printf("Error getting fuel stop stats: %v", err)
|
|
h.writeJSONError(w, "Failed to retrieve statistics", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Prepare response structure
|
|
response := struct {
|
|
Basic *models.FuelStopStats `json:"basic"`
|
|
Daily []DailyStats `json:"daily,omitempty"`
|
|
Monthly []MonthlyStats `json:"monthly,omitempty"`
|
|
ByVehicle []VehicleStats `json:"by_vehicle,omitempty"`
|
|
Summary StatsSummary `json:"summary"`
|
|
}{
|
|
Basic: stats,
|
|
}
|
|
|
|
// Additional statistics would require more complex database queries
|
|
// For now, we'll just return basic stats
|
|
_ = groupBy // Acknowledge the parameter
|
|
|
|
// Calculate summary statistics
|
|
response.Summary = h.calculateStatsSummary(stats)
|
|
|
|
h.writeJSONResponse(w, response, http.StatusOK)
|
|
}
|
|
|
|
// Helper structs for statistics
|
|
type DailyStats struct {
|
|
Date string `json:"date"`
|
|
TotalStops int `json:"total_stops"`
|
|
TotalCost float64 `json:"total_cost"`
|
|
TotalLiters float64 `json:"total_liters"`
|
|
}
|
|
|
|
type MonthlyStats struct {
|
|
Month string `json:"month"`
|
|
Year int `json:"year"`
|
|
TotalStops int `json:"total_stops"`
|
|
TotalCost float64 `json:"total_cost"`
|
|
TotalLiters float64 `json:"total_liters"`
|
|
AvgPrice float64 `json:"avg_price"`
|
|
}
|
|
|
|
type VehicleStats struct {
|
|
VehicleID uint `json:"vehicle_id"`
|
|
VehicleName string `json:"vehicle_name"`
|
|
TotalStops int `json:"total_stops"`
|
|
TotalCost float64 `json:"total_cost"`
|
|
TotalLiters float64 `json:"total_liters"`
|
|
AvgPrice float64 `json:"avg_price"`
|
|
LastFillUp string `json:"last_fillup"`
|
|
}
|
|
|
|
type StatsSummary struct {
|
|
CostPerKm float64 `json:"cost_per_km"`
|
|
FuelEfficiency float64 `json:"fuel_efficiency"`
|
|
MonthlyAverage float64 `json:"monthly_average"`
|
|
WeeklyAverage float64 `json:"weekly_average"`
|
|
MostUsedStation string `json:"most_used_station"`
|
|
PreferredFuel string `json:"preferred_fuel"`
|
|
}
|
|
|
|
// validateAPIFuelStopRequest validates the JSON request for creating fuel stops
|
|
func (h *Handler) validateAPIFuelStopRequest(req *struct {
|
|
VehicleID uint `json:"vehicle_id"`
|
|
Date string `json:"date"`
|
|
StationName string `json:"station_name"`
|
|
Location string `json:"location"`
|
|
FuelType string `json:"fuel_type"`
|
|
Liters float64 `json:"liters"`
|
|
PricePerL float64 `json:"price_per_l"`
|
|
TotalPrice float64 `json:"total_price"`
|
|
Currency string `json:"currency"`
|
|
Odometer int `json:"odometer"`
|
|
TripLength float64 `json:"trip_length"`
|
|
Notes string `json:"notes"`
|
|
}) error {
|
|
if req.VehicleID == 0 {
|
|
return fmt.Errorf("vehicle_id is required")
|
|
}
|
|
|
|
if req.Date == "" {
|
|
return fmt.Errorf("date is required")
|
|
}
|
|
|
|
if req.StationName == "" && req.Location == "" {
|
|
return fmt.Errorf("station_name or location is required")
|
|
}
|
|
|
|
if req.FuelType == "" {
|
|
return fmt.Errorf("fuel_type is required")
|
|
}
|
|
|
|
if req.Liters <= 0 {
|
|
return fmt.Errorf("liters must be greater than 0")
|
|
}
|
|
|
|
if req.PricePerL <= 0 {
|
|
return fmt.Errorf("price_per_l must be greater than 0")
|
|
}
|
|
|
|
if req.TotalPrice <= 0 {
|
|
return fmt.Errorf("total_price must be greater than 0")
|
|
}
|
|
|
|
if req.Odometer < 0 {
|
|
return fmt.Errorf("odometer cannot be negative")
|
|
}
|
|
|
|
if req.TripLength < 0 {
|
|
return fmt.Errorf("trip_length cannot be negative")
|
|
}
|
|
|
|
if len(req.Notes) > 500 {
|
|
return fmt.Errorf("notes cannot be longer than 500 characters")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// APIGetVehicleHandler returns vehicle information as JSON
|
|
func (h *Handler) APIGetVehicleHandler(w http.ResponseWriter, r *http.Request) {
|
|
userID, _ := h.getCurrentUser(r)
|
|
if userID == 0 {
|
|
h.writeJSONError(w, "Unauthorized", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
// Get vehicle ID from URL path
|
|
vars := mux.Vars(r)
|
|
vehicleIDStr := vars["id"]
|
|
vehicleID, err := strconv.ParseUint(vehicleIDStr, 10, 32)
|
|
if err != nil {
|
|
h.writeJSONError(w, "Invalid vehicle ID", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Get vehicle from database
|
|
vehicle, err := h.db.GetVehicleByID(uint(vehicleID), userID)
|
|
if err != nil {
|
|
log.Printf("Error getting vehicle: %v", err)
|
|
h.writeJSONError(w, "Failed to retrieve vehicle", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
if vehicle == nil {
|
|
h.writeJSONError(w, "Vehicle not found", http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
// Return vehicle information
|
|
h.writeJSONResponse(w, vehicle, http.StatusOK)
|
|
}
|
|
|
|
// calculateStatsSummary calculates additional summary statistics
|
|
func (h *Handler) calculateStatsSummary(stats *models.FuelStopStats) StatsSummary {
|
|
summary := StatsSummary{}
|
|
|
|
if stats.TotalStops > 0 {
|
|
// Calculate monthly average (assuming data spans multiple months)
|
|
monthlyAvg := stats.TotalSpent / 12 // This is a simplified calculation
|
|
summary.MonthlyAverage = monthlyAvg
|
|
|
|
// Calculate weekly average
|
|
summary.WeeklyAverage = monthlyAvg / 4.33 // Average weeks per month
|
|
|
|
// Calculate fuel efficiency (simplified)
|
|
summary.FuelEfficiency = stats.AverageConsumption
|
|
|
|
// These would require additional database queries in a real implementation
|
|
summary.MostUsedStation = "N/A"
|
|
summary.PreferredFuel = "N/A"
|
|
}
|
|
|
|
return summary
|
|
}
|
|
|
|
// writeJSONResponse writes a JSON response with the given status code
|
|
func (h *Handler) writeJSONResponse(w http.ResponseWriter, data interface{}, statusCode int) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(statusCode)
|
|
|
|
if err := json.NewEncoder(w).Encode(data); err != nil {
|
|
log.Printf("Error encoding JSON response: %v", err)
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
// writeJSONError writes a JSON error response
|
|
func (h *Handler) writeJSONError(w http.ResponseWriter, message string, statusCode int) {
|
|
errorResponse := struct {
|
|
Error string `json:"error"`
|
|
Message string `json:"message"`
|
|
Code int `json:"code"`
|
|
}{
|
|
Error: http.StatusText(statusCode),
|
|
Message: message,
|
|
Code: statusCode,
|
|
}
|
|
|
|
h.writeJSONResponse(w, errorResponse, statusCode)
|
|
}
|