initial commit

This commit is contained in:
2023-03-27 20:54:56 +02:00
commit 77c8576254
40 changed files with 7460 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
# Golang interoperability library for the OCSS standard
This library is part of the COOPGO Technical Platform and provides client and server functionalities to implement OCSS compliant REST API.
OCSS (Open Carpooling Service Standard) is a carpooling interoperability standard defined by the french carpooling ecosystem, including COOPGO, Mobicoop, Klaxit, Blablacar, ...

View File

@@ -0,0 +1,34 @@
package ocss
import "time"
type BookingStatus int64
const (
BookingWaitingConfirmation BookingStatus = iota
BookingConfirmed
BookingCancelled
BookingCompletedPendingValidation
BookingValidated
)
type Booking struct {
ID string `json:"id"` // TODO check uuidv4
Driver User `json:"driver"`
Passenger User `json:"passenger"`
PassengerPickupDate time.Time `json:"passengerPickupDate"`
PassengerPickupLat float64 `json:"passengerPickupLat"`
PassengerPickupLng float64 `json:"passengerPickupLng"`
PassengerDropLat float64 `json:"passengerDropLat"`
PassengerDropLng float64 `json:"passengerDropLng"`
PassengerPickupAddress *string `json:"passengerPickupAddress,omitempty"`
PassengerDropAddress *string `json:"passengerDropAddress,omitempty"`
Status BookingStatus `json:"status"`
Distance *int64 `json:"distance,omitempty"`
Duration *time.Duration `json:"duration,omitempty"`
WebUrl *string `json:"webUrl,omitempty"`
Price Price `json:"price"`
Car *Car `json:"car"`
DriverJourneyID string `json:"driverJourneyId"`
PassengerJourneyID string `json:"passengerJourneyId"`
}

View File

@@ -0,0 +1,50 @@
package ocss
import "time"
type CarpoolBookingStatus int64
const (
CarpoolBookingWaitingConfirmation CarpoolBookingStatus = iota
CarpoolBookingConfirmed
CarpoolBookingCancelled
CarpoolBookingCompletedPendingValidation
CarpoolBookingValidated
)
type CarpoolBookingEventData struct {
CarpoolBooking
DriverCarpoolBooking
PassengerCarpoolBooking
}
type CarpoolBookingEvent struct {
ID string `json:"id"` // TODO validate UUID
IDToken string `json:"idToken"`
Data CarpoolBookingEventData `json:"data"`
}
type CarpoolBooking struct {
ID string `json:"id"`
PassengerPickupDate time.Time `json:"passengerPickupDate"`
PassengerPickupLat float64 `json:"passengerPickupLat"`
PassengerPickupLng float64 `json:"passengerPickupLng"`
PassengerDropLat float64 `json:"passengerDropLat"`
PassengerDropLng float64 `json:"passengerDropLng"`
PassengerPickupAddress *string `json:"passengerPickupAddress,omitempty"`
PassengerDropAddress *string `json:"passengerDropAddress,omitempty"`
Status CarpoolBookingStatus `json:"status"`
Distance *int64 `json:"distance,omitempty"`
Duration *time.Duration `json:"duration,omitempty"`
WebUrl string `json:"webUrl"`
}
type PassengerCarpoolBooking struct {
Passenger User `json:"passenger"`
}
type DriverCarpoolBooking struct {
Driver User `json:"driver"`
Price Price `json:"price"`
Car *Car `json:"car,omitempty"`
}

View File

@@ -0,0 +1,6 @@
package ocss
type Car struct {
Model *string `json:"model,omitempty"`
Brand *string `json:"brand,omitempty"`
}

View File

@@ -0,0 +1,9 @@
package ocss
import "errors"
type Client struct{}
func (c Client) GetDriverJourneys() ([]DriverJourney, error) {
return nil, errors.New("not implemented")
}

View File

@@ -0,0 +1,8 @@
module git.coopgo.io/coopgo-platform/carpool-service/interoperability/ocss
go 1.18
require (
github.com/gorilla/schema v1.2.0
golang.org/x/crypto v0.7.0
)

View File

@@ -0,0 +1,4 @@
github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc=
github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=

View File

@@ -0,0 +1,57 @@
package ocss
import (
"fmt"
"time"
)
type JourneyScheduleType int64
const (
Planned JourneyScheduleType = iota
Dynamic
Line
)
func (t JourneyScheduleType) MarshalJSON() ([]byte, error) {
types := map[JourneyScheduleType]string{
Planned: "\"PLANNED\"",
Dynamic: "\"DYNAMIC\"",
Line: "\"Line\"",
}
return []byte(types[t]), nil
}
type JSONTime time.Time
func (t JSONTime) MarshalJSON() ([]byte, error) {
//do your serializing here
stamp := fmt.Sprintf("%v", time.Time(t).Unix())
return []byte(stamp), nil
}
type JourneySchedule struct {
ID *string `json:"id,omitempty"`
PassengerPickupDate JSONTime `json:"passengerPickupDate"`
PassengerDepartureDate *JSONTime `json:"passengerDepartureDate,omitempty"`
DriverDepartureDate *JSONTime `json:"driverDepartureDate,omitempty"`
WebUrl *string `json:"webUrl,omitempty"`
Type JourneyScheduleType `json:"type"`
}
type DriverJourney struct {
DriverTrip
JourneySchedule
AvailableSteats *int `json:"requestedSeats,omitempty"`
Price *Price `json:"price,omitempty"`
}
type PassengerJourney struct {
PassengerTrip
JourneySchedule
//TODO how to handle requested driverDepartureDate
RequestedSteats *int64 `json:"requestedSeats,omitempty"`
}

