package server import ( "context" "fmt" "net" "git.coopgo.io/coopgo-platform/saved-search/core/service" "git.coopgo.io/coopgo-platform/saved-search/servers/grpc/proto/gen" "git.coopgo.io/coopgo-platform/saved-search/servers/grpc/transformers" "github.com/rs/zerolog/log" "github.com/spf13/viper" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/reflection" "google.golang.org/grpc/status" ) // SavedSearchServerImpl implements the SavedSearchService gRPC service type SavedSearchServerImpl struct { gen.UnimplementedSavedSearchServiceServer service *service.SavedSearchService } // NewSavedSearchServerImpl creates a new SavedSearchServerImpl func NewSavedSearchServerImpl(service *service.SavedSearchService) *SavedSearchServerImpl { return &SavedSearchServerImpl{ service: service, } } // CreateSavedSearch creates a new saved search func (s *SavedSearchServerImpl) CreateSavedSearch(ctx context.Context, req *gen.CreateSavedSearchRequest) (*gen.CreateSavedSearchResponse, error) { departure, err := transformers.GeoJsonFeatureFromProto(req.Departure) if err != nil { log.Error().Err(err).Msg("failed to convert departure") return nil, status.Errorf(codes.InvalidArgument, "invalid departure: %v", err) } destination, err := transformers.GeoJsonFeatureFromProto(req.Destination) if err != nil { log.Error().Err(err).Msg("failed to convert destination") return nil, status.Errorf(codes.InvalidArgument, "invalid destination: %v", err) } var data map[string]interface{} if req.Data != nil { data = req.Data.AsMap() } params := service.CreateSavedSearchParams{ OwnerID: req.OwnerId, Departure: departure, Destination: destination, DateTime: req.Datetime.AsTime(), Data: data, } search, err := s.service.CreateSavedSearch(ctx, params) if err != nil { log.Error().Err(err).Msg("failed to create saved search") return nil, status.Errorf(codes.Internal, "failed to create saved search: %v", err) } protoSearch, err := transformers.SavedSearchTypeToProto(search) if err != nil { log.Error().Err(err).Msg("failed to convert saved search to proto") return nil, status.Errorf(codes.Internal, "failed to convert saved search: %v", err) } return &gen.CreateSavedSearchResponse{ SavedSearch: protoSearch, }, nil } // GetSavedSearch retrieves a saved search by ID func (s *SavedSearchServerImpl) GetSavedSearch(ctx context.Context, req *gen.GetSavedSearchRequest) (*gen.GetSavedSearchResponse, error) { search, err := s.service.GetSavedSearch(ctx, req.Id) if err != nil { log.Error().Err(err).Str("id", req.Id).Msg("failed to get saved search") return nil, status.Errorf(codes.NotFound, "saved search not found: %v", err) } protoSearch, err := transformers.SavedSearchTypeToProto(search) if err != nil { log.Error().Err(err).Msg("failed to convert saved search to proto") return nil, status.Errorf(codes.Internal, "failed to convert saved search: %v", err) } return &gen.GetSavedSearchResponse{ SavedSearch: protoSearch, }, nil } // GetSavedSearchesByOwner retrieves all saved searches for a specific owner func (s *SavedSearchServerImpl) GetSavedSearchesByOwner(ctx context.Context, req *gen.GetSavedSearchesByOwnerRequest) (*gen.GetSavedSearchesByOwnerResponse, error) { searches, err := s.service.GetSavedSearchesByOwner(ctx, req.OwnerId) if err != nil { log.Error().Err(err).Str("owner_id", req.OwnerId).Msg("failed to get saved searches by owner") return nil, status.Errorf(codes.Internal, "failed to get saved searches: %v", err) } var protoSearches []*gen.SavedSearch for _, search := range searches { protoSearch, err := transformers.SavedSearchTypeToProto(search) if err != nil { log.Error().Err(err).Str("id", search.ID).Msg("failed to convert saved search to proto") continue } protoSearches = append(protoSearches, protoSearch) } return &gen.GetSavedSearchesByOwnerResponse{ SavedSearches: protoSearches, }, nil } // UpdateSavedSearch updates an existing saved search func (s *SavedSearchServerImpl) UpdateSavedSearch(ctx context.Context, req *gen.UpdateSavedSearchRequest) (*gen.UpdateSavedSearchResponse, error) { departure, err := transformers.GeoJsonFeatureFromProto(req.Departure) if err != nil { log.Error().Err(err).Msg("failed to convert departure") return nil, status.Errorf(codes.InvalidArgument, "invalid departure: %v", err) } destination, err := transformers.GeoJsonFeatureFromProto(req.Destination) if err != nil { log.Error().Err(err).Msg("failed to convert destination") return nil, status.Errorf(codes.InvalidArgument, "invalid destination: %v", err) } var data map[string]interface{} if req.Data != nil { data = req.Data.AsMap() } params := service.UpdateSavedSearchParams{ ID: req.Id, OwnerID: req.OwnerId, Departure: departure, Destination: destination, DateTime: req.Datetime.AsTime(), Data: data, } search, err := s.service.UpdateSavedSearch(ctx, params) if err != nil { log.Error().Err(err).Str("id", req.Id).Msg("failed to update saved search") return nil, status.Errorf(codes.Internal, "failed to update saved search: %v", err) } protoSearch, err := transformers.SavedSearchTypeToProto(search) if err != nil { log.Error().Err(err).Msg("failed to convert saved search to proto") return nil, status.Errorf(codes.Internal, "failed to convert saved search: %v", err) } return &gen.UpdateSavedSearchResponse{ SavedSearch: protoSearch, }, nil } // DeleteSavedSearch deletes a saved search func (s *SavedSearchServerImpl) DeleteSavedSearch(ctx context.Context, req *gen.DeleteSavedSearchRequest) (*gen.DeleteSavedSearchResponse, error) { err := s.service.DeleteSavedSearch(ctx, req.Id, req.OwnerId) if err != nil { log.Error().Err(err).Str("id", req.Id).Msg("failed to delete saved search") return nil, status.Errorf(codes.Internal, "failed to delete saved search: %v", err) } return &gen.DeleteSavedSearchResponse{ Success: true, }, nil } // ListSavedSearches retrieves paginated saved searches func (s *SavedSearchServerImpl) ListSavedSearches(ctx context.Context, req *gen.ListSavedSearchesRequest) (*gen.ListSavedSearchesResponse, error) { params := service.ListSavedSearchesParams{ OwnerID: req.OwnerId, Limit: int(req.Limit), Offset: int(req.Offset), } result, err := s.service.ListSavedSearches(ctx, params) if err != nil { log.Error().Err(err).Str("owner_id", req.OwnerId).Msg("failed to list saved searches") return nil, status.Errorf(codes.Internal, "failed to list saved searches: %v", err) } var protoSearches []*gen.SavedSearch for _, search := range result.SavedSearches { protoSearch, err := transformers.SavedSearchTypeToProto(search) if err != nil { log.Error().Err(err).Str("id", search.ID).Msg("failed to convert saved search to proto") continue } protoSearches = append(protoSearches, protoSearch) } return &gen.ListSavedSearchesResponse{ SavedSearches: protoSearches, Total: result.Total, Limit: int32(result.Limit), Offset: int32(result.Offset), }, nil } // Run starts the gRPC server following solidarity transport pattern func Run(done chan error, cfg *viper.Viper, savedSearchService *service.SavedSearchService) { var ( dev_env = cfg.GetBool("dev_env") address = ":" + cfg.GetString("services.grpc.port") ) server := grpc.NewServer() gen.RegisterSavedSearchServiceServer(server, NewSavedSearchServerImpl(savedSearchService)) l, err := net.Listen("tcp", address) if err != nil { log.Fatal().Err(err).Msg("could not register saved search grpc server") return } if dev_env { reflection.Register(server) } if err := server.Serve(l); err != nil { fmt.Println("gRPC service ended") done <- err } }