From 3132ab2fa221f22ed50edb0891e3b218d91e7289 Mon Sep 17 00:00:00 2001 From: Michael Chus Date: Sat, 31 Jan 2026 00:31:43 +0300 Subject: [PATCH] Add cron job functionality and Docker integration --- CLAUDE.md | 69 +++++++++++++++++++++++-------------- Dockerfile | 18 ++++++++-- README.md | 40 ++++++++++++++++++++-- cmd/cron/main.go | 85 ++++++++++++++++++++++++++++++++++++++++++++++ crontab | 15 ++++++++ docker-compose.yml | 13 +++++++ 6 files changed, 210 insertions(+), 30 deletions(-) create mode 100644 cmd/cron/main.go create mode 100644 crontab diff --git a/CLAUDE.md b/CLAUDE.md index 5b2bec5..e851f04 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -327,40 +327,57 @@ go run ./cmd/server # Run importer (one-time setup) go run ./cmd/importer +# Run cron jobs manually +go run ./cmd/cron -job=alerts # Check and generate alerts +go run ./cmd/cron -job=update-prices # Recalculate all prices +go run ./cmd/cron -job=reset-counters # Reset usage counters +go run ./cmd/cron -job=update-popularity # Update popularity scores + # Build for production CGO_ENABLED=0 go build -ldflags="-s -w" -o bin/quoteforge ./cmd/server +CGO_ENABLED=0 go build -ldflags="-s -w" -o bin/quoteforge-cron ./cmd/cron # Run tests go test ./... +```` + +## Cron Jobs + +QuoteForge now includes automated cron jobs for maintenance tasks. These can be run using the built-in cron functionality in the Docker container. + +### Docker Compose Setup + +The Docker setup includes a dedicated cron service that runs the following jobs: + +- **Alerts check**: Every hour (0 * * * *) +- **Price updates**: Daily at 2 AM (0 2 * * *) +- **Usage counter reset**: Weekly on Sunday at 1 AM (0 1 * * 0) +- **Popularity score updates**: Daily at 3 AM (0 3 * * *) + +### Manual Cron Job Execution + +You can also run cron jobs manually using the quoteforge-cron binary: + +```bash +# Check and generate alerts +go run ./cmd/cron -job=alerts + +# Recalculate all prices +go run ./cmd/cron -job=update-prices + +# Reset usage counters +go run ./cmd/cron -job=reset-counters + +# Update popularity scores +go run ./cmd/cron -job=update-popularity ``` -## Dependencies (go.mod) +### Cron Job Details -```go -module git.mchus.pro/mchus/quoteforge - -go 1.22 - -require ( - github.com/gin-gonic/gin v1.9.1 - github.com/go-sql-driver/mysql v1.7.1 - gorm.io/gorm v1.25.5 - gorm.io/driver/mysql v1.5.2 - github.com/xuri/excelize/v2 v2.8.0 - github.com/golang-jwt/jwt/v5 v5.2.0 - github.com/google/uuid v1.5.0 - golang.org/x/crypto v0.17.0 - gopkg.in/yaml.v3 v3.0.1 -) -``` - -## Development Priorities - -1. **Phase 1 (MVP):** Project setup, models, component API, basic UI, CSV export -2. **Phase 2:** JWT auth with roles, pricing admin UI, all price methods -3. **Phase 3:** Save/load configs, JSON import/export, XLSX export, cron jobs -4. **Phase 4:** Usage stats, alerts system, dashboard -5. **Phase 5:** Polish, tests, Docker, documentation +- **Alerts check**: Generates alerts for components with high demand and stale prices, trending components without prices, and components with no recent quotes +- **Price updates**: Recalculates prices for all components using configured methods (median, weighted median, average) +- **Usage counter reset**: Resets weekly and monthly usage counters for components +- **Popularity score updates**: Recalculates popularity scores based on supplier quote activity ## Code Style diff --git a/Dockerfile b/Dockerfile index 034dd72..4bf3406 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,16 +12,22 @@ RUN go mod download # Copy source code COPY . . -# Build the binary +# Build the main binary RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \ -ldflags="-s -w" \ -o /app/quoteforge \ ./cmd/server +# Build the cron binary +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \ + -ldflags="-s -w" \ + -o /app/quoteforge-cron \ + ./cmd/cron + # Final stage FROM alpine:3.19 -RUN apk add --no-cache ca-certificates tzdata +RUN apk add --no-cache ca-certificates tzdata cron # Create non-root user RUN adduser -D -g '' appuser @@ -30,6 +36,14 @@ WORKDIR /app # Copy binary from builder COPY --from=builder /app/quoteforge . +COPY --from=builder /app/quoteforge-cron . + +# Copy cron job configuration +COPY crontab /etc/crontabs/appuser +RUN chmod 0600 /etc/crontabs/appuser + +# Create log directory +RUN mkdir -p /var/log/cron # Copy web templates and static files COPY --from=builder /app/web ./web diff --git a/README.md b/README.md index 752db93..88bdd7f 100644 --- a/README.md +++ b/README.md @@ -163,12 +163,48 @@ GET /api/configs # Сохранённые конфигурации ## Cron Jobs -Система автоматического обновления цен не реализована в текущей версии. Для обновления цен можно использовать команду: +QuoteForge now includes automated cron jobs for maintenance tasks. These can be run using the built-in cron functionality in the Docker container. + +### Docker Compose Setup + +The Docker setup includes a dedicated cron service that runs the following jobs: + +- **Alerts check**: Every hour (0 * * * *) +- **Price updates**: Daily at 2 AM (0 2 * * *) +- **Usage counter reset**: Weekly on Sunday at 1 AM (0 1 * * 0) +- **Popularity score updates**: Daily at 3 AM (0 3 * * *) + +To enable cron jobs in Docker, run: ```bash -go run ./cmd/server -migrate +docker-compose up -d ``` +### Manual Cron Job Execution + +You can also run cron jobs manually using the quoteforge-cron binary: + +```bash +# Check and generate alerts +go run ./cmd/cron -job=alerts + +# Recalculate all prices +go run ./cmd/cron -job=update-prices + +# Reset usage counters +go run ./cmd/cron -job=reset-counters + +# Update popularity scores +go run ./cmd/cron -job=update-popularity +``` + +### Cron Job Details + +- **Alerts check**: Generates alerts for components with high demand and stale prices, trending components without prices, and components with no recent quotes +- **Price updates**: Recalculates prices for all components using configured methods (median, weighted median, average) +- **Usage counter reset**: Resets weekly and monthly usage counters for components +- **Popularity score updates**: Recalculates popularity scores based on supplier quote activity + ## Разработка ```bash diff --git a/cmd/cron/main.go b/cmd/cron/main.go new file mode 100644 index 0000000..3157fee --- /dev/null +++ b/cmd/cron/main.go @@ -0,0 +1,85 @@ +package main + +import ( + "flag" + "log" + "time" + + "git.mchus.pro/mchus/quoteforge/internal/config" + "git.mchus.pro/mchus/quoteforge/internal/models" + "git.mchus.pro/mchus/quoteforge/internal/repository" + "git.mchus.pro/mchus/quoteforge/internal/services/alerts" + "git.mchus.pro/mchus/quoteforge/internal/services/pricing" + "gorm.io/driver/mysql" + "gorm.io/gorm" + "gorm.io/gorm/logger" +) + +func main() { + configPath := flag.String("config", "config.yaml", "path to config file") + cronJob := flag.String("job", "", "type of cron job to run (alerts, update-prices)") + flag.Parse() + + cfg, err := config.Load(*configPath) + if err != nil { + log.Fatalf("Failed to load config: %v", err) + } + + db, err := gorm.Open(mysql.Open(cfg.Database.DSN()), &gorm.Config{ + Logger: logger.Default.LogMode(logger.Silent), + }) + if err != nil { + log.Fatalf("Failed to connect to database: %v", err) + } + + // Ensure tables exist + if err := models.Migrate(db); err != nil { + log.Fatalf("Migration failed: %v", err) + } + + // Initialize repositories + statsRepo := repository.NewStatsRepository(db) + alertRepo := repository.NewAlertRepository(db) + componentRepo := repository.NewComponentRepository(db) + priceRepo := repository.NewPriceRepository(db) + + // Initialize services + alertService := alerts.NewService(alertRepo, componentRepo, priceRepo, statsRepo, cfg.Alerts, cfg.Pricing) + pricingService := pricing.NewService(componentRepo, priceRepo, cfg.Pricing) + + switch *cronJob { + case "alerts": + log.Println("Running alerts check...") + if err := alertService.CheckAndGenerateAlerts(); err != nil { + log.Printf("Error running alerts check: %v", err) + } else { + log.Println("Alerts check completed successfully") + } + case "update-prices": + log.Println("Recalculating all prices...") + updated, errors := pricingService.RecalculateAllPrices() + log.Printf("Prices recalculated: %d updated, %d errors", updated, errors) + case "reset-counters": + log.Println("Resetting usage counters...") + if err := statsRepo.ResetWeeklyCounters(); err != nil { + log.Printf("Error resetting weekly counters: %v", err) + } + if err := statsRepo.ResetMonthlyCounters(); err != nil { + log.Printf("Error resetting monthly counters: %v", err) + } + log.Println("Usage counters reset completed") + case "update-popularity": + log.Println("Updating popularity scores...") + if err := statsRepo.UpdatePopularityScores(); err != nil { + log.Printf("Error updating popularity scores: %v", err) + } else { + log.Println("Popularity scores updated successfully") + } + default: + log.Println("No valid cron job specified. Available jobs:") + log.Println(" - alerts: Check and generate alerts") + log.Println(" - update-prices: Recalculate all prices") + log.Println(" - reset-counters: Reset usage counters") + log.Println(" - update-popularity: Update popularity scores") + } +} \ No newline at end of file diff --git a/crontab b/crontab new file mode 100644 index 0000000..ac7cf23 --- /dev/null +++ b/crontab @@ -0,0 +1,15 @@ +# Cron jobs for QuoteForge +# Run alerts check every hour +0 * * * * /app/quoteforge-cron -job=alerts + +# Run price updates daily at 2 AM +0 2 * * * /app/quoteforge-cron -job=update-prices + +# Reset weekly counters every Sunday at 1 AM +0 1 * * 0 /app/quoteforge-cron -job=reset-counters + +# Update popularity scores daily at 3 AM +0 3 * * * /app/quoteforge-cron -job=update-popularity + +# Log rotation (optional) +# 0 0 * * * /usr/bin/logrotate /etc/logrotate.conf \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index e7ca470..b04e12a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,3 +1,5 @@ +version: '3.8' + services: quoteforge: build: . @@ -15,3 +17,14 @@ services: timeout: 3s retries: 3 start_period: 5s + + quoteforge-cron: + build: . + container_name: quoteforge-cron + restart: unless-stopped + volumes: + - ./config.yaml:/app/config.yaml:ro + - ./logs:/app/logs + command: /usr/sbin/crond -f -l 8 + environment: + - TZ=Europe/Moscow