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: ×tamp.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: ×tamp.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: ×tamp.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: ×tamp.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: ×tamp.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 }