solidarity-service/servers/grpc/server/services.go

624 lines
20 KiB
Go

package grpcserver
import (
"context"
"errors"
"github.com/golang/protobuf/ptypes/timestamp"
"github.com/paulmach/orb"
"github.com/paulmach/orb/geojson"
"github.com/rs/zerolog/log"
"solidarity-service/internal"
"solidarity-service/servers/grpc/proto"
"solidarity-service/utils"
"strings"
"sync"
)
func (s *SolidarityServiceServerImpl) SetDriverRegularAvailabilities(ctx context.Context, req *proto.DriverRegularAvailabilities) (resp *proto.DriverAvailabilitiesResponse, err error) {
if req.DriverRequest.Driver.Id == "" || req.DriverRequest.Driver.Alias == "" || req.DriverAvailabilities == nil || req.DriverRequest.DriverAddress == nil || req.DriverRequest.DriverRadius == 0 {
return &proto.DriverAvailabilitiesResponse{
Success: false,
}, errors.New("missing required fields")
}
driver := internal.Driver{
Driver_departure_address: &geojson.Feature{
Type: "Feature",
Geometry: orb.Geometry(orb.Point{req.DriverRequest.DriverAddress.Lat, req.DriverRequest.DriverAddress.Long}),
Properties: geojson.Properties{
"name": req.DriverRequest.DriverAddress.Address,
},
},
Radius: req.DriverRequest.DriverRadius,
Driver: internal.User{
ID: req.DriverRequest.Driver.Id,
Alias: req.DriverRequest.Driver.Alias,
},
AvailabilitiesType: internal.Regular,
}
if req.DriverRequest.Driver.FirstName != nil {
driver.Driver.FirstName = *req.DriverRequest.Driver.FirstName
}
if req.DriverRequest.Driver.LastName != nil {
driver.Driver.LastName = *req.DriverRequest.Driver.LastName
}
if req.DriverRequest.Driver.Grade != nil {
driver.Driver.Grade = *req.DriverRequest.Driver.Grade
}
if req.DriverRequest.Driver.Picture != nil {
driver.Driver.Picture = *req.DriverRequest.Driver.Picture
}
if req.DriverRequest.Driver.Gender != nil {
driver.Driver.Gender = *req.DriverRequest.Driver.Gender
}
if req.DriverRequest.Driver.VerifiedIdentity != nil {
driver.Driver.VerifiedIdentity = *req.DriverRequest.Driver.VerifiedIdentity
}
if req.DriverRequest.Car != nil {
driver.Car = internal.Car{
Model: *req.DriverRequest.Car.Model,
Brand: *req.DriverRequest.Car.Brand,
}
}
if req.DriverRequest.Preferences != nil {
driver.Preferences = internal.Preferences{
Smoking: *req.DriverRequest.Preferences.Smoking,
Animals: *req.DriverRequest.Preferences.Animals,
Music: *req.DriverRequest.Preferences.Music,
Is_talker: *req.DriverRequest.Preferences.IsTalker,
Luggage_size: *req.DriverRequest.Preferences.LuggageSize,
}
}
for _, v := range req.DriverAvailabilities {
driver.RegularAvailabilities = append(driver.RegularAvailabilities, internal.RegularAvailabilities{
DayOfWeek: v.DayOfWeek.String(),
StartTime: v.StartTime,
EndTime: v.EndTime,
})
}
err = s.Handler.SetDriverAvailabilities(context.Background(), driver)
if err != nil {
return &proto.DriverAvailabilitiesResponse{
Success: false,
}, err
}
return &proto.DriverAvailabilitiesResponse{
Success: true,
}, nil
}
func (s *SolidarityServiceServerImpl) SetDriverPunctualAvailabilities(ctx context.Context, req *proto.DriverPunctualAvailabilities) (resp *proto.DriverAvailabilitiesResponse, err error) {
if req.DriverRequest.Driver.Id == "" || req.DriverRequest.Driver.Alias == "" || req.DriverAvailabilities == nil || req.DriverRequest.DriverAddress == nil || req.DriverRequest.DriverRadius == 0 {
return &proto.DriverAvailabilitiesResponse{
Success: false,
}, errors.New("missing required fields")
}
driver := internal.Driver{
Driver_departure_address: &geojson.Feature{
Type: "Feature",
Geometry: orb.Geometry(orb.Point{req.DriverRequest.DriverAddress.Lat, req.DriverRequest.DriverAddress.Long}),
Properties: geojson.Properties{
"name": req.DriverRequest.DriverAddress.Address,
},
},
Radius: req.DriverRequest.DriverRadius,
Driver: internal.User{
ID: req.DriverRequest.Driver.Id,
Alias: req.DriverRequest.Driver.Alias,
},
AvailabilitiesType: internal.Punctual,
}
if req.DriverRequest.Driver.FirstName != nil {
driver.Driver.FirstName = *req.DriverRequest.Driver.FirstName
}
if req.DriverRequest.Driver.LastName != nil {
driver.Driver.LastName = *req.DriverRequest.Driver.LastName
}
if req.DriverRequest.Driver.Grade != nil {
driver.Driver.Grade = *req.DriverRequest.Driver.Grade
}
if req.DriverRequest.Driver.Picture != nil {
driver.Driver.Picture = *req.DriverRequest.Driver.Picture
}
if req.DriverRequest.Driver.Gender != nil {
driver.Driver.Gender = *req.DriverRequest.Driver.Gender
}
if req.DriverRequest.Driver.VerifiedIdentity != nil {
driver.Driver.VerifiedIdentity = *req.DriverRequest.Driver.VerifiedIdentity
}
if req.DriverRequest.Car != nil {
driver.Car = internal.Car{
Model: *req.DriverRequest.Car.Model,
Brand: *req.DriverRequest.Car.Brand,
}
}
if req.DriverRequest.Preferences != nil {
driver.Preferences = internal.Preferences{
Smoking: *req.DriverRequest.Preferences.Smoking,
Animals: *req.DriverRequest.Preferences.Animals,
Music: *req.DriverRequest.Preferences.Music,
Is_talker: *req.DriverRequest.Preferences.IsTalker,
Luggage_size: *req.DriverRequest.Preferences.LuggageSize,
}
}
for _, v := range req.DriverAvailabilities {
driver.PunctualAvailabilities = append(driver.PunctualAvailabilities, internal.PunctualAvailabilities{
Date: v.Date.Seconds,
StartTime: v.StartTime,
EndTime: v.EndTime,
})
}
err = s.Handler.SetDriverAvailabilities(context.Background(), driver)
if err != nil {
return &proto.DriverAvailabilitiesResponse{
Success: false,
}, err
}
return &proto.DriverAvailabilitiesResponse{
Success: true,
}, nil
}
func (s *SolidarityServiceServerImpl) CreateBooking(ctx context.Context, req *proto.CreateBookingRequest) (resp *proto.CreateBookingResponse, err error) {
if req.Booking.DriverId == "" || req.Booking.PassengerId == "" || req.Booking.Id == "" || req.Booking.Status.String() == "" || req.Booking.DepartureAddress == nil || req.Booking.DestinationAddress == nil || req.Booking.PickupDate.Seconds == 0 {
return nil, errors.New("missing required fields")
}
bookingRequest := internal.BookingRequest{
ID: req.Booking.Id,
Passenger_id: req.Booking.PassengerId,
Driver_id: req.Booking.DriverId,
Status: internal.BookingStatus(req.Booking.Status.String()),
Destination_address: &geojson.Feature{
Type: "Feature",
Geometry: orb.Geometry(orb.Point{req.Booking.DestinationAddress.Lat, req.Booking.DestinationAddress.Long}),
Properties: geojson.Properties{
"name": req.Booking.DestinationAddress.Address,
},
},
Departure_address: &geojson.Feature{
Type: "Feature",
Geometry: orb.Geometry(orb.Point{req.Booking.DepartureAddress.Lat, req.Booking.DepartureAddress.Long}),
Properties: geojson.Properties{
"name": req.Booking.DepartureAddress.Address,
},
},
Pickup_date: req.Booking.PickupDate.Seconds,
}
passenger, driver, err := s.Handler.CreateBooking(context.Background(), bookingRequest)
if err != nil {
if strings.Contains(err.Error(), utils.SQL_DUPLICATE) {
err = errors.New("ID is already used")
}
return nil, err
}
duration, err := s.Handler.CalculateDurationBetweenFeatures(bookingRequest.Departure_address, bookingRequest.Destination_address)
if err != nil {
duration = 0
}
distance := s.Handler.CalculateDistanceBetweenFeatures(bookingRequest.Departure_address, bookingRequest.Destination_address)
priceType := proto.PriceType_FREE
resp = &proto.CreateBookingResponse{
Booking: &proto.Booking{},
}
resp.Booking = &proto.Booking{
Id: bookingRequest.ID,
Driver: &proto.User{
Id: driver.Driver.ID,
Alias: driver.Driver.Alias,
FirstName: &driver.Driver.FirstName,
LastName: &driver.Driver.LastName,
Grade: &driver.Driver.Grade,
Picture: &driver.Driver.Picture,
Gender: &driver.Driver.Gender,
VerifiedIdentity: &driver.Driver.VerifiedIdentity,
},
Passenger: &proto.User{
Id: passenger.Passenger.ID,
Alias: passenger.Passenger.Alias,
FirstName: &passenger.Passenger.FirstName,
LastName: &passenger.Passenger.LastName,
Grade: &passenger.Passenger.Grade,
Picture: &passenger.Passenger.Picture,
Gender: &passenger.Passenger.Gender,
VerifiedIdentity: &passenger.Passenger.VerifiedIdentity,
},
PassengerPickupDate: &timestamp.Timestamp{
Seconds: bookingRequest.Pickup_date,
},
PassengerDepartureRoute: &proto.Feature{
Lat: bookingRequest.Departure_address.Point().Lat(),
Long: bookingRequest.Departure_address.Point().Lon(),
Address: bookingRequest.Departure_address.Properties.MustString("name"),
},
PassengerDestinationRoute: &proto.Feature{
Lat: bookingRequest.Destination_address.Point().Lat(),
Long: bookingRequest.Destination_address.Point().Lon(),
Address: bookingRequest.Destination_address.Properties.MustString("name"),
},
Status: ConvertInternalToProtoBookingStatus(bookingRequest.Status),
Duration: &duration,
Distance: &distance,
Price: &proto.Price{
Type: &priceType,
},
Car: &proto.Car{
Model: &driver.Car.Model,
Brand: &driver.Car.Brand,
},
}
return resp, nil
}
func (s *SolidarityServiceServerImpl) UpdateBooking(ctx context.Context, req *proto.UpdateBookingRequest) (resp *proto.UpdateBookingResponse, err error) {
if req.BookingId == "" || req.Status.String() == "" {
return &proto.UpdateBookingResponse{
Success: false,
}, errors.New("missing required fields")
}
bookingStatus := internal.BookingStatus(req.Status.String())
err = s.Handler.UpdateBooking(context.Background(), req.BookingId, bookingStatus)
if err != nil {
if strings.Contains(err.Error(), utils.SQL_NO_ROWS) {
err = errors.New("invalid ID")
}
return &proto.UpdateBookingResponse{
Success: false,
}, err
}
return &proto.UpdateBookingResponse{
Success: true,
}, nil
}
func (s *SolidarityServiceServerImpl) GetBooking(ctx context.Context, req *proto.GetBookingRequest) (resp *proto.GetBookingResponse, err error) {
if req.BookingId == "" {
return nil, errors.New("empty booking ID")
}
booking, err := s.Handler.GetBooking(context.Background(), req.BookingId)
if err != nil {
if strings.Contains(err.Error(), utils.SQL_NO_ROWS) {
err = errors.New("invalid ID")
}
return nil, err
}
resp = &proto.GetBookingResponse{
Booking: &proto.Booking{},
}
priceType := proto.PriceType_FREE
driver, err := s.Handler.GetDriver(context.Background(), booking.Driver.ID)
if err != nil {
return nil, err
}
car := driver.Car
distance := int64(booking.Distance)
resp.Booking = &proto.Booking{
Id: booking.ID,
Driver: &proto.User{
Id: booking.Driver.ID,
Alias: booking.Driver.Alias,
FirstName: &booking.Driver.FirstName,
LastName: &booking.Driver.LastName,
Grade: &booking.Driver.Grade,
Picture: &booking.Driver.Picture,
Gender: &booking.Driver.Gender,
VerifiedIdentity: &booking.Driver.VerifiedIdentity,
},
Passenger: &proto.User{
Id: booking.Passenger.ID,
Alias: booking.Passenger.Alias,
FirstName: &booking.Passenger.FirstName,
LastName: &booking.Passenger.LastName,
Grade: &booking.Passenger.Grade,
Picture: &booking.Passenger.Picture,
Gender: &booking.Passenger.Gender,
VerifiedIdentity: &booking.Passenger.VerifiedIdentity,
},
PassengerPickupDate: &timestamp.Timestamp{
Seconds: booking.Pickup_date,
},
PassengerDepartureRoute: &proto.Feature{
Lat: booking.PassengerPickupAddress.Point().Lat(),
Long: booking.PassengerPickupAddress.Point().Lon(),
Address: booking.PassengerPickupAddress.Properties.MustString("name"),
},
PassengerDestinationRoute: &proto.Feature{
Lat: booking.PassengerDropAddress.Point().Lat(),
Long: booking.PassengerDropAddress.Point().Lon(),
Address: booking.PassengerDropAddress.Properties.MustString("name"),
},
Status: ConvertInternalToProtoBookingStatus(booking.Status),
Duration: &booking.Duration,
Distance: &distance,
Car: &proto.Car{
Model: &car.Model,
Brand: &car.Brand,
},
Price: &proto.Price{
Type: &priceType,
},
}
return resp, nil
}
func (s *SolidarityServiceServerImpl) GetBookingsByStatus(ctx context.Context, req *proto.GetBookingsByStatusRequest) (resp *proto.GetBookingsByStatusResponse, err error) {
if req.Type.String() == "" || req.Status.String() == "" || req.UserId == "" {
return nil, errors.New("missing required fields")
}
bookings, err := s.Handler.GetBookingsByStatus(ctx, req.Status.String(), req.Type.String(), req.UserId)
if err != nil {
if strings.Contains(err.Error(), utils.SQL_NO_ROWS) {
return nil, errors.New("invalid ID")
}
return nil, err
}
// Use a goroutine to concurrently convert bookings to proto
respChan := make(chan []*proto.Booking, 1)
go func() {
respChan <- convertInternalBookingsToProto(s, bookings, 50)
}()
select {
case convertedBookings := <-respChan:
close(respChan)
resp = &proto.GetBookingsByStatusResponse{
Booking: convertedBookings,
}
return resp, nil
case <-ctx.Done():
// If the context is canceled, return an error
return nil, ctx.Err()
}
}
func convertInternalBookingsToProto(s *SolidarityServiceServerImpl, bookings []internal.Booking, maxGoroutines int) []*proto.Booking {
var responses []*proto.Booking
var wg sync.WaitGroup
var mu sync.Mutex
semaphore := make(chan struct{}, maxGoroutines)
for _, v := range bookings {
wg.Add(1)
semaphore <- struct{}{}
go func(booking internal.Booking) {
defer func() {
<-semaphore
wg.Done()
}()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var driver internal.Driver
var driverErr error
driverCh := make(chan struct {
Driver internal.Driver
Err error
})
go func() {
d, err := s.Handler.GetDriver(ctx, booking.Driver.ID)
driverCh <- struct {
Driver internal.Driver
Err error
}{Driver: d, Err: err}
}()
driverRes := <-driverCh
driver = driverRes.Driver
driverErr = driverRes.Err
if driverErr != nil {
log.Error().Err(driverErr).Msg("Error getting driver")
return
}
car := driver.Car
priceType := proto.PriceType_FREE
distance := int64(booking.Distance)
protoBooking := &proto.Booking{
Id: booking.ID,
Status: ConvertInternalToProtoBookingStatus(booking.Status),
Driver: convertInternalUserToProtoUser(driver.Driver),
Passenger: convertInternalUserToProtoUser(booking.Passenger),
PassengerPickupDate: &timestamp.Timestamp{
Seconds: booking.Pickup_date,
},
PassengerDepartureRoute: convertInternalFeatureToProtoFeature(booking.PassengerPickupAddress),
PassengerDestinationRoute: convertInternalFeatureToProtoFeature(booking.PassengerDropAddress),
Duration: &booking.Duration,
Distance: &distance,
Car: &proto.Car{
Model: &car.Model,
Brand: &car.Brand,
},
Price: &proto.Price{
Type: &priceType,
},
}
// Use a mutex to safely append to the responses slice
mu.Lock()
responses = append(responses, protoBooking)
mu.Unlock()
}(v)
}
// Wait for all goroutines to finish before returning
wg.Wait()
close(semaphore)
return responses
}
func convertInternalUserToProtoUser(user internal.User) *proto.User {
return &proto.User{
Id: user.ID,
Alias: user.Alias,
FirstName: &user.FirstName,
LastName: &user.LastName,
Grade: &user.Grade,
Picture: &user.Picture,
Gender: &user.Gender,
VerifiedIdentity: &user.VerifiedIdentity,
}
}
func convertInternalFeatureToProtoFeature(feature *geojson.Feature) *proto.Feature {
return &proto.Feature{
Lat: feature.Point().Lat(),
Long: feature.Point().Lon(),
Address: feature.Properties.MustString("name"),
}
}
func (s *SolidarityServiceServerImpl) DriverJourneys(ctx context.Context, req *proto.DriverJourneysRequest) (resp *proto.DriverJourneysResponse, err error) {
if req.DepartureDate.Seconds == 0 || req.Departure == nil {
return nil, errors.New("missing required fields")
}
drivers, err := s.Handler.GetDriverJourneys(context.Background(), &geojson.Feature{
Type: "Feature",
Geometry: orb.Geometry(orb.Point{req.Departure.Lat, req.Departure.Long}),
Properties: geojson.Properties{
"name": req.Departure.Address,
}},
req.DepartureDate.Seconds)
if err != nil {
return nil, err
}
resp = &proto.DriverJourneysResponse{
DriverJourneys: []*proto.DriverJourney{},
}
response := []*proto.DriverJourney{}
for _, v := range drivers {
temp := &proto.DriverJourney{}
if v.AvailabilitiesType == internal.Regular && v.RegularAvailabilities != nil {
regularAvailabilities := make([]*proto.RegularAvailabilitySlot, 0) // Initialize an empty slice
for _, v := range v.RegularAvailabilities {
day := ConvertToProtoDayOfTheWeek(v.DayOfWeek)
regularAvailabilities = append(regularAvailabilities, &proto.RegularAvailabilitySlot{
DayOfWeek: day,
StartTime: v.StartTime,
EndTime: v.EndTime,
})
}
convertedAvailability := &proto.RepeatedRegularAvailabilitySlot{
RegularAvailabilities: regularAvailabilities,
}
temp.Availabilities = &proto.DriverJourney_RepeatedRegularAvailabilities{
RepeatedRegularAvailabilities: convertedAvailability,
}
} else if v.AvailabilitiesType == internal.Punctual && v.PunctualAvailabilities != nil {
punctualAvailabilities := make([]*proto.PunctualAvailabilitySlot, 0) // Initialize an empty slice
for _, v := range v.PunctualAvailabilities {
punctualAvailabilities = append(punctualAvailabilities, &proto.PunctualAvailabilitySlot{
Date: &timestamp.Timestamp{
Seconds: v.Date,
},
StartTime: v.StartTime,
EndTime: v.EndTime,
})
}
convertedAvailability := &proto.RepeatedPunctualAvailabilitySlot{
PunctualAvailabilities: punctualAvailabilities,
}
temp.Availabilities = &proto.DriverJourney_RepeatedPunctualAvailabilities{
RepeatedPunctualAvailabilities: convertedAvailability,
}
}
priceType := proto.PriceType_FREE
tamp := &proto.DriverJourney{
User: &proto.User{
Id: v.Driver.ID,
Alias: v.Driver.Alias,
FirstName: &v.Driver.FirstName,
LastName: &v.Driver.LastName,
Grade: &v.Driver.Grade,
Picture: &v.Driver.Picture,
Gender: &v.Driver.Gender,
VerifiedIdentity: &v.Driver.VerifiedIdentity,
},
Car: &proto.Car{
Model: &v.Car.Model,
Brand: &v.Car.Brand,
},
DriverDeparture_Date: &timestamp.Timestamp{
Seconds: req.DepartureDate.Seconds,
},
Price: &proto.Price{
Type: &priceType,
},
DriverDeparture_Address: v.Driver_departure_address.Properties.MustString("name"),
}
temp.DriverDeparture_Address = tamp.DriverDeparture_Address
temp.Car = tamp.Car
temp.User = tamp.User
temp.DriverDeparture_Date = tamp.DriverDeparture_Date
temp.Price = tamp.Price
response = append(response, temp)
}
resp.DriverJourneys = response
return resp, nil
}
func (s *SolidarityServiceServerImpl) SetPassengerTrip(ctx context.Context, req *proto.PassengerTripRequest) (resp *proto.PassengerTripResponse, err error) {
if req.Passenger.Id == "" || req.Passenger.Alias == "" {
return &proto.PassengerTripResponse{
Success: false,
}, errors.New("missing required fields")
}
passenger := internal.Passenger{}
passenger.Passenger.ID = req.Passenger.Id
passenger.Passenger.Alias = req.Passenger.Alias
if req.Passenger.FirstName != nil {
passenger.Passenger.FirstName = *req.Passenger.FirstName
}
if req.Passenger.LastName != nil {
passenger.Passenger.LastName = *req.Passenger.LastName
}
if req.Passenger.Grade != nil {
passenger.Passenger.Grade = *req.Passenger.Grade
}
if req.Passenger.Picture != nil {
passenger.Passenger.Picture = *req.Passenger.Picture
}
if req.Passenger.Gender != nil {
passenger.Passenger.Gender = *req.Passenger.Gender
}
if req.Passenger.VerifiedIdentity != nil {
passenger.Passenger.VerifiedIdentity = *req.Passenger.VerifiedIdentity
}
if req.Preferences != nil {
passenger.Preferences = internal.Preferences{
Smoking: *req.Preferences.Smoking,
Animals: *req.Preferences.Animals,
Music: *req.Preferences.Music,
Is_talker: *req.Preferences.IsTalker,
Luggage_size: *req.Preferences.LuggageSize,
}
}
err = s.Handler.SetPassengerTrip(context.Background(), passenger)
if err != nil {
return &proto.PassengerTripResponse{
Success: false,
}, err
}
return &proto.PassengerTripResponse{
Success: true,
}, nil
}