fix(backend): harden write registrar boundaries
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
"senlinai-agent/backend/internal/models"
|
||||
)
|
||||
|
||||
@@ -15,8 +16,8 @@ type Service struct {
|
||||
}
|
||||
|
||||
type LinkedObject struct {
|
||||
ObjectType string `json:"object_type"`
|
||||
ObjectID uint `json:"object_id"`
|
||||
ObjectType string `json:"objectType"`
|
||||
ObjectID string `json:"objectId"`
|
||||
}
|
||||
|
||||
// NewService 接受数据库或上层事务,确保任务写入、标签调整和分享检查使用同一依赖。
|
||||
@@ -35,13 +36,20 @@ func (s *Service) database() *gorm.DB {
|
||||
return models.DBService
|
||||
}
|
||||
|
||||
func (s *Service) Assign(taskID uint, assigneeID uint) error {
|
||||
// Assign 在同一事务内由用户 identity 解析内部主键,并同步两个指派字段,避免 DTO 返回陈旧 identity。
|
||||
func (s *Service) Assign(taskID uint, assigneeIdentity string) error {
|
||||
return s.database().Transaction(func(tx *gorm.DB) error {
|
||||
var task models.SenlinAgentTask
|
||||
if err := tx.First(&task, taskID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Model(&task).Update("assignee_id", assigneeID).Error; err != nil {
|
||||
var assignee models.SenlinAgentUser
|
||||
if err := tx.Where("identity = ?", assigneeIdentity).First(&assignee).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Model(&task).Updates(map[string]any{
|
||||
"assignee_id": assignee.ID, "assignee_identity": assignee.Identity,
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Create(&models.SenlinAgentProjectEvent{
|
||||
@@ -50,7 +58,7 @@ func (s *Service) Assign(taskID uint, assigneeID uint) error {
|
||||
EventType: "task_assigned",
|
||||
EntityType: "task",
|
||||
EntityID: task.ID,
|
||||
Summary: fmt.Sprintf("Task assigned to user %d", assigneeID),
|
||||
Summary: fmt.Sprintf("Task assigned to user %s", assignee.Identity),
|
||||
}).Error
|
||||
})
|
||||
}
|
||||
@@ -61,8 +69,8 @@ func (s *Service) ShareObject(taskID uint, objectType string, objectID uint) err
|
||||
return errors.New("unsupported shared object type")
|
||||
}
|
||||
return s.database().Transaction(func(tx *gorm.DB) error {
|
||||
var task models.SenlinAgentTask
|
||||
if err := tx.First(&task, taskID).Error; err != nil {
|
||||
task, err := lockTaskByID(tx, taskID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ensureSharedObjectInProject(tx, task.ProjectID, objectType, objectID); err != nil {
|
||||
@@ -97,7 +105,7 @@ func (s *Service) VisibleLinkedObjects(taskID uint, viewerID uint) ([]LinkedObje
|
||||
}
|
||||
objects := make([]LinkedObject, 0, len(shares))
|
||||
for _, share := range shares {
|
||||
objects = append(objects, LinkedObject{ObjectType: share.ObjectType, ObjectID: share.ObjectID})
|
||||
objects = append(objects, LinkedObject{ObjectType: share.ObjectType, ObjectID: share.ObjectIdentity})
|
||||
}
|
||||
return objects, nil
|
||||
}
|
||||
@@ -141,13 +149,16 @@ func (s *Service) Create(ownerID uint, projectIdentity string, input CreateTaskI
|
||||
func (s *Service) Update(ownerID uint, projectIdentity, taskIdentity string, input UpdateTaskInput) (TaskDTO, error) {
|
||||
var result TaskDTO
|
||||
err := s.database().Transaction(func(tx *gorm.DB) error {
|
||||
task, err := lockTaskByIdentity(tx, taskIdentity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currentProject, err := findOwnedProject(tx, ownerID, projectIdentity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var task models.SenlinAgentTask
|
||||
if err := tx.Where("identity = ? AND project_id = ?", taskIdentity, currentProject.ID).First(&task).Error; err != nil {
|
||||
return err
|
||||
if task.ProjectID != currentProject.ID {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
targetProject := currentProject
|
||||
if strings.TrimSpace(input.NextProjectIdentity) != "" && input.NextProjectIdentity != currentProject.Identity {
|
||||
@@ -187,15 +198,31 @@ func (s *Service) Update(ownerID uint, projectIdentity, taskIdentity string, inp
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := tx.Save(&task).Error; err != nil {
|
||||
if err := tx.Save(task).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
result = makeTaskDTO(task, targetProject.Identity, tagIdentity, tagName)
|
||||
result = makeTaskDTO(*task, targetProject.Identity, tagIdentity, tagName)
|
||||
return nil
|
||||
})
|
||||
return result, err
|
||||
}
|
||||
|
||||
func lockTaskByID(tx *gorm.DB, taskID uint) (*models.SenlinAgentTask, error) {
|
||||
var task models.SenlinAgentTask
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&task, taskID).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &task, nil
|
||||
}
|
||||
|
||||
func lockTaskByIdentity(tx *gorm.DB, taskIdentity string) (*models.SenlinAgentTask, error) {
|
||||
var task models.SenlinAgentTask
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("identity = ?", taskIdentity).First(&task).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &task, nil
|
||||
}
|
||||
|
||||
func findOwnedProject(tx *gorm.DB, ownerID uint, identity string) (*models.SenlinAgentProject, error) {
|
||||
var project models.SenlinAgentProject
|
||||
if err := tx.Where("owner_id = ? AND identity = ?", ownerID, identity).First(&project).Error; err != nil {
|
||||
|
||||
Reference in New Issue
Block a user