refactor(notes): remove legacy whole-document saves; all block changes go through sync
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m14s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m14s
Phase 5 of the collaboration plan:
- Remove PATCH /documents/{id}/blocks, PatchDocumentBlocks, the
DOC_BLOCKS_PATCHED event and its types, FlushDocumentState and
LoadDocumentState. PUT /documents/{id} rejects blocks (400) and only
changes title, icon, folder and pin; editors may now change title/icon.
- The to-do overview ticks via a single operation on /docs/{id}/sync
(new skipLog flag). Task links set the block tick with a targeted
operation and notify open editors; creating a linked task needs edit
rights.
- Keep the three share tables: DocRole already decides centrally, and
rewriting the visibility queries would only add risk (documented in plan).
- Tests adapted to the new paths; share e2e test now also ticks a to-do in
the overview.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8b7a53daab
commit
aea4324c20
18 files changed
+155
-376
No files matched your search
@@ -6,6 +6,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/sandrodach/mindsift/internal/auth"
|
||||
"github.com/sandrodach/mindsift/internal/collab"
|
||||
"github.com/sandrodach/mindsift/internal/domain"
|
||||
)
|
||||
|
||||
@@ -208,13 +209,13 @@ func TestStoreLifecycle(t *testing.T) {
|
||||
|
||||
// user2 tries to edit read-only doc -> must fail
|
||||
updatedTitle := "Hacked Title"
|
||||
_, err = store.UpdateDocument(user2.ID, newDoc.ID, &updatedTitle, nil, nil, nil, nil, nil)
|
||||
_, err = store.UpdateDocument(user2.ID, newDoc.ID, &updatedTitle, nil, nil, nil)
|
||||
if err == nil {
|
||||
t.Fatalf("expected UpdateDocument to fail for read-only user, but succeeded")
|
||||
}
|
||||
|
||||
// user2 is a read-only collaborator -> editing must fail
|
||||
_, err = store.UpdateDocument(user2.ID, newDoc.ID, &updatedTitle, nil, nil, nil, nil, nil)
|
||||
_, err = store.UpdateDocument(user2.ID, newDoc.ID, &updatedTitle, nil, nil, nil)
|
||||
if err == nil {
|
||||
t.Fatalf("expected UpdateDocument to fail for read-only collaborator, but succeeded")
|
||||
}
|
||||
@@ -271,7 +272,7 @@ func TestPerUserDocPinning(t *testing.T) {
|
||||
|
||||
// user2 pins the shared document (even with read-only access)
|
||||
pinTrue := true
|
||||
updatedDoc2, err := store.UpdateDocument(user2.ID, doc.ID, nil, nil, nil, nil, nil, &pinTrue)
|
||||
updatedDoc2, err := store.UpdateDocument(user2.ID, doc.ID, nil, nil, nil, &pinTrue)
|
||||
if err != nil {
|
||||
t.Fatalf("user2 pinning read-only doc failed: %v", err)
|
||||
}
|
||||
@@ -290,7 +291,7 @@ func TestPerUserDocPinning(t *testing.T) {
|
||||
|
||||
// user1 unpins the doc
|
||||
pinFalse := false
|
||||
_, err = store.UpdateDocument(user1.ID, doc.ID, nil, nil, nil, nil, nil, &pinFalse)
|
||||
_, err = store.UpdateDocument(user1.ID, doc.ID, nil, nil, nil, &pinFalse)
|
||||
if err != nil {
|
||||
t.Fatalf("user1 unpinning doc failed: %v", err)
|
||||
}
|
||||
@@ -518,10 +519,7 @@ func TestWorkspaceBlockHashtagsAndDetails(t *testing.T) {
|
||||
updatedBlocks := []domain.DocumentBlock{
|
||||
{ID: "b1", Type: "bullet", Content: "Einkauf planen #Kühlregal #Vorrat", Level: 0},
|
||||
}
|
||||
_, err = store.UpdateDocument(user.ID, doc.ID, nil, nil, nil, &updatedBlocks, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("UpdateDocument failed: %v", err)
|
||||
}
|
||||
store.replaceDocBlocksInDB(doc.ID, user.ID, updatedBlocks, "")
|
||||
|
||||
tagsAfter, err := store.GetWorkspaceTags(user.ID)
|
||||
if err != nil {
|
||||
@@ -585,43 +583,22 @@ func TestDatabaseFirstWorkspaceArchitecture(t *testing.T) {
|
||||
t.Fatalf("MoveDocument by userB failed: %v", err)
|
||||
}
|
||||
|
||||
// User B is a read-only collaborator -> PatchDocumentBlocks must fail
|
||||
_, _, err = store.PatchDocumentBlocks(userB.ID, doc.ID, domain.DocBlocksPatchRequest{
|
||||
Upserted: []domain.DocumentBlock{
|
||||
{ID: "b2", Type: "bullet", Content: "Zeile 2 von B bearbeitet", Level: 0},
|
||||
{ID: "b3", Type: "bullet", Content: "Zeile 3 neu von B", Level: 0},
|
||||
},
|
||||
BlockOrder: []string{"b1", "b2", "b3"},
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatalf("expected PatchDocumentBlocks by read-only userB to fail, but succeeded")
|
||||
}
|
||||
|
||||
// User A's client only knew about b1, b2 when sending its patch (adds b4)
|
||||
patchedDocA, _, err := store.PatchDocumentBlocks(userA.ID, doc.ID, domain.DocBlocksPatchRequest{
|
||||
Upserted: []domain.DocumentBlock{
|
||||
{ID: "b1", Type: "bullet", Content: "Zeile 1 von A bearbeitet", Level: 0},
|
||||
{ID: "b4", Type: "bullet", Content: "Zeile 4 neu von A", Level: 0},
|
||||
},
|
||||
BlockOrder: []string{"b1", "b2", "b4"}, // stale order without b3!
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("PatchDocumentBlocks by userA failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify User A's blocks are preserved: b1, b2, b4 (3 blocks)
|
||||
if len(patchedDocA.Blocks) != 3 {
|
||||
t.Fatalf("expected 3 blocks after patch, got %d: %+v", len(patchedDocA.Blocks), patchedDocA.Blocks)
|
||||
// User A edits b1 and adds b4 through the operation log; blocks A did not touch stay
|
||||
editedB1, newB4, top := "Zeile 1 von A bearbeitet", "Zeile 4 neu von A", ""
|
||||
res, _, err := store.ApplyDocOps(doc.ID, collab.Actor{UserID: userA.ID, ClientID: "tab-a"}, []collab.Op{
|
||||
{OpID: "a-b1", Kind: collab.OpSetContent, BlockID: "b1", BaseVersion: 1, Content: &editedB1},
|
||||
{OpID: "a-b4", Kind: collab.OpInsert, BlockID: "b4", ParentID: &top, OrderKey: "a9", Content: &newB4},
|
||||
}, nil)
|
||||
if err != nil || res[0].Status != collab.StatusApplied || res[1].Status != collab.StatusApplied {
|
||||
t.Fatalf("ApplyDocOps by userA failed: %+v %v", res, err)
|
||||
}
|
||||
patchedDocA, _ := store.GetDocumentByID(userA.ID, doc.ID)
|
||||
blockContent := make(map[string]string)
|
||||
for _, b := range patchedDocA.Blocks {
|
||||
blockContent[b.ID] = b.Content
|
||||
}
|
||||
if blockContent["b1"] != "Zeile 1 von A bearbeitet" {
|
||||
t.Fatalf("expected b1 updated by A, got %q", blockContent["b1"])
|
||||
}
|
||||
if blockContent["b4"] != "Zeile 4 neu von A" {
|
||||
t.Fatalf("expected b4 added by A, got %q", blockContent["b4"])
|
||||
if blockContent["b1"] != editedB1 || blockContent["b4"] != newB4 || blockContent["b2"] == "" {
|
||||
t.Fatalf("unexpected blocks after edit: %+v", blockContent)
|
||||
}
|
||||
|
||||
// Verify folder independence: User A still sees "Privat", User B still sees "Arbeit/Eingang"
|
||||
@@ -710,7 +687,7 @@ func TestDatabaseFirstWorkspaceArchitecture(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlushDocumentStateAndSchema(t *testing.T) {
|
||||
func TestBlockListAndDerivedData(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
dbPath := filepath.Join(tmpDir, "tabula_rasa_test.db")
|
||||
|
||||
@@ -746,9 +723,8 @@ func TestFlushDocumentStateAndSchema(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
if err := store.FlushDocumentState(doc.ID, "Architektur Notiz", blocks); err != nil {
|
||||
t.Fatalf("FlushDocumentState failed: %v", err)
|
||||
}
|
||||
store.replaceDocBlocksInDB(doc.ID, user.ID, blocks, "")
|
||||
store.applyLegacyTitle(doc.ID, user.ID, "Architektur Notiz")
|
||||
|
||||
updatedDoc, err := store.GetDocumentByID(user.ID, doc.ID)
|
||||
if err != nil {
|
||||
|
||||
Reference in new issue
Block a user