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 }