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