Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions internal/api/handlers/scene.go
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,8 @@ func (h *SceneHandler) UpdateScene(c *gin.Context) {
// Build update query dynamically
updates := []string{}
args := []interface{}{}
sceneNameForTasks := existing.Name
syncTaskSceneName := false

if req.FactoryID != nil {
factoryIDStr := strings.TrimSpace(*req.FactoryID)
Expand Down Expand Up @@ -462,6 +464,8 @@ func (h *SceneHandler) UpdateScene(c *gin.Context) {
if name != "" {
updates = append(updates, "name = ?")
args = append(args, name)
sceneNameForTasks = name
syncTaskSceneName = name != existing.Name
}
}

Expand Down Expand Up @@ -503,6 +507,13 @@ func (h *SceneHandler) UpdateScene(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update scene"})
return
}
if syncTaskSceneName {
if err := syncTaskSceneNameBySceneID(h.db, id, sceneNameForTasks); err != nil {
logger.Printf("[SCENE] Failed to sync task scene snapshot: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update scene"})
return
}
}

// Fetch the updated scene
var s sceneRow
Expand Down
13 changes: 13 additions & 0 deletions internal/api/handlers/subscene.go
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,19 @@ func (h *SubsceneHandler) UpdateSubscene(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update subscene"})
return
}
if effectiveSceneID != existing.SceneID || finalName != existing.Name {
var sceneName string
if err := h.db.Get(&sceneName, "SELECT name FROM scenes WHERE id = ? AND deleted_at IS NULL", effectiveSceneID); err != nil {
logger.Printf("[SUBSCENE] Failed to query scene name for task sync: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update subscene"})
return
}
if err := syncTaskSubsceneSnapshotBySubsceneID(h.db, id, effectiveSceneID, sceneName, finalName); err != nil {
logger.Printf("[SUBSCENE] Failed to sync task subscene snapshot: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update subscene"})
return
}
}

// Fetch the updated subscene
var s subsceneRow
Expand Down
33 changes: 33 additions & 0 deletions internal/api/handlers/task_snapshot_sync.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: 2026 ArcheBase
//
// SPDX-License-Identifier: MulanPSL-2.0

package handlers

import "database/sql"

type taskSnapshotExecer interface {
Exec(query string, args ...interface{}) (sql.Result, error)
}

func syncTaskSceneNameBySceneID(db taskSnapshotExecer, sceneID int64, sceneName string) error {
_, err := db.Exec(
`UPDATE tasks SET scene_name = ? WHERE scene_id = ? AND deleted_at IS NULL`,
sceneName,
sceneID,
)
return err
}

func syncTaskSubsceneSnapshotBySubsceneID(db taskSnapshotExecer, subsceneID int64, sceneID int64, sceneName string, subsceneName string) error {
_, err := db.Exec(
`UPDATE tasks
SET scene_id = ?, scene_name = ?, subscene_name = ?
WHERE subscene_id = ? AND deleted_at IS NULL`,
sceneID,
sceneName,
subsceneName,
subsceneID,
)
return err
}
121 changes: 121 additions & 0 deletions internal/api/handlers/task_snapshot_sync_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// SPDX-FileCopyrightText: 2026 ArcheBase
//
// SPDX-License-Identifier: MulanPSL-2.0

package handlers

import (
"testing"

"github.com/jmoiron/sqlx"
_ "modernc.org/sqlite"
)

func setupTaskSnapshotSyncDB(t *testing.T) *sqlx.DB {
t.Helper()

db, err := sqlx.Open("sqlite", ":memory:")
if err != nil {
t.Fatalf("open sqlite db: %v", err)
}
t.Cleanup(func() {
if err := db.Close(); err != nil {
t.Fatalf("close sqlite db: %v", err)
}
})

_, err = db.Exec(`
CREATE TABLE tasks (
id INTEGER PRIMARY KEY,
scene_id INTEGER,
subscene_id INTEGER,
scene_name TEXT,
subscene_name TEXT,
deleted_at TEXT
)
`)
if err != nil {
t.Fatalf("create tasks table: %v", err)
}
return db
}

func TestSyncTaskSceneNameBySceneID(t *testing.T) {
db := setupTaskSnapshotSyncDB(t)
if _, err := db.Exec(`
INSERT INTO tasks (id, scene_id, subscene_id, scene_name, subscene_name, deleted_at) VALUES
(1, 10, 20, 'old-scene', 'sub-a', NULL),
(2, 10, 21, 'old-scene', 'sub-b', '2026-01-01'),
(3, 11, 22, 'other-scene', 'sub-c', NULL)
`); err != nil {
t.Fatalf("insert tasks: %v", err)
}

if err := syncTaskSceneNameBySceneID(db, 10, "new-scene"); err != nil {
t.Fatalf("syncTaskSceneNameBySceneID returned error: %v", err)
}

rows := []struct {
ID int `db:"id"`
SceneName string `db:"scene_name"`
}{}
if err := db.Select(&rows, "SELECT id, scene_name FROM tasks ORDER BY id"); err != nil {
t.Fatalf("select tasks: %v", err)
}
want := map[int]string{1: "new-scene", 2: "old-scene", 3: "other-scene"}
for _, row := range rows {
if row.SceneName != want[row.ID] {
t.Fatalf("task %d scene_name = %q, want %q", row.ID, row.SceneName, want[row.ID])
}
}
}

func TestSyncTaskSubsceneSnapshotBySubsceneID(t *testing.T) {
db := setupTaskSnapshotSyncDB(t)
if _, err := db.Exec(`
INSERT INTO tasks (id, scene_id, subscene_id, scene_name, subscene_name, deleted_at) VALUES
(1, 10, 20, 'old-scene', 'old-subscene', NULL),
(2, 10, 20, 'old-scene', 'old-subscene', '2026-01-01'),
(3, 10, 21, 'old-scene', 'other-subscene', NULL)
`); err != nil {
t.Fatalf("insert tasks: %v", err)
}

if err := syncTaskSubsceneSnapshotBySubsceneID(db, 20, 11, "new-scene", "new-subscene"); err != nil {
t.Fatalf("syncTaskSubsceneSnapshotBySubsceneID returned error: %v", err)
}

rows := []struct {
ID int `db:"id"`
SceneID int `db:"scene_id"`
SceneName string `db:"scene_name"`
SubsceneName string `db:"subscene_name"`
}{}
if err := db.Select(&rows, "SELECT id, scene_id, scene_name, subscene_name FROM tasks ORDER BY id"); err != nil {
t.Fatalf("select tasks: %v", err)
}

want := map[int]struct {
sceneID int
sceneName string
subsceneName string
}{
1: {sceneID: 11, sceneName: "new-scene", subsceneName: "new-subscene"},
2: {sceneID: 10, sceneName: "old-scene", subsceneName: "old-subscene"},
3: {sceneID: 10, sceneName: "old-scene", subsceneName: "other-subscene"},
}
for _, row := range rows {
w := want[row.ID]
if row.SceneID != w.sceneID || row.SceneName != w.sceneName || row.SubsceneName != w.subsceneName {
t.Fatalf("task %d = (%d, %q, %q), want (%d, %q, %q)",
row.ID,
row.SceneID,
row.SceneName,
row.SubsceneName,
w.sceneID,
w.sceneName,
w.subsceneName,
)
}
}
}
Loading