package page import ( "context" "fmt" "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" ) // DeletePagesUseCase handles page deletion type DeletePagesUseCase struct { pageRepo domainpage.Repository siteRepo domainsite.Repository searchClient *search.Client logger *zap.Logger } // ProvideDeletePagesUseCase creates a new DeletePagesUseCase func ProvideDeletePagesUseCase( pageRepo domainpage.Repository, siteRepo domainsite.Repository, searchClient *search.Client, logger *zap.Logger, ) *DeletePagesUseCase { return &DeletePagesUseCase{ pageRepo: pageRepo, siteRepo: siteRepo, searchClient: searchClient, logger: logger, } } // DeletePagesInput is the input for deleting pages type DeletePagesInput struct { PageIDs []string `json:"page_ids"` } // DeletePagesOutput is the output after deleting pages type DeletePagesOutput struct { DeletedCount int `json:"deleted_count"` DeindexedCount int `json:"deindexed_count"` FailedPages []string `json:"failed_pages,omitempty"` Message string `json:"message"` } // Execute deletes pages from both database and search index func (uc *DeletePagesUseCase) Execute(ctx context.Context, tenantID, siteID gocql.UUID, input *DeletePagesInput) (*DeletePagesOutput, error) { uc.logger.Info("executing delete pages use case", zap.String("tenant_id", tenantID.String()), zap.String("site_id", siteID.String()), zap.Int("page_count", len(input.PageIDs))) // Get site to validate 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 } deletedCount := 0 deindexedCount := 0 var failedPages []string // Delete pages from database if len(input.PageIDs) > 1 { // Use batch delete for multiple pages if err := uc.pageRepo.DeleteMultiple(ctx, siteID, input.PageIDs); err != nil { uc.logger.Error("failed to batch delete pages", zap.Error(err)) return nil, fmt.Errorf("failed to delete pages: %w", err) } deletedCount = len(input.PageIDs) } else if len(input.PageIDs) == 1 { // Single page delete if err := uc.pageRepo.Delete(ctx, siteID, input.PageIDs[0]); err != nil { uc.logger.Error("failed to delete page", zap.String("page_id", input.PageIDs[0]), zap.Error(err)) failedPages = append(failedPages, input.PageIDs[0]) } else { deletedCount = 1 } } // Delete from search index if deletedCount > 0 { if len(input.PageIDs) > 1 { // Batch delete from Meilisearch _, err := uc.searchClient.DeleteDocuments(siteID.String(), input.PageIDs) if err != nil { uc.logger.Error("failed to delete documents from search index", zap.Error(err)) // Don't fail the whole operation since database delete succeeded } else { deindexedCount = len(input.PageIDs) } } else if len(input.PageIDs) == 1 && len(failedPages) == 0 { // Single document delete _, err := uc.searchClient.DeleteDocument(siteID.String(), input.PageIDs[0]) if err != nil { uc.logger.Error("failed to delete document from search index", zap.String("page_id", input.PageIDs[0]), zap.Error(err)) // Don't fail the whole operation since database delete succeeded } else { deindexedCount = 1 } } } uc.logger.Info("pages deleted successfully", zap.String("site_id", siteID.String()), zap.Int("deleted", deletedCount), zap.Int("deindexed", deindexedCount), zap.Int("failed", len(failedPages))) message := fmt.Sprintf("Successfully deleted %d pages from database, removed %d from search index", deletedCount, deindexedCount) if len(failedPages) > 0 { message += fmt.Sprintf(", failed %d pages", len(failedPages)) } return &DeletePagesOutput{ DeletedCount: deletedCount, DeindexedCount: deindexedCount, FailedPages: failedPages, Message: message, }, nil } // DeleteAllPagesInput is the input for deleting all pages for a site type DeleteAllPagesInput struct{} // ExecuteDeleteAll deletes all pages for a site func (uc *DeletePagesUseCase) ExecuteDeleteAll(ctx context.Context, tenantID, siteID gocql.UUID) (*DeletePagesOutput, error) { uc.logger.Info("executing delete all pages use case", zap.String("tenant_id", tenantID.String()), zap.String("site_id", siteID.String())) // Get site to validate 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 pages before deletion count, 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) } // Delete all pages from database if err := uc.pageRepo.DeleteBySiteID(ctx, siteID); err != nil { uc.logger.Error("failed to delete all pages", zap.Error(err)) return nil, fmt.Errorf("failed to delete pages: %w", err) } // Delete all documents from search index _, err = uc.searchClient.DeleteAllDocuments(siteID.String()) if err != nil { uc.logger.Error("failed to delete all documents from search index", zap.Error(err)) // Don't fail the whole operation since database delete succeeded } uc.logger.Info("all pages deleted successfully", zap.String("site_id", siteID.String()), zap.Int64("count", count)) return &DeletePagesOutput{ DeletedCount: int(count), DeindexedCount: int(count), Message: fmt.Sprintf("Successfully deleted all %d pages", count), }, nil }