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