package ocss import ( "context" "encoding/json" "errors" "fmt" "net/http" "strings" "time" "github.com/gorilla/schema" "github.com/rs/zerolog/log" "golang.org/x/crypto/bcrypt" ) const OperatorContextKey = "operator" type Handler interface { // IV GetDriverJourneys(ctx context.Context, departureLat float64, departureLng float64, arrivalLat float64, arrivalLng float64, departureDate time.Time, timeDelta *time.Duration, departureRadius *float64, arrivalRadius *float64, count *int64) ([]DriverJourney, error) GetPassengerJourneys(ctx context.Context, departureLat float64, departureLng float64, arrivalLat float64, arrivalLng float64, departureDate time.Time, timeDelta *time.Duration, departureRadius *float64, arrivalRadius *float64, count *int64) ([]PassengerJourney, error) GetDriverRegularTrips(ctx context.Context, departureLat float64, departureLng float64, arrivalLat float64, arrivalLng float64, departureTimeOfDay string, departureWeekDays *[]string, timeDelta *time.Duration, departureRadius *float64, arrivalRadius *float64, minDepartureDate *time.Time, maxDepartureDate *time.Time, count *int64) ([]DriverTrip, error) GetPassengerRegularTrips(ctx context.Context, departureLat float64, departureLng float64, arrivalLat float64, arrivalLng float64, departureTimeOfDay string, departureWeekDays *[]string, timeDelta *time.Duration, departureRadius *float64, arrivalRadius *float64, minDepartureDate *time.Time, maxDepartureDate *time.Time, count *int64) ([]PassengerTrip, error) //Booking by API PostBookings(ctx context.Context, booking Booking) (*Booking, error) PatchBookings(ctx context.Context, bookingId string, status BookingStatus, message *string) error GetBookings(ctx context.Context, bookingId string) (*Booking, error) // Webhooks PostBookingEvents(ctx context.Context, event CarpoolBookingEvent) error // Status Status() error } type AuthorizedOperator struct { Operator string ApiKey string // encoded using bcrypt } type GetDriverJourneysRequest struct { DepartureLat float64 `schema:"departureLat,required"` DepartureLng float64 `schema:"departureLng,required"` ArrivalLat float64 `schema:"arrivalLat,required"` ArrivalLng float64 `schema:"arrivalLng,required"` DepartureDate int64 `schema:"departureDate,required"` TimeDelta *int64 `schema:"timeDelta"` DepartureRadius *float64 `schema:"departureRadius"` ArrivalRadius *float64 `schema:"arrivalRadius"` Count *int64 `schema:"count"` } type GetPassengerJourneysRequest struct { DepartureLat float64 `schema:"departureLat,required"` DepartureLng float64 `schema:"departureLng,required"` ArrivalLat float64 `schema:"arrivalLat,required"` ArrivalLng float64 `schema:"arrivalLng,required"` DepartureDate int64 `schema:"departureDate,required"` TimeDelta *int64 `schema:"timeDelta"` DepartureRadius *float64 `schema:"departureRadius"` ArrivalRadius *float64 `schema:"arrivalRadius"` Count *int64 `schema:"count"` } type GetDriverRegularTripsRequest struct { DepartureLat float64 `schema:"departureLat,required"` DepartureLng float64 `schema:"departureLng,required"` ArrivalLat float64 `schema:"arrivalLat,required"` ArrivalLng float64 `schema:"arrivalLng,required"` DepartureTimeOfDay string `schema:"departureTimeOfDay,required"` DepartureWeekDays *[]string `schema:"departureWeekdays"` TimeDelta *int64 `schema:"timeDelta"` DepartureRadius *float64 `schema:"departureRadius"` ArrivalRadius *float64 `schema:"arrivalRadius"` MinDepartureDate *int64 `schema:"minDepartureDate"` MaxDepartureDate *int64 `schema:"maxDepartureDate"` Count *int64 `schema:"count"` } type GetPassengerRegularTripsRequest struct { DepartureLat float64 `schema:"departureLat,required"` DepartureLng float64 `schema:"departureLng,required"` ArrivalLat float64 `schema:"arrivalLat,required"` ArrivalLng float64 `schema:"arrivalLng,required"` DepartureTimeOfDay string `schema:"departureTimeOfDay,required"` DepartureWeekDays *[]string `schema:"departureWeekdays"` TimeDelta *int64 `schema:"timeDelta"` DepartureRadius *float64 `schema:"departureRadius"` ArrivalRadius *float64 `schema:"arrivalRadius"` MinDepartureDate *int64 `schema:"minDepartureDate"` MaxDepartureDate *int64 `schema:"maxDepartureDate"` Count *int64 `schema:"count"` } type PatchBookingsRequest struct { BookingId string `json:"bookingId"` Status string `json:"status"` Message string `json:"message"` } type Server struct { Handler Handler AuthorizedOperators []AuthorizedOperator } func NewServer(handler Handler) *Server { return &Server{ Handler: handler, } } func (s *Server) FindApiKey(key string) (operator string, err error) { for _, o := range s.AuthorizedOperators { if e := bcrypt.CompareHashAndPassword([]byte(o.ApiKey), []byte(key)); e == nil { return o.Operator, nil } } return "", errors.New("operator not found") } func (s *Server) AddOperator(operator string, apiKey string) error { encryptedKey, err := bcrypt.GenerateFromPassword([]byte(apiKey), bcrypt.DefaultCost) if err != nil { return err } s.AuthorizedOperators = append(s.AuthorizedOperators, AuthorizedOperator{ Operator: operator, ApiKey: string(encryptedKey), }) return nil } func (s Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { apiKey := r.Header.Get("X-Api-Key") operator, err := s.FindApiKey(apiKey) if err != nil { log.Error().Err(err).Msg("unauthorized") w.WriteHeader(http.StatusUnauthorized) return } ctx := context.WithValue(r.Context(), OperatorContextKey, operator) p := strings.Split(r.URL.Path, "/")[1:] n := len(p) switch { case r.Method == "GET" && n == 1 && p[0] == "driver_journeys": s.getDriverJourneys(w, r.WithContext(ctx)) return case r.Method == "GET" && n == 1 && p[0] == "passenger_journeys": s.getPassengerJourneys(w, r.WithContext(ctx)) return case r.Method == "GET" && n == 1 && p[0] == "driver_regular_trips": s.getDriverRegularTrips(w, r.WithContext(ctx)) return case r.Method == "GET" && n == 1 && p[0] == "passenger_regular_trips": s.getPassengerRegularTrips(w, r.WithContext(ctx)) return case r.Method == "POST" && n == 1 && p[0] == "bookings": s.postBookings(w, r.WithContext(ctx)) return case r.Method == "PATCH" && n == 2 && p[0] == "bookings": s.patchBookings(w, p[1], r.WithContext(ctx)) return case r.Method == "GET" && n == 2 && p[0] == "bookings": s.getBookings(w, p[1], r.WithContext(ctx)) return // case r.Method == "GET" && n == 1 && p[0] == "status": // s.getStatus(w, r.WithContext(ctx)) // return default: w.WriteHeader(http.StatusNotFound) } } func (s *Server) getDriverJourneys(w http.ResponseWriter, r *http.Request) { var request GetDriverJourneysRequest var decoder = schema.NewDecoder() if err := decoder.Decode(&request, r.URL.Query()); err != nil { log.Error().Err(err).Msg("could not parse the request") badRequest(w, fmt.Errorf("could not parse the request : %s", err)) return } departureDate := time.Unix(request.DepartureDate, 0) var timeDelta *time.Duration timeDelta = nil if request.TimeDelta != nil { duration := time.Duration(*request.TimeDelta) timeDelta = &duration } driverJourneys, err := s.Handler.GetDriverJourneys( r.Context(), request.DepartureLat, request.DepartureLng, request.ArrivalLat, request.ArrivalLng, departureDate, timeDelta, request.DepartureRadius, request.ArrivalRadius, request.Count) if err != nil { log.Error().Err(err).Msg("GetDriverJourneys failed") jsonError(w, err, http.StatusInternalServerError) return } jsonResponse(w, driverJourneys, http.StatusOK) } func (s *Server) getPassengerJourneys(w http.ResponseWriter, r *http.Request) { var request GetPassengerJourneysRequest var decoder = schema.NewDecoder() if err := decoder.Decode(&request, r.URL.Query()); err != nil { log.Error().Err(err).Msg("could not parse the request") badRequest(w, fmt.Errorf("could not parse the request : %s", err)) return } departureDate := time.Unix(request.DepartureDate, 0) var timeDelta *time.Duration timeDelta = nil if request.TimeDelta != nil { duration := time.Duration(*request.TimeDelta) timeDelta = &duration } passengerJourneys, err := s.Handler.GetPassengerJourneys( r.Context(), request.DepartureLat, request.DepartureLng, request.ArrivalLat, request.ArrivalLng, departureDate, timeDelta, request.DepartureRadius, request.ArrivalRadius, request.Count) if err != nil { log.Error().Err(err).Msg("GetPassengerJourneys failed") jsonError(w, err, http.StatusInternalServerError) return } jsonResponse(w, passengerJourneys, http.StatusOK) } func (s *Server) getDriverRegularTrips(w http.ResponseWriter, r *http.Request) { var request GetDriverRegularTripsRequest var decoder = schema.NewDecoder() if err := decoder.Decode(&request, r.URL.Query()); err != nil { log.Error().Err(err).Msg("could not parse the request") badRequest(w, fmt.Errorf("could not parse the request : %s", err)) return } var minDepartureDate *time.Time if request.MinDepartureDate != nil { d := time.Unix(*request.MinDepartureDate, 0) minDepartureDate = &d } var maxDepartureDate *time.Time if request.MinDepartureDate != nil { d := time.Unix(*request.MinDepartureDate, 0) maxDepartureDate = &d } var timeDelta *time.Duration timeDelta = nil if request.TimeDelta != nil { duration := time.Duration(*request.TimeDelta) timeDelta = &duration } driverJourneys, err := s.Handler.GetDriverRegularTrips( r.Context(), request.DepartureLat, request.DepartureLng, request.ArrivalLat, request.ArrivalLng, request.DepartureTimeOfDay, request.DepartureWeekDays, timeDelta, request.DepartureRadius, request.ArrivalRadius, minDepartureDate, maxDepartureDate, request.Count) if err != nil { log.Error().Err(err).Msg("GetDriverRegularTrips failed") jsonError(w, err, http.StatusInternalServerError) return } jsonResponse(w, driverJourneys, http.StatusOK) } func (s *Server) getPassengerRegularTrips(w http.ResponseWriter, r *http.Request) { var request GetPassengerRegularTripsRequest var decoder = schema.NewDecoder() if err := decoder.Decode(&request, r.URL.Query()); err != nil { log.Error().Err(err).Msg("could not parse the request") badRequest(w, fmt.Errorf("could not parse the request : %s", err)) return } var minDepartureDate *time.Time if request.MinDepartureDate != nil { d := time.Unix(*request.MinDepartureDate, 0) minDepartureDate = &d } var maxDepartureDate *time.Time if request.MinDepartureDate != nil { d := time.Unix(*request.MinDepartureDate, 0) maxDepartureDate = &d } var timeDelta *time.Duration timeDelta = nil if request.TimeDelta != nil { duration := time.Duration(*request.TimeDelta) timeDelta = &duration } passengerTrips, err := s.Handler.GetPassengerRegularTrips( r.Context(), request.DepartureLat, request.DepartureLng, request.ArrivalLat, request.ArrivalLng, request.DepartureTimeOfDay, request.DepartureWeekDays, timeDelta, request.DepartureRadius, request.ArrivalRadius, minDepartureDate, maxDepartureDate, request.Count) if err != nil { log.Error().Err(err).Msg("GetPassengerRegularTrips failed") jsonError(w, err, http.StatusInternalServerError) return } jsonResponse(w, passengerTrips, http.StatusOK) } func (s *Server) postBookings(w http.ResponseWriter, r *http.Request) { var request Booking var decoder = json.NewDecoder(r.Body) if err := decoder.Decode(&request); err != nil { log.Error().Err(err).Msg("PostBookings - could not parse the request ") badRequest(w, fmt.Errorf("could not parse the request : %s", err)) return } response, err := s.Handler.PostBookings( r.Context(), request, ) if err != nil { log.Error().Err(err).Msg("error in PostBookings") jsonError(w, err, http.StatusInternalServerError) return } jsonResponse(w, response, http.StatusOK) } func (s *Server) patchBookings(w http.ResponseWriter, bookingId string, r *http.Request) { var request PatchBookingsRequest var decoder = json.NewDecoder(r.Body) if err := decoder.Decode(&request); err != nil { log.Error().Err(err).Msg("PatchBookings - could not parse the request ") badRequest(w, fmt.Errorf("could not parse the request : %s", err)) return } err := s.Handler.PatchBookings( r.Context(), bookingId, BookingConfirmed, &request.Message, ) if err != nil { log.Error().Err(err).Msg("error in PatchBookings") jsonError(w, err, http.StatusInternalServerError) return } jsonResponse(w, map[string]any{}, http.StatusOK) } func (s *Server) getBookings(w http.ResponseWriter, bookingId string, r *http.Request) { log.Debug().Str("booking id", bookingId).Msg("GetBooking request") booking, err := s.Handler.GetBookings( r.Context(), bookingId, ) if err != nil { log.Error().Err(err).Msg("error in GetBookings") jsonError(w, err, http.StatusInternalServerError) return } jsonResponse(w, booking, http.StatusOK) } func jsonResponse(w http.ResponseWriter, response any, statuscode int) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(statuscode) err := json.NewEncoder(w).Encode(response) if err != nil { log.Error().Err(err).Msg("json error encoding issue") } } func jsonError(w http.ResponseWriter, err error, statuscode int) { jsonResponse(w, map[string]any{"error": err.Error()}, statuscode) } func badRequest(w http.ResponseWriter, err error) { jsonError(w, err, http.StatusBadRequest) }