package page import ( "context" "fmt" "github.com/gocql/gocql" "go.uber.org/zap" domainpage "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/domain/page" pageusecase "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/usecase/page" ) // SyncPagesService handles page synchronization operations type SyncPagesService interface { SyncPages(ctx context.Context, tenantID, siteID gocql.UUID, input *pageusecase.SyncPagesInput) (*pageusecase.SyncPagesOutput, error) } type syncPagesService struct { // Focused usecases validateSiteUC *pageusecase.ValidateSiteUseCase ensureIndexUC *pageusecase.EnsureSearchIndexUseCase createPageUC *pageusecase.CreatePageEntityUseCase upsertPageUC *pageusecase.UpsertPageUseCase indexPageUC *pageusecase.IndexPageToSearchUseCase updateUsageUC *pageusecase.UpdateSiteUsageUseCase logger *zap.Logger } // NewSyncPagesService creates a new SyncPagesService func NewSyncPagesService( validateSiteUC *pageusecase.ValidateSiteUseCase, ensureIndexUC *pageusecase.EnsureSearchIndexUseCase, createPageUC *pageusecase.CreatePageEntityUseCase, upsertPageUC *pageusecase.UpsertPageUseCase, indexPageUC *pageusecase.IndexPageToSearchUseCase, updateUsageUC *pageusecase.UpdateSiteUsageUseCase, logger *zap.Logger, ) SyncPagesService { return &syncPagesService{ validateSiteUC: validateSiteUC, ensureIndexUC: ensureIndexUC, createPageUC: createPageUC, upsertPageUC: upsertPageUC, indexPageUC: indexPageUC, updateUsageUC: updateUsageUC, logger: logger.Named("sync-pages-service"), } } // SyncPages orchestrates the page synchronization workflow func (s *syncPagesService) SyncPages(ctx context.Context, tenantID, siteID gocql.UUID, input *pageusecase.SyncPagesInput) (*pageusecase.SyncPagesOutput, error) { s.logger.Info("syncing pages", zap.String("tenant_id", tenantID.String()), zap.String("site_id", siteID.String()), zap.Int("page_count", len(input.Pages))) // Step 1: Validate site (no quota check - usage-based billing) site, err := s.validateSiteUC.Execute(ctx, tenantID, siteID) if err != nil { s.logger.Error("failed to validate site", zap.Error(err)) return nil, err } // Step 2: Ensure search index exists if err := s.ensureIndexUC.Execute(ctx, siteID); err != nil { s.logger.Error("failed to ensure search index", zap.Error(err)) return nil, err } // Step 3: Process pages (create, save, prepare for indexing) syncedCount, failedPages, pagesToIndex := s.processPages(ctx, siteID, site.TenantID, input.Pages) // Step 4: Bulk index pages to search indexedCount, err := s.indexPageUC.Execute(ctx, siteID, pagesToIndex) if err != nil { s.logger.Error("failed to index pages", zap.Error(err)) return nil, err } // Step 5: Update site usage tracking (for billing) if indexedCount > 0 { if err := s.updateUsageUC.Execute(ctx, site, indexedCount); err != nil { s.logger.Warn("failed to update usage (non-fatal)", zap.Error(err)) // Don't fail the whole operation } } // Step 6: Build output message := fmt.Sprintf("Successfully synced %d pages, indexed %d pages", syncedCount, indexedCount) if len(failedPages) > 0 { message += fmt.Sprintf(", failed %d pages", len(failedPages)) } s.logger.Info("pages synced successfully", zap.String("site_id", siteID.String()), zap.Int("synced", syncedCount), zap.Int("indexed", indexedCount), zap.Int("failed", len(failedPages))) return &pageusecase.SyncPagesOutput{ SyncedCount: syncedCount, IndexedCount: indexedCount, FailedPages: failedPages, Message: message, }, nil } // Helper: Process pages - create entities, save to DB, collect pages to index func (s *syncPagesService) processPages( ctx context.Context, siteID, tenantID gocql.UUID, pages []pageusecase.SyncPageInput, ) (int, []string, []*domainpage.Page) { syncedCount := 0 var failedPages []string var pagesToIndex []*domainpage.Page for _, pageInput := range pages { // Create page entity (usecase) page, err := s.createPageUC.Execute(siteID, tenantID, pageInput) if err != nil { failedPages = append(failedPages, pageInput.PageID) continue } // Save to database (usecase) if err := s.upsertPageUC.Execute(ctx, page); err != nil { failedPages = append(failedPages, pageInput.PageID) continue } syncedCount++ // Collect pages that should be indexed if page.ShouldIndex() { pagesToIndex = append(pagesToIndex, page) } } return syncedCount, failedPages, pagesToIndex }