459 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Go
		
	
	
		
			Executable File
		
	
	
			
		
		
	
	
			459 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Go
		
	
	
		
			Executable File
		
	
	
package application
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"fmt"
 | 
						|
	"slices"
 | 
						|
	"sort"
 | 
						|
	"strings"
 | 
						|
	"sync"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/sorting"
 | 
						|
	carpoolproto "git.coopgo.io/coopgo-platform/carpool-service/servers/grpc/proto"
 | 
						|
	fleets "git.coopgo.io/coopgo-platform/fleets/grpcapi"
 | 
						|
	fleetsstorage "git.coopgo.io/coopgo-platform/fleets/storage"
 | 
						|
	mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
 | 
						|
	"git.coopgo.io/coopgo-platform/multimodal-routing/libs/transit/transitous"
 | 
						|
	savedsearchtypes "git.coopgo.io/coopgo-platform/saved-search/data/types"
 | 
						|
	savedsearchproto "git.coopgo.io/coopgo-platform/saved-search/servers/grpc/proto/gen"
 | 
						|
	savedsearchtransformers "git.coopgo.io/coopgo-platform/saved-search/servers/grpc/transformers"
 | 
						|
	"git.coopgo.io/coopgo-platform/solidarity-transport/servers/grpc/proto/gen"
 | 
						|
	"git.coopgo.io/coopgo-platform/solidarity-transport/servers/grpc/transformers"
 | 
						|
	"github.com/paulmach/orb/geojson"
 | 
						|
	"github.com/rs/zerolog/log"
 | 
						|
 | 
						|
	"google.golang.org/protobuf/types/known/structpb"
 | 
						|
	"google.golang.org/protobuf/types/known/timestamppb"
 | 
						|
)
 | 
						|
 | 
						|
type SearchJourneysResult struct {
 | 
						|
	CarpoolResults       []*geojson.FeatureCollection
 | 
						|
	TransitResults       []*transitous.Itinerary
 | 
						|
	VehicleResults       []fleetsstorage.Vehicle
 | 
						|
	Searched             bool
 | 
						|
	DriverJourneys       []*gen.SolidarityTransportDriverJourney
 | 
						|
	Drivers              map[string]mobilityaccountsstorage.Account
 | 
						|
	OrganizedCarpools    []*carpoolproto.CarpoolServiceDriverJourney
 | 
						|
	KnowledgeBaseResults []any
 | 
						|
}
 | 
						|
 | 
						|
// SearchJourneyOptions contains per-request options for journey search
 | 
						|
type SearchJourneyOptions struct {
 | 
						|
	DisableSolidarityTransport bool
 | 
						|
	DisableOrganizedCarpool    bool
 | 
						|
	DisableCarpoolOperators    bool
 | 
						|
	DisableTransit             bool
 | 
						|
	DisableFleetVehicles       bool
 | 
						|
	DisableKnowledgeBase       bool
 | 
						|
}
 | 
						|
 | 
						|
// SearchJourneys performs the business logic for journey search
 | 
						|
