fix(backend): harden final MVP invariants
This commit is contained in:
@@ -3,8 +3,10 @@ package projects
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
"senlinai-agent/backend/internal/models"
|
||||
)
|
||||
|
||||
@@ -28,7 +30,7 @@ func (s *Service) CreateCronPlan(ownerID uint, projectID uint, input CreateCronP
|
||||
}
|
||||
plan := &models.SenlinAgentCronPlan{
|
||||
ProjectID: projectID, CreatedBy: ownerID, Title: title, Schedule: schedule,
|
||||
Enabled: input.Enabled, NextRunAt: input.NextRunAt, LastResult: "Not run yet",
|
||||
Enabled: input.Enabled, NextRunAt: input.NextRunAt,
|
||||
}
|
||||
return plan, models.DBService.Create(plan).Error
|
||||
}
|
||||
@@ -45,16 +47,40 @@ func (s *Service) CreateTag(projectID uint, name string) (*models.SenlinAgentTag
|
||||
if name == "" {
|
||||
return nil, ErrTagNameRequired
|
||||
}
|
||||
var existing models.SenlinAgentTag
|
||||
err := models.DBService.Where("project_id = ? AND name = ?", projectID, name).First(&existing).Error
|
||||
if err == nil {
|
||||
return &existing, nil
|
||||
var lastErr error
|
||||
for attempt := 0; attempt < 5; attempt++ {
|
||||
candidate := models.SenlinAgentTag{ProjectID: projectID, Name: name}
|
||||
lastErr = models.DBService.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "project_id"}, {Name: "name"}},
|
||||
DoNothing: true,
|
||||
}).Create(&candidate).Error
|
||||
if lastErr == nil {
|
||||
var stored models.SenlinAgentTag
|
||||
lastErr = models.DBService.Where("project_id = ? AND name = ?", projectID, name).First(&stored).Error
|
||||
if lastErr == nil {
|
||||
return &stored, nil
|
||||
}
|
||||
}
|
||||
if !retryableTagWrite(lastErr) {
|
||||
return nil, lastErr
|
||||
}
|
||||
time.Sleep(time.Duration(attempt+1) * time.Millisecond)
|
||||
}
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, err
|
||||
return nil, lastErr
|
||||
}
|
||||
|
||||
func retryableTagWrite(err error) bool {
|
||||
if errors.Is(err, gorm.ErrDuplicatedKey) || errors.Is(err, gorm.ErrRecordNotFound) || strings.Contains(strings.ToLower(err.Error()), "database is locked") {
|
||||
return true
|
||||
}
|
||||
tag := &models.SenlinAgentTag{ProjectID: projectID, Name: name}
|
||||
return tag, models.DBService.Create(tag).Error
|
||||
var sqlState interface{ SQLState() string }
|
||||
if errors.As(err, &sqlState) {
|
||||
switch sqlState.SQLState() {
|
||||
case "40001", "40P01", "55P03":
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Service) ListProjectTags(ownerID uint, projectID uint) ([]models.SenlinAgentTag, error) {
|
||||
|
||||
Reference in New Issue
Block a user