prixdugaz/main.go
2026-04-02 09:50:45 -04:00

171 lines
3.4 KiB
Go

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
}