337 lines
8.8 KiB
Go
337 lines
8.8 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"log"
|
|
"os"
|
|
|
|
"github.com/joho/godotenv"
|
|
|
|
"bahndb_rest/queries"
|
|
)
|
|
|
|
var pool *pgxpool.Pool
|
|
|
|
func convErrorHandler(str string, err error) string {
|
|
if err != nil {
|
|
return err.Error()
|
|
}
|
|
|
|
return str
|
|
}
|
|
|
|
type RequestError struct {
|
|
Code string `json:"code"`
|
|
Message string `json:"messaage"`
|
|
}
|
|
|
|
func run() error {
|
|
// load environment file
|
|
err := godotenv.Load()
|
|
if err != nil {
|
|
log.Fatal("Error loading .env file")
|
|
}
|
|
|
|
connectstr := "host=" + os.Getenv("DB_HOST") + " port=" + os.Getenv("DB_PORT") + " user=" + os.Getenv("DB_USER") + " password=" + os.Getenv("DB_PASSWORD") + " dbname=" + os.Getenv("DB_DATABASE") + " pool_max_conns=70 pool_max_conn_lifetime=1h30m"
|
|
|
|
// connect to db
|
|
pool, err = pgxpool.New(context.Background(), connectstr)
|
|
if err != nil {
|
|
log.Fatal("Error creating pool", err.Error())
|
|
}
|
|
fmt.Println("Connected to database")
|
|
|
|
//create new request multiplexer
|
|
mux := http.NewServeMux()
|
|
|
|
// register stopinfo handler
|
|
mux.Handle("/stop/search", &stopInfoHandler{})
|
|
mux.Handle("/stop/{id}/info", &stopIdInfoHandler{})
|
|
mux.Handle("/stop/{id}/departures", &stopDeparturesHandler{})
|
|
|
|
fmt.Println("Starting server on port 8080")
|
|
|
|
err = http.ListenAndServe(":8080", mux)
|
|
if err != nil {
|
|
log.Fatal("Error serving server", err.Error())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func main() {
|
|
if err := run(); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// Search For a Stop by Name
|
|
type stopInfoHandler struct{}
|
|
|
|
func (h *stopInfoHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
enc := json.NewEncoder(w)
|
|
ctx := context.Background()
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
conn, err := pool.Acquire(ctx)
|
|
if err != nil {
|
|
w.WriteHeader(500)
|
|
_ = enc.Encode(RequestError{Code: "0001", Message: "DB Error: " + err.Error()})
|
|
return
|
|
}
|
|
defer conn.Release()
|
|
|
|
// load all generated queries
|
|
queries := bahndb_rest.New(conn)
|
|
|
|
// Parse query text
|
|
var searchQuery = r.FormValue("query")
|
|
|
|
if searchQuery == "" {
|
|
w.WriteHeader(400)
|
|
_ = enc.Encode(RequestError{Code: "0000", Message: "'query' is required"})
|
|
return
|
|
}
|
|
|
|
var searchQueries = strings.Split(searchQuery, " ")
|
|
|
|
for i, query := range searchQueries {
|
|
if i == 0 {
|
|
// Parse whether result shall be exact
|
|
if r.FormValue("contains") != "true" {
|
|
searchQueries[0] = "%" + query + "%"
|
|
} else {
|
|
searchQueries[0] = query + "%"
|
|
}
|
|
} else {
|
|
searchQueries[i] = "%" + query + "%"
|
|
}
|
|
}
|
|
|
|
var longDistance pgtype.Bool
|
|
|
|
// Parse whether only long distance stations shall be returned
|
|
if r.FormValue("longDistance") == "true" {
|
|
longDistance = pgtype.Bool{Bool: true, Valid: true}
|
|
} else {
|
|
longDistance = pgtype.Bool{Bool: false, Valid: false}
|
|
}
|
|
|
|
stations, err := queries.GetStationsByName(ctx, bahndb_rest.GetStationsByNameParams{SearchParams: searchQueries, LongDistance: longDistance})
|
|
if err != nil {
|
|
w.WriteHeader(500)
|
|
_ = enc.Encode(RequestError{Code: "0001", Message: "DB Error: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
err = enc.Encode(stations)
|
|
|
|
if err != nil {
|
|
w.WriteHeader(500)
|
|
_ = enc.Encode(RequestError{Code: "0002", Message: "Could not write response: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
type stopIdInfoHandler struct{}
|
|
|
|
func (h *stopIdInfoHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
enc := json.NewEncoder(w)
|
|
ctx := context.Background()
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
conn, err := pool.Acquire(ctx)
|
|
if err != nil {
|
|
w.WriteHeader(500)
|
|
_ = enc.Encode(RequestError{Code: "0001", Message: "DB Error: " + err.Error()})
|
|
return
|
|
}
|
|
defer conn.Release()
|
|
|
|
// load all generated queries
|
|
queries := bahndb_rest.New(conn)
|
|
|
|
// Parse stop id
|
|
var stopId = r.PathValue("id")
|
|
if stopId == "" {
|
|
w.WriteHeader(400)
|
|
_ = enc.Encode(RequestError{Code: "0000", Message: "'id' is required"})
|
|
return
|
|
}
|
|
|
|
// Execute DB Query
|
|
stations, err := queries.GetStationById(ctx, stopId)
|
|
if err != nil {
|
|
w.WriteHeader(500)
|
|
_ = enc.Encode(RequestError{Code: "0001", Message: "DB Error: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
// Return
|
|
w.WriteHeader(http.StatusOK)
|
|
err = enc.Encode(stations)
|
|
|
|
if err != nil {
|
|
w.WriteHeader(500)
|
|
_ = enc.Encode(RequestError{Code: "0002", Message: "Could not write response: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// Return departures at specific stop
|
|
type GetDeparturesByIdRowWithString struct {
|
|
bahndb_rest.GetDeparturesByIdRow
|
|
}
|
|
|
|
func (r GetDeparturesByIdRowWithString) MarshalJSON() ([]byte, error) {
|
|
var direction map[string]interface{}
|
|
err := json.Unmarshal(r.Direction, &direction)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Create an anonymous struct that mirrors the original but with string Direction
|
|
tmp := struct {
|
|
ID string `json:"id"`
|
|
LineName pgtype.Text `json:"lineName"`
|
|
LineProductName pgtype.Text `json:"lineProductName"`
|
|
DepartureDelay pgtype.Int4 `json:"departureDelay"`
|
|
Direction interface{} `json:"direction"`
|
|
Stopovers interface{} `json:"stopovers"`
|
|
Departure pgtype.Timestamptz `json:"departure"`
|
|
PlannedDeparture pgtype.Timestamptz `json:"plannedDeparture"`
|
|
DeparturePlatform pgtype.Text `json:"departurePlattform"`
|
|
PlannedDeparturePlatform pgtype.Text `json:"plannedDeparturePlattform"`
|
|
Cancelled pgtype.Text `json:"cancelled"`
|
|
RealtimeDataUpdatedAt pgtype.Timestamptz `json:"realtimeDataUpdatedAt"`
|
|
}{
|
|
ID: r.ID,
|
|
LineName: r.LineName,
|
|
LineProductName: r.LineProductName,
|
|
DepartureDelay: r.DepartureDelay,
|
|
Direction: direction,
|
|
Stopovers: r.Stopovers,
|
|
Departure: r.Departure,
|
|
PlannedDeparture: r.PlannedDeparture,
|
|
DeparturePlatform: r.DeparturePlatform,
|
|
PlannedDeparturePlatform: r.PlannedDeparturePlatform,
|
|
Cancelled: r.Cancelled,
|
|
RealtimeDataUpdatedAt: r.RealtimeDataUpdatedAt,
|
|
}
|
|
|
|
return json.Marshal(tmp)
|
|
}
|
|
|
|
type stopDeparturesHandler struct{}
|
|
|
|
func (h *stopDeparturesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
enc := json.NewEncoder(w)
|
|
ctx := context.Background()
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
// Acquire DB Connection
|
|
conn, err := pool.Acquire(ctx)
|
|
if err != nil {
|
|
w.WriteHeader(500)
|
|
_ = enc.Encode(RequestError{Code: "0000", Message: "'id' is required"})
|
|
return
|
|
}
|
|
defer conn.Release()
|
|
|
|
queries := bahndb_rest.New(conn)
|
|
|
|
// Parse stop id
|
|
var stopId = r.PathValue("id")
|
|
if stopId == "" {
|
|
w.WriteHeader(400)
|
|
_ = enc.Encode(RequestError{Code: "0001", Message: "DB Error: " + err.Error()})
|
|
return
|
|
}
|
|
stopIdPg := pgtype.Text{String: stopId, Valid: true}
|
|
|
|
// Parse time from string, default to now
|
|
timeFromStr := strings.Replace(r.FormValue("from"), " ", "+", -1)
|
|
var timeFrom time.Time
|
|
|
|
if timeFromStr == "" {
|
|
timeFrom = time.Now()
|
|
} else {
|
|
timeFrom, err = time.Parse(time.RFC3339, timeFromStr)
|
|
}
|
|
|
|
timeFromPg := pgtype.Timestamptz{Time: timeFrom, Valid: true}
|
|
|
|
// Parse Duration to get departures for, default to 60
|
|
duration, err := strconv.Atoi(r.FormValue("duration"))
|
|
if err != nil {
|
|
duration = 60
|
|
}
|
|
|
|
// Parse upper Time limit, default to from duration minutes
|
|
timeToStr := strings.Replace(r.FormValue("to"), " ", "+", -1)
|
|
var timeTo time.Time
|
|
if timeToStr == "" {
|
|
timeTo = timeFrom.Add(time.Duration(duration) * time.Minute)
|
|
} else {
|
|
timeTo, err = time.Parse(time.RFC3339, timeFromStr)
|
|
}
|
|
timeToPg := pgtype.Timestamptz{Time: timeTo, Valid: true}
|
|
|
|
// Parse realtime point in time, default to most recent
|
|
timeRtUpdateStr := strings.Replace(r.FormValue("dataAt"), " ", "+", -1)
|
|
var timeRtUpdate time.Time
|
|
if timeRtUpdateStr == "" {
|
|
timeRtUpdate = time.Now().Add(time.Duration(48) * time.Hour)
|
|
} else {
|
|
timeRtUpdate, err = time.Parse(time.RFC3339, timeFromStr)
|
|
}
|
|
timeRtUpdatePg := pgtype.Timestamptz{Time: timeRtUpdate, Valid: true}
|
|
|
|
// Execute Query
|
|
stations, err := queries.GetDeparturesById(ctx, bahndb_rest.GetDeparturesByIdParams{StopID: stopIdPg,
|
|
DepartureFrom: timeFromPg,
|
|
DepartureTo: timeToPg,
|
|
RtUpdatedAt: timeRtUpdatePg})
|
|
|
|
if err != nil {
|
|
w.WriteHeader(500)
|
|
_ = enc.Encode(RequestError{Code: "0001", Message: "DB Error: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
var stationsReturn []GetDeparturesByIdRowWithString
|
|
|
|
for _, station := range stations {
|
|
stationsReturn = append(stationsReturn, GetDeparturesByIdRowWithString{station})
|
|
}
|
|
|
|
// Return Data
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
err = enc.Encode(stationsReturn)
|
|
|
|
if err != nil {
|
|
w.WriteHeader(500)
|
|
_ = enc.Encode(RequestError{Code: "0002", Message: "Could not write response: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
return
|
|
}
|