fix(listener): reclaim soft-deleted names so UNIQUE(name) can be reused

This commit is contained in:
風雪 committed 2026-08-28 23:32:28 +08:00
1 parent 69df7e5165
commit 2c8b7fc887
1 file changed
+22 -3
+22 -3
View File
@@ -123,6 +123,9 @@ func (s *Service) Delete(id uint) error {
if err := s.db.Delete(&models.Listener{}, id).Error; err != nil {
return fmt.Errorf("failed to delete listener: %w", err)
}
// Soft-delete keeps the row; rename so UNIQUE(name) can be reused immediately.
freed := fmt.Sprintf("%s__deleted_%d", previous.Name, id)
_ = s.db.Unscoped().Model(&models.Listener{}).Where("id = ?", id).Update("name", freed).Error
if err := s.regenerateConfigLocked(); err != nil {
if rollbackErr := s.db.Unscoped().Save(&previous).Error; rollbackErr != nil {
return fmt.Errorf("%v; rollback deleted listener failed: %w", err, rollbackErr)
@@ -145,17 +148,33 @@ func (s *Service) ensureUniqueName(candidate *models.Listener) error {
if name == "" {
return fmt.Errorf("listener name is required")
}
var count int64
// Only live rows should block create/update. Soft-deleted rows still occupy
// the SQLite UNIQUE index, so reclaim those names first.
var active int64
q := s.db.Model(&models.Listener{}).Where("name = ?", name)
if candidate.ID != 0 {
q = q.Where("id <> ?", candidate.ID)
}
if err := q.Count(&count).Error; err != nil {
if err := q.Count(&active).Error; err != nil {
return fmt.Errorf("check listener name uniqueness: %w", err)
}
if count > 0 {
if active > 0 {
return fmt.Errorf("listener name %q already exists", name)
}
var soft []models.Listener
sq := s.db.Unscoped().Where("name = ? AND deleted_at IS NOT NULL", name)
if candidate.ID != 0 {
sq = sq.Where("id <> ?", candidate.ID)
}
if err := sq.Find(&soft).Error; err != nil {
return fmt.Errorf("check soft-deleted listener names: %w", err)
}
for _, row := range soft {
newName := fmt.Sprintf("%s__deleted_%d", name, row.ID)
if err := s.db.Unscoped().Model(&models.Listener{}).Where("id = ?", row.ID).Update("name", newName).Error; err != nil {
return fmt.Errorf("reclaim soft-deleted name %q: %w", name, err)
}
}
return nil
}