fix(canvas): 画布版本 - 合并 PR #528 防止旧内容覆盖并支持历史恢复

将画布 revision CAS 与历史快照接到 schema v23,保留当前顶栏积分弹窗和已有菜单入口。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ddCat
2026-09-18 10:40:23 +08:00
69 changed files with 3571 additions and 293 deletions

View File

@@ -11,7 +11,7 @@ import (
"gorm.io/gorm"
)
const CurrentSchemaVersion int64 = 22
const CurrentSchemaVersion int64 = 23
const baselineSchemaChecksum = "sha256:open-ai-canvas-schema-v1-20260830"
const schemaMigrationAppliedAtIndexChecksum = "sha256:schema-migrations-applied-at-index-v2-20260830"
@@ -86,6 +86,9 @@ var schemaMigrations = []migration{
{version: 22, name: "banner_announcement_notice_type", checksum: "sha256:banner-announcement-notice-type-v22-20260917", apply: func(tx *gorm.DB) error {
return tx.AutoMigrate(&model.BannerAnnouncement{})
}},
{version: 23, name: "canvas_revision_history", checksum: "sha256:canvas-revision-history-v23-20260918", apply: func(tx *gorm.DB) error {
return tx.AutoMigrate(&model.CanvasProject{}, &model.CanvasSnapshot{}, &model.CanvasSnapshotResource{})
}},
}
func migrateSchemaV14(tx *gorm.DB) error {

View File

@@ -307,6 +307,44 @@ func TestMigrateSchemaV22AddsBannerAnnouncementNoticeType(t *testing.T) {
}
}
func TestMigrateSchemaV23BackfillsCanvasRevisions(t *testing.T) {
db, err := Open(Config{Driver: "sqlite", DSN: "file:migration-canvas-revisions-v23?mode=memory&cache=shared"})
if err != nil {
t.Fatal(err)
}
if err := MigrateSchema(db); err != nil {
t.Fatal(err)
}
if err := db.Migrator().DropTable(&model.CanvasSnapshotResource{}, &model.CanvasSnapshot{}); err != nil {
t.Fatal(err)
}
if err := db.Migrator().DropColumn(&model.CanvasProject{}, "Revision"); err != nil {
t.Fatal(err)
}
if err := db.Exec(`INSERT INTO canvas_projects (id, user_id, title, payload_json) VALUES ('legacy', 'owner', 'Existing canvas', '{"nodes":[]}')`).Error; err != nil {
t.Fatal(err)
}
if err := db.Where("version = ?", 23).Delete(&schemaMigration{}).Error; err != nil {
t.Fatal(err)
}
if err := MigrateSchema(db); err != nil {
t.Fatal(err)
}
var project model.CanvasProject
if err := db.First(&project, "id = ?", "legacy").Error; err != nil {
t.Fatal(err)
}
if project.Revision != 1 || project.PayloadJSON != `{"nodes":[]}` {
t.Fatalf("legacy canvas changed: %+v", project)
}
if !db.Migrator().HasTable(&model.CanvasSnapshot{}) || !db.Migrator().HasTable(&model.CanvasSnapshotResource{}) {
t.Fatal("history tables missing")
}
if err := MigrateSchema(db); err != nil {
t.Fatalf("migration not idempotent: %v", err)
}
}
func TestMigrateSchemaV8AllowsReusingArchivedLogicalModelCode(t *testing.T) {
db, err := Open(Config{Driver: "sqlite", DSN: "file:migration-logical-model-active-code?mode=memory&cache=shared"})
if err != nil {

View File

@@ -86,6 +86,8 @@ func Models() []any {
&model.WorkflowStepTask{},
&model.ProductionTaskLink{},
&model.CanvasProject{},
&model.CanvasSnapshot{},
&model.CanvasSnapshotResource{},
&model.CanvasShare{},
&model.PromptTemplate{},
&model.UserPromptCustomization{},