Add cron job functionality and Docker integration
This commit is contained in:
69
CLAUDE.md
69
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
|
||||
|
||||
|
||||
18
Dockerfile
18
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
|
||||
|
||||
40
README.md
40
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
|
||||
|
||||
85
cmd/cron/main.go
Normal file
85
cmd/cron/main.go
Normal file
@@ -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")
|
||||
}
|
||||
}
|
||||
15
crontab
Normal file
15
crontab
Normal file
@@ -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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user