Enhance TED scraper and worker initialization with startup catch-up logic
continuous-integration/drone/push Build is passing

- Introduced a mutex to ensure only one TED scraper run executes at a time, preventing concurrent executions during startup and scheduled runs.
- Implemented a mechanism to check if today's TED scrape has already been completed during startup, logging appropriate messages for both completed and new runs.
- Added startup catch-up logic for tender translations and unprocessed notices, ensuring that any missed tasks are executed without blocking the application startup.

This update improves the reliability and efficiency of the TED scraper and worker processes, ensuring that all necessary tasks are completed after a server restart.
This commit is contained in:
Mazyar
2026-06-28 00:05:10 +03:30
parent 69445130ce
commit 582f8b5c02
6 changed files with 197 additions and 8 deletions
+105
View File
@@ -0,0 +1,105 @@
package schedule
import (
"context"
"errors"
"fmt"
"time"
"tm/pkg/logger"
"tm/pkg/mongo"
"go.mongodb.org/mongo-driver/v2/bson"
mongodriver "go.mongodb.org/mongo-driver/v2/mongo"
"go.mongodb.org/mongo-driver/v2/mongo/options"
)
const (
dailyJobRunsCollection = "daily_job_runs"
DailyJobStatusCompleted = "completed"
TEDScraperJobName = "ted_scraper"
)
type dailyJobRun struct {
ID string `bson:"_id"`
JobName string `bson:"job_name"`
Date string `bson:"date"`
CompletedAt int64 `bson:"completed_at"`
Status string `bson:"status"`
}
// DailyJobTracker records successful daily job runs so startup catch-up can skip
// dates that were already completed before a server restart.
type DailyJobTracker struct {
mongo *mongo.ConnectionManager
logger logger.Logger
}
func NewDailyJobTracker(mongoManager *mongo.ConnectionManager, log logger.Logger) *DailyJobTracker {
return &DailyJobTracker{
mongo: mongoManager,
logger: log,
}
}
func dailyJobRunID(jobName string, date time.Time) string {
return fmt.Sprintf("%s:%s", jobName, date.Format("2006-01-02"))
}
// IsCompleted reports whether jobName finished successfully for the given local calendar date.
func (t *DailyJobTracker) IsCompleted(ctx context.Context, jobName string, date time.Time) (bool, error) {
if jobName == "" {
return false, fmt.Errorf("daily job tracker: job name is required")
}
var doc dailyJobRun
err := t.mongo.GetCollection(dailyJobRunsCollection).FindOne(
ctx,
bson.M{"_id": dailyJobRunID(jobName, date), "status": DailyJobStatusCompleted},
).Decode(&doc)
if err != nil {
if errors.Is(err, mongodriver.ErrNoDocuments) {
return false, nil
}
return false, fmt.Errorf("daily job tracker: check completed %q: %w", jobName, err)
}
return true, nil
}
// MarkCompleted records a successful daily job run for the given local calendar date.
func (t *DailyJobTracker) MarkCompleted(ctx context.Context, jobName string, date time.Time) error {
if jobName == "" {
return fmt.Errorf("daily job tracker: job name is required")
}
dateKey := date.Format("2006-01-02")
now := time.Now().Unix()
_, err := t.mongo.GetCollection(dailyJobRunsCollection).UpdateOne(
ctx,
bson.M{"_id": dailyJobRunID(jobName, date)},
bson.M{
"$set": bson.M{
"job_name": jobName,
"date": dateKey,
"completed_at": now,
"status": DailyJobStatusCompleted,
},
"$setOnInsert": bson.M{"_id": dailyJobRunID(jobName, date)},
},
options.UpdateOne().SetUpsert(true),
)
if err != nil {
return fmt.Errorf("daily job tracker: mark completed %q on %s: %w", jobName, dateKey, err)
}
t.logger.Debug("Daily job marked completed", map[string]interface{}{
"job_name": jobName,
"date": dateKey,
"completed_at": now,
})
return nil
}
+15
View File
@@ -0,0 +1,15 @@
package schedule
import (
"testing"
"time"
)
func TestDailyJobRunID(t *testing.T) {
date := time.Date(2026, 6, 27, 15, 30, 0, 0, time.Local)
got := dailyJobRunID(TEDScraperJobName, date)
want := "ted_scraper:2026-06-27"
if got != want {
t.Fatalf("dailyJobRunID() = %q, want %q", got, want)
}
}
Executable → Regular
View File