package rule import ( "context" "errors" "strings" "time" "github.com/google/uuid" "gorm.io/gorm" "gorm.io/gorm/clause" ) type PageQuery struct { PageIndex int `form:"pageIndex"` PageSize int `form:"pageSize"` Name string `form:"name"` Enabled *bool `form:"enabled"` } type Service struct{ DB *gorm.DB } func NewService(db *gorm.DB) Service { return Service{DB: db} } func (s Service) List(ctx context.Context, query PageQuery) ([]Rule, int64, error) { page, size := pageValues(query.PageIndex, query.PageSize) db := s.DB.WithContext(ctx).Model(&Rule{}) if name := strings.TrimSpace(query.Name); name != "" { db = db.Where("name ILIKE ? OR code ILIKE ?", "%"+name+"%", "%"+name+"%") } if query.Enabled != nil { db = db.Where("enabled = ?", *query.Enabled) } var count int64 if err := db.Count(&count).Error; err != nil { return nil, 0, err } items := make([]Rule, 0) err := db.Order("created_at DESC, id DESC").Offset((page - 1) * size).Limit(size).Find(&items).Error return items, count, err } func (s Service) Create(ctx context.Context, input WriteInput, actorID int) (Rule, error) { normalized, err := Normalize(input, true) if err != nil { return Rule{}, err } now := time.Now().UTC() item := Rule{ ID: uuid.NewString(), Code: normalized.Code, Name: normalized.Name, Enabled: true, EventType: normalized.EventType, MinimumSeverity: normalized.MinimumSeverity, LocationContains: normalized.LocationContains, Version: 1, CreatedBy: actorID, UpdatedBy: actorID, CreatedAt: now, UpdatedAt: now, } if err = s.DB.WithContext(ctx).Create(&item).Error; err != nil { return Rule{}, err } return item, nil } func (s Service) Update(ctx context.Context, id string, input WriteInput, actorID int) (Rule, error) { if _, err := uuid.Parse(id); err != nil { return Rule{}, ErrNotFound } normalized, err := Normalize(input, false) if err != nil { return Rule{}, err } var item Rule err = s.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&item, "id = ?", id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return ErrNotFound } return err } updates := map[string]any{ "name": normalized.Name, "event_type": normalized.EventType, "minimum_severity": normalized.MinimumSeverity, "location_contains": normalized.LocationContains, "version": item.Version + 1, "updated_by": actorID, "updated_at": time.Now().UTC(), } if err := tx.Model(&item).Updates(updates).Error; err != nil { return err } return tx.First(&item, "id = ?", id).Error }) return item, err } func (s Service) SetEnabled(ctx context.Context, id string, enabled bool, actorID int) (Rule, error) { if _, err := uuid.Parse(id); err != nil { return Rule{}, ErrNotFound } var item Rule err := s.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&item, "id = ?", id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return ErrNotFound } return err } if item.Enabled == enabled { return nil } if err := tx.Model(&item).Updates(map[string]any{ "enabled": enabled, "version": item.Version + 1, "updated_by": actorID, "updated_at": time.Now().UTC(), }).Error; err != nil { return err } return tx.First(&item, "id = ?", id).Error }) return item, err } func pageValues(page, size int) (int, int) { if page < 1 { page = 1 } if size < 1 || size > 100 { size = 20 } return page, size }