Compare commits

..

No commits in common. "93b69c4cbb646f6c28ec3488c8c01adec7ef79eb" and "8592659432e563c2431cba585dd037cbf20f9dc0" have entirely different histories.

13 changed files with 814 additions and 8 deletions

View file

@ -115,9 +115,15 @@ Once all routes are ported or confirmed dead, remove the entire `html/template/`
---
## 3. esbuild (`build.js`) — Removed ✅
## 3. esbuild (`build.js`) — Dead Build Tool
*(Completed 2026-05-09: `build.js` removed and `pkgs.esbuild` dropped from flake.nix devShell — Vite is the build tool)*
**Status:** `build.js` is an esbuild-based build script that predates the Vite migration. It is not referenced by `package.json` scripts (those use `vite build`). It is also not referenced by any CI or Nix config.
### 3a. Remove esbuild-related files
- `build.js` — the esbuild build script
- Remove `pkgs.esbuild` from `flake.nix` devShell dependencies (if not used elsewhere)
- Remove esbuild-related dependencies from `package.json` if present (currently no esbuild deps are in `package.json` — they may have been already cleaned)
---
@ -163,9 +169,14 @@ The map-locator, address-suggestion, and photo-upload functionality has Vue equi
---
## 5. TomTom Integration — Removed ✅
## 5. TomTom Integration — Unused
*(Completed 2026-05-09: `tomtom/` directory removed — zero imports outside itself, Stadia Maps is now the geocoding/tile provider)*
**Status:** The `tomtom/` directory contains a TomTom routing/geocoding client. It is not imported by any file outside the `tomtom/` directory. Stadia Maps is now used for geocoding and tiles.
### 5a. Remove TomTom entirely
- `tomtom/` — entire directory
- `tomtom/example/` — example code
---
@ -236,7 +247,9 @@ Verify that all code references use the external package, not a local path.
## 10. Old Generated Files & Artifacts
### 10a. `query.go` at project root — Removed ✅
### 10a. `query.go` at project root
Contains a commented-out Bob query interface and an unused `QueryWriter` interface. The `insertQueryToString` function is entirely commented out. Either repurpose or remove.
### 10b. `db/sql/` directory
@ -282,10 +295,10 @@ Empty placeholder file. Remove.
## Priority Summary
1. **High impact, low effort:**
- ~~Remove `tomtom/` (unused, no imports)~~ ✅
- ~~Remove `build.js` (dead, replaced by Vite)~~ ✅
- Remove `tomtom/` (unused, no imports)
- Remove `build.js` (dead, replaced by Vite)
- Remove commented-out routes in `sync/routes.go` and `rmo/routes.go`
- ~~Remove `query.go` commented-out code~~ ✅
- Remove `query.go` commented-out code
- Remove `static/gen/main.js` stale artifact
- Remove `static/css/placeholder`

92
build.js Normal file
View file

@ -0,0 +1,92 @@
import esbuild from "esbuild";
import vue from "esbuild-plugin-vue3";
import { sassPlugin } from "esbuild-sass-plugin";
const args = process.argv.slice(2);
const watch = args.includes("--watch");
const minify = args.includes("--minify");
// Plugin to show build status
const buildStatusPlugin = {
name: "build-status",
setup(build) {
let buildStart;
build.onStart(() => {
buildStart = Date.now();
// Clear console and move cursor to top
console.clear();
console.log(
"\x1b[36m%s\x1b[0m",
`🔨 Building... [${new Date().toLocaleTimeString()}]`,
);
});
build.onEnd((result) => {
const buildTime = Date.now() - buildStart;
if (result.errors.length > 0) {
console.log(
"\x1b[31m%s\x1b[0m",
`❌ Build failed (${buildTime}ms) [${new Date().toLocaleTimeString()}]`,
);
} else {
console.log(
"\x1b[32m%s\x1b[0m",
`✅ Build complete (${buildTime}ms) [${new Date().toLocaleTimeString()}]`,
);
}
console.log("\x1b[33m%s\x1b[0m", "\n👀 Watching for changes...");
});
},
};
const config = {
entryPoints: ["ts/main.ts"],
bundle: true,
format: "esm",
plugins: [
buildStatusPlugin, // Add this first
sassPlugin({
quietDeps: true,
precompile(source, pathname) {
// Only inject variables into Vue component styles
// (not the main scss files to avoid circular imports)
if (pathname.endsWith(".vue")) {
return `@import "./ts/style/variables.scss";\n${source}`;
}
return source;
},
silenceDeprecations: ["import"],
type: "css",
}),
vue({
sourceMap: true,
}),
],
sourcemap: true,
sourcesContent: true,
define: {
__VUE_OPTIONS_API__: "true",
__VUE_PROD_DEVTOOLS__: "false",
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: "false",
},
minify,
loader: {
".css": "css",
".woff": "file",
".woff2": "file",
".ttf": "file",
".eot": "file",
},
outdir: "static/gen",
outbase: "ts",
assetNames: "fonts/[name]",
};
if (watch) {
const ctx = await esbuild.context(config);
await ctx.watch();
console.log("\x1b[33m%s\x1b[0m", "👀 Watching for changes...\n");
} else {
await esbuild.build(config);
}

