package page import ( "context" "fmt" "time" "github.com/gocql/gocql" "go.uber.org/zap" domainpage "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/domain/page" domainsite "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/domain/site" "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/search" ) // GetSyncStatusUseCase handles retrieving synchronization status type GetSyncStatusUseCase struct { pageRepo domainpage.Repository siteRepo domainsite.Repository searchClient *search.Client logger *zap.Logger } // ProvideGetSyncStatusUseCase creates a new GetSyncStatusUseCase func ProvideGetSyncStatusUseCase( pageRepo domainpage.Repository, siteRepo domainsite.Repository, searchClient *search.Client, logger *zap.Logger, ) *GetSyncStatusUseCase { return &GetSyncStatusUseCase{ pageRepo: pageRepo, siteRepo: siteRepo, searchClient: searchClient, logger: logger, } } // SyncStatusOutput provides synchronization status information type SyncStatusOutput struct { SiteID string `json:"site_id"` TotalPages int64 `json:"total_pages"` PublishedPages int64 `json:"published_pages"` DraftPages int64 `json:"draft_pages"` LastSyncedAt time.Time `json:"last_synced_at"` PagesIndexedMonth int64 `json:"pages_indexed_month"` // Usage tracking SearchRequestsMonth int64 `json:"search_requests_month"` // Usage tracking LastResetAt time.Time `json:"last_reset_at"` // Monthly billing cycle SearchIndexStatus string `json:"search_index_status"` SearchIndexDocCount int64 `json:"search_index_doc_count"` } // Execute retrieves the current sync status for a site func (uc *GetSyncStatusUseCase) Execute(ctx context.Context, tenantID, siteID gocql.UUID) (*SyncStatusOutput, error) { uc.logger.Info("executing get sync status use case", zap.String("tenant_id", tenantID.String()), zap.String("site_id", siteID.String())) // Get site to validate and get quota information 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 } // Count total pages in database totalPages, err := uc.pageRepo.CountBySiteID(ctx, siteID) if err != nil { uc.logger.Error("failed to count pages", zap.Error(err)) return nil, fmt.Errorf("failed to count pages: %w", err) } // Get all pages to count by status (this could be optimized with a dedicated query) pages, err := uc.pageRepo.GetBySiteID(ctx, siteID) if err != nil { uc.logger.Error("failed to get pages", zap.Error(err)) return nil, fmt.Errorf("failed to get pages: %w", err) } // Count pages by status var publishedPages, draftPages int64 for _, page := range pages { if page.Status == "publish" { publishedPages++ } else if page.Status == "draft" { draftPages++ } } // Check search index status indexStatus := "not_created" var indexDocCount int64 = 0 indexExists, err := uc.searchClient.IndexExists(siteID.String()) if err != nil { uc.logger.Error("failed to check index existence", zap.Error(err)) indexStatus = "error" } else if indexExists { indexStatus = "active" // Get index stats stats, err := uc.searchClient.GetStats(siteID.String()) if err != nil { uc.logger.Error("failed to get index stats", zap.Error(err)) } else { indexDocCount = stats.NumberOfDocuments } } uc.logger.Info("sync status retrieved successfully", zap.String("site_id", siteID.String()), zap.Int64("total_pages", totalPages), zap.Int64("published", publishedPages), zap.Int64("draft", draftPages)) return &SyncStatusOutput{ SiteID: siteID.String(), TotalPages: totalPages, PublishedPages: publishedPages, DraftPages: draftPages, LastSyncedAt: site.LastIndexedAt, PagesIndexedMonth: site.MonthlyPagesIndexed, SearchRequestsMonth: site.SearchRequestsCount, LastResetAt: site.LastResetAt, SearchIndexStatus: indexStatus, SearchIndexDocCount: indexDocCount, }, nil } // GetPageDetailsInput is the input for getting page details type GetPageDetailsInput struct { PageID string `json:"page_id"` } // PageDetailsOutput provides detailed information about a specific page type PageDetailsOutput struct { PageID string `json:"page_id"` Title string `json:"title"` Excerpt string `json:"excerpt"` URL string `json:"url"` Status string `json:"status"` PostType string `json:"post_type"` Author string `json:"author"` PublishedAt time.Time `json:"published_at"` ModifiedAt time.Time `json:"modified_at"` IndexedAt time.Time `json:"indexed_at"` MeilisearchDocID string `json:"meilisearch_doc_id"` IsIndexed bool `json:"is_indexed"` } // ExecuteGetPageDetails retrieves detailed information about a specific page func (uc *GetSyncStatusUseCase) ExecuteGetPageDetails(ctx context.Context, tenantID, siteID gocql.UUID, input *GetPageDetailsInput) (*PageDetailsOutput, error) { uc.logger.Info("executing get page details use case", zap.String("tenant_id", tenantID.String()), zap.String("site_id", siteID.String()), zap.String("page_id", input.PageID)) // Get site to validate _, err := uc.siteRepo.GetByID(ctx, tenantID, siteID) if err != nil { uc.logger.Error("failed to get site", zap.Error(err)) return nil, domainsite.ErrSiteNotFound } // Get page from database page, err := uc.pageRepo.GetByID(ctx, siteID, input.PageID) if err != nil { uc.logger.Error("failed to get page", zap.Error(err)) return nil, fmt.Errorf("page not found") } // Check if page is indexed in Meilisearch isIndexed := !page.IndexedAt.IsZero() uc.logger.Info("page details retrieved successfully", zap.String("site_id", siteID.String()), zap.String("page_id", input.PageID)) return &PageDetailsOutput{ PageID: page.PageID, Title: page.Title, Excerpt: page.Excerpt, URL: page.URL, Status: page.Status, PostType: page.PostType, Author: page.Author, PublishedAt: page.PublishedAt, ModifiedAt: page.ModifiedAt, IndexedAt: page.IndexedAt, MeilisearchDocID: page.MeilisearchDocID, IsIndexed: isIndexed, }, nil }