2023-03-27 18:54:56 +00:00
|
|
|
package ocss
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/gorilla/schema"
|
2023-03-29 22:45:18 +00:00
|
|
|
"github.com/rs/zerolog/log"
|
2023-03-27 18:54:56 +00:00
|
|
|
"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)
|
2023-03-29 22:45:18 +00:00
|
|
|
PatchBookings(ctx context.Context, bookingId string, status BookingStatus, message *string) error
|
|
|
|
GetBookings(ctx context.Context, bookingId string) (*Booking, error)
|
2023-03-27 18:54:56 +00:00
|
|
|
|
|
|
|
// 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"`
|
|
|
|
}
|
|
|
|
|
2023-03-29 22:45:18 +00:00
|
|
|
type PatchBookingsRequest struct {
|
|
|
|
BookingId string `json:"bookingId"`
|
|
|
|
Status string `json:"status"`
|
|
|
|
Message string `json:message"`
|
|
|
|
}
|
|
|
|
|
2023-03-27 18:54:56 +00:00
|
|
|
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 {
|
2023-03-29 22:45:18 +00:00
|
|
|
log.Error().Err(err).Msg("unauthorized")
|
2023-03-27 18:54:56 +00:00
|
|
|
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
|
2023-03-29 22:45:18 +00:00
|
|
|
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
|
2023-03-27 18:54:56 +00:00
|
|
|
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 {
|
2023-03-29 22:45:18 +00:00
|
|
|
log.Error().Err(err).Msg("could not parse the request")
|
2023-03-27 18:54:56 +00:00
|
|
|
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 {
|
2023-03-29 22:45:18 +00:00
|
|
|
log.Error().Err(err).Msg("GetDriverJourneys failed")
|
2023-03-27 18:54:56 +00:00
|
|
|
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 {
|
2023-03-29 22:45:18 +00:00
|
|
|
log.Error().Err(err).Msg("could not parse the request")
|
2023-03-27 18:54:56 +00:00
|
|
|
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 {
|
2023-03-29 22:45:18 +00:00
|
|
|
log.Error().Err(err).Msg("GetPassengerJourneys failed")
|
2023-03-27 18:54:56 +00:00
|
|
|
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 {
|
2023-03-29 22:45:18 +00:00
|
|
|
log.Error().Err(err).Msg("could not parse the request")
|
2023-03-27 18:54:56 +00:00
|
|
|
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 {
|
2023-03-29 22:45:18 +00:00
|
|
|
log.Error().Err(err).Msg("GetDriverRegularTrips failed")
|
2023-03-27 18:54:56 +00:00
|
|
|
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 {
|
2023-03-29 22:45:18 +00:00
|
|
|
log.Error().Err(err).Msg("could not parse the request")
|
2023-03-27 18:54:56 +00:00
|
|
|
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 {
|
2023-03-29 22:45:18 +00:00
|
|
|
log.Error().Err(err).Msg("GetPassengerRegularTrips failed")
|
2023-03-27 18:54:56 +00:00
|
|
|
jsonError(w, err, http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
jsonResponse(w, passengerTrips, http.StatusOK)
|
|
|
|
}
|
|
|
|
|
2023-03-29 22:45:18 +00:00
|
|
|
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) {
|
|
|
|
|
2023-03-30 06:44:58 +00:00
|
|
|
log.Debug().Str("booking id", bookingId).Msg("GetBooking request")
|
|
|
|
|
2023-03-29 22:45:18 +00:00
|
|
|
booking, err := s.Handler.GetBookings(
|
|
|
|
r.Context(),
|
|
|
|
bookingId,
|
|
|
|
)
|
|
|
|
|
|
|
|
if err != nil {
|
2023-03-30 06:44:58 +00:00
|
|
|
log.Error().Err(err).Msg("error in GetBookings")
|
2023-03-29 22:45:18 +00:00
|
|
|
jsonError(w, err, http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
jsonResponse(w, booking, http.StatusOK)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2023-03-27 18:54:56 +00:00
|
|
|
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)
|
2023-03-29 22:45:18 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Error().Err(err).Msg("json error encoding issue")
|
|
|
|
}
|
2023-03-27 18:54:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|