Operations Audit (new feature): - Add Operation domain model with status tracking (pending, running, completed, failed, cancelled) - Add OperationRepository with PostgreSQL implementation - Add OperationService for CRUD and lifecycle management - Add operations handlers (list, get, cancel endpoints) - Add migration 015_operations.sql for operations table - Add operation cleanup worker for stale operation handling - Add ErrOperationNotFound to domain errors Template Improvements: - Add CLAUDE.md configuration files to astro-landing, default, and go-api templates - Fix PORT template variable usage in nginx configs for app templates - Add replace directives for local pkg module in Go templates - Simplify Go service/worker Dockerfiles for workspace builds - Fix TypeScript error in logger template Other: - Refactor landing-test.sh cookbook script - Update CLAUDE.md version reference Note: Some files exceed 500-line limit (pre-existing debt + new feature) - component.go: 550 lines (unchanged, pre-existing) - main.go: 522 lines (added operations wiring) - operation_repo.go: 569 lines (new, needs splitting) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
126 lines
2.9 KiB
Go
126 lines
2.9 KiB
Go
package worker
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/orchard9/rdev/internal/port"
|
|
)
|
|
|
|
// OperationCleanup runs periodic cleanup of old operations.
|
|
// Operations older than the retention period (default 30 days) are deleted.
|
|
type OperationCleanup struct {
|
|
repo port.OperationRepository
|
|
logger *slog.Logger
|
|
retentionPeriod time.Duration
|
|
cleanupInterval time.Duration
|
|
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
wg sync.WaitGroup
|
|
}
|
|
|
|
// OperationCleanupConfig holds configuration for operation cleanup.
|
|
type OperationCleanupConfig struct {
|
|
// RetentionPeriod is how long to keep operations.
|
|
// Default: 30 days.
|
|
RetentionPeriod time.Duration
|
|
|
|
// CleanupInterval is how often to run cleanup.
|
|
// Default: 1 hour.
|
|
CleanupInterval time.Duration
|
|
|
|
Logger *slog.Logger
|
|
}
|
|
|
|
// DefaultOperationCleanupConfig returns sensible defaults.
|
|
func DefaultOperationCleanupConfig() *OperationCleanupConfig {
|
|
return &OperationCleanupConfig{
|
|
RetentionPeriod: 30 * 24 * time.Hour, // 30 days
|
|
CleanupInterval: 1 * time.Hour,
|
|
Logger: slog.Default(),
|
|
}
|
|
}
|
|
|
|
// NewOperationCleanup creates a new operation cleanup worker.
|
|
func NewOperationCleanup(repo port.OperationRepository, cfg *OperationCleanupConfig) *OperationCleanup {
|
|
if cfg == nil {
|
|
cfg = DefaultOperationCleanupConfig()
|
|
}
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
return &OperationCleanup{
|
|
repo: repo,
|
|
logger: cfg.Logger.With("component", "operation-cleanup"),
|
|
retentionPeriod: cfg.RetentionPeriod,
|
|
cleanupInterval: cfg.CleanupInterval,
|
|
ctx: ctx,
|
|
cancel: cancel,
|
|
}
|
|
}
|
|
|
|
// Start begins the cleanup loop.
|
|
func (c *OperationCleanup) Start() {
|
|
c.logger.Info("operation cleanup started",
|
|
"retention_period", c.retentionPeriod,
|
|
"cleanup_interval", c.cleanupInterval,
|
|
)
|
|
|
|
c.wg.Add(1)
|
|
go c.cleanupLoop()
|
|
}
|
|
|
|
// Stop gracefully shuts down the cleanup worker.
|
|
func (c *OperationCleanup) Stop() {
|
|
c.logger.Info("operation cleanup stopping")
|
|
c.cancel()
|
|
c.wg.Wait()
|
|
c.logger.Info("operation cleanup stopped")
|
|
}
|
|
|
|
// cleanupLoop runs periodic cleanup.
|
|
func (c *OperationCleanup) cleanupLoop() {
|
|
defer c.wg.Done()
|
|
|
|
// Run immediately on start
|
|
c.runCleanup()
|
|
|
|
ticker := time.NewTicker(c.cleanupInterval)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-c.ctx.Done():
|
|
return
|
|
case <-ticker.C:
|
|
c.runCleanup()
|
|
}
|
|
}
|
|
}
|
|
|
|
// runCleanup deletes operations older than the retention period.
|
|
func (c *OperationCleanup) runCleanup() {
|
|
ctx, cancel := context.WithTimeout(c.ctx, 30*time.Second)
|
|
defer cancel()
|
|
|
|
cutoff := time.Now().Add(-c.retentionPeriod)
|
|
deleted, err := c.repo.DeleteOlderThan(ctx, cutoff)
|
|
if err != nil {
|
|
c.logger.Error("failed to cleanup old operations",
|
|
"error", err,
|
|
"cutoff", cutoff,
|
|
)
|
|
return
|
|
}
|
|
|
|
if deleted > 0 {
|
|
c.logger.Info("cleaned up old operations",
|
|
"deleted", deleted,
|
|
"cutoff", cutoff,
|
|
)
|
|
}
|
|
}
|