246 lines
7.5 KiB
Go
246 lines
7.5 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"git.coopgo.io/coopgo-platform/saved-search/data/storage"
|
|
"git.coopgo.io/coopgo-platform/saved-search/data/types"
|
|
"github.com/google/uuid"
|
|
"github.com/paulmach/orb/geojson"
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
// SavedSearchService handles the business logic for saved searches
|
|
type SavedSearchService struct {
|
|
storage storage.Storage
|
|
}
|
|
|
|
// NewSavedSearchService creates a new SavedSearchService instance
|
|
func NewSavedSearchService(storage storage.Storage) *SavedSearchService {
|
|
return &SavedSearchService{
|
|
storage: storage,
|
|
}
|
|
}
|
|
|
|
// CreateSavedSearchParams represents the parameters for creating a saved search
|
|
type CreateSavedSearchParams struct {
|
|
OwnerID string `json:"owner_id"`
|
|
Departure *geojson.Feature `json:"departure"`
|
|
Destination *geojson.Feature `json:"destination"`
|
|
DateTime time.Time `json:"datetime"`
|
|
Data map[string]interface{} `json:"data"`
|
|
}
|
|
|
|
// UpdateSavedSearchParams represents the parameters for updating a saved search
|
|
type UpdateSavedSearchParams struct {
|
|
ID string `json:"id"`
|
|
OwnerID string `json:"owner_id"`
|
|
Departure *geojson.Feature `json:"departure"`
|
|
Destination *geojson.Feature `json:"destination"`
|
|
DateTime time.Time `json:"datetime"`
|
|
Data map[string]interface{} `json:"data"`
|
|
}
|
|
|
|
// ListSavedSearchesParams represents the parameters for listing saved searches
|
|
type ListSavedSearchesParams struct {
|
|
OwnerID string `json:"owner_id"`
|
|
Limit int `json:"limit"`
|
|
Offset int `json:"offset"`
|
|
}
|
|
|
|
// ListSavedSearchesResult represents the result of listing saved searches
|
|
type ListSavedSearchesResult struct {
|
|
SavedSearches []*types.SavedSearch `json:"saved_searches"`
|
|
Total int64 `json:"total"`
|
|
Limit int `json:"limit"`
|
|
Offset int `json:"offset"`
|
|
}
|
|
|
|
// CreateSavedSearch creates a new saved search
|
|
func (s *SavedSearchService) CreateSavedSearch(ctx context.Context, params CreateSavedSearchParams) (*types.SavedSearch, error) {
|
|
// Validate required fields
|
|
if params.OwnerID == "" {
|
|
return nil, fmt.Errorf("owner_id is required")
|
|
}
|
|
|
|
if params.Departure == nil {
|
|
return nil, fmt.Errorf("departure is required")
|
|
}
|
|
|
|
if params.Destination == nil {
|
|
return nil, fmt.Errorf("destination is required")
|
|
}
|
|
|
|
if params.DateTime.IsZero() {
|
|
return nil, fmt.Errorf("datetime is required")
|
|
}
|
|
|
|
// Ensure data is not nil
|
|
if params.Data == nil {
|
|
params.Data = make(map[string]interface{})
|
|
}
|
|
|
|
// Create saved search
|
|
search := types.SavedSearch{
|
|
ID: uuid.NewString(),
|
|
OwnerID: params.OwnerID,
|
|
Departure: params.Departure,
|
|
Destination: params.Destination,
|
|
DateTime: params.DateTime,
|
|
Data: params.Data,
|
|
}
|
|
|
|
if err := s.storage.CreateSavedSearch(ctx, search); err != nil {
|
|
log.Error().Err(err).Str("owner_id", params.OwnerID).Msg("failed to create saved search")
|
|
return nil, fmt.Errorf("failed to create saved search: %w", err)
|
|
}
|
|
|
|
log.Info().Str("id", search.ID).Str("owner_id", search.OwnerID).Msg("saved search created successfully")
|
|
return &search, nil
|
|
}
|
|
|
|
// GetSavedSearch retrieves a saved search by ID
|
|
func (s *SavedSearchService) GetSavedSearch(ctx context.Context, id string) (*types.SavedSearch, error) {
|
|
if id == "" {
|
|
return nil, fmt.Errorf("id is required")
|
|
}
|
|
|
|
search, err := s.storage.GetSavedSearch(ctx, id)
|
|
if err != nil {
|
|
log.Error().Err(err).Str("id", id).Msg("failed to get saved search")
|
|
return nil, fmt.Errorf("failed to get saved search: %w", err)
|
|
}
|
|
|
|
return search, nil
|
|
}
|
|
|
|
// GetSavedSearchesByOwner retrieves all saved searches for a specific owner
|
|
func (s *SavedSearchService) GetSavedSearchesByOwner(ctx context.Context, ownerID string) ([]*types.SavedSearch, error) {
|
|
if ownerID == "" {
|
|
return nil, fmt.Errorf("owner_id is required")
|
|
}
|
|
|
|
searches, err := s.storage.GetSavedSearchesByOwner(ctx, ownerID)
|
|
if err != nil {
|
|
log.Error().Err(err).Str("owner_id", ownerID).Msg("failed to get saved searches by owner")
|
|
return nil, fmt.Errorf("failed to get saved searches: %w", err)
|
|
}
|
|
|
|
return searches, nil
|
|
}
|
|
|
|
// UpdateSavedSearch updates an existing saved search
|
|
func (s *SavedSearchService) UpdateSavedSearch(ctx context.Context, params UpdateSavedSearchParams) (*types.SavedSearch, error) {
|
|
// Validate required fields
|
|
if params.ID == "" {
|
|
return nil, fmt.Errorf("id is required")
|
|
}
|
|
|
|
if params.OwnerID == "" {
|
|
return nil, fmt.Errorf("owner_id is required")
|
|
}
|
|
|
|
if params.Departure == nil {
|
|
return nil, fmt.Errorf("departure is required")
|
|
}
|
|
|
|
if params.Destination == nil {
|
|
return nil, fmt.Errorf("destination is required")
|
|
}
|
|
|
|
if params.DateTime.IsZero() {
|
|
return nil, fmt.Errorf("datetime is required")
|
|
}
|
|
|
|
// Check if the saved search exists and belongs to the owner
|
|
existing, err := s.storage.GetSavedSearch(ctx, params.ID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("saved search not found: %w", err)
|
|
}
|
|
|
|
if existing.OwnerID != params.OwnerID {
|
|
return nil, fmt.Errorf("access denied: saved search belongs to different owner")
|
|
}
|
|
|
|
// Ensure data is not nil
|
|
if params.Data == nil {
|
|
params.Data = make(map[string]interface{})
|
|
}
|
|
|
|
// Update saved search
|
|
search := types.SavedSearch{
|
|
ID: params.ID,
|
|
OwnerID: params.OwnerID,
|
|
Departure: params.Departure,
|
|
Destination: params.Destination,
|
|
DateTime: params.DateTime,
|
|
Data: params.Data,
|
|
CreatedAt: existing.CreatedAt, // Preserve creation time
|
|
}
|
|
|
|
if err := s.storage.UpdateSavedSearch(ctx, search); err != nil {
|
|
log.Error().Err(err).Str("id", params.ID).Msg("failed to update saved search")
|
|
return nil, fmt.Errorf("failed to update saved search: %w", err)
|
|
}
|
|
|
|
log.Info().Str("id", search.ID).Str("owner_id", search.OwnerID).Msg("saved search updated successfully")
|
|
return &search, nil
|
|
}
|
|
|
|
// DeleteSavedSearch deletes a saved search by ID, with owner verification
|
|
func (s *SavedSearchService) DeleteSavedSearch(ctx context.Context, id, ownerID string) error {
|
|
if id == "" {
|
|
return fmt.Errorf("id is required")
|
|
}
|
|
|
|
if ownerID == "" {
|
|
return fmt.Errorf("owner_id is required")
|
|
}
|
|
|
|
// Check if the saved search exists and belongs to the owner
|
|
existing, err := s.storage.GetSavedSearch(ctx, id)
|
|
if err != nil {
|
|
return fmt.Errorf("saved search not found: %w", err)
|
|
}
|
|
|
|
if existing.OwnerID != ownerID {
|
|
return fmt.Errorf("access denied: saved search belongs to different owner")
|
|
}
|
|
|
|
if err := s.storage.DeleteSavedSearch(ctx, id); err != nil {
|
|
log.Error().Err(err).Str("id", id).Msg("failed to delete saved search")
|
|
return fmt.Errorf("failed to delete saved search: %w", err)
|
|
}
|
|
|
|
log.Info().Str("id", id).Str("owner_id", ownerID).Msg("saved search deleted successfully")
|
|
return nil
|
|
}
|
|
|
|
// ListSavedSearches retrieves paginated saved searches with optional filtering
|
|
func (s *SavedSearchService) ListSavedSearches(ctx context.Context, params ListSavedSearchesParams) (*ListSavedSearchesResult, error) {
|
|
// Set default pagination values
|
|
if params.Limit <= 0 {
|
|
params.Limit = 10
|
|
}
|
|
if params.Limit > 100 {
|
|
params.Limit = 100 // Max limit
|
|
}
|
|
if params.Offset < 0 {
|
|
params.Offset = 0
|
|
}
|
|
|
|
searches, total, err := s.storage.ListSavedSearches(ctx, params.OwnerID, params.Limit, params.Offset)
|
|
if err != nil {
|
|
log.Error().Err(err).Str("owner_id", params.OwnerID).Msg("failed to list saved searches")
|
|
return nil, fmt.Errorf("failed to list saved searches: %w", err)
|
|
}
|
|
|
|
return &ListSavedSearchesResult{
|
|
SavedSearches: searches,
|
|
Total: total,
|
|
Limit: params.Limit,
|
|
Offset: params.Offset,
|
|
}, nil
|
|
} |