You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
111 lines
2.9 KiB
111 lines
2.9 KiB
package api |
|
|
|
import ( |
|
"context" |
|
"encoding/json" |
|
"fmt" |
|
"io" |
|
"net/http" |
|
"net/url" |
|
"time" |
|
) |
|
|
|
type TrainResponse struct { |
|
Rank string `json:"rank"` |
|
Number string `json:"number"` |
|
Date string `json:"date"` |
|
Operator string `json:"operator"` |
|
Groups []TrainGroup `json:"groups"` |
|
} |
|
|
|
type TrainGroup struct { |
|
Route struct { |
|
From string `json:"from"` |
|
To string `json:"to"` |
|
} `json:"route"` |
|
Status *struct { |
|
Delay int `json:"delay"` |
|
Station string `json:"station"` |
|
State string `json:"state"` |
|
} `json:"status"` |
|
Stations []TrainStation `json:"stations"` |
|
} |
|
|
|
type TrainStation struct { |
|
Name string `json:"name"` |
|
LinkName string `json:"linkName"` |
|
Km int `json:"km"` |
|
StoppingTime *int `json:"stoppingTime"` |
|
Platform *string `json:"platform"` |
|
Arrival *TrainArrDep `json:"arrival"` |
|
Departure *TrainArrDep `json:"departure"` |
|
Notes []any `json:"notes"` |
|
} |
|
|
|
type TrainArrDep struct { |
|
ScheduleTime time.Time `json:"scheduleTime"` |
|
Status *struct { |
|
Delay int `json:"delay"` |
|
Real bool `json:"real"` |
|
Cancelled bool `json:"cancelled"` |
|
} `json:"status"` |
|
} |
|
|
|
const ( |
|
trainApiEndpoint = "https://scraper.infotren.dcdev.ro/v3" |
|
) |
|
|
|
var ( |
|
TrainNotFound = fmt.Errorf("train not found") |
|
ServerError = fmt.Errorf("server error") |
|
) |
|
|
|
func GetTrain(ctx context.Context, trainNumber string, date time.Time) (*TrainResponse, error) { |
|
u, _ := url.Parse(trainApiEndpoint) |
|
u.Path, _ = url.JoinPath(u.Path, "trains", trainNumber) |
|
query := u.Query() |
|
query.Add("date", date.Format(time.RFC3339)) |
|
u.RawQuery = query.Encode() |
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) |
|
if err != nil { |
|
return nil, fmt.Errorf("error getting train %s: %w", trainNumber, err) |
|
} |
|
|
|
res, err := http.DefaultClient.Do(req) |
|
if err != nil { |
|
return nil, fmt.Errorf("error getting train %s: %w", trainNumber, err) |
|
} |
|
defer func() { |
|
_ = res.Body.Close() |
|
}() |
|
|
|
switch { |
|
case res.StatusCode == http.StatusNotFound: |
|
return nil, fmt.Errorf("error getting train %s: %w", trainNumber, TrainNotFound) |
|
case res.StatusCode/100 != 2: |
|
return nil, fmt.Errorf("error getting train %s: status code %d: %w", trainNumber, res.StatusCode, ServerError) |
|
} |
|
|
|
var body []byte |
|
if res.ContentLength > 0 { |
|
body = make([]byte, res.ContentLength) |
|
n, err := io.ReadFull(res.Body, body) |
|
if err != nil && err != io.EOF { |
|
return nil, fmt.Errorf("error getting train %s: %w", trainNumber, err) |
|
} else if n != int(res.ContentLength) { |
|
body = body[0:n] |
|
} |
|
} else { |
|
body, err = io.ReadAll(res.Body) |
|
if err != nil { |
|
return nil, fmt.Errorf("error getting train %s: %w", trainNumber, err) |
|
} |
|
} |
|
|
|
var trainData TrainResponse |
|
if err := json.Unmarshal(body, &trainData); err != nil { |
|
return nil, fmt.Errorf("error getting train %s: %w", trainNumber, err) |
|
} |
|
|
|
return &trainData, nil |
|
}
|
|
|