View File

@@ -0,0 +1,9 @@
package ocss
type Preferences struct {
Smoking *bool `json:"smoking,omitempty"`
Animals *bool `json:"animals,omitempty"`
Music *bool `json:"music,omitempty"`
IsTalker *bool `json:"isTalker,omitempty"`
LuggageSize *int64 `json:"luggageSize,omitempty"`
}

View File

@@ -0,0 +1,15 @@
package ocss
type PriceType int64
const (
Free PriceType = iota
Paying
Unknown
)
type Price struct {
Type *PriceType `json:"type,omitempty"`
Amount *float64 `json:"amount,omitempty"`
Currency *string `json:"currency,omitempty"`
}

View File

@@ -0,0 +1,19 @@
package ocss
type Day int64
const (
Monday Day = iota
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday
)
type Schedule struct {
PassengerPickupDay *Day `json:"passengerPickupDay,omitempty"`
JourneySchedules *[]JourneySchedule `json:"journeySchedules,omitempty"`
}

View File

@@ -0,0 +1,362 @@
package ocss
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
"time"
"github.com/gorilla/schema"
"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)
PatchBooking(ctx context.Context, bookingId string, status BookingStatus, message *string) error
GetBooking(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 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 {
fmt.Println(err)
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
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 {
fmt.Println(err)
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 {
fmt.Println(err)
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 {
fmt.Println(err)
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 {
fmt.Println(err)
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 {
fmt.Println(err)
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 {
fmt.Println(err)
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 {
fmt.Println(err)
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 {
fmt.Println(err)
jsonError(w, err, http.StatusInternalServerError)
return
}
jsonResponse(w, passengerTrips, 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)
fmt.Println(err)
}
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)
}

View File

@@ -0,0 +1,43 @@
package ocss
import "time"
type Trip struct {
Operator string `json:"operator"`
PassengerPickupLat float64 `json:"passengerPickupLat"`
PassengerPickupLng float64 `json:"passengerPickupLng"`
PassengerDropLat float64 `json:"passengerDropLat"`
PassengerDropLng float64 `json:"passengerDropLng"`
Duration time.Duration `json:"duration"`
PassengerPickupAddress *string `json:"passengerPickupAddress,omitempty"`
PassengerDropAddress *string `json:"passengerDropAddress,omitempty"`
Distance *int64 `json:"distance,omitempty"`
DriverDepartureLat *float64 `json:"driverDepartureLat,omitempty"`
DriverDepartureLng *float64 `json:"driverDepartureLng,omitempty"`
DriverArrivalLat *float64 `json:"driverArrivalLat,omitempty"`
DriverArrivalLng *float64 `json:"driverArrivalLng,omitempty"`
DriverDepartureAddress *string `json:"driverDepartureAddress,omitempty"`
DriverArrivalAddress *string `json:"driverArrivalAddress,omitempty"`
JourneyPolyline *string `json:"journeyPolyline,omitempty"`
//WebUrl *string `json:"webUrl,omitempty"`
Preferences *Preferences `json:"preferences,omitempty"`
}
type DriverTrip struct {
Driver User `json:"driver"`
DepartureToPickupWalkingDistance *int64 `json:"departureToPickupWalkingDistance,omitempty"`
DepartureToPickupWalkingDuration *time.Duration `json:"departureToPickupWalkingDuration,omitempty"`
DepartureToPickupWalkingPolyline *string `json:"departureToPickupWalkingPolyline,omitempty"`
DropoffToArrivalWalkingDistance *int64 `json:"dropoffToArrivalWalkingDistance,omitempty"`
DropoffToArrivalWalkingDuration *time.Duration `json:"dropoffToArrivalWalkingDuration,omitempty"`
DropoffToArrivalWalkingPolyline *string `json:"dropoffToArrivalWalkingPolyline,omitempty"`
Car *Car `json:"car,omitempty"`
Trip
}
type PassengerTrip struct {
Passenger User `json:"passenger"`
Trip
}

View File

@@ -0,0 +1,21 @@
package ocss
type Gender int64
const (
Female Gender = iota
Male
Other
)
type User struct {
ID string `json:"id"`
Operator string `json:"operator"`
Alias string `json:"alias"`
FirstName *string `json:"firstName,omitempty"`
LastName *string `json:"lastName,omitempty"`
Grade *int64 `json:"grade,omitempty"`
Picture *string `json:"picture,omitempty"`
Gender *Gender `json:"gender,omitempty"`
VerifiedIdentity *bool `json:"verifiedIdentity,omitempty"`
}

View File

@@ -0,0 +1,5 @@
# Golang interoperability library for the RDEX standard
See [RDEX](http://rdex.org) for RDEX specification.
This standard is now deprecated on the COOPGO Technical Platform.