Files
tm_back/internal/document_scraper/handler.go
T
Mazyar c46a8d54f4
continuous-integration/drone/push Build is passing
Refactor document scraper to utilize active publication windows for tender retrieval
- Updated the ListPendingTenders method to filter tenders based on their active publication submission window instead of just deadlines, enhancing the accuracy of tender listings.
- Introduced a new HasActivePublicationWindow method in the Tender entity to determine if the publication-based submission window is still open.
- Modified the GetTenderByNoticeID method to reflect the new logic for filtering tenders based on their publication status.
- Enhanced the documentation for relevant API endpoints to clarify the changes in tender retrieval criteria.

This update improves the precision of tender scraping by ensuring only relevant tenders with active submission windows are processed, contributing to better data management and scraping efficiency.
2026-07-01 19:22:45 +03:30

95 lines
4.3 KiB
Go

package document_scraper
import (
"errors"
"tm/pkg/logger"
"tm/pkg/response"
"github.com/labstack/echo/v4"
)
// Handler handles HTTP requests for the document scraper API
type Handler struct {
service Service
logger logger.Logger
}
// NewHandler creates a new document scraper handler
func NewHandler(service Service, logger logger.Logger) *Handler {
return &Handler{
service: service,
logger: logger,
}
}
// ListPendingTenders retrieves tenders that need document scraping
// @Summary List pending tenders for document scraping
// @Description Retrieve tenders that have not yet been scraped for documents and whose document URL matches a portal supported by the AI scraping service. Each item includes contract_folder_id, notice_publication_id, document_url, title, and description. Optionally filter by created_at range (Unix timestamps in seconds). By default only tenders with an active publication submission window are returned (submission_deadline, or 48 working hours after publication_date); set include_expired=true to include expired tenders.
// @Tags Document-Scraper
// @Produce json
// @Param limit query int false "Number of items per page (default: 20, max: 100)"
// @Param offset query int false "Number of items to skip (default: 0)"
// @Param created_at_from query int64 false "Filter by created_at lower bound (Unix timestamp, seconds)"
// @Param created_at_to query int64 false "Filter by created_at upper bound (Unix timestamp, seconds)"
// @Param include_expired query bool false "When true, include tenders whose deadline has passed (default: false)"
// @Success 200 {object} response.APIResponse{data=DocumentScraperListResponse} "List of pending tenders"
// @Failure 400 {object} response.APIResponse "Invalid query parameters"
// @Failure 401 {object} response.APIResponse "Invalid or missing API key"
// @Failure 503 {object} response.APIResponse "Scrape portals service is not configured"
// @Failure 500 {object} response.APIResponse "Internal server error"
// @Security ApiKeyAuth
// @Router /api/v1/scraper/tenders [get]
func (h *Handler) ListPendingTenders(c echo.Context) error {
form, err := response.Parse[DocumentScraperListRequest](c)
if err != nil {
return response.ValidationError(c, "Invalid request data", err.Error())
}
tenders, meta, err := h.service.ListPendingTenders(c.Request().Context(), *form)
if err != nil {
if errors.Is(err, ErrInvalidDateRange) {
return response.BadRequest(c, "Invalid date range", err.Error())
}
if errors.Is(err, ErrScrapePortalsUnavailable) {
return response.ServiceUnavailable(c, "Scrape portals service is not configured")
}
return response.InternalServerError(c, "Failed to list pending tenders")
}
return response.SuccessWithMeta(c, tenders, meta, "Pending tenders retrieved successfully")
}
// GetTenderByNoticeID retrieves a specific tender by its notice publication ID
// @Summary Get tender by notice ID for document scraping
// @Description Retrieve a specific tender whose publication submission window is still open and whose document URL matches a supported scrape portal.
// @Tags Document-Scraper
// @Produce json
// @Param notice_id path string true "Notice Publication ID"
// @Success 200 {object} response.APIResponse{data=DocumentScraperTenderResponse} "Tender details"
// @Failure 400 {object} response.APIResponse "Invalid notice ID"
// @Failure 401 {object} response.APIResponse "Invalid or missing API key"
// @Failure 404 {object} response.APIResponse "Tender not found"
// @Failure 503 {object} response.APIResponse "Scrape portals service is not configured"
// @Failure 500 {object} response.APIResponse "Internal server error"
// @Security ApiKeyAuth
// @Router /api/v1/scraper/tenders/{notice_id} [get]
func (h *Handler) GetTenderByNoticeID(c echo.Context) error {
noticeID := c.Param("notice_id")
if noticeID == "" {
return response.BadRequest(c, "Notice ID is required", "notice_id parameter cannot be empty")
}
tender, err := h.service.GetTenderByNoticeID(c.Request().Context(), noticeID)
if err != nil {
if errors.Is(err, ErrScrapePortalsUnavailable) {
return response.ServiceUnavailable(c, "Scrape portals service is not configured")
}
if err.Error() == "tender not found" {
return response.NotFound(c, "Tender not found")
}
return response.InternalServerError(c, "Failed to get tender")
}
return response.Success(c, tender, "Tender retrieved successfully")
}