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 } if r.FormValue("exact") != "true" { searchQuery = "%" + searchQuery + "%" } queryPgText := pgtype.Text{String: searchQuery, Valid: true} stations, err := queries.GetStationsByName(ctx, queryPgText) 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 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("2006-01-02T15:04:05-0700", 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("2006-01-02T15:04:05-0700", timeToStr) } 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("2006-01-02T15:04:05-0700", timeRtUpdateStr) } timeRtUpdatePg := pgtype.Timestamptz{Time: timeRtUpdate, Valid: true} // Execute Query stations, err := queries.GetDeparturesById(ctx, bahndb_rest.GetDeparturesByIdParams{Stop: stopIdPg, Departure: timeFromPg, Departure_2: timeToPg, Realtimedataupdatedat: timeRtUpdatePg}) if err != nil { w.WriteHeader(500) _ = enc.Encode(RequestError{Code: "0001", Message: "DB Error: " + err.Error()}) return } // Return Data w.Header().Set("Content-Type", "application/json") 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 }