package run import ( "errors" "fmt" "image/png" "net/http" "net/url" "strconv" "time" "git.fjla.uk/fred.boniface/map-dots/imaging" "git.fjla.uk/fred.boniface/map-dots/log" "git.fjla.uk/fred.boniface/map-dots/traccar" "go.uber.org/zap" ) func Server() { http.HandleFunc("/traccar/", handleTraccarRequest) http.HandleFunc("/help/", handleHelpRequest) serverAddr := ":8198" // Set your desired server address fmt.Printf("Starting server on http://%s\n", serverAddr) log.Msg.Info("Starting server on http://" + serverAddr) err := http.ListenAndServe(serverAddr, nil) if err != nil { fmt.Printf("Error starting server: %s\n", err) log.Msg.Error("Server failed to start:" + err.Error()) } } func handleTraccarRequest(w http.ResponseWriter, r *http.Request) { queryValues := r.URL.Query() id, from, to, height, width, style, format, err := validateAndProcessParams(queryValues) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } log.Msg.Debug("Requesting Data from Traccar", zap.String("id", id), zap.Time("from", from), zap.Time("to", to), ) locations, err := traccar.GetPositions(id, from, to) if err != nil { fmt.Println("Error fetching data: " + err.Error()) log.Msg.Error("Error fetching traccar data", zap.String("id", id), zap.Time("from", from), zap.Time("to", to), zap.Error(err), ) } else { log.Msg.Debug("Position data fetched") } img := imaging.Generate(height, width, style, locations) fmt.Printf("Requested format, %s, returning PNG as only supported format", format) switch format { case "png": w.Header().Set("Content-Type", "image/png") err = png.Encode(w, img) if err != nil { http.Error(w, "Error encoding image", http.StatusInternalServerError) return } default: w.Header().Set("Content-Type", "text/plain") w.Write([]byte("Unsupported format, only 'PNG' is supported at present")) } } func validateAndProcessParams(queryValues url.Values) (string, time.Time, time.Time, int, int, string, string, error) { // Validate and process individual parameters id := queryValues.Get("id") if id == "" { return "", time.Time{}, time.Time{}, 0, 0, "", "", errors.New("missing required parameter 'id'") } fromStr := queryValues.Get("from") toStr := queryValues.Get("to") heightStr := queryValues.Get("height") widthStr := queryValues.Get("width") style := queryValues.Get("style") format := queryValues.Get("format") // Apply defaults if parameters are not specified if fromStr == "" { thirtyDaysAgo := time.Now().AddDate(0, 0, -30) fromStr = thirtyDaysAgo.UTC().Format(time.RFC3339) } if toStr == "" { toStr = time.Now().UTC().Format(time.RFC3339) } if heightStr == "" { heightStr = "1080" } if widthStr == "" { widthStr = "1920" } if style == "" { style = "circle" } if format == "" { format = "png" } // VALIDATE HEIGHT/WIDTH // Convert height and width to integers height, errHeight := strconv.Atoi(heightStr) width, errWidth := strconv.Atoi(widthStr) if errHeight != nil || errWidth != nil { return "", time.Time{}, time.Time{}, 0, 0, "", "", errors.New("invalid height or width") } if height >= 7680 { return "", time.Time{}, time.Time{}, 0, 0, "", "", errors.New("invalid height, max: 7680") } if width >= 4320 { return "", time.Time{}, time.Time{}, 0, 0, "", "", errors.New("invalid width, max: 4320") } // VALIDATE FROM/TO // Parse the ISO date strings to time.Time objects from, errFrom := time.Parse(time.RFC3339, fromStr) to, errTo := time.Parse(time.RFC3339, toStr) if errFrom != nil || errTo != nil { return "", time.Time{}, time.Time{}, 0, 0, "", "", errors.New("invalid date format") } // Define the maximum allowable time duration (e.g., 90 days) maxAllowableDuration := time.Hour * 24 * 90 // Calculate the duration between from and to duration := to.Sub(from) if duration > maxAllowableDuration { return "", time.Time{}, time.Time{}, 0, 0, "", "", errors.New("date range is too wide, max: 90d") } // ... Validate other parameters as needed return id, from, to, height, width, style, format, nil } func handleHelpRequest(w http.ResponseWriter, r *http.Request) { helpText := ` API Usage Information: Endpoint: /traccar/:id Parameters: - id: Traccar device ID - from: Start date in ISO format (90-days or less after 'to') - to: End date in ISO format - height: Output image height (1-7680) - width: Output image width (1-4320) - style: Output image style (circles) - format: Output image format (png, jpeg, gif, bmp, tiff, webp) Example: /traccar/?id=1&from=2023-01-01T00:00:00Z&to=2023-02-01T00:00:00Z&height=600&width=800&style=circle&format=png ` w.Header().Set("Content-Type", "text/plain") fmt.Fprint(w, helpText) }