package document_scraper import ( "context" "fmt" "tm/internal/tender" "tm/pkg/logger" "tm/pkg/response" "tm/ted" ) // Service defines business logic for the document scraper API type Service interface { // ListPendingTenders retrieves tenders that need document scraping ListPendingTenders(ctx context.Context, limit, offset int) (*DocumentScraperListResponse, *response.Meta, error) // GetTenderByNoticeID retrieves a specific tender by its notice publication ID GetTenderByNoticeID(ctx context.Context, noticeID string) (*DocumentScraperTenderResponse, error) } type service struct { tenderRepo tender.TenderRepository tedParser *ted.TEDParser logger logger.Logger } // NewService creates a new document scraper service func NewService(tenderRepo tender.TenderRepository, logger logger.Logger) Service { return &service{ tenderRepo: tenderRepo, tedParser: ted.NewTEDParser(), logger: logger, } } // buildTenderResponse creates a DocumentScraperTenderResponse from a tender func (s *service) buildTenderResponse(_ context.Context, t *tender.Tender) *DocumentScraperTenderResponse { documentURL := t.DocumentURI if documentURL == "" { documentURL = t.TenderURL } return &DocumentScraperTenderResponse{ ID: t.ID.Hex(), NoticePublicationID: t.NoticePublicationID, DocumentURL: documentURL, Title: t.Title, Description: t.Description, } } // ListPendingTenders retrieves tenders that have not yet been scraped for documents func (s *service) ListPendingTenders(ctx context.Context, limit, offset int) (*DocumentScraperListResponse, *response.Meta, error) { s.logger.Info("Listing pending tenders for document scraper", map[string]interface{}{ "limit": limit, "offset": offset, }) tenders, totalCount, err := s.tenderRepo.GetUnScrapedTenders(ctx, limit, offset) if err != nil { s.logger.Error("Failed to list pending tenders", map[string]interface{}{ "error": err.Error(), }) return nil, nil, fmt.Errorf("failed to list pending tenders: %w", err) } // Convert to response result := &DocumentScraperListResponse{ Tenders: make([]DocumentScraperTenderResponse, len(tenders)), } for i := range tenders { result.Tenders[i] = *s.buildTenderResponse(ctx, &tenders[i]) } // Build pagination metadata meta := &response.Meta{ Total: int(totalCount), Limit: limit, Offset: offset, } if meta.Limit > 0 { meta.Page = (offset / meta.Limit) + 1 meta.Pages = (int(totalCount) + meta.Limit - 1) / meta.Limit } s.logger.Info("Pending tenders retrieved successfully", map[string]interface{}{ "count": len(tenders), "total_count": totalCount, }) return result, meta, nil } // GetTenderByNoticeID retrieves a specific tender by its notice publication ID func (s *service) GetTenderByNoticeID(ctx context.Context, noticeID string) (*DocumentScraperTenderResponse, error) { s.logger.Info("Getting tender by notice ID for document scraper", map[string]interface{}{ "notice_id": noticeID, }) t, err := s.tenderRepo.GetByNoticePublicationID(ctx, noticeID) if err != nil { s.logger.Error("Failed to get tender by notice ID", map[string]interface{}{ "notice_id": noticeID, "error": err.Error(), }) return nil, fmt.Errorf("failed to get tender: %w", err) } if t == nil { return nil, fmt.Errorf("tender not found") } result := s.buildTenderResponse(ctx, t) s.logger.Info("Tender retrieved successfully for document scraper", map[string]interface{}{ "notice_id": noticeID, "tender_id": t.ID.Hex(), }) return result, nil }