Files
tankstopp-app/internal/handlers/api.go
T
2025-07-07 01:44:12 +02:00

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)
}