refactor(documents): rename document tree model
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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} {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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":
|
||||
|
||||
@@ -36,7 +36,7 @@ func AutoMigrate(database *gorm.DB) error {
|
||||
&SaInboxItem{},
|
||||
&SaInboxSuggestion{},
|
||||
&SaTask{},
|
||||
&SaDocument{},
|
||||
&SaDocumentTree{},
|
||||
&SaDocumentContent{},
|
||||
&SaDocumentBlob{},
|
||||
&SaDocumentShare{},
|
||||
|
||||
@@ -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))),
|
||||
|
||||
@@ -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{})
|
||||
|
||||
Reference in New Issue
Block a user