feat(documents): rebuild project document workbench
This commit is contained in:
@@ -29,13 +29,13 @@ func TestPostgresMovePreventsOldProjectShareFromBeingInsertedConcurrently(t *tes
|
||||
require.NoError(t, database.Create(&first).Error)
|
||||
require.NoError(t, database.Create(&second).Error)
|
||||
task := models.SaTask{ProjectID: first.ID, CreatedBy: owner.ID, Title: "Move", Status: "open"}
|
||||
note := models.SaNote{ProjectID: first.ID, CreatedBy: owner.ID, Title: "Old context", Markdown: "private"}
|
||||
document := models.SaDocument{ProjectID: first.ID, CreatedBy: owner.ID, Kind: models.DocumentKindFile, Name: "Old context.md", NormalizedName: "old context.md", Extension: ".md", Revision: 1}
|
||||
require.NoError(t, database.Create(&task).Error)
|
||||
require.NoError(t, database.Create(¬e).Error)
|
||||
require.NoError(t, database.Create(&document).Error)
|
||||
t.Cleanup(func() {
|
||||
database.Where("task_id = ?", task.ID).Delete(&models.SaTaskShare{})
|
||||
database.Where("entity_type = ? AND entity_id = ?", "task", task.ID).Delete(&models.SaProjectEvent{})
|
||||
database.Delete(¬e)
|
||||
database.Delete(&document)
|
||||
database.Delete(&task)
|
||||
database.Delete(&first)
|
||||
database.Delete(&second)
|
||||
@@ -78,7 +78,7 @@ func TestPostgresMovePreventsOldProjectShareFromBeingInsertedConcurrently(t *tes
|
||||
}
|
||||
|
||||
shareResult := make(chan error, 1)
|
||||
go func() { shareResult <- service.ShareObject(task.ID, "note", note.ID) }()
|
||||
go func() { shareResult <- service.ShareObject(task.ID, "document", document.ID) }()
|
||||
select {
|
||||
case err := <-shareResult:
|
||||
t.Fatalf("ShareObject returned before the moving transaction released its task lock: %v", err)
|
||||
|
||||
@@ -50,9 +50,9 @@ func TestTaskRegistrarMovesTaskByIdentityAndClearsForeignProjectTag(t *testing.T
|
||||
require.NoError(t, database.Create(&tag).Error)
|
||||
task := models.SaTask{ProjectID: first.ID, CreatedBy: 1, TagID: &tag.ID, Title: "迁移任务", Status: "open"}
|
||||
require.NoError(t, database.Create(&task).Error)
|
||||
note := models.SaNote{ProjectID: first.ID, CreatedBy: 1, Title: "旧项目资料", Markdown: "仅可在 Alpha 分享"}
|
||||
require.NoError(t, database.Create(¬e).Error)
|
||||
require.NoError(t, NewService(database).ShareObject(task.ID, "note", note.ID))
|
||||
document := models.SaDocument{ProjectID: first.ID, CreatedBy: 1, Kind: models.DocumentKindFile, Name: "旧项目资料.md", NormalizedName: "旧项目资料.md", Extension: ".md", Revision: 1}
|
||||
require.NoError(t, database.Create(&document).Error)
|
||||
require.NoError(t, NewService(database).ShareObject(task.ID, "document", document.ID))
|
||||
body := bytes.NewBufferString(fmt.Sprintf(`{"title":"迁移任务","description":"已移动","completed":false,"nextProjectId":%q}`, strings.ToUpper(second.Identity)))
|
||||
req := httptest.NewRequest(http.MethodPatch, "/api/v1/projects/"+first.Identity+"/tasks/"+task.Identity, body)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
@@ -63,9 +63,9 @@ func (s *Service) Assign(taskID uint, assigneeIdentity string) error {
|
||||
})
|
||||
}
|
||||
|
||||
// ShareObject 只允许显式分享 note/source,并在同一事务中验证关联对象属于任务所在项目。
|
||||
// ShareObject only allows an explicitly shared document from the task project.
|
||||
func (s *Service) ShareObject(taskID uint, objectType string, objectID uint) error {
|
||||
if objectType != "note" && objectType != "source" {
|
||||
if objectType != "document" {
|
||||
return errors.New("unsupported shared object type")
|
||||
}
|
||||
return s.database().Transaction(func(tx *gorm.DB) error {
|
||||
@@ -73,7 +73,7 @@ func (s *Service) ShareObject(taskID uint, objectType string, objectID uint) err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ensureSharedObjectInProject(tx, task.ProjectID, objectType, objectID); err != nil {
|
||||
if err := ensureSharedObjectInProject(tx, task.ProjectID, objectID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&models.SaTaskShare{TaskID: taskID, ObjectType: objectType, ObjectID: objectID}).Error; err != nil {
|
||||
@@ -193,7 +193,7 @@ func (s *Service) Update(ownerID uint, projectIdentity, taskIdentity string, inp
|
||||
task.TagID = tagID
|
||||
task.TagIdentity = tagIdentity
|
||||
if targetProject.ID != currentProject.ID {
|
||||
// 现有分享都在原项目边界内;移动后必须清空,避免旧项目 note/source 继续对被指派人可见。
|
||||
// 现有分享都在原项目边界内;移动后必须清空,避免旧项目 document 继续对被指派人可见。
|
||||
if err := tx.Where("task_id = ?", task.ID).Delete(&models.SaTaskShare{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -267,24 +267,13 @@ func utcOptionalTime(value *time.Time) *time.Time {
|
||||
return &result
|
||||
}
|
||||
|
||||
func ensureSharedObjectInProject(tx *gorm.DB, projectID uint, objectType string, objectID uint) error {
|
||||
switch objectType {
|
||||
case "note":
|
||||
var count int64
|
||||
if err := tx.Model(&models.SaNote{}).Where("id = ? AND project_id = ?", objectID, projectID).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count == 0 {
|
||||
return errors.New("shared object not found in task project")
|
||||
}
|
||||
case "source":
|
||||
var count int64
|
||||
if err := tx.Model(&models.SaSource{}).Where("id = ? AND project_id = ?", objectID, projectID).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count == 0 {
|
||||
return errors.New("shared object not found in task project")
|
||||
}
|
||||
func ensureSharedObjectInProject(tx *gorm.DB, projectID uint, objectID uint) error {
|
||||
var count int64
|
||||
if err := tx.Model(&models.SaDocument{}).Where("id = ? AND project_id = ?", objectID, projectID).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count == 0 {
|
||||
return errors.New("shared object not found in task project")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -24,11 +24,11 @@ func TestUpdateLocksTaskBeforeProjectValidation(t *testing.T) {
|
||||
|
||||
func TestShareObjectLocksTaskBeforeObjectValidation(t *testing.T) {
|
||||
database, _, project, task := newTaskLockFixture(t)
|
||||
note := models.SaNote{ProjectID: project.ID, CreatedBy: task.CreatedBy, Title: "Context", Markdown: "Private"}
|
||||
require.NoError(t, database.Create(¬e).Error)
|
||||
document := models.SaDocument{ProjectID: project.ID, CreatedBy: task.CreatedBy, Kind: models.DocumentKindFile, Name: "Context.md", NormalizedName: "context.md", Extension: ".md", Revision: 1}
|
||||
require.NoError(t, database.Create(&document).Error)
|
||||
queries := captureTaskQueryOrder(t, database)
|
||||
|
||||
err := NewService(database).ShareObject(task.ID, "note", note.ID)
|
||||
err := NewService(database).ShareObject(task.ID, "document", document.ID)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, *queries)
|
||||
@@ -39,21 +39,21 @@ func TestAssigneeOnlySeesExplicitlySharedObjects(t *testing.T) {
|
||||
database := newTestDB(t)
|
||||
assigneeID := uint(2)
|
||||
task := models.SaTask{ProjectID: 1, CreatedBy: 1, AssigneeID: &assigneeID, Title: "处理合同"}
|
||||
note := models.SaNote{ProjectID: 1, CreatedBy: 1, Title: "合同背景", Markdown: "只在共享后可见"}
|
||||
document := models.SaDocument{ProjectID: 1, CreatedBy: 1, Kind: models.DocumentKindFile, Name: "合同背景.md", NormalizedName: "合同背景.md", Extension: ".md", Revision: 1}
|
||||
require.NoError(t, database.Create(&task).Error)
|
||||
require.NoError(t, database.Create(¬e).Error)
|
||||
require.NoError(t, database.Create(&document).Error)
|
||||
service := NewService()
|
||||
|
||||
before, err := service.VisibleLinkedObjects(task.ID, assigneeID)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, before)
|
||||
|
||||
require.NoError(t, service.ShareObject(task.ID, "note", note.ID))
|
||||
require.NoError(t, service.ShareObject(task.ID, "document", document.ID))
|
||||
after, err := service.VisibleLinkedObjects(task.ID, assigneeID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, after, 1)
|
||||
require.Equal(t, "note", after[0].ObjectType)
|
||||
require.Equal(t, note.Identity, after[0].ObjectID)
|
||||
require.Equal(t, "document", after[0].ObjectType)
|
||||
require.Equal(t, document.Identity, after[0].ObjectID)
|
||||
}
|
||||
|
||||
func TestShareObjectRejectsUnsupportedType(t *testing.T) {
|
||||
@@ -68,12 +68,12 @@ func TestShareObjectRejectsUnsupportedType(t *testing.T) {
|
||||
func TestShareObjectRejectsObjectFromAnotherProject(t *testing.T) {
|
||||
database := newTestDB(t)
|
||||
task := models.SaTask{ProjectID: 1, CreatedBy: 1, Title: "Review"}
|
||||
note := models.SaNote{ProjectID: 2, CreatedBy: 1, Title: "Other project", Markdown: "Private context"}
|
||||
document := models.SaDocument{ProjectID: 2, CreatedBy: 1, Kind: models.DocumentKindFile, Name: "Other project.md", NormalizedName: "other project.md", Extension: ".md", Revision: 1}
|
||||
require.NoError(t, database.Create(&task).Error)
|
||||
require.NoError(t, database.Create(¬e).Error)
|
||||
require.NoError(t, database.Create(&document).Error)
|
||||
service := NewService()
|
||||
|
||||
err := service.ShareObject(task.ID, "note", note.ID)
|
||||
err := service.ShareObject(task.ID, "document", document.ID)
|
||||
|
||||
require.ErrorContains(t, err, "shared object not found in task project")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user