monorepo/cloud/maplepress-backend/internal/usecase/page/search.go

134 lines
4 KiB
Go

package page
import (
"context"
"fmt"
"github.com/gocql/gocql"
"go.uber.org/zap"
domainsite "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/domain/site"
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/search"
)
// SearchPagesUseCase handles page search functionality
type SearchPagesUseCase struct {
siteRepo domainsite.Repository
searchClient *search.Client
logger *zap.Logger
}
// ProvideSearchPagesUseCase creates a new SearchPagesUseCase
func ProvideSearchPagesUseCase(
siteRepo domainsite.Repository,
searchClient *search.Client,
logger *zap.Logger,
) *SearchPagesUseCase {
return &SearchPagesUseCase{
siteRepo: siteRepo,
searchClient: searchClient,
logger: logger,
}
}
// SearchPagesInput is the input for searching pages
type SearchPagesInput struct {
Query string `json:"query"`
Limit int64 `json:"limit"`
Offset int64 `json:"offset"`
Filter string `json:"filter,omitempty"` // e.g., "status = publish AND post_type = post"
}
// SearchPagesOutput is the output after searching pages
type SearchPagesOutput struct {
Hits interface{} `json:"hits"` // meilisearch.Hits
Query string `json:"query"`
ProcessingTimeMs int64 `json:"processing_time_ms"`
TotalHits int64 `json:"total_hits"`
Limit int64 `json:"limit"`
Offset int64 `json:"offset"`
}
// Execute performs a search on the site's indexed pages
func (uc *SearchPagesUseCase) Execute(ctx context.Context, tenantID, siteID gocql.UUID, input *SearchPagesInput) (*SearchPagesOutput, error) {
uc.logger.Info("executing search pages use case",
zap.String("tenant_id", tenantID.String()),
zap.String("site_id", siteID.String()),
zap.String("query", input.Query))
// Get site to validate and check quotas
site, err := uc.siteRepo.GetByID(ctx, tenantID, siteID)
if err != nil {
uc.logger.Error("failed to get site", zap.Error(err))
return nil, domainsite.ErrSiteNotFound
}
// Verify site is verified (skip for test mode)
if site.RequiresVerification() && !site.IsVerified {
uc.logger.Warn("site not verified", zap.String("site_id", siteID.String()))
return nil, domainsite.ErrSiteNotVerified
}
// No quota checking - usage-based billing (anti-abuse via rate limiting only)
// Set default limits if not provided
limit := input.Limit
if limit <= 0 || limit > 100 {
limit = 20 // Default to 20 results
}
offset := input.Offset
if offset < 0 {
offset = 0
}
// Build search request
searchReq := search.SearchRequest{
Query: input.Query,
Limit: limit,
Offset: offset,
Filter: input.Filter,
}
// If no filter provided, default to only published pages
if searchReq.Filter == "" {
searchReq.Filter = "status = publish"
}
// Perform search
result, err := uc.searchClient.Search(siteID.String(), searchReq)
if err != nil {
uc.logger.Error("failed to search pages", zap.Error(err))
return nil, fmt.Errorf("failed to search pages: %w", err)
}
// Increment search request count (for usage tracking/billing)
site.IncrementSearchCount()
uc.logger.Info("incremented search count",
zap.String("site_id", siteID.String()),
zap.Int64("new_count", site.SearchRequestsCount))
if err := uc.siteRepo.UpdateUsage(ctx, site); err != nil {
uc.logger.Error("failed to update search usage", zap.Error(err))
// Don't fail the search, just log the error
} else {
uc.logger.Info("search usage updated successfully",
zap.String("site_id", siteID.String()),
zap.Int64("search_count", site.SearchRequestsCount))
}
uc.logger.Info("search completed successfully",
zap.String("site_id", siteID.String()),
zap.String("query", input.Query),
zap.Int64("total_hits", result.TotalHits),
zap.Int64("processing_time_ms", result.ProcessingTimeMs))
return &SearchPagesOutput{
Hits: result.Hits,
Query: result.Query,
ProcessingTimeMs: result.ProcessingTimeMs,
TotalHits: result.TotalHits,
Limit: result.Limit,
Offset: result.Offset,
}, nil
}