View file

@ -31,6 +31,7 @@
pkgs.air
pkgs.autoprefixer
pkgs.dart-sass
pkgs.esbuild
pkgs.go
pkgs.go-jet
pkgs.golangci-lint

34
query.go Normal file
View file

@ -0,0 +1,34 @@
package main
import (
"bytes"
"context"
"fmt"
"io"
//"github.com/Gleipnir-Technology/bob"
//"github.com/Gleipnir-Technology/bob/dialect/psql"
)
type QueryWriter interface {
WriteQuery(ctx context.Context, w io.Writer, start int) ([]any, error)
}
func queryToString(query QueryWriter) string {
buf := new(bytes.Buffer)
_, err := query.WriteQuery(context.TODO(), buf, 0)
if err != nil {
return fmt.Sprintf("Failed to write query to buffer: %v", err)
}
return buf.String()
}
/*
func insertQueryToString(query bob.BaseQuery[*dialect.InsertQuery]) string {
buf := new(bytes.Buffer)
_, err := query.WriteQuery(context.TODO(), buf, 0)
if err != nil {
return fmt.Sprintf("Failed to write query: %v", err)
}
return buf.String()
}
*/

52
tomtom/.air.toml Normal file
View file

@ -0,0 +1,52 @@
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"
[build]
args_bin = []
bin = "./tmp/main"
cmd = "go build -o ./tmp/main ./example/geocode-and-route"
delay = 1000
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
exclude_file = []
exclude_regex = ["_test.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "tpl", "tmpl", "html"]
include_file = []
kill_delay = "0s"
log = "build-errors.log"
poll = false
poll_interval = 0
post_cmd = []
pre_cmd = []
rerun = false
rerun_delay = 500
send_interrupt = false
stop_on_error = true
[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"
[log]
main_only = false
silent = false
time = false
[misc]
clean_on_exit = false
[proxy]
app_port = 0
enabled = false
proxy_port = 0
[screen]
clear_on_rebuild = false
keep_scroll = true

View file

@ -0,0 +1,61 @@
package main
import (
"log"
"github.com/Gleipnir-Technology/nidus-sync/tomtom"
)
func main() {
client := tomtom.NewClient()
// Example 1: Geocode a series of addresses
waypoints := []string{
"1737 W Houston Ave, Visalia, CA 93291",
"1138 W Prescott Ave, Visalia, CA 93291",
"3228 W Clinton Ct, Visalia, CA 93291",
"3800 N Mendonca St, Visalia, CA 93291",
}
coords := make([]tomtom.Point, 0)
for _, wp := range waypoints {
geocode, err := client.Geocode(wp)
if err != nil {
log.Fatal("Failed to geocode '%s': %w", wp, err)
}
if len(geocode.Results) == 0 {
log.Fatal("Failed to get any results for '%s'", wp)
}
result := geocode.Results[0]
coords = append(coords, result.Position.AsPoint())
log.Printf("Geocoded %s to %f, %f", wp, result.Position.Longitude, result.Position.Latitude)
}
// Example 2: Calculate a simple route through them
traffic := false
routeRequest := &tomtom.CalculateRouteRequest{
Locations: coords,
Traffic: &traffic,
TravelMode: tomtom.TravelModeCar,
RouteType: tomtom.RouteTypeFastest,
}
//client.SetDebug(true)
routeResp, err := client.CalculateRoute(routeRequest)
if err != nil {
log.Fatal(err)
}
all_points := make([]tomtom.Point, 0)
all_stops := make([]tomtom.Point, 0)
for i, route := range routeResp.Routes {
s := route.Summary
log.Printf("%d: %d meters, %d seconds, %s traffic delay", i, s.LengthInMeters, s.TravelTimeInSeconds, s.TrafficDelayInSeconds)
for _, leg := range route.Legs {
all_stops = append(all_stops, leg.Points[0])
all_points = append(all_points, leg.Points...)
}
}
lines := tomtom.PolylineToGeoJSON(all_points)
log.Printf("%s", lines)
stops := tomtom.PolylineToGeoJSON(all_stops)
log.Printf("%s", stops)
}

View file

@ -0,0 +1,31 @@
package main
import (
"fmt"
"log"
"github.com/Gleipnir-Technology/nidus-sync/tomtom"
)
func main() {
client := tomtom.NewClient()
// Example 1: Calculate a simple route
traffic := false
routeRequest := &tomtom.CalculateRouteRequest{
Locations: []tomtom.Point{
tomtom.P(52.50931, 13.42936),
tomtom.P(52.50274, 13.43872),
},
Traffic: &traffic,
TravelMode: tomtom.TravelModeCar,
RouteType: tomtom.RouteTypeFastest,
}
routeResp, err := client.CalculateRoute(routeRequest)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Route distance: %d meters\n", routeResp.Routes[0].Summary.LengthInMeters)
}

96
tomtom/geocode.go Normal file
View file

@ -0,0 +1,96 @@
package tomtom
import (
"fmt"
)
type PointShort struct {
Latitude float64 `json:"lat"`
Longitude float64 `json:"lon"`
}
func (ps PointShort) AsPoint() Point {
return Point(ps)
}
type GeocodeResult struct {
Type string `json:"type"`
ID string `json:"id"`
Score float64 `json:"score"`
Dist float64 `json:"dist"`
MatchConfidence MatchConfidence `json:"matchConfidence"`
Address Address `json:"address"`
Position PointShort `json:"position"`
Viewport Viewport `json:"viewport"`
EntryPoints []EntryPoint `json:"entryPoints"`
}
// MatchConfidence represents the confidence score for a match
type MatchConfidence struct {
Score float64 `json:"score"`
}
// Address contains detailed address information
type Address struct {
StreetNumber string `json:"streetNumber"`
StreetName string `json:"streetName"`
Municipality string `json:"municipality"`
CountrySecondarySubdivision string `json:"countrySecondarySubdivision"`
CountrySubdivision string `json:"countrySubdivision"`
CountrySubdivisionName string `json:"countrySubdivisionName"`
CountrySubdivisionCode string `json:"countrySubdivisionCode"`
PostalCode string `json:"postalCode"`
ExtendedPostalCode string `json:"extendedPostalCode"`
CountryCode string `json:"countryCode"`
Country string `json:"country"`
CountryCodeISO3 string `json:"countryCodeISO3"`
FreeformAddress string `json:"freeformAddress"`
LocalName string `json:"localName"`
}
// Viewport defines a geographic bounding box
type Viewport struct {
TopLeftPoint PointShort `json:"topLeftPoint"`
BtmRightPoint PointShort `json:"btmRightPoint"`
}
// EntryPoint contains information about a point of entry to a location
type EntryPoint struct {
Type string `json:"type"`
Position PointShort `json:"position"`
}
type GeocodeSummary struct {
Query string `json:"query"`
QueryType string `json:"queryType"`
QueryTime uint `json:"queryTime"`
NumResults uint `json:"numResults"`
Offset uint `json:"offset"`
TotalResults uint `json:"totalResults"`
FuzzyLevel uint `json:"fuzzyLevel"`
GeoBias PointShort `json:"geoBias"`
}
type GeocodeResponse struct {
Summary GeocodeSummary `json:"summary"`
Results []GeocodeResult `json:"results"`
}
// CalculateRoute sends a route calculation request to TomTom API
func (c *TomTom) Geocode(address string) (*GeocodeResponse, error) {
var result GeocodeResponse
resp, err := c.client.R().
SetResult(&result).
SetPathParam("address", address).
SetPathParam("urlBase", c.urlBase).
SetQueryParam("key", c.APIKey).
SetQueryParam("storeResult", "false").
Get("https://{urlBase}/search/2/geocode/{address}.json")
if err != nil {
return nil, fmt.Errorf("calculate route get: %w", err)
}
if !resp.IsSuccess() {
return nil, fmt.Errorf("calculate route status: %d", resp.Status)
}
return &result, nil
}

27
tomtom/geojson.go Normal file
View file

@ -0,0 +1,27 @@
package tomtom
import (
"fmt"
"strings"
)
// Convert a slice of points to GeoJSON
func PolylineToGeoJSON(polyline []Point) string {
var sb strings.Builder
sb.WriteString(`{"type":"LineString","coordinates":[`)
for i, point := range polyline {
// IMPORTANT: GeoJSON uses [longitude, latitude] order!
sb.WriteString(fmt.Sprintf("[%g,%g]", point.Longitude, point.Latitude))
// Add comma if not the last point
if i < len(polyline)-1 {
sb.WriteString(",")
}
}
sb.WriteString("]}")
return sb.String()
}

126
tomtom/route.go Normal file
View file

@ -0,0 +1,126 @@
package tomtom
import (
"fmt"
"net/url"
"strings"
"github.com/google/go-querystring/query"
)
// CalculateRouteRequest combines both path parameters and POST body
type CalculateRouteRequest struct {
// Path parameters
Locations Locations
// Query parameters
MaxAlternatives *int
AlternativeType string
MinDeviationDistance *int
MinDeviationTime *int
InstructionsType string
Language string
ComputeBestOrder *bool
RouteRepresentation string
ComputeTravelTimeFor string
VehicleHeading *int
SectionType []string
IncludeTollPaymentTypes string
Callback string
Report string
DepartAt string
ArriveAt string
RouteType string
Traffic *bool
Avoid []string
TravelMode string
Hilliness string
Windingness string
VehicleMaxSpeed *int
VehicleWeight *int
VehicleAxleWeight *int
VehicleNumberOfAxles *int
VehicleLength *float64
VehicleWidth *float64
VehicleHeight *float64
VehicleCommercial *bool
VehicleLoadType []string
VehicleAdrTunnelRestrictionCode string
VehicleHasElectricTollCollectionTransponder string
VehicleEngineType string
ConstantSpeedConsumptionInLitersPerHundredkm string
CurrentFuelInLiters *float64
AuxiliaryPowerInLitersPerHour *float64
FuelEnergyDensityInMJoulesPerLiter *float64
AccelerationEfficiency *float64
DecelerationEfficiency *float64
UphillEfficiency *float64
DownhillEfficiency *float64
ConsumptionInkWhPerkmAltitudeGain *float64
RecuperationInkWhPerkmAltitudeLoss *float64
ConstantSpeedConsumptionInkWhPerHundredkm string
CurrentChargeInkWh *float64
MaxChargeInkWh *float64
AuxiliaryPowerInkW *float64
}
func (sgr CalculateRouteRequest) toQueryParams() (url.Values, error) {
return query.Values(sgr)
}
type CalculateRouteResponse struct {
Routes []Route `json:"routes"`
}
// CalculateRoute sends a route calculation request to TomTom API
func (c *TomTom) CalculateRoute(req *CalculateRouteRequest) (*CalculateRouteResponse, error) {
/*url, err := req.BuildURL(c.APIKey)
if err != nil {
return nil, err
}*/
var result CalculateRouteResponse
/*
query, err := req.toQueryParams()
if err != nil {
return nil, fmt.Errorf("structured geocode query: %w", err)
}
*/
resp, err := c.client.R().
//SetQueryParamsFromValues(query).
SetResult(&result).
SetPathParam("locations", locationString(req.Locations)).
SetPathParam("urlBase", c.urlBase).
SetQueryParam("key", c.APIKey).
Get("https://{urlBase}/routing/1/calculateRoute/{locations}/json")
if err != nil {
return nil, fmt.Errorf("calculate route get: %w", err)
}
if !resp.IsSuccess() {
return nil, fmt.Errorf("calculate route status: %d", resp.Status)
}
return &result, nil
}
func P(lat, lng float64) Point {
return Point{
Latitude: lat,
Longitude: lng,
}
}
type Locations = []Point
func locationString(locations Locations) string {
var sb strings.Builder
for i, p := range locations {
if i == 0 {
sb.WriteString(fmt.Sprintf("%f,%f", p.Latitude, p.Longitude))
} else {
sb.WriteString(fmt.Sprintf(":%f,%f", p.Latitude, p.Longitude))
}
}
return sb.String()
}

1
tomtom/routing.go Normal file
View file

@ -0,0 +1 @@
package tomtom

37
tomtom/tomtom.go Normal file
View file

@ -0,0 +1,37 @@
package tomtom
import (
"os"
"resty.dev/v3"
)
type TomTom struct {
APIKey string
client *resty.Client
urlBase string
}
func NewClient() *TomTom {
api_key := os.Getenv("TOMTOM_API_KEY")
r := resty.New()
return &TomTom{
APIKey: api_key,
client: r,
urlBase: "api.tomtom.com",
}
}
func (s *TomTom) Close() {
s.client.Close()
}
func (s *TomTom) SetDebug(enabled bool) {
s.client.Close()
if enabled {
s.client = resty.New().SetDebug(true)
} else {
s.client = resty.New().SetDebug(false)
}
}

235
tomtom/types.go Normal file
View file

@ -0,0 +1,235 @@
package tomtom
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
)
// Base URLs and API constants
const (
BaseURL = "https://api.tomtom.com"
RouteTypeFastest = "fastest"
TravelModeCar = "car"
)
// Coordinates represents latitude and longitude values
type Coordinates struct {
Latitude string `json:"latitude" xml:"latitude,attr"`
Longitude string `json:"longitude" xml:"longitude,attr"`
}
// Rectangle represents a geographic rectangle
type Rectangle struct {
SouthWestCorner Coordinates `json:"southWestCorner" xml:"southWestCorner"`
NorthEastCorner Coordinates `json:"northEastCorner" xml:"northEastCorner"`
}
// AvoidAreas represents areas to avoid in routing
type AvoidAreas struct {
Rectangles []Rectangle `json:"rectangles" xml:"rectangles>rectangle"`
}
// SupportingPoint represents a supporting point in the route calculation
type SupportingPoint struct {
Latitude string `json:"latitude" xml:"latitude,attr"`
Longitude string `json:"longitude" xml:"longitude,attr"`
}
// Client represents a TomTom API client
type Client struct {
APIKey string
HTTPClient *http.Client
}
// CalculateRoutePostData represents the POST body for Calculate Route API
type CalculateRoutePostData struct {
SupportingPoints []SupportingPoint `json:"supportingPoints,omitempty" xml:"supportingPoints>supportingPoint,omitempty"`
AvoidVignette []string `json:"avoidVignette,omitempty" xml:"avoidVignette,omitempty"`
AllowVignette []string `json:"allowVignette,omitempty" xml:"allowVignette,omitempty"`
AvoidAreas *AvoidAreas `json:"avoidAreas,omitempty" xml:"avoidAreas,omitempty"`
}
// Route response structures - These would need to be completed based on actual API response
type Summary struct {
LengthInMeters int `json:"lengthInMeters"`
TravelTimeInSeconds int `json:"travelTimeInSeconds"`
TrafficDelayInSeconds int `json:"trafficDelayInSeconds"`
DepartureTime string `json:"departureTime"`
ArrivalTime string `json:"arrivalTime"`
FuelConsumptionInLiters float64 `json:"fuelConsumptionInLiters,omitempty"`
ElectricEnergyConsumptionInkWh float64 `json:"electricEnergyConsumptionInkWh,omitempty"`
}
type Point struct {
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
}
type Leg struct {
Summary Summary `json:"summary"`
Points []Point `json:"points,omitempty"`
}
type Route struct {
Summary Summary `json:"summary"`
Legs []Leg `json:"legs,omitempty"`
}
// CalculateReachableRange API structures
// CalculateReachableRangeParams holds the parameters for the Calculate Reachable Range API
type CalculateReachableRangeParams struct {
// Path parameters
Origin string
ContentType string // "json" or "jsonp"
// Query parameters
FuelBudgetInLiters *float64
EnergyBudgetInkWh *float64
TimeBudgetInSec *float64
Callback string
Report string
DepartAt string
ArriveAt string
RouteType string
Traffic *bool
Avoid []string
TravelMode string
Hilliness string
Windingness string
VehicleMaxSpeed *int
VehicleWeight *int
VehicleAxleWeight *int
VehicleNumberOfAxles *int
VehicleLength *float64
VehicleWidth *float64
VehicleHeight *float64
VehicleCommercial *bool
VehicleLoadType []string
VehicleAdrTunnelRestrictionCode string
VehicleHasElectricTollCollectionTransponder string
VehicleEngineType string
ConstantSpeedConsumptionInLitersPerHundredkm string
CurrentFuelInLiters *float64
AuxiliaryPowerInLitersPerHour *float64
FuelEnergyDensityInMJoulesPerLiter *float64
AccelerationEfficiency *float64
DecelerationEfficiency *float64
UphillEfficiency *float64
DownhillEfficiency *float64
ConsumptionInkWhPerkmAltitudeGain *float64
RecuperationInkWhPerkmAltitudeLoss *float64
ConstantSpeedConsumptionInkWhPerHundredkm string
CurrentChargeInkWh *float64
MaxChargeInkWh *float64
AuxiliaryPowerInkW *float64
}
// CalculateReachableRangePostData represents the POST body for Calculate Reachable Range API
type CalculateReachableRangePostData struct {
AvoidVignette []string `json:"avoidVignette,omitempty" xml:"avoidVignette,omitempty"`
AllowVignette []string `json:"allowVignette,omitempty" xml:"allowVignette,omitempty"`
AvoidAreas *AvoidAreas `json:"avoidAreas,omitempty" xml:"avoidAreas,omitempty"`
}
// CalculateReachableRangeRequest combines both path parameters and POST body
type CalculateReachableRangeRequest struct {
Params CalculateReachableRangeParams
PostData *CalculateReachableRangePostData
}
// Reachable Range response structures
type Polygon struct {
Exterior []Point `json:"exterior"`
Interior [][]Point `json:"interior,omitempty"`
}
type CalculateReachableRangeResponse struct {
Polygon Polygon `json:"polygon"`
Summary struct {
DistanceLimit float64 `json:"distanceLimit,omitempty"`
TimeLimit int `json:"timeLimit,omitempty"`
FuelConsumptionLimit float64 `json:"fuelConsumptionLimit,omitempty"`
EnergyConsumptionLimit float64 `json:"energyConsumptionLimit,omitempty"`
} `json:"summary"`
}
// BuildURL builds the URL for the Calculate Reachable Range request
func (req *CalculateReachableRangeRequest) BuildURL(apiKey string) (string, error) {
baseURL := fmt.Sprintf("%s/routing/%d/calculateReachableRange/%s/%s",
BaseURL,
req.Params.Origin,
req.Params.ContentType)
// Add query parameters
query := url.Values{}
query.Add("key", apiKey)
if req.Params.FuelBudgetInLiters != nil {
query.Add("fuelBudgetInLiters", fmt.Sprintf("%f", *req.Params.FuelBudgetInLiters))
}
if req.Params.EnergyBudgetInkWh != nil {
query.Add("energyBudgetInkWh", fmt.Sprintf("%f", *req.Params.EnergyBudgetInkWh))
}
if req.Params.TimeBudgetInSec != nil {
query.Add("timeBudgetInSec", fmt.Sprintf("%f", *req.Params.TimeBudgetInSec))
}
// Add other parameters similarly...
return baseURL + "?" + query.Encode(), nil
}
// Client methods for executing requests
// CalculateReachableRange sends a reachable range calculation request to TomTom API
func (c *Client) CalculateReachableRange(req *CalculateReachableRangeRequest) (*CalculateReachableRangeResponse, error) {
url, err := req.BuildURL(c.APIKey)
if err != nil {
return nil, err
}
var response CalculateReachableRangeResponse
var httpReq *http.Request
if req.PostData != nil {
// POST request
jsonData, err := json.Marshal(req.PostData)
if err != nil {
return nil, err
}
httpReq, err = http.NewRequest("POST", url, strings.NewReader(string(jsonData)))
if err != nil {
return nil, err
}
httpReq.Header.Set("Content-Type", "application/json")
} else {
// GET request
httpReq, err = http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
}
resp, err := c.HTTPClient.Do(httpReq)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("API error: %s", resp.Status)
}
err = json.NewDecoder(resp.Body).Decode(&response)
if err != nil {
return nil, err
}
return &response, nil
}