Files
weichen 88e33282db fix(freedoc): persist uploaded file metadata and text body server-side
上传文档跨端不可访问修复(ISSUE-20260927-upload-doc-cross-device):
- 迁移 00076:freeform_documents 增加 source_filename/mime/size_bytes/asset_id
- freedoc:Create/Get/List 读写源文件元数据,负值拒绝
- 前端:注册前全部附件先登记 CAS 并预读文本正文;文本正文嵌入注册
  markdown;跨端恢复 sourceType/fileMeta 路由回文件预览器
- 大小显示 formatFileSize(<512B 不再显示 0 KB)
- E2E 新增 FS-UPLOAD-02 跨浏览器上下文可访问性用例

回归:go vet/arch-check/go test -short(隔离 PG)+ web npm test/build 全绿;
存量无元数据旧文档不可恢复(重新上传即可)。
2026-09-27 07:45:15 +00:00

126 lines
5.3 KiB
Go

package freedoc
import (
"errors"
"time"
)
var (
ErrNotFound = errors.New("freeform document not found")
ErrForbidden = errors.New("permission denied to access document")
ErrConflict = errors.New("optimistic concurrency conflict: version or sha mismatch")
ErrInvalidParam = errors.New("invalid parameter")
ErrMessageNotExportable = errors.New("chat message is not exportable or content is empty")
)
// FreeformDocument 自由私密/共享文档元数据
type FreeformDocument struct {
DocID string `json:"doc_id"`
ProjectID int64 `json:"project_id"`
OwnerUserID int `json:"owner_user_id"`
SourceMessageID *int64 `json:"source_message_id,omitempty"`
SourceContentSHA256 string `json:"source_content_sha256"`
ClientKey *string `json:"client_key,omitempty"`
Title string `json:"title"`
GovernanceMode string `json:"governance_mode"` // 恒为 'freeform'
Space string `json:"space"` // 'my_private' | 'project_shared' | 'trash'
CurrentVersionID int `json:"current_version_id"`
CurrentSHA256 string `json:"current_sha256"`
// 上传附件注册时持久化的源文件元数据;非上传文档恒为零值。
SourceFilename string `json:"source_filename,omitempty"`
SourceMimeType string `json:"source_mime_type,omitempty"`
SourceSizeBytes int64 `json:"source_size_bytes,omitempty"`
SourceAssetID int64 `json:"source_asset_id,omitempty"`
IsDeleted bool `json:"is_deleted"`
DeletedAt *time.Time `json:"deleted_at,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// FreeformDocumentVersion 自由文档历史版本快照
type FreeformDocumentVersion struct {
ID int64 `json:"id"`
DocID string `json:"doc_id"`
VersionNo int `json:"version_no"`
ContentSHA256 string `json:"content_sha256"`
ContentMarkdown string `json:"content_markdown"`
CreatedBy int `json:"created_by"`
ChangeSummary string `json:"change_summary"`
CreatedAt time.Time `json:"created_at"`
}
// FreeformDocDetail 自由文档详情(含当前版本 Markdown 正文)
type FreeformDocDetail struct {
Document FreeformDocument `json:"document"`
Content string `json:"content_markdown"`
}
// FreeformDocSummary 列表项概要
type FreeformDocSummary struct {
DocID string `json:"doc_id"`
ProjectID int64 `json:"project_id"`
OwnerUserID int `json:"owner_user_id"`
Title string `json:"title"`
Space string `json:"space"`
CurrentVersionID int `json:"current_version_id"`
CurrentSHA256 string `json:"current_sha256"`
// 上传附件注册时持久化的源文件元数据;非上传文档恒为零值。
SourceFilename string `json:"source_filename,omitempty"`
SourceMimeType string `json:"source_mime_type,omitempty"`
SourceSizeBytes int64 `json:"source_size_bytes,omitempty"`
SourceAssetID int64 `json:"source_asset_id,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// CreateFromMessageRequest 从单条消息保存到私密文档请求
type CreateFromMessageRequest struct {
MessageID int64 `json:"message_id"`
ProjectID int64 `json:"project_id"`
Title string `json:"title,omitempty"`
ExpectedSHA256 string `json:"expected_sha256,omitempty"`
}
// CreateFreeformDocRequest 直接创建空白/自定义自由文档请求(上传附件即时注册事实源)
type CreateFreeformDocRequest struct {
ProjectID int64 `json:"project_id"`
Title string `json:"title"`
ContentMarkdown string `json:"content_markdown"`
Space string `json:"space,omitempty"` // 默认 'my_private'
// ClientKey 可选幂等键(如 doc-upload-{ts}-{idx}),同用户重复提交返回已有文档
ClientKey string `json:"client_key,omitempty"`
// 可选上传源文件元数据:换设备/共享后恢复文件预览与路由的事实源。
SourceFilename string `json:"source_filename,omitempty"`
SourceMimeType string `json:"source_mime_type,omitempty"`
SourceSizeBytes int64 `json:"source_size_bytes,omitempty"`
SourceAssetID int64 `json:"source_asset_id,omitempty"`
}
// UpdateFreeformDocRequest 更新自由文档请求
type UpdateFreeformDocRequest struct {
Title *string `json:"title,omitempty"`
ContentMarkdown *string `json:"content_markdown,omitempty"`
ChangeSummary string `json:"change_summary,omitempty"`
ExpectedVersion *int `json:"expected_version,omitempty"`
ExpectedSHA256 *string `json:"expected_sha256,omitempty"`
}
// summary 投影为列表概要(内存模式与 PG 行扫描共用同一字段集)。
func (d FreeformDocument) summary() FreeformDocSummary {
return FreeformDocSummary{
DocID: d.DocID,
ProjectID: d.ProjectID,
OwnerUserID: d.OwnerUserID,
Title: d.Title,
Space: d.Space,
CurrentVersionID: d.CurrentVersionID,
CurrentSHA256: d.CurrentSHA256,
SourceFilename: d.SourceFilename,
SourceMimeType: d.SourceMimeType,
SourceSizeBytes: d.SourceSizeBytes,
SourceAssetID: d.SourceAssetID,
CreatedAt: d.CreatedAt,
UpdatedAt: d.UpdatedAt,
}
}