2023-08-11 14:32:44 +01:00
|
|
|
package run
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2023-08-12 08:20:28 +01:00
|
|
|
"image/png"
|
2023-08-11 14:32:44 +01:00
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
"strconv"
|
|
|
|
"time"
|
2023-08-11 15:33:18 +01:00
|
|
|
|
|
|
|
"git.fjla.uk/fred.boniface/map-dots/imaging"
|
|
|
|
"git.fjla.uk/fred.boniface/map-dots/log"
|
2023-08-11 19:34:32 +01:00
|
|
|
"git.fjla.uk/fred.boniface/map-dots/traccar"
|
|
|
|
"go.uber.org/zap"
|
2023-08-11 14:32:44 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
func Server() {
|
|
|
|
|
|
|
|
http.HandleFunc("/traccar/", handleTraccarRequest)
|
|
|
|
http.HandleFunc("/help/", handleHelpRequest)
|
|
|
|
|
2023-08-12 09:45:35 +01:00
|
|
|
serverAddr := ":8198" // Set your desired server address
|
2023-08-11 15:33:18 +01:00
|
|
|
fmt.Printf("Starting server on http://%s\n", serverAddr)
|
|
|
|
log.Msg.Info("Starting server on http://" + serverAddr)
|
2023-08-11 14:32:44 +01:00
|
|
|
err := http.ListenAndServe(serverAddr, nil)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Error starting server: %s\n", err)
|
2023-08-11 15:33:18 +01:00
|
|
|
log.Msg.Error("Server failed to start:" + err.Error())
|
2023-08-11 14:32:44 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2023-08-11 20:51:46 +01:00
|
|
|
log.Msg.Debug("Requesting Data from Traccar",
|
|
|
|
zap.String("id", id),
|
|
|
|
zap.Time("from", from),
|
|
|
|
zap.Time("to", to),
|
|
|
|
)
|
2023-08-11 15:33:18 +01:00
|
|
|
|
2023-08-11 19:34:32 +01:00
|
|
|
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),
|
2023-08-11 20:51:46 +01:00
|
|
|
zap.Error(err),
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
log.Msg.Debug("Position data fetched")
|
2023-08-11 19:34:32 +01:00
|
|
|
}
|
2023-08-11 15:33:18 +01:00
|
|
|
|
2023-08-12 08:20:28 +01:00
|
|
|
img := imaging.Generate(height, width, style, locations)
|
2023-08-11 14:32:44 +01:00
|
|
|
|
2023-08-12 08:20:28 +01:00
|
|
|
fmt.Printf("Requested format, %s, returning PNG as only supported format", format)
|
2023-08-11 14:32:44 +01:00
|
|
|
|
2023-08-12 08:20:28 +01:00
|
|
|
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"))
|
2023-08-11 14:32:44 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-11 19:34:32 +01:00
|
|
|
func validateAndProcessParams(queryValues url.Values) (string, time.Time, time.Time, int, int, string, string, error) {
|
2023-08-11 14:32:44 +01:00
|
|
|
// Validate and process individual parameters
|
|
|
|
id := queryValues.Get("id")
|
|
|
|
if id == "" {
|
2023-08-11 19:34:32 +01:00
|
|
|
return "", time.Time{}, time.Time{}, 0, 0, "", "", errors.New("missing required parameter 'id'")
|
2023-08-11 14:32:44 +01:00
|
|
|
}
|
|
|
|
|
2023-08-11 19:34:32 +01:00
|
|
|
fromStr := queryValues.Get("from")
|
|
|
|
toStr := queryValues.Get("to")
|
2023-08-11 14:32:44 +01:00
|
|
|
heightStr := queryValues.Get("height")
|
|
|
|
widthStr := queryValues.Get("width")
|
|
|
|
style := queryValues.Get("style")
|
|
|
|
format := queryValues.Get("format")
|
|
|
|
|
|
|
|
// Apply defaults if parameters are not specified
|
2023-08-11 19:34:32 +01:00
|
|
|
if fromStr == "" {
|
2023-08-11 14:32:44 +01:00
|
|
|
thirtyDaysAgo := time.Now().AddDate(0, 0, -30)
|
2023-08-11 19:34:32 +01:00
|
|
|
fromStr = thirtyDaysAgo.UTC().Format(time.RFC3339)
|
2023-08-11 14:32:44 +01:00
|
|
|
}
|
2023-08-11 19:34:32 +01:00
|
|
|
if toStr == "" {
|
|
|
|
toStr = time.Now().UTC().Format(time.RFC3339)
|
2023-08-11 14:32:44 +01:00
|
|
|
}
|
|
|
|
if heightStr == "" {
|
2023-08-11 15:33:18 +01:00
|
|
|
heightStr = "1080"
|
2023-08-11 14:32:44 +01:00
|
|
|
}
|
|
|
|
if widthStr == "" {
|
2023-08-11 15:33:18 +01:00
|
|
|
widthStr = "1920"
|
2023-08-11 14:32:44 +01:00
|
|
|
}
|
|
|
|
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 {
|
2023-08-11 19:34:32 +01:00
|
|
|
return "", time.Time{}, time.Time{}, 0, 0, "", "", errors.New("invalid height or width")
|
2023-08-11 14:32:44 +01:00
|
|
|
}
|
|
|
|
if height >= 7680 {
|
2023-08-11 19:34:32 +01:00
|
|
|
return "", time.Time{}, time.Time{}, 0, 0, "", "", errors.New("invalid height, max: 7680")
|
2023-08-11 14:32:44 +01:00
|
|
|
}
|
|
|
|
if width >= 4320 {
|
2023-08-11 19:34:32 +01:00
|
|
|
return "", time.Time{}, time.Time{}, 0, 0, "", "", errors.New("invalid width, max: 4320")
|
2023-08-11 14:32:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// VALIDATE FROM/TO
|
|
|
|
// Parse the ISO date strings to time.Time objects
|
2023-08-11 19:34:32 +01:00
|
|
|
from, errFrom := time.Parse(time.RFC3339, fromStr)
|
|
|
|
to, errTo := time.Parse(time.RFC3339, toStr)
|
2023-08-11 14:32:44 +01:00
|
|
|
if errFrom != nil || errTo != nil {
|
2023-08-11 19:34:32 +01:00
|
|
|
return "", time.Time{}, time.Time{}, 0, 0, "", "", errors.New("invalid date format")
|
2023-08-11 14:32:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Define the maximum allowable time duration (e.g., 90 days)
|
|
|
|
maxAllowableDuration := time.Hour * 24 * 90
|
|
|
|
|
2023-08-11 19:34:32 +01:00
|
|
|
// Calculate the duration between from and to
|
|
|
|
duration := to.Sub(from)
|
2023-08-11 14:32:44 +01:00
|
|
|
if duration > maxAllowableDuration {
|
2023-08-11 19:34:32 +01:00
|
|
|
return "", time.Time{}, time.Time{}, 0, 0, "", "", errors.New("date range is too wide, max: 90d")
|
2023-08-11 14:32:44 +01:00
|
|
|
}
|
|
|
|
// ... 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)
|
|
|
|
}
|