saved-search/core/service/service.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
}