func (h *ApplicationHandler) SearchJourneys(
 | 
						|
	ctx context.Context,
 | 
						|
	departureDateTime time.Time,
 | 
						|
	departureGeo *geojson.Feature,
 | 
						|
	destinationGeo *geojson.Feature,
 | 
						|
	passengerID string,
 | 
						|
	solidarityTransportExcludeDriver string,
 | 
						|
	solidarityExcludeGroupId string,
 | 
						|
	options *SearchJourneyOptions,
 | 
						|
) (*SearchJourneysResult, error) {
 | 
						|
	var (
 | 
						|
		// Results
 | 
						|
		transitResults             []*transitous.Itinerary
 | 
						|
		carpoolResults             []*geojson.FeatureCollection
 | 
						|
		vehicleResults             []fleetsstorage.Vehicle
 | 
						|
		solidarityTransportResults []*gen.SolidarityTransportDriverJourney
 | 
						|
		organizedCarpoolResults    []*carpoolproto.CarpoolServiceDriverJourney
 | 
						|
		knowledgeBaseResults       []any
 | 
						|
 | 
						|
		drivers  = map[string]mobilityaccountsstorage.Account{}
 | 
						|
		searched = false
 | 
						|
	)
 | 
						|
 | 
						|
	// Only search if we have complete departure and destination info
 | 
						|
	if departureGeo != nil && destinationGeo != nil && !departureDateTime.IsZero() {
 | 
						|
		searched = true
 | 
						|
 | 
						|
		// Default options if not provided
 | 
						|
		if options == nil {
 | 
						|
			options = &SearchJourneyOptions{}
 | 
						|
		}
 | 
						|
 | 
						|
		// Check solution type configurations (global config AND per-request options)
 | 
						|
		solidarityTransportEnabled := h.config.GetBool("modules.journeys.solutions.solidarity_transport.enabled") && !options.DisableSolidarityTransport
 | 
						|
		organizedCarpoolEnabled := h.config.GetBool("modules.journeys.solutions.organized_carpool.enabled") && !options.DisableOrganizedCarpool
 | 
						|
		carpoolOperatorsEnabled := h.config.GetBool("modules.journeys.solutions.carpool_operators.enabled") && !options.DisableCarpoolOperators
 | 
						|
		transitEnabled := h.config.GetBool("modules.journeys.solutions.transit.enabled") && !options.DisableTransit
 | 
						|
		fleetVehiclesEnabled := h.config.GetBool("modules.journeys.solutions.fleet_vehicles.enabled") && !options.DisableFleetVehicles
 | 
						|
		knowledgeBaseEnabled := h.config.GetBool("modules.journeys.solutions.knowledge_base.enabled") && !options.DisableKnowledgeBase
 | 
						|
 | 
						|
		// SOLIDARITY TRANSPORT
 | 
						|
		var err error
 | 
						|
		drivers, err = h.services.GetAccountsInNamespacesMap([]string{"solidarity_drivers", "organized_carpool_drivers"})
 | 
						|
		if err != nil {
 | 
						|
			drivers = map[string]mobilityaccountsstorage.Account{}
 | 
						|
		}
 | 
						|
 | 
						|
		protodep, _ := transformers.GeoJsonToProto(departureGeo)
 | 
						|
		protodest, _ := transformers.GeoJsonToProto(destinationGeo)
 | 
						|
 | 
						|
		// Get driver IDs to exclude based on group_id (drivers who already have bookings in this group)
 | 
						|
		excludedDriverIds := make(map[string]bool)
 | 
						|
		if solidarityExcludeGroupId != "" {
 | 
						|
			bookingsResp, err := h.services.GRPC.SolidarityTransport.GetSolidarityTransportBookings(ctx, &gen.GetSolidarityTransportBookingsRequest{
 | 
						|
				StartDate: timestamppb.New(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)),
 | 
						|
				EndDate:   timestamppb.New(time.Date(9999, 12, 31, 0, 0, 0, 0, time.UTC)),
 | 
						|
			})
 | 
						|
			if err == nil {
 | 
						|
				for _, booking := range bookingsResp.Bookings {
 | 
						|
					if booking.GroupId == solidarityExcludeGroupId {
 | 
						|
						excludedDriverIds[booking.DriverId] = true
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if solidarityTransportEnabled {
 | 
						|
			log.Debug().Time("departure time", departureDateTime).Msg("calling driver journeys with ...")
 | 
						|
 | 
						|
			res, err := h.services.GRPC.SolidarityTransport.GetDriverJourneys(ctx, &gen.GetDriverJourneysRequest{
 | 
						|
				Departure:     protodep,
 | 
						|
				Arrival:       protodest,
 | 
						|
				DepartureDate: timestamppb.New(departureDateTime),
 | 
						|
			})
 | 
						|
			if err != nil {
 | 
						|
				log.Error().Err(err).Msg("error in grpc call to GetDriverJourneys")
 | 
						|
			} else {
 | 
						|
				solidarityTransportResults = slices.Collect(func(yield func(*gen.SolidarityTransportDriverJourney) bool) {
 | 
						|
					for _, dj := range res.DriverJourneys {
 | 
						|
						if a, ok := drivers[dj.DriverId].Data["archived"]; ok {
 | 
						|
							if archived, ok := a.(bool); ok {
 | 
						|
								if archived {
 | 
						|
									continue
 | 
						|
								}
 | 
						|
							}
 | 
						|
						}
 | 
						|
						if dj.DriverId == solidarityTransportExcludeDriver {
 | 
						|
							continue
 | 
						|
						}
 | 
						|
						// Skip drivers who already have bookings in the same group
 | 
						|
						if excludedDriverIds[dj.DriverId] {
 | 
						|
							continue
 | 
						|
						}
 | 
						|
						if !yield(dj) {
 | 
						|
							return
 | 
						|
						}
 | 
						|
					}
 | 
						|
				})
 | 
						|
				sort.Slice(solidarityTransportResults, func(i, j int) bool {
 | 
						|
					return solidarityTransportResults[i].DriverDistance < solidarityTransportResults[j].DriverDistance
 | 
						|
				})
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// Get departure and destination addresses from properties
 | 
						|
		var departureAddress, destinationAddress string
 | 
						|
		if departureGeo.Properties != nil {
 | 
						|
			if label, ok := departureGeo.Properties["label"].(string); ok {
 | 
						|
				departureAddress = label
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if destinationGeo.Properties != nil {
 | 
						|
			if label, ok := destinationGeo.Properties["label"].(string); ok {
 | 
						|
				destinationAddress = label
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// ORGANIZED CARPOOL
 | 
						|
		if organizedCarpoolEnabled {
 | 
						|
			radius := float64(5)
 | 
						|
			organizedCarpoolResultsRes, err := h.services.GRPC.CarpoolService.DriverJourneys(ctx, &carpoolproto.DriverJourneysRequest{
 | 
						|
				DepartureLat:     departureGeo.Point().Lat(),
 | 
						|
				DepartureLng:     departureGeo.Point().Lon(),
 | 
						|
				ArrivalLat:       destinationGeo.Point().Lat(),
 | 
						|
				ArrivalLng:       destinationGeo.Point().Lon(),
 | 
						|
				DepartureDate:    timestamppb.New(departureDateTime),
 | 
						|
				DepartureAddress: &departureAddress,
 | 
						|
				ArrivalAddress:   &destinationAddress,
 | 
						|
				DepartureRadius:  &radius,
 | 
						|
				ArrivalRadius:    &radius,
 | 
						|
			})
 | 
						|
			if err != nil {
 | 
						|
				log.Error().Err(err).Msg("error retrieving organized carpools")
 | 
						|
			} else {
 | 
						|
				organizedCarpoolResults = organizedCarpoolResultsRes.DriverJourneys
 | 
						|
				sort.Slice(organizedCarpoolResults, func(i, j int) bool {
 | 
						|
					return *organizedCarpoolResults[i].Distance < *organizedCarpoolResults[j].Distance
 | 
						|
				})
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		var wg sync.WaitGroup
 | 
						|
		// CARPOOL OPERATORS
 | 
						|
		if carpoolOperatorsEnabled {
 | 
						|
			carpools := make(chan *geojson.FeatureCollection)
 | 
						|
			go h.services.InteropCarpool.Search(carpools, *departureGeo, *destinationGeo, departureDateTime)
 | 
						|
			wg.Add(1)
 | 
						|
			go func() {
 | 
						|
				defer wg.Done()
 | 
						|
				for c := range carpools {
 | 
						|
					carpoolResults = append(carpoolResults, c)
 | 
						|
				}
 | 
						|
			}()
 | 
						|
		}
 | 
						|
 | 
						|
		// TRANSIT
 | 
						|
		if transitEnabled {
 | 
						|
			transitch := make(chan *transitous.Itinerary)
 | 
						|
			go func(transitch chan *transitous.Itinerary, departure *geojson.Feature, destination *geojson.Feature, datetime *time.Time) {
 | 
						|
				defer close(transitch)
 | 
						|
				response, err := h.services.TransitRouting.PlanWithResponse(ctx, &transitous.PlanParams{
 | 
						|
					FromPlace: fmt.Sprintf("%f,%f", departure.Point().Lat(), departure.Point().Lon()),
 | 
						|
					ToPlace:   fmt.Sprintf("%f,%f", destination.Point().Lat(), destination.Point().Lon()),
 | 
						|
					Time:      datetime,
 | 
						|
				})
 | 
						|
				if err != nil {
 | 
						|
					log.Error().Err(err).Msg("error retrieving transit data from Transitous server")
 | 
						|
					return
 | 
						|
				}
 | 
						|
				for _, i := range response.Itineraries {
 | 
						|
					transitch <- &i
 | 
						|
				}
 | 
						|
			}(transitch, departureGeo, destinationGeo, &departureDateTime)
 | 
						|
			wg.Add(1)
 | 
						|
			go func() {
 | 
						|
				defer wg.Done()
 | 
						|
				paris, _ := time.LoadLocation("Europe/Paris")
 | 
						|
				requestedDay := departureDateTime.In(paris).Truncate(24 * time.Hour)
 | 
						|
 | 
						|
				for itinerary := range transitch {
 | 
						|
					// Only include journeys that start on the requested day (in Paris timezone)
 | 
						|
					if !itinerary.StartTime.IsZero() && !itinerary.EndTime.IsZero() {
 | 
						|
						log.Info().
 | 
						|
							Time("startTime", itinerary.StartTime).
 | 
						|
							Time("endTime", itinerary.EndTime).
 | 
						|
							Str("startTimezone", itinerary.StartTime.Location().String()).
 | 
						|
							Str("endTimezone", itinerary.EndTime.Location().String()).
 | 
						|
							Str("startTimeRFC3339", itinerary.StartTime.Format(time.RFC3339)).
 | 
						|
							Str("endTimeRFC3339", itinerary.EndTime.Format(time.RFC3339)).
 | 
						|
							Msg("Journey search - received transit itinerary from Transitous")
 | 
						|
 | 
						|
						startInParis := itinerary.StartTime.In(paris)
 | 
						|
						startDay := startInParis.Truncate(24 * time.Hour)
 | 
						|
 | 
						|
						// Check if journey starts on the requested day
 | 
						|
						if startDay.Equal(requestedDay) {
 | 
						|
							transitResults = append(transitResults, itinerary)
 | 
						|
						} else {
 | 
						|
							log.Info().
 | 
						|
								Str("requestedDay", requestedDay.Format("2006-01-02")).
 | 
						|
								Str("startDay", startDay.Format("2006-01-02")).
 | 
						|
								Msg("Journey search - filtered out transit journey (not on requested day)")
 | 
						|
						}
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}()
 | 
						|
		}
 | 
						|
 | 
						|
		// VEHICLES
 | 
						|
		if fleetVehiclesEnabled {
 | 
						|
			vehiclech := make(chan fleetsstorage.Vehicle)
 | 
						|
			go h.vehicleRequest(vehiclech, departureDateTime.Add(-24*time.Hour), departureDateTime.Add(168*time.Hour))
 | 
						|
			wg.Add(1)
 | 
						|
			go func() {
 | 
						|
				defer wg.Done()
 | 
						|
				for vehicle := range vehiclech {
 | 
						|
					vehicleResults = append(vehicleResults, vehicle)
 | 
						|
				}
 | 
						|
				slices.SortFunc(vehicleResults, sorting.VehiclesByDistanceFrom(*departureGeo))
 | 
						|
			}()
 | 
						|
		}
 | 
						|
		wg.Wait()
 | 
						|
 | 
						|
		// KNOWLEDGE BASE
 | 
						|
		if knowledgeBaseEnabled {
 | 
						|
			departureGeoSearch, _ := h.services.Geography.GeoSearch(departureGeo)
 | 
						|
			kbData := h.config.Get("knowledge_base")
 | 
						|
			if kb, ok := kbData.([]any); ok {
 | 
						|
				for _, sol := range kb {
 | 
						|
					if solution, ok := sol.(map[string]any); ok {
 | 
						|
						if g, ok := solution["geography"]; ok {
 | 
						|
							if geography, ok := g.([]any); ok {
 | 
						|
								for _, gg := range geography {
 | 
						|
									if geog, ok := gg.(map[string]any); ok {
 | 
						|
										if layer, ok := geog["layer"].(string); ok {
 | 
						|
											code := geog["code"]
 | 
						|
											geo, err := h.services.Geography.Find(layer, fmt.Sprintf("%v", code))
 | 
						|
											if err == nil {
 | 
						|
												geog["geography"] = geo
 | 
						|
												geog["name"] = geo.Properties.MustString("nom")
 | 
						|
											}
 | 
						|
											if strings.Compare(fmt.Sprintf("%v", code), departureGeoSearch[layer].Properties.MustString("code")) == 0 {
 | 
						|
												knowledgeBaseResults = append(knowledgeBaseResults, solution)
 | 
						|
												break
 | 
						|
											}
 | 
						|
										}
 | 
						|
									}
 | 
						|
								}
 | 
						|
							}
 | 
						|
						}
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return &SearchJourneysResult{
 | 
						|
		CarpoolResults:       carpoolResults,
 | 
						|
		TransitResults:       transitResults,
 | 
						|
		VehicleResults:       vehicleResults,
 | 
						|
		Searched:             searched,
 | 
						|
		DriverJourneys:       solidarityTransportResults,
 | 
						|
		Drivers:              drivers,
 | 
						|
		OrganizedCarpools:    organizedCarpoolResults,
 | 
						|
		KnowledgeBaseResults: knowledgeBaseResults,
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
func (h *ApplicationHandler) vehicleRequest(vehiclech chan fleetsstorage.Vehicle, start time.Time, end time.Time) {
 | 
						|
	defer close(vehiclech)
 | 
						|
	vehiclerequest := &fleets.GetVehiclesRequest{
 | 
						|
		Namespaces: []string{"parcoursmob"},
 | 
						|
	}
 | 
						|
	vehicleresp, err := h.services.GRPC.Fleets.GetVehicles(context.TODO(), vehiclerequest)
 | 
						|
	if err != nil {
 | 
						|
		log.Error().Err(err).Msg("")
 | 
						|
		return
 | 
						|
	}
 | 
						|
	for _, vehicle := range vehicleresp.Vehicles {
 | 
						|
		v := vehicle.ToStorageType()
 | 
						|
		if v.Free(start, end) {
 | 
						|
			vehiclech <- v
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// SaveSearch saves a group's search to the saved-search microservice
 | 
						|
func (h *ApplicationHandler) SaveSearch(
 | 
						|
	ctx context.Context,
 | 
						|
	groupID string,
 | 
						|
	departureDateTime time.Time,
 | 
						|
	departureGeo *geojson.Feature,
 | 
						|
	destinationGeo *geojson.Feature,
 | 
						|
	additionalData map[string]interface{},
 | 
						|
) error {
 | 
						|
	// Convert geojson.Feature to proto format
 | 
						|
	var protoDepart, protoDest *savedsearchproto.SavedSearchGeoJsonFeature
 | 
						|
 | 
						|
	log.Debug().
 | 
						|
		Bool("departure_nil", departureGeo == nil).
 | 
						|
		Bool("destination_nil", destinationGeo == nil).
 | 
						|
		Msg("SaveSearch: checking geo features")
 | 
						|
 | 
						|
	if departureGeo != nil {
 | 
						|
		departureBytes, err := departureGeo.MarshalJSON()
 | 
						|
		if err != nil {
 | 
						|
			return fmt.Errorf("error marshaling departure: %w", err)
 | 
						|
		}
 | 
						|
		protoDepart = &savedsearchproto.SavedSearchGeoJsonFeature{
 | 
						|
			Serialized: string(departureBytes),
 | 
						|
		}
 | 
						|
		log.Debug().Str("departure_json", string(departureBytes)).Msg("SaveSearch: departure converted")
 | 
						|
	}
 | 
						|
 | 
						|
	if destinationGeo != nil {
 | 
						|
		destinationBytes, err := destinationGeo.MarshalJSON()
 | 
						|
		if err != nil {
 | 
						|
			return fmt.Errorf("error marshaling destination: %w", err)
 | 
						|
		}
 | 
						|
		protoDest = &savedsearchproto.SavedSearchGeoJsonFeature{
 | 
						|
			Serialized: string(destinationBytes),
 | 
						|
		}
 | 
						|
		log.Debug().Str("destination_json", string(destinationBytes)).Msg("SaveSearch: destination converted")
 | 
						|
	}
 | 
						|
 | 
						|
	// Convert additional data to protobuf Struct
 | 
						|
	var protoData *structpb.Struct
 | 
						|
	if additionalData != nil && len(additionalData) > 0 {
 | 
						|
		var err error
 | 
						|
		protoData, err = structpb.NewStruct(additionalData)
 | 
						|
		if err != nil {
 | 
						|
			return fmt.Errorf("error converting additional data: %w", err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Handle zero time value
 | 
						|
	var protoDateTime *timestamppb.Timestamp
 | 
						|
	if !departureDateTime.IsZero() {
 | 
						|
		protoDateTime = timestamppb.New(departureDateTime)
 | 
						|
	}
 | 
						|
 | 
						|
	// Call the saved-search service
 | 
						|
	_, err := h.services.GRPC.SavedSearch.CreateSavedSearch(ctx, &savedsearchproto.CreateSavedSearchRequest{
 | 
						|
		OwnerId:     groupID,
 | 
						|
		Departure:   protoDepart,
 | 
						|
		Destination: protoDest,
 | 
						|
		Datetime:    protoDateTime,
 | 
						|
		Data:        protoData,
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("error calling saved-search service: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	log.Info().Str("group_id", groupID).Msg("search saved successfully")
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// GetSavedSearchesByOwner retrieves saved searches for a group
 | 
						|
func (h *ApplicationHandler) GetSavedSearchesByOwner(
 | 
						|
	ctx context.Context,
 | 
						|
	groupID string,
 | 
						|
) ([]*savedsearchtypes.SavedSearch, error) {
 | 
						|
	// Call the saved-search service to get searches by owner
 | 
						|
	response, err := h.services.GRPC.SavedSearch.GetSavedSearchesByOwner(ctx, &savedsearchproto.GetSavedSearchesByOwnerRequest{
 | 
						|
		OwnerId: groupID,
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("error calling saved-search service: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	// Convert protobuf searches to domain types
 | 
						|
	var searches []*savedsearchtypes.SavedSearch
 | 
						|
	for _, protoSearch := range response.SavedSearches {
 | 
						|
		search, err := savedsearchtransformers.SavedSearchProtoToType(protoSearch)
 | 
						|
		if err != nil {
 | 
						|
			log.Error().Err(err).Str("search_id", protoSearch.Id).Msg("failed to convert saved search")
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		searches = append(searches, search)
 | 
						|
	}
 | 
						|
 | 
						|
	// Sort searches by datetime (earliest first)
 | 
						|
	sort.Slice(searches, func(i, j int) bool {
 | 
						|
		return searches[i].DateTime.Before(searches[j].DateTime)
 | 
						|
	})
 | 
						|
 | 
						|
	return searches, nil
 | 
						|
}
 | 
						|
 | 
						|
// DeleteSavedSearch deletes a saved search by ID for the specified owner
 | 
						|
func (h *ApplicationHandler) DeleteSavedSearch(
 | 
						|
	ctx context.Context,
 | 
						|
	searchID string,
 | 
						|
	ownerID string,
 | 
						|
) error {
 | 
						|
	// Call the saved-search service to delete the search
 | 
						|
	_, err := h.services.GRPC.SavedSearch.DeleteSavedSearch(ctx, &savedsearchproto.DeleteSavedSearchRequest{
 | 
						|
		Id:      searchID,
 | 
						|
		OwnerId: ownerID, // For authorization - ensure only the owner can delete
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("error calling saved-search service: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	log.Info().Str("search_id", searchID).Str("owner_id", ownerID).Msg("saved search deleted successfully")
 | 
						|
	return nil
 | 
						|
}
 |