initial commit
This commit is contained in:
246
core/service/service.go
Normal file
246
core/service/service.go
Normal file
@@ -0,0 +1,246 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user