diff --git a/main.go b/main.go new file mode 100644 index 0000000..2483998 --- /dev/null +++ b/main.go @@ -0,0 +1,261 @@ +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 +} + +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() + + conn, err := pool.Acquire(ctx) + if err != nil { + w.WriteHeader(500) + _, _ = w.Write([]byte(err.Error())) + return + } + defer conn.Release() + + // load all generated queries + queries := bahndb_rest.New(conn) + + // get all results + var searchQuery = r.FormValue("query") + + if searchQuery == "" { + w.WriteHeader(400) + _, _ = w.Write([]byte("'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) + _, _ = w.Write([]byte(err.Error())) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + err = enc.Encode(stations) + + if err != nil { + w.WriteHeader(500) + _, _ = w.Write([]byte(err.Error())) + return + } + + return +} + +type stopIdInfoHandler struct{} + +func (h *stopIdInfoHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + enc := json.NewEncoder(w) + ctx := context.Background() + + conn, err := pool.Acquire(ctx) + if err != nil { + w.WriteHeader(500) + _, _ = w.Write([]byte(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) + _, _ = w.Write([]byte("'id' is required")) + return + } + + // Execute DB Query + stations, err := queries.GetStationById(ctx, stopId) + if err != nil { + w.WriteHeader(500) + _, _ = w.Write([]byte(err.Error())) + return + } + + // Return + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + err = enc.Encode(stations) + + if err != nil { + w.WriteHeader(500) + _, _ = w.Write([]byte(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() + + // Acquire DB Connection + conn, err := pool.Acquire(ctx) + if err != nil { + w.WriteHeader(500) + _, _ = w.Write([]byte(err.Error())) + return + } + defer conn.Release() + + queries := bahndb_rest.New(conn) + + // Parse stop id + var stopId = r.PathValue("id") + if stopId == "" { + w.WriteHeader(400) + _, _ = w.Write([]byte("'id' is required")) + 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) + _, _ = w.Write([]byte(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) + _, _ = w.Write([]byte(err.Error())) + return + } + + return +}