initial project setup
This commit is contained in:
commit
46c024e8bd
8 changed files with 574 additions and 0 deletions
171
main.go
Normal file
171
main.go
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/xuri/excelize/v2"
|
||||
)
|
||||
|
||||
//go:embed static/*
|
||||
var staticFiles embed.FS
|
||||
|
||||
const (
|
||||
dataURL = "https://regieessencequebec.ca/data/stations-20260402132004.xlsx"
|
||||
defaultPort = "8080"
|
||||
)
|
||||
|
||||
type Station struct {
|
||||
Name string `json:"name"`
|
||||
Brand string `json:"brand"`
|
||||
Address string `json:"address"`
|
||||
Region string `json:"region"`
|
||||
PostalCode string `json:"postalCode"`
|
||||
Lat float64 `json:"lat"`
|
||||
Lng float64 `json:"lng"`
|
||||
Regular float64 `json:"regular"`
|
||||
Super float64 `json:"super"`
|
||||
Diesel float64 `json:"diesel"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
port := os.Getenv("PORT")
|
||||
if port == "" {
|
||||
port = defaultPort
|
||||
}
|
||||
|
||||
staticSub, err := fs.Sub(staticFiles, "static")
|
||||
if err != nil {
|
||||
log.Fatalf("static files: %v", err)
|
||||
}
|
||||
|
||||
http.HandleFunc("/api/stations", handleStations)
|
||||
http.Handle("/", http.FileServer(http.FS(staticSub)))
|
||||
|
||||
log.Printf("Listening on http://localhost:%s", port)
|
||||
log.Fatal(http.ListenAndServe(":"+port, nil))
|
||||
}
|
||||
|
||||
func handleStations(w http.ResponseWriter, r *http.Request) {
|
||||
stations, err := fetchAndParse()
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("error: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Cache-Control", "public, max-age=3600")
|
||||
json.NewEncoder(w).Encode(stations)
|
||||
}
|
||||
|
||||
func fetchAndParse() ([]Station, error) {
|
||||
log.Println("Fetching Excel data...")
|
||||
resp, err := http.Get(dataURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fetching data: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("unexpected status: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
tmp, err := os.CreateTemp("", "stations-*.xlsx")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating temp file: %w", err)
|
||||
}
|
||||
defer os.Remove(tmp.Name())
|
||||
|
||||
if _, err := io.Copy(tmp, resp.Body); err != nil {
|
||||
tmp.Close()
|
||||
return nil, fmt.Errorf("writing temp file: %w", err)
|
||||
}
|
||||
tmp.Close()
|
||||
|
||||
f, err := excelize.OpenFile(tmp.Name())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("opening excel: %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
sheets := f.GetSheetList()
|
||||
if len(sheets) == 0 {
|
||||
return nil, fmt.Errorf("no sheets found")
|
||||
}
|
||||
|
||||
rows, err := f.GetRows(sheets[0])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading rows: %w", err)
|
||||
}
|
||||
|
||||
if len(rows) < 2 {
|
||||
return nil, fmt.Errorf("not enough rows")
|
||||
}
|
||||
|
||||
var stations []Station
|
||||
for _, row := range rows[1:] {
|
||||
if len(row) < 8 {
|
||||
continue
|
||||
}
|
||||
|
||||
lat, err := strconv.ParseFloat(row[5], 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
lng, err := strconv.ParseFloat(row[6], 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
regular := parsePrice(row[7])
|
||||
if regular <= 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
s := Station{
|
||||
Name: row[0],
|
||||
Brand: row[1],
|
||||
Address: row[2],
|
||||
Region: row[3],
|
||||
PostalCode: row[4],
|
||||
Lat: lat,
|
||||
Lng: lng,
|
||||
Regular: regular,
|
||||
}
|
||||
|
||||
if len(row) > 8 {
|
||||
s.Super = parsePrice(row[8])
|
||||
}
|
||||
if len(row) > 9 {
|
||||
s.Diesel = parsePrice(row[9])
|
||||
}
|
||||
|
||||
stations = append(stations, s)
|
||||
}
|
||||
|
||||
log.Printf("Parsed %d stations", len(stations))
|
||||
return stations, nil
|
||||
}
|
||||
|
||||
// parsePrice converts "190.9¢" to 190.9
|
||||
func parsePrice(s string) float64 {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" || s == "N/D" {
|
||||
return 0
|
||||
}
|
||||
s = strings.TrimSuffix(s, "¢")
|
||||
s = strings.TrimSuffix(s, "\u00a2") // cent sign
|
||||
v, err := strconv.ParseFloat(s, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return v
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue