Add trust features: history, trash, export, and collection sharing
release gate / check (push) Failing after 6m36s

Implement Notion-like upgrade phase 4: append-only page revisions with restore-as-a-new-version; recoverable bookmark/page/collection deletions whose restores never resurrect public flags or shares; owner-scoped JSON export; and explicit, revocable read-only collection sharing with owner-only mutations and no transitive third-party bookmark leaks. Add migration 009, API/docs, integration coverage, and page/collections/trash UI.
This commit is contained in:
Andrew S Erwin
2026-10-19 23:07:38 -05:00
parent 9db69efe4b
commit 5eab58f99c
22 changed files with 1496 additions and 76 deletions
+15 -2
View File
@@ -121,6 +121,9 @@ func HandlePagesCreate(w http.ResponseWriter, r *http.Request) {
return e
}
}
if e := capturePageRevision(r, tx, d["id"].(string)); e != nil {
return e
}
// Acknowledge the same snapshot the write produced (the update path's
// CR-18 discipline), so the 201 already carries links/embeds.
v, e = pageView(r.Context(), tx, d["id"].(string), user(r))
@@ -218,6 +221,9 @@ func HandlePagesUpdate(w http.ResponseWriter, r *http.Request) {
if e := syncPageGraph(r.Context(), tx, id, user(r), content); e != nil {
return e
}
if e := capturePageRevision(r, tx, id); e != nil {
return e
}
}
if bid := str(b, "addBookmarkId"); bid != "" {
if _, e := db.One(r.Context(), tx, `SELECT to_jsonb(b) FROM "Bookmark" b WHERE b.id=$1 AND `+db.BookmarkVisible, bid, user(r)); e != nil {
@@ -254,8 +260,15 @@ func HandlePagesBacklinks(w http.ResponseWriter, r *http.Request) {
}
func HandlePagesDelete(w http.ResponseWriter, r *http.Request) {
// Children are promoted to the top level by the self-FK's SET NULL; a
// delete never silently takes nested pages with it.
respond(w, 200, OK{true}, remove(r, "Page", r.PathValue("id"), "ownerId"))
// delete never silently takes nested pages with it. The page, its history
// and its bookmark links are snapshotted for restore.
e := db.Transaction(r.Context(), database(r), func(tx *sql.Tx) error {
if e := lockUser(r, tx); e != nil {
return e
}
return trashPage(r, tx, r.PathValue("id"))
})
respond(w, 200, OK{true}, e)
}
func HandlePagesMarkdown(w http.ResponseWriter, r *http.Request) {
v, e := page(r, r.PathValue("id"))