package main
import (
"encoding/json"
"errors"
"fmt"
"html/template"
"io"
"log"
"net/http"
"os"
"sort"
"strconv"
"strings"
)
type Favourite struct {
Name string `json:"name"`
Code string `json:"code"`
}
type Car struct {
Time string `json:"time"`
}
type MaybeString string
func (ms *MaybeString) UnmarshalJSON(b []byte) error {
var i any
err := json.Unmarshal(b, &i)
if err != nil {
return err
}
switch val := i.(type) {
case int, float32, float64:
*ms = MaybeString(fmt.Sprint(val))
return nil
case string:
*ms = MaybeString(val)
return nil
default:
return fmt.Errorf("unknown type %T", val)
}
}
type Line struct {
Type string `json:"vehicle_type"`
Name MaybeString `json:"name"`
Cars []Car `json:"arrivals"`
Color string
}
type SofiaTraffic struct {
Name string `json:"name"`
Code string `json:"code"`
Lines []Line `json:"lines"`
}
func lessLine(left Line, right Line) bool {
val := strings.Compare(left.Type, right.Type)
if val != 0 {
return val < 0
} else {
l, lerr := strconv.Atoi(string(left.Name))
r, rerr := strconv.Atoi(string(right.Name))
switch {
case lerr == nil && rerr == nil:
return l < r
case lerr == nil && rerr != nil:
return true
case lerr != nil && rerr == nil:
return false
case lerr != nil && rerr != nil:
val = strings.Compare(string(left.Name), string(right.Name))
return val < 0
}
}
return true
}
func handle(resp http.ResponseWriter, req *http.Request) {
id := strings.TrimPrefix(req.URL.Path, "/")
find_id := req.URL.Query().Get("find")
if len(find_id) != 0 {
find_int, err := strconv.Atoi(find_id)
if err != nil {
log.Println(err)
show500(resp)
return
}
http.Redirect(resp, req, fmt.Sprintf("/%04d", find_int), http.StatusFound)
} else if len(id) == 0 {
showFavourites(resp)
} else {
showTable(resp, id)
}
}
func show500(resp http.ResponseWriter) {
resp.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(resp, "
500 Internal Server Error
")
}
func fixLine(line *Line) {
switch line.Type {
case "bus":
line.Type = "А"
case "trolley":
line.Type = "ТБ"
case "tram":
line.Type = "ТМ"
}
switch line.Name[0] {
case 'E':
line.Type = "А"
line.Name = line.Name[1:]
}
}
func setLineColor(line *Line) {
switch {
case line.Type == "А":
line.Color = "BD202E"
case line.Type == "ТБ":
line.Color = "2AA9E0"
case line.Type == "ТМ":
line.Color = "F7941F"
default:
line.Color = "000000"
}
}
func getSofiaTrafficData(code string) (SofiaTraffic, error) {
var data SofiaTraffic
url := fmt.Sprintf("https://api-arrivals.sofiatraffic.bg/api/v1/arrivals/%s/", code)
respAPI, err := http.Get(url)
if err != nil {
log.Println(err)
return data, errors.New("get failed")
}
if respAPI.StatusCode != 200 {
log.Printf("Status code for Code %v is %v", code, respAPI.StatusCode)
return data, errors.New("bad status code")
}
if respAPI.Body != nil {
defer respAPI.Body.Close()
}
body, err := io.ReadAll(respAPI.Body)
if err != nil {
log.Println(err)
return data, errors.New("failed to read body")
}
err = json.Unmarshal(body, &data)
if err != nil {
log.Println(err)
return data, errors.New("failed to unmarshal json")
}
return data, nil
}
func showTable(resp http.ResponseWriter, code string) {
data, err := getSofiaTrafficData(code)
if err != nil {
fmt.Println("POOTIS")
show500(resp)
return
}
for idx := range(data.Lines) {
// Fix Line
fixLine(&(data.Lines[idx]))
// Set Line Colors
setLineColor(&(data.Lines[idx]))
// Do not show seconds
for car_idx := range(data.Lines[idx].Cars) {
data.Lines[idx].Cars[car_idx].Time = data.Lines[idx].Cars[car_idx].Time[:5]
}
}
// Sort Lines
sort.Slice(data.Lines, func (i int, j int) bool {
return lessLine(data.Lines[i], data.Lines[j])
})
templ := template.Must(template.ParseFiles("stop.html"))
templ.Execute(resp, data)
}
func readFavourites() []Favourite {
var favourites []Favourite
jsonFile, err := os.Open("favourites.json")
if err != nil {
log.Println(err)
return favourites
}
defer jsonFile.Close()
bytes, err := io.ReadAll(jsonFile)
if err != nil {
log.Println(err)
return favourites
}
err = json.Unmarshal(bytes, &favourites)
if err != nil {
log.Println(err)
}
return favourites
}
func showFavourites(resp http.ResponseWriter) {
// Read And Parse JSON
favourites := readFavourites()
data := make(map[string] []Favourite)
data["Favourites"] = favourites
// Read And Execute Template
templ := template.Must(template.ParseFiles("index.html"))
templ.Execute(resp, data)
}
func main() {
http.HandleFunc("/", handle)
log.Println("Server Running")
http.ListenAndServe(":8000", nil)
}