refactor(documents): rename document tree model

This commit is contained in:
2026-07-24 12:23:18 +08:00
parent 1578c81a89
commit ab8ba3c03f
24 changed files with 77 additions and 77 deletions

View File

@@ -159,7 +159,7 @@ func TestCreateAISessionWithoutKeyReturnsAuditedErrorAndCreatesNoFormalObjects(t
for _, model := range []any{
&models.SaAISession{},
&models.SaTask{},
&models.SaDocument{},
&models.SaDocumentTree{},
} {
var count int64
require.NoError(t, database.Model(model).Count(&count).Error)
@@ -272,7 +272,7 @@ func TestCreateAISessionReturnsIdentityDTOAndCompleteAuditWithoutAutomaticObject
require.Equal(t, "ai_session_create", call.Action)
require.Equal(t, "ready", call.Status)
require.Empty(t, call.Error)
for _, model := range []any{&models.SaTask{}, &models.SaDocument{}} {
for _, model := range []any{&models.SaTask{}, &models.SaDocumentTree{}} {
var count int64
require.NoError(t, database.Model(model).Count(&count).Error)
require.Zero(t, count)

View File

@@ -90,7 +90,7 @@ func TestDatasetAPIConnectsSourcesItemsAndSyncTasks(t *testing.T) {
"projectId": project.Identity,
})
require.Equal(t, http.StatusCreated, deposited.Code)
var depositedDocument models.SaDocument
var depositedDocument models.SaDocumentTree
require.NoError(t, database.Where("project_id = ?", project.ID).First(&depositedDocument).Error)
require.Equal(t, item.Title+".md", depositedDocument.Name)
var depositedContent models.SaDocumentContent
@@ -241,7 +241,7 @@ func newDatasetTestDatabase(t *testing.T) *gorm.DB {
&models.SaDatasetSource{},
&models.SaDatasetItem{},
&models.SaProject{},
&models.SaDocument{},
&models.SaDocumentTree{},
&models.SaDocumentContent{},
))
return database

View File

@@ -75,7 +75,7 @@ type ItemPatch struct {
type DepositResult struct {
DocumentIdentity string
ProjectIdentity string
ProjectIdentity string
}
type SourceRecord struct {
@@ -363,9 +363,9 @@ func (s *Service) DepositItem(userID uint, itemIdentity, projectIdentity string)
if !strings.HasSuffix(strings.ToLower(name), ".md") {
name += ".md"
}
var document models.SaDocument
var document models.SaDocumentTree
err = s.db.Transaction(func(tx *gorm.DB) error {
document = models.SaDocument{
document = models.SaDocumentTree{
ProjectID: project.ID, CreatedBy: userID, Kind: models.DocumentKindFile,
Name: name, NormalizedName: strings.ToLower(name), Extension: ".md",
MimeType: "text/markdown; charset=utf-8", Size: int64(len([]byte(body))),

View File

@@ -145,7 +145,7 @@ func (h *Handler) create(c *gin.Context, folder bool) {
}
input.SourceInboxItemID = &inbox.ID
}
var document *models.SaDocument
var document *models.SaDocumentTree
var err error
if folder {
document, err = h.service.CreateFolder(userID, projectID, input)
@@ -496,7 +496,7 @@ func requestScope(c *gin.Context) (uint, string, bool) {
return userID, projectID, ok
}
func documentDTO(document models.SaDocument, markdown *string, hasBlob bool) DocumentDTO {
func documentDTO(document models.SaDocumentTree, markdown *string, hasBlob bool) DocumentDTO {
return DocumentDTO{
ID: document.Identity, ProjectID: document.ProjectIdentity, ParentID: document.ParentIdentity,
Kind: document.Kind, Name: document.Name, Extension: document.Extension, MimeType: document.MimeType,

View File

@@ -10,7 +10,7 @@ import (
func TestSharedPreviewBodySanitizesMarkdownHTMLAndUnsafeLinks(t *testing.T) {
body := sharedPreviewBody(SharedDocument{
Document: models.SaDocument{Name: "shared.md", Extension: ".md"},
Document: models.SaDocumentTree{Name: "shared.md", Extension: ".md"},
Content: &models.SaDocumentContent{
Markdown: "# 标题\n\n<script>alert(1)</script>\n\n[危险链接](javascript:alert(2))",
},
@@ -23,7 +23,7 @@ func TestSharedPreviewBodySanitizesMarkdownHTMLAndUnsafeLinks(t *testing.T) {
func TestSharedPreviewBodyEmbedsSupportedBlobWithoutDownloadLink(t *testing.T) {
body := sharedPreviewBody(SharedDocument{
Document: models.SaDocument{Name: "manual.pdf", Extension: ".pdf", MimeType: "application/pdf"},
Document: models.SaDocumentTree{Name: "manual.pdf", Extension: ".pdf", MimeType: "application/pdf"},
Blob: &models.SaDocumentBlob{StorageKey: "blob"},
}, "https://example.test/content")

View File

@@ -69,7 +69,7 @@ type StoredBlob struct {
}
type SharedDocument struct {
Document models.SaDocument
Document models.SaDocumentTree
Content *models.SaDocumentContent
Blob *models.SaDocumentBlob
}
@@ -94,26 +94,26 @@ func (s *Service) database() *gorm.DB {
return models.DBService
}
func (s *Service) List(userID uint, projectIdentity string) ([]models.SaDocument, error) {
func (s *Service) List(userID uint, projectIdentity string) ([]models.SaDocumentTree, error) {
project, err := s.ownedProject(userID, projectIdentity)
if err != nil {
return nil, err
}
var documents []models.SaDocument
var documents []models.SaDocumentTree
err = s.database().Where("project_id = ?", project.ID).
Order("kind asc, normalized_name asc, id asc").Find(&documents).Error
return documents, err
}
func (s *Service) CreateFolder(userID uint, projectIdentity string, input CreateInput) (*models.SaDocument, error) {
func (s *Service) CreateFolder(userID uint, projectIdentity string, input CreateInput) (*models.SaDocumentTree, error) {
return s.createNode(userID, projectIdentity, input, models.DocumentKindFolder, "", "", 0)
}
func (s *Service) CreateMarkdown(userID uint, projectIdentity string, input CreateInput) (*models.SaDocument, error) {
func (s *Service) CreateMarkdown(userID uint, projectIdentity string, input CreateInput) (*models.SaDocumentTree, error) {
if strings.TrimSpace(filepath.Ext(input.Name)) == "" {
input.Name += ".md"
}
var created *models.SaDocument
var created *models.SaDocumentTree
err := s.database().Transaction(func(tx *gorm.DB) error {
service := NewService(tx, s.root)
document, err := service.createNode(userID, projectIdentity, input, models.DocumentKindFile, ".md", "text/markdown; charset=utf-8", int64(len([]byte(input.Markdown))))
@@ -129,7 +129,7 @@ func (s *Service) CreateMarkdown(userID uint, projectIdentity string, input Crea
return created, err
}
func (s *Service) createNode(userID uint, projectIdentity string, input CreateInput, kind, extension, mimeType string, size int64) (*models.SaDocument, error) {
func (s *Service) createNode(userID uint, projectIdentity string, input CreateInput, kind, extension, mimeType string, size int64) (*models.SaDocumentTree, error) {
project, err := s.ownedProject(userID, projectIdentity)
if err != nil {
return nil, err
@@ -149,11 +149,11 @@ func (s *Service) createNode(userID uint, projectIdentity string, input CreateIn
if kind == models.DocumentKindFile {
extension = strings.ToLower(filepath.Ext(name))
}
document := &models.SaDocument{
document := &models.SaDocumentTree{
ProjectID: project.ID, ProjectIdentity: project.Identity,
ParentID: parentID, ParentIdentity: parentIdentity,
ParentScope: identityValue(parentIdentity),
CreatedBy: userID, SourceInboxItemID: input.SourceInboxItemID,
CreatedBy: userID, SourceInboxItemID: input.SourceInboxItemID,
Kind: kind, Name: name, NormalizedName: normalizeName(name),
Extension: extension, MimeType: mimeType, Size: size, Revision: 1,
ScanStatus: "not_scanned",
@@ -167,8 +167,8 @@ func (s *Service) createNode(userID uint, projectIdentity string, input CreateIn
return document, nil
}
func (s *Service) CreateUploaded(userID uint, projectIdentity, parentIdentity string, stored StoredBlob) (*models.SaDocument, error) {
var created *models.SaDocument
func (s *Service) CreateUploaded(userID uint, projectIdentity, parentIdentity string, stored StoredBlob) (*models.SaDocumentTree, error) {
var created *models.SaDocumentTree
err := s.database().Transaction(func(tx *gorm.DB) error {
service := NewService(tx, s.root)
document, err := service.createNode(userID, projectIdentity, CreateInput{
@@ -197,7 +197,7 @@ func (s *Service) Get(userID uint, projectIdentity, documentIdentity string) (*S
if err != nil {
return nil, err
}
var document models.SaDocument
var document models.SaDocumentTree
if err := s.database().Where("identity = ? AND project_id = ?", strings.TrimSpace(documentIdentity), project.ID).First(&document).Error; err != nil {
return nil, err
}
@@ -212,7 +212,7 @@ func (s *Service) Update(userID uint, projectIdentity, documentIdentity string,
if err != nil {
return err
}
var document models.SaDocument
var document models.SaDocumentTree
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
Where("identity = ? AND project_id = ?", strings.TrimSpace(documentIdentity), project.ID).
First(&document).Error; err != nil {
@@ -306,7 +306,7 @@ func (s *Service) Delete(userID uint, projectIdentity, documentIdentity string)
if err != nil {
return err
}
var document models.SaDocument
var document models.SaDocumentTree
if err := s.database().Where("identity = ? AND project_id = ?", documentIdentity, project.ID).First(&document).Error; err != nil {
return err
}
@@ -314,7 +314,7 @@ func (s *Service) Delete(userID uint, projectIdentity, documentIdentity string)
if err != nil {
return err
}
return s.database().Where("id IN ?", ids).Delete(&models.SaDocument{}).Error
return s.database().Where("id IN ?", ids).Delete(&models.SaDocumentTree{}).Error
}
func (s *Service) Store(projectIdentity, originalName string, source io.Reader) (StoredBlob, error) {
@@ -482,7 +482,7 @@ func (s *Service) ResolveShare(token string) (*SharedDocument, error) {
).First(&share).Error; err != nil {
return nil, ErrShareNotFound
}
var document models.SaDocument
var document models.SaDocumentTree
if err := s.database().Where("id = ?", share.DocumentID).First(&document).Error; err != nil {
return nil, ErrShareNotFound
}
@@ -508,7 +508,7 @@ func (s *Service) Export(document SharedDocument, format string) ([]byte, string
}
}
func (s *Service) loadDocument(document models.SaDocument) (*SharedDocument, error) {
func (s *Service) loadDocument(document models.SaDocumentTree) (*SharedDocument, error) {
result := &SharedDocument{Document: document}
var content models.SaDocumentContent
if err := s.database().Where("document_id = ?", document.ID).First(&content).Error; err == nil {
@@ -536,7 +536,7 @@ func (s *Service) resolveParent(projectID uint, identity string) (*uint, *string
if identity == "" {
return nil, nil, nil
}
var parent models.SaDocument
var parent models.SaDocumentTree
if err := s.database().Where("identity = ? AND project_id = ? AND kind = ?", identity, projectID, models.DocumentKindFolder).First(&parent).Error; err != nil {
return nil, nil, ErrInvalidParent
}
@@ -559,7 +559,7 @@ func (s *Service) availableName(projectID uint, parentID *uint, name string) (st
}
func (s *Service) nameExists(projectID uint, parentID *uint, normalized string, exceptID uint) bool {
query := s.database().Model(&models.SaDocument{}).Where("project_id = ? AND normalized_name = ?", projectID, normalized)
query := s.database().Model(&models.SaDocumentTree{}).Where("project_id = ? AND normalized_name = ?", projectID, normalized)
if parentID == nil {
query = query.Where("parent_id IS NULL")
} else {
@@ -578,7 +578,7 @@ func (s *Service) isDescendant(ancestorID, candidateID uint) (bool, error) {
if current == ancestorID {
return true, nil
}
var document models.SaDocument
var document models.SaDocumentTree
if err := s.database().Unscoped().Select("parent_id").Where("id = ?", current).First(&document).Error; err != nil {
return false, err
}
@@ -594,7 +594,7 @@ func (s *Service) subtreeIDs(projectID, rootID uint) ([]uint, error) {
result := []uint{rootID}
frontier := []uint{rootID}
for len(frontier) > 0 {
var children []models.SaDocument
var children []models.SaDocumentTree
if err := s.database().Select("id").Where("project_id = ? AND parent_id IN ?", projectID, frontier).Find(&children).Error; err != nil {
return nil, err
}

View File

@@ -60,10 +60,10 @@ func TestDocumentTreeRevisionMoveDeleteAndShareLifecycle(t *testing.T) {
require.NoError(t, service.Delete(user.ID, project.Identity, folder.Identity))
var visible int64
require.NoError(t, database.Model(&models.SaDocument{}).Where("project_id = ?", project.ID).Count(&visible).Error)
require.NoError(t, database.Model(&models.SaDocumentTree{}).Where("project_id = ?", project.ID).Count(&visible).Error)
require.Zero(t, visible)
var retained int64
require.NoError(t, database.Unscoped().Model(&models.SaDocument{}).Where("project_id = ?", project.ID).Count(&retained).Error)
require.NoError(t, database.Unscoped().Model(&models.SaDocumentTree{}).Where("project_id = ?", project.ID).Count(&retained).Error)
require.Equal(t, int64(3), retained)
}

View File

@@ -279,7 +279,7 @@ func createConfirmedObject(tx *gorm.DB, item models.SaInboxItem, suggestion mode
if !strings.HasSuffix(strings.ToLower(name), ".md") {
name += ".md"
}
document := models.SaDocument{
document := models.SaDocumentTree{
ProjectID: item.ProjectID, ProjectIdentity: item.ProjectIdentity, CreatedBy: item.CreatedBy,
SourceInboxItemID: &sourceInboxItemID, Kind: models.DocumentKindFile, Name: name,
NormalizedName: strings.ToLower(name), Extension: ".md", MimeType: "text/markdown; charset=utf-8",

View File

@@ -155,7 +155,7 @@ func TestConfirmCreatesOnlySelectedSavedSuggestionsWithInboxIdentity(t *testing.
require.Equal(t, fixture.owner.ID, task.CreatedBy)
requireSourceInboxIdentity(t, item, task.SourceInboxItemID, task.SourceInboxItemIdentity)
var documents []models.SaDocument
var documents []models.SaDocumentTree
require.NoError(t, fixture.database.Order("id asc").Find(&documents).Error)
require.Len(t, documents, 2)
require.Equal(t, "会议纪要.md", documents[0].Name)
@@ -231,7 +231,7 @@ func TestConfirmIsTransactionalWhenASelectedWriteFails(t *testing.T) {
const callbackName = "test:fail_inbox_document_create"
require.NoError(t, fixture.database.Callback().Create().Before("gorm:create").Register(callbackName, func(tx *gorm.DB) {
document, ok := tx.Statement.Dest.(*models.SaDocument)
document, ok := tx.Statement.Dest.(*models.SaDocumentTree)
if ok && document.Name == "失败资料.md" {
tx.AddError(errors.New("simulated source write failure"))
}
@@ -266,7 +266,7 @@ func TestRepeatedConfirmReturnsPersistedResultAfterRelatedObjectsChange(t *testi
requireFormalObjectCounts(t, fixture.database, 1, 1, 0)
sourceInboxItemID := item.ID
extraDocument := models.SaDocument{
extraDocument := models.SaDocumentTree{
ProjectID: item.ProjectID, CreatedBy: fixture.owner.ID, SourceInboxItemID: &sourceInboxItemID,
Kind: models.DocumentKindFile, Name: "后续关联资料.md", NormalizedName: "后续关联资料.md",
Extension: ".md", Revision: 1,
@@ -348,7 +348,7 @@ func requireFormalObjectCounts(t *testing.T, database *gorm.DB, tasks, notes, so
want int64
}{
{model: &models.SaTask{}, want: tasks},
{model: &models.SaDocument{}, want: notes + sources},
{model: &models.SaDocumentTree{}, want: notes + sources},
} {
var count int64
require.NoError(t, database.Model(check.model).Count(&count).Error)

View File

@@ -127,8 +127,8 @@ func TestWorkspaceMatchesFrontendContract(t *testing.T) {
require.NoError(t, database.Create(&models.SaTask{ProjectID: project.ID, CreatedBy: 1, Title: "Confirm IA", Description: "Review channel layout.", Status: "open", DueAt: &nextRun}).Error)
require.NoError(t, database.Create(&models.SaTask{ProjectID: project.ID, CreatedBy: 1, Title: "Archive notes", Description: "Move outdated notes.", Status: "done"}).Error)
require.NoError(t, database.Create(&models.SaAISession{ProjectID: project.ID, CreatedBy: 1, Title: "UI prototype critique", Context: "Discuss channel behavior."}).Error)
require.NoError(t, database.Create(&models.SaDocument{ProjectID: project.ID, CreatedBy: 1, Kind: models.DocumentKindFile, Name: "Workbench principles.md", NormalizedName: "workbench principles.md", Extension: ".md", Revision: 1}).Error)
require.NoError(t, database.Create(&models.SaDocument{ProjectID: project.ID, CreatedBy: 1, Kind: models.DocumentKindFile, Name: "Reference board.pdf", NormalizedName: "reference board.pdf", Extension: ".pdf", Revision: 1}).Error)
require.NoError(t, database.Create(&models.SaDocumentTree{ProjectID: project.ID, CreatedBy: 1, Kind: models.DocumentKindFile, Name: "Workbench principles.md", NormalizedName: "workbench principles.md", Extension: ".md", Revision: 1}).Error)
require.NoError(t, database.Create(&models.SaDocumentTree{ProjectID: project.ID, CreatedBy: 1, Kind: models.DocumentKindFile, Name: "Reference board.pdf", NormalizedName: "reference board.pdf", Extension: ".pdf", Revision: 1}).Error)
require.NoError(t, database.Create(&models.SaProjectChannel{ProjectID: project.ID, Title: "Roadmap Board", Icon: "link", URL: "https://example.com/roadmap-a1", SortOrder: 7}).Error)
require.NoError(t, database.Create(&models.SaProjectChannel{ProjectID: other.ID, Title: "Other Board", Icon: "link", URL: "https://example.com/other", SortOrder: 7}).Error)
require.NoError(t, database.Create(&models.SaCronPlan{ProjectID: project.ID, CreatedBy: 1, Title: "Weekly inbox review reminder", Schedule: "0 9 * * 1", NextRunAt: &nextRun, Enabled: true, LastResult: "Not run yet"}).Error)

View File

@@ -10,11 +10,11 @@ import (
)
type workspaceCounts struct {
inbox int64
tasks int64
aiSessions int64
documents int64
cronPlans int64
inbox int64
tasks int64
aiSessions int64
documents int64
cronPlans int64
}
// Workspace 在确认项目归属后执行首屏聚合;后续子查询只接收已授权的内部主键。
@@ -90,7 +90,7 @@ func (s *Service) workspaceCounts(projectID uint) (workspaceCounts, error) {
if err := models.DBService.Model(&models.SaAISession{}).Where("project_id = ?", projectID).Count(&counts.aiSessions).Error; err != nil {
return counts, err
}
if err := models.DBService.Model(&models.SaDocument{}).Where("project_id = ?", projectID).Count(&counts.documents).Error; err != nil {
if err := models.DBService.Model(&models.SaDocumentTree{}).Where("project_id = ?", projectID).Count(&counts.documents).Error; err != nil {
return counts, err
}
if err := models.DBService.Model(&models.SaCronPlan{}).Where("project_id = ?", projectID).Count(&counts.cronPlans).Error; err != nil {
@@ -224,7 +224,7 @@ func (s *Service) workspaceAISessions(projectID uint, projectIdentity string) ([
}
func (s *Service) workspaceDocuments(projectID uint, projectIdentity string) ([]WorkspaceDocumentDTO, error) {
var documents []models.SaDocument
var documents []models.SaDocumentTree
if err := models.DBService.Where("project_id = ?", projectID).Order("updated_at desc, id desc").Limit(50).Find(&documents).Error; err != nil {
return nil, err
}

View File

@@ -33,8 +33,8 @@ func TestWorkspaceUsesIdentitiesUTCTimesAndProjectScopedTags(t *testing.T) {
inbox := models.SaInboxItem{ProjectID: project.ID, CreatedBy: 1, SourceType: "Manual", Title: "Collect notes", Body: "Turn research into tasks.", Status: "open", CreatedAt: createdAt, UpdatedAt: updatedAt}
task := models.SaTask{ProjectID: project.ID, CreatedBy: 1, TagID: &projectTag.ID, Title: "Confirm IA", Description: "Review channel layout.", Status: "done", DueAt: &nextRun, CreatedAt: createdAt, UpdatedAt: updatedAt}
session := models.SaAISession{ProjectID: project.ID, CreatedBy: 1, Title: "UI critique", Context: "Discuss channel behavior.", CreatedAt: createdAt, UpdatedAt: updatedAt}
firstDocument := models.SaDocument{ProjectID: project.ID, CreatedBy: 1, Kind: models.DocumentKindFile, Name: "Workbench principles.md", NormalizedName: "workbench principles.md", Extension: ".md", MimeType: "text/markdown", Revision: 1, CreatedAt: createdAt, UpdatedAt: updatedAt}
secondDocument := models.SaDocument{ProjectID: project.ID, CreatedBy: 1, Kind: models.DocumentKindFile, Name: "Reference board.pdf", NormalizedName: "reference board.pdf", Extension: ".pdf", MimeType: "application/pdf", Revision: 1, CreatedAt: createdAt, UpdatedAt: updatedAt}
firstDocument := models.SaDocumentTree{ProjectID: project.ID, CreatedBy: 1, Kind: models.DocumentKindFile, Name: "Workbench principles.md", NormalizedName: "workbench principles.md", Extension: ".md", MimeType: "text/markdown", Revision: 1, CreatedAt: createdAt, UpdatedAt: updatedAt}
secondDocument := models.SaDocumentTree{ProjectID: project.ID, CreatedBy: 1, Kind: models.DocumentKindFile, Name: "Reference board.pdf", NormalizedName: "reference board.pdf", Extension: ".pdf", MimeType: "application/pdf", Revision: 1, CreatedAt: createdAt, UpdatedAt: updatedAt}
channel := models.SaProjectChannel{ProjectID: project.ID, Title: "Roadmap Board", Icon: "link", URL: "https://example.com/roadmap", SortOrder: 7, CreatedAt: createdAt, UpdatedAt: updatedAt}
plan := models.SaCronPlan{ProjectID: project.ID, CreatedBy: 1, Title: "Weekly review", Schedule: "0 9 * * 1", NextRunAt: &nextRun, Enabled: true, CreatedAt: createdAt, UpdatedAt: updatedAt}
for _, record := range []any{&inbox, &task, &session, &firstDocument, &secondDocument, &channel, &plan} {

View File

@@ -92,7 +92,7 @@ func (s *Service) Search(userID uint, query string) ([]SearchResultDTO, error) {
})
}
var documents []models.SaDocument
var documents []models.SaDocumentTree
documentFilter := fmt.Sprintf(`(sa_projects.owner_id = ? OR EXISTS (
SELECT 1 FROM sa_task_shares
JOIN sa_tasks ON sa_tasks.id = sa_task_shares.task_id

View File

@@ -21,7 +21,7 @@ func TestPostgresSearchMatchesChinesePartialKeywords(t *testing.T) {
project := models.SaProject{OwnerID: owner.ID, Name: "中文回调平台", Identifier: "PG-CALLBACK"}
require.NoError(t, database.Create(&project).Error)
require.NoError(t, database.Create(&models.SaTask{ProjectID: project.ID, CreatedBy: owner.ID, Title: "回调任务", Status: "open"}).Error)
createPostgresSearchDocument(t, database, project, owner.ID, models.SaDocument{Name: "说明.md"}, "回调验签流程")
createPostgresSearchDocument(t, database, project, owner.ID, models.SaDocumentTree{Name: "说明.md"}, "回调验签流程")
results, err := NewService(database).Search(owner.ID, "回调")
@@ -38,8 +38,8 @@ func TestPostgresSearchKeepsAssignmentAndExplicitShareBoundaries(t *testing.T) {
assigned := models.SaTask{ProjectID: project.ID, CreatedBy: owner.ID, AssigneeID: &viewer.ID, Title: "边界词已指派", Status: "open"}
require.NoError(t, database.Create(&assigned).Error)
require.NoError(t, database.Create(&models.SaTask{ProjectID: project.ID, CreatedBy: owner.ID, Title: "边界词私有任务", Status: "open"}).Error)
sharedDocument := createPostgresSearchDocument(t, database, project, owner.ID, models.SaDocument{Name: "边界词共享笔记.md"}, "")
createPostgresSearchDocument(t, database, project, owner.ID, models.SaDocument{Name: "边界词私有笔记.md"}, "")
sharedDocument := createPostgresSearchDocument(t, database, project, owner.ID, models.SaDocumentTree{Name: "边界词共享笔记.md"}, "")
createPostgresSearchDocument(t, database, project, owner.ID, models.SaDocumentTree{Name: "边界词私有笔记.md"}, "")
require.NoError(t, database.Create(&models.SaTaskShare{TaskID: assigned.ID, ObjectType: "document", ObjectID: sharedDocument.ID}).Error)
results, err := NewService(database).Search(viewer.ID, "边界词")
@@ -70,7 +70,7 @@ func TestPostgresSearchUsesStableFairLimitAndRuneBoundedSnippets(t *testing.T) {
Status: "open",
UpdatedAt: baseTime.Add(time.Duration(index) * time.Minute),
}).Error)
createPostgresSearchDocument(t, database, project, owner.ID, models.SaDocument{
createPostgresSearchDocument(t, database, project, owner.ID, models.SaDocumentTree{
Identity: fmt.Sprintf("00000000-0000-7003-8000-%012d", index),
Name: fmt.Sprintf("稳定排序笔记 %02d.md", index),
UpdatedAt: baseTime.Add(time.Duration(index) * time.Minute),
@@ -110,9 +110,9 @@ func createPostgresSearchDocument(
database *gorm.DB,
project models.SaProject,
ownerID uint,
document models.SaDocument,
document models.SaDocumentTree,
markdown string,
) models.SaDocument {
) models.SaDocumentTree {
t.Helper()
document.ProjectID = project.ID
document.ProjectIdentity = project.Identity

View File

@@ -52,7 +52,7 @@ func TestSearchAppliesStableFairGlobalLimitAcrossResultTypes(t *testing.T) {
Status: "open",
UpdatedAt: baseTime.Add(time.Duration(index) * time.Minute),
}).Error)
document := models.SaDocument{
document := models.SaDocumentTree{
Identity: fmt.Sprintf("00000000-0000-7003-8000-%012d", index),
ProjectID: project.ID,
CreatedBy: 7,
@@ -136,9 +136,9 @@ func TestSearchOnlyReturnsNavigableProjectTaskAndDocumentResults(t *testing.T) {
require.Len(t, results, 3)
}
func createSearchDocument(t *testing.T, database *gorm.DB, projectID, userID uint, name, markdown, identity string, updatedAt time.Time) models.SaDocument {
func createSearchDocument(t *testing.T, database *gorm.DB, projectID, userID uint, name, markdown, identity string, updatedAt time.Time) models.SaDocumentTree {
t.Helper()
document := models.SaDocument{
document := models.SaDocumentTree{
Identity: identity, ProjectID: projectID, CreatedBy: userID, Kind: models.DocumentKindFile,
Name: name, NormalizedName: strings.ToLower(name), Extension: ".md",
MimeType: "text/markdown", Size: int64(len([]byte(markdown))), Revision: 1,

View File

@@ -29,7 +29,7 @@ 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"}
document := models.SaDocument{ProjectID: first.ID, CreatedBy: owner.ID, Kind: models.DocumentKindFile, Name: "Old context.md", NormalizedName: "old context.md", Extension: ".md", Revision: 1}
document := models.SaDocumentTree{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(&document).Error)
t.Cleanup(func() {

View File

@@ -50,7 +50,7 @@ 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)
document := models.SaDocument{ProjectID: first.ID, CreatedBy: 1, Kind: models.DocumentKindFile, Name: "旧项目资料.md", NormalizedName: "旧项目资料.md", Extension: ".md", Revision: 1}
document := models.SaDocumentTree{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)))

View File

@@ -269,7 +269,7 @@ func utcOptionalTime(value *time.Time) *time.Time {
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 {
if err := tx.Model(&models.SaDocumentTree{}).Where("id = ? AND project_id = ?", objectID, projectID).Count(&count).Error; err != nil {
return err
}
if count == 0 {

View File

@@ -24,7 +24,7 @@ func TestUpdateLocksTaskBeforeProjectValidation(t *testing.T) {
func TestShareObjectLocksTaskBeforeObjectValidation(t *testing.T) {
database, _, project, task := newTaskLockFixture(t)
document := models.SaDocument{ProjectID: project.ID, CreatedBy: task.CreatedBy, Kind: models.DocumentKindFile, Name: "Context.md", NormalizedName: "context.md", Extension: ".md", Revision: 1}
document := models.SaDocumentTree{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)
@@ -39,7 +39,7 @@ func TestAssigneeOnlySeesExplicitlySharedObjects(t *testing.T) {
database := newTestDB(t)
assigneeID := uint(2)
task := models.SaTask{ProjectID: 1, CreatedBy: 1, AssigneeID: &assigneeID, Title: "处理合同"}
document := models.SaDocument{ProjectID: 1, CreatedBy: 1, Kind: models.DocumentKindFile, Name: "合同背景.md", NormalizedName: "合同背景.md", Extension: ".md", Revision: 1}
document := models.SaDocumentTree{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(&document).Error)
service := NewService()
@@ -68,7 +68,7 @@ func TestShareObjectRejectsUnsupportedType(t *testing.T) {
func TestShareObjectRejectsObjectFromAnotherProject(t *testing.T) {
database := newTestDB(t)
task := models.SaTask{ProjectID: 1, CreatedBy: 1, Title: "Review"}
document := models.SaDocument{ProjectID: 2, CreatedBy: 1, Kind: models.DocumentKindFile, Name: "Other project.md", NormalizedName: "other project.md", Extension: ".md", Revision: 1}
document := models.SaDocumentTree{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(&document).Error)
service := NewService()

View File

@@ -11,7 +11,7 @@ const (
DocumentKindFile = "file"
)
type SaDocument struct {
type SaDocumentTree struct {
ID uint `gorm:"primaryKey"`
Identity string `gorm:"type:char(36);uniqueIndex"`
ProjectID uint `gorm:"index;not null;uniqueIndex:uidx_sa_document_sibling,priority:1"`
@@ -37,6 +37,6 @@ type SaDocument struct {
DeletedAt gorm.DeletedAt `gorm:"index"`
}
func (SaDocument) TableName() string {
func (SaDocumentTree) TableName() string {
return "sa_documents"
}

View File

@@ -75,7 +75,7 @@ func (m *SaTask) BeforeCreate(tx *gorm.DB) error {
return resolveOptionalIdentity(tx, &SaInboxItem{}, m.SourceInboxItemID, &m.SourceInboxItemIdentity)
}
func (m *SaDocument) BeforeCreate(tx *gorm.DB) error {
func (m *SaDocumentTree) BeforeCreate(tx *gorm.DB) error {
if err := ensureIdentity(&m.Identity); err != nil {
return err
}
@@ -85,7 +85,7 @@ func (m *SaDocument) BeforeCreate(tx *gorm.DB) error {
if err := resolveIdentity(tx, &SaUser{}, m.CreatedBy, &m.CreatedByIdentity); err != nil {
return err
}
if err := resolveOptionalIdentity(tx, &SaDocument{}, m.ParentID, &m.ParentIdentity); err != nil {
if err := resolveOptionalIdentity(tx, &SaDocumentTree{}, m.ParentID, &m.ParentIdentity); err != nil {
return err
}
return resolveOptionalIdentity(tx, &SaInboxItem{}, m.SourceInboxItemID, &m.SourceInboxItemIdentity)
@@ -95,7 +95,7 @@ func (m *SaDocumentShare) BeforeCreate(tx *gorm.DB) error {
if err := ensureIdentity(&m.Identity); err != nil {
return err
}
if err := resolveIdentity(tx, &SaDocument{}, m.DocumentID, &m.DocumentIdentity); err != nil {
if err := resolveIdentity(tx, &SaDocumentTree{}, m.DocumentID, &m.DocumentIdentity); err != nil {
return err
}
return nil
@@ -234,7 +234,7 @@ func resolveEntityIdentity(tx *gorm.DB, entityType string, entityID uint, identi
case "task":
return resolveIdentity(tx, &SaTask{}, entityID, identity)
case "document":
return resolveIdentity(tx, &SaDocument{}, entityID, identity)
return resolveIdentity(tx, &SaDocumentTree{}, entityID, identity)
case "ai_session":
return resolveIdentity(tx, &SaAISession{}, entityID, identity)
case "tag":

View File

@@ -36,7 +36,7 @@ func AutoMigrate(database *gorm.DB) error {
&SaInboxItem{},
&SaInboxSuggestion{},
&SaTask{},
&SaDocument{},
&SaDocumentTree{},
&SaDocumentContent{},
&SaDocumentBlob{},
&SaDocumentShare{},

View File

@@ -257,7 +257,7 @@ func firstOrCreateAISession(tx *gorm.DB, session models.SaAISession) error {
}
func firstOrCreateDocument(tx *gorm.DB, projectID, userID uint, name, markdown string) error {
var document models.SaDocument
var document models.SaDocumentTree
err := tx.Where("project_id = ? AND normalized_name = ?", projectID, strings.ToLower(name)).First(&document).Error
if err == nil {
return nil
@@ -265,7 +265,7 @@ func firstOrCreateDocument(tx *gorm.DB, projectID, userID uint, name, markdown s
if err != gorm.ErrRecordNotFound {
return err
}
document = models.SaDocument{
document = models.SaDocumentTree{
ProjectID: projectID, CreatedBy: userID, Kind: models.DocumentKindFile,
Name: name, NormalizedName: strings.ToLower(name), Extension: ".md",
MimeType: "text/markdown; charset=utf-8", Size: int64(len([]byte(markdown))),

View File

@@ -147,7 +147,7 @@ func TestPostgresDemoSeedSerializesConcurrentRunsForSameOwner(t *testing.T) {
assertPostgresDemoCount(t, database, &models.SaTask{}, "project_id IN ?", []any{projectIDs}, 6)
assertPostgresDemoCount(t, database, &models.SaInboxItem{}, "project_id IN ?", []any{projectIDs}, 7)
assertPostgresDemoCount(t, database, &models.SaAISession{}, "project_id = ?", []any{projects[0].ID}, 4)
assertPostgresDemoCount(t, database, &models.SaDocument{}, "project_id = ?", []any{projects[0].ID}, 2)
assertPostgresDemoCount(t, database, &models.SaDocumentTree{}, "project_id = ?", []any{projects[0].ID}, 2)
assertPostgresDemoCount(t, database, &models.SaCronPlan{}, "project_id = ?", []any{projects[0].ID}, 3)
assertPostgresDemoCount(t, database, &models.SaProjectChannel{}, "project_id = ?", []any{projects[0].ID}, 2)
}
@@ -216,7 +216,7 @@ func cleanupPostgresDemoSeed(database *gorm.DB, userID uint) {
}
database.Where("project_id IN ?", projectIDs).Delete(&models.SaProjectEvent{})
database.Where("project_id IN ?", projectIDs).Delete(&models.SaTask{})
database.Where("project_id IN ?", projectIDs).Delete(&models.SaDocument{})
database.Where("project_id IN ?", projectIDs).Delete(&models.SaDocumentTree{})
database.Where("project_id IN ?", projectIDs).Delete(&models.SaAISession{})
database.Where("project_id IN ?", projectIDs).Delete(&models.SaTag{})
database.Where("project_id IN ?", projectIDs).Delete(&models.SaProjectChannel{})