package handlers import ( "encoding/json" "fmt" "net/http" "time" "github.com/go-chi/chi/v5" ) // QuixzoomHandler hanterar quiXzoom-integration type QuixzoomHandler struct { baseURL string token string } // NewQuixzoomHandler skapar en ny handler func NewQuixzoomHandler() *QuixzoomHandler { return &QuixzoomHandler{ baseURL: getEnv("QUIXZOOM_API_URL", ""), token: getEnv("QUIXZOOM_API_TOKEN", ""), } } func (h *QuixzoomHandler) isConfigured() bool { return h.baseURL != "" && h.token != "" } func (h *QuixzoomHandler) apiGet(path string) (*http.Response, error) { req, err := http.NewRequest("GET", h.baseURL+path, nil) if err != nil { return nil, err } req.Header.Set("Authorization", "Bearer "+h.token) return http.DefaultClient.Do(req) } // Zoomer representerar en quiXzoom-användare (fältarbetare) type Zoomer struct { ID string `json:"id"` Name string `json:"name"` Email string `json:"email"` Phone string `json:"phone"` Status string `json:"status"` Country string `json:"country"` City string `json:"city"` JoinedAt time.Time `json:"joined_at"` LastActive time.Time `json:"last_active"` TotalTasks int `json:"total_tasks"` CompletedTasks int `json:"completed_tasks"` Rating float64 `json:"rating"` Earnings float64 `json:"earnings"` PayoutMethod string `json:"payout_method"` Verified bool `json:"verified"` } // FieldData representerar insamlad fältdata type FieldData struct { ID string `json:"id"` ZoomerID string `json:"zoomer_id"` ZoomerName string `json:"zoomer_name"` Type string `json:"type"` Status string `json:"status"` Location Location `json:"location"` Images []Image `json:"images"` Metadata map[string]interface{} `json:"metadata"` CreatedAt time.Time `json:"created_at"` ProcessedAt *time.Time `json:"processed_at,omitempty"` AIResult *AIResult `json:"ai_result,omitempty"` } // Location representerar en geografisk plats type Location struct { Latitude float64 `json:"lat"` Longitude float64 `json:"lng"` Address string `json:"address"` City string `json:"city"` Country string `json:"country"` } // Image representerar en bild type Image struct { ID string `json:"id"` URL string `json:"url"` Thumbnail string `json:"thumbnail"` Status string `json:"status"` } // AIResult representerar AI-analysresultat type AIResult struct { Engine string `json:"engine"` Confidence float64 `json:"confidence"` Detections []Detection `json:"detections"` ProcessedAt time.Time `json:"processed_at"` Metadata map[string]interface{} `json:"metadata"` } // Detection representerar en AI-detektering type Detection struct { Label string `json:"label"` Confidence float64 `json:"confidence"` BoundingBox struct { X float64 `json:"x"` Y float64 `json:"y"` Width float64 `json:"width"` Height float64 `json:"height"` } `json:"bounding_box"` } // Payout representerar en utbetalning type Payout struct { ID string `json:"id"` ZoomerID string `json:"zoomer_id"` ZoomerName string `json:"zoomer_name"` Amount float64 `json:"amount"` Currency string `json:"currency"` Status string `json:"status"` Method string `json:"method"` Period string `json:"period"` CreatedAt time.Time `json:"created_at"` ProcessedAt *time.Time `json:"processed_at,omitempty"` Tax float64 `json:"tax"` Fee float64 `json:"fee"` NetAmount float64 `json:"net_amount"` } // GetZoomers returnerar alla zoomers func (h *QuixzoomHandler) GetZoomers(w http.ResponseWriter, r *http.Request) { if !h.isConfigured() { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusServiceUnavailable) json.NewEncoder(w).Encode(map[string]interface{}{ "ok": false, "error": "quiXzoom API not configured. Set QUIXZOOM_API_URL and QUIXZOOM_API_TOKEN environment variables.", }) return } resp, err := h.apiGet("/api/v1/zoomers") if err != nil { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusServiceUnavailable) json.NewEncoder(w).Encode(map[string]interface{}{ "ok": false, "error": fmt.Sprintf("quiXzoom API error: %v", err), }) return } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { w.Header().Set("Content-Type", "application/json") w.WriteHeader(resp.StatusCode) json.NewEncoder(w).Encode(map[string]interface{}{ "ok": false, "error": fmt.Sprintf("quiXzoom API returned %d", resp.StatusCode), }) return } var result map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusInternalServerError) json.NewEncoder(w).Encode(map[string]interface{}{ "ok": false, "error": "Failed to decode quiXzoom response", }) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(result) } // GetZoomer returnerar en specifik zoomer func (h *QuixzoomHandler) GetZoomer(w http.ResponseWriter, r *http.Request) { if !h.isConfigured() { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusServiceUnavailable) json.NewEncoder(w).Encode(map[string]interface{}{ "ok": false, "error": "quiXzoom API not configured. Set QUIXZOOM_API_URL and QUIXZOOM_API_TOKEN environment variables.", }) return } zoomerID := chi.URLParam(r, "id") resp, err := h.apiGet("/api/v1/zoomers/" + zoomerID) if err != nil { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusServiceUnavailable) json.NewEncoder(w).Encode(map[string]interface{}{ "ok": false, "error": fmt.Sprintf("quiXzoom API error: %v", err), }) return } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { w.Header().Set("Content-Type", "application/json") w.WriteHeader(resp.StatusCode) json.NewEncoder(w).Encode(map[string]interface{}{ "ok": false, "error": fmt.Sprintf("quiXzoom API returned %d", resp.StatusCode), }) return } var result map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusInternalServerError) json.NewEncoder(w).Encode(map[string]interface{}{ "ok": false, "error": "Failed to decode quiXzoom response", }) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(result) } // GetFieldData returnerar all fältdata func (h *QuixzoomHandler) GetFieldData(w http.ResponseWriter, r *http.Request) { if !h.isConfigured() { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusServiceUnavailable) json.NewEncoder(w).Encode(map[string]interface{}{ "ok": false, "error": "quiXzoom API not configured. Set QUIXZOOM_API_URL and QUIXZOOM_API_TOKEN environment variables.", }) return } resp, err := h.apiGet("/api/v1/field-data") if err != nil { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusServiceUnavailable) json.NewEncoder(w).Encode(map[string]interface{}{ "ok": false, "error": fmt.Sprintf("quiXzoom API error: %v", err), }) return } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { w.Header().Set("Content-Type", "application/json") w.WriteHeader(resp.StatusCode) json.NewEncoder(w).Encode(map[string]interface{}{ "ok": false, "error": fmt.Sprintf("quiXzoom API returned %d", resp.StatusCode), }) return } var result map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusInternalServerError) json.NewEncoder(w).Encode(map[string]interface{}{ "ok": false, "error": "Failed to decode quiXzoom response", }) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(result) } // GetPayouts returnerar alla utbetalningar func (h *QuixzoomHandler) GetPayouts(w http.ResponseWriter, r *http.Request) { if !h.isConfigured() { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusServiceUnavailable) json.NewEncoder(w).Encode(map[string]interface{}{ "ok": false, "error": "quiXzoom API not configured. Set QUIXZOOM_API_URL and QUIXZOOM_API_TOKEN environment variables.", }) return } resp, err := h.apiGet("/api/v1/payouts") if err != nil { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusServiceUnavailable) json.NewEncoder(w).Encode(map[string]interface{}{ "ok": false, "error": fmt.Sprintf("quiXzoom API error: %v", err), }) return } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { w.Header().Set("Content-Type", "application/json") w.WriteHeader(resp.StatusCode) json.NewEncoder(w).Encode(map[string]interface{}{ "ok": false, "error": fmt.Sprintf("quiXzoom API returned %d", resp.StatusCode), }) return } var result map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusInternalServerError) json.NewEncoder(w).Encode(map[string]interface{}{ "ok": false, "error": "Failed to decode quiXzoom response", }) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(result) } // GetPayoutStats returnerar utbetalningsstatistik func (h *QuixzoomHandler) GetPayoutStats(w http.ResponseWriter, r *http.Request) { if !h.isConfigured() { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusServiceUnavailable) json.NewEncoder(w).Encode(map[string]interface{}{ "ok": false, "error": "quiXzoom API not configured. Set QUIXZOOM_API_URL and QUIXZOOM_API_TOKEN environment variables.", }) return } resp, err := h.apiGet("/api/v1/payouts/stats") if err != nil { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusServiceUnavailable) json.NewEncoder(w).Encode(map[string]interface{}{ "ok": false, "error": fmt.Sprintf("quiXzoom API error: %v", err), }) return } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { w.Header().Set("Content-Type", "application/json") w.WriteHeader(resp.StatusCode) json.NewEncoder(w).Encode(map[string]interface{}{ "ok": false, "error": fmt.Sprintf("quiXzoom API returned %d", resp.StatusCode), }) return } var result map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusInternalServerError) json.NewEncoder(w).Encode(map[string]interface{}{ "ok": false, "error": "Failed to decode quiXzoom response", }) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(result) } // GetInsights returnerar quiXzoom-insikter (Urban Intelligence Index) func (h *QuixzoomHandler) GetInsights(w http.ResponseWriter, r *http.Request) { if !h.isConfigured() { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusServiceUnavailable) json.NewEncoder(w).Encode(map[string]interface{}{ "ok": false, "error": "quiXzoom API not configured. Set QUIXZOOM_API_URL and QUIXZOOM_API_TOKEN environment variables.", }) return } resp, err := h.apiGet("/api/v1/insights") if err != nil { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusServiceUnavailable) json.NewEncoder(w).Encode(map[string]interface{}{ "ok": false, "error": fmt.Sprintf("quiXzoom API error: %v", err), }) return } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { w.Header().Set("Content-Type", "application/json") w.WriteHeader(resp.StatusCode) json.NewEncoder(w).Encode(map[string]interface{}{ "ok": false, "error": fmt.Sprintf("quiXzoom API returned %d", resp.StatusCode), }) return } var result map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusInternalServerError) json.NewEncoder(w).Encode(map[string]interface{}{ "ok": false, "error": "Failed to decode quiXzoom response", }) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(result) } func timePtr(t time.Time) *time.Time { return &t }