fix(freedoc): widen freeform project_id to BIGINT
Snowflake project IDs exceed int4; upload registration failed with pgx encode errors. Align column and Go types with projects.id.
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
---
|
||||
title: "freeform project_id BIGINT 对齐 projects.id"
|
||||
task_id: "freeform-project-id-bigint-2026-09-22"
|
||||
status: in_progress
|
||||
created: 2026-09-22
|
||||
owner: Cursor
|
||||
task_type: implementation
|
||||
branch: feature/freeform-project-id-bigint-2026-09-22
|
||||
worktree: .worktrees/freeform-project-id-bigint-2026-09-22
|
||||
write_scope: ["deploy/postgres/goose", "internal/freedoc", "internal/server", ".doc"]
|
||||
allowed_environments: ["dev"]
|
||||
mutex: "deploy/postgres/goose + freeform"
|
||||
---
|
||||
|
||||
# freeform project_id BIGINT
|
||||
|
||||
## 问题
|
||||
`POST /api/v1/freeform-documents` 在雪花 `project_id`(>int4 max)时 pgx 编码失败(OID 23),阻塞 UI-11 附件注册。
|
||||
|
||||
## 修复
|
||||
- goose `00063`:`freeform_documents.project_id` → BIGINT
|
||||
- Go `ProjectID`/`ListDocuments` → int64;list query 用 `ParseInt`
|
||||
- 活库已手工 ALTER(schema 仍报 61);合入后 goose 幂等加固
|
||||
|
||||
## 验证
|
||||
- `go test ./internal/freedoc/ -short` PASS(含 snowflake 回归)
|
||||
- 活库 column = bigint
|
||||
@@ -0,0 +1,10 @@
|
||||
-- +goose Up
|
||||
-- freeform_documents.project_id must match projects.id (BIGINT snowflake).
|
||||
-- Prior INT rejected inserts once project IDs exceeded int4 max (~2.1e9).
|
||||
|
||||
ALTER TABLE freeform_documents
|
||||
ALTER COLUMN project_id TYPE BIGINT;
|
||||
|
||||
-- +goose Down
|
||||
ALTER TABLE freeform_documents
|
||||
ALTER COLUMN project_id TYPE INT;
|
||||
@@ -59,6 +59,25 @@ func TestCreateDocument_ClientKeyIdempotentAndBootstrapsLedger(t *testing.T) {
|
||||
assert.NotEqual(t, detail.Document.DocID, otherUser.Document.DocID)
|
||||
}
|
||||
|
||||
func TestCreateDocument_AcceptsSnowflakeProjectID(t *testing.T) {
|
||||
// projects.id is BIGINT; freeform must accept IDs above int4 max (regression: int4 encode 500).
|
||||
const snowflakeProjectID int64 = 1789095399489
|
||||
svc := freedoc.NewService(nil, nil)
|
||||
detail, err := svc.CreateDocument(context.Background(), 42, freedoc.CreateFreeformDocRequest{
|
||||
ProjectID: snowflakeProjectID,
|
||||
Title: "upload-snowflake.txt",
|
||||
ContentMarkdown: "# snowflake project id\n",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, detail)
|
||||
assert.Equal(t, snowflakeProjectID, detail.Document.ProjectID)
|
||||
|
||||
list, err := svc.ListDocuments(context.Background(), 42, snowflakeProjectID, "my_private")
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, list)
|
||||
assert.Equal(t, snowflakeProjectID, list[0].ProjectID)
|
||||
}
|
||||
|
||||
func TestCreateDocument_RejectsEmptyContent(t *testing.T) {
|
||||
svc := freedoc.NewService(nil, nil)
|
||||
_, err := svc.CreateDocument(context.Background(), 1, freedoc.CreateFreeformDocRequest{
|
||||
|
||||
@@ -525,7 +525,7 @@ func (s *Service) GetDocument(ctx context.Context, userID int, docID string) (*F
|
||||
}
|
||||
|
||||
// ListDocuments 按空间与权限列出自由文档概要
|
||||
func (s *Service) ListDocuments(ctx context.Context, userID int, projectID int, space string) ([]FreeformDocSummary, error) {
|
||||
func (s *Service) ListDocuments(ctx context.Context, userID int, projectID int64, space string) ([]FreeformDocSummary, error) {
|
||||
if space != "my_private" && space != "project_shared" && space != "trash" {
|
||||
space = "my_private"
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ var (
|
||||
// FreeformDocument 自由私密/共享文档元数据
|
||||
type FreeformDocument struct {
|
||||
DocID string `json:"doc_id"`
|
||||
ProjectID int `json:"project_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"`
|
||||
@@ -53,7 +53,7 @@ type FreeformDocDetail struct {
|
||||
// FreeformDocSummary 列表项概要
|
||||
type FreeformDocSummary struct {
|
||||
DocID string `json:"doc_id"`
|
||||
ProjectID int `json:"project_id"`
|
||||
ProjectID int64 `json:"project_id"`
|
||||
OwnerUserID int `json:"owner_user_id"`
|
||||
Title string `json:"title"`
|
||||
Space string `json:"space"`
|
||||
@@ -66,14 +66,14 @@ type FreeformDocSummary struct {
|
||||
// CreateFromMessageRequest 从单条消息保存到私密文档请求
|
||||
type CreateFromMessageRequest struct {
|
||||
MessageID int64 `json:"message_id"`
|
||||
ProjectID int `json:"project_id"`
|
||||
ProjectID int64 `json:"project_id"`
|
||||
Title string `json:"title,omitempty"`
|
||||
ExpectedSHA256 string `json:"expected_sha256,omitempty"`
|
||||
}
|
||||
|
||||
// CreateFreeformDocRequest 直接创建空白/自定义自由文档请求(上传附件即时注册事实源)
|
||||
type CreateFreeformDocRequest struct {
|
||||
ProjectID int `json:"project_id"`
|
||||
ProjectID int64 `json:"project_id"`
|
||||
Title string `json:"title"`
|
||||
ContentMarkdown string `json:"content_markdown"`
|
||||
Space string `json:"space,omitempty"` // 默认 'my_private'
|
||||
|
||||
@@ -181,9 +181,11 @@ func (s *Server) handleListFreeformDocs(w http.ResponseWriter, r *http.Request)
|
||||
if space == "" {
|
||||
space = "my_private"
|
||||
}
|
||||
projectID := 0
|
||||
var projectID int64
|
||||
if pStr := r.URL.Query().Get("project_id"); pStr != "" {
|
||||
projectID, _ = strconv.Atoi(pStr)
|
||||
if parsed, err := strconv.ParseInt(pStr, 10, 64); err == nil {
|
||||
projectID = parsed
|
||||
}
|
||||
}
|
||||
|
||||
list, err := s.freeDoc.ListDocuments(r.Context(), userID, projectID, space)
|
||||
|
||||
Reference in New Issue
Block a user