Feat v0.4.1 folders (#23)
All checks were successful
All checks were successful
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit was merged in pull request #23.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# Notes — Starlark Backend (v0.1.0)
|
||||
# Notes — Starlark Backend (v0.2.0)
|
||||
#
|
||||
# Markdown notes surface using ext_data.
|
||||
#
|
||||
@@ -76,6 +76,27 @@ def on_request(req):
|
||||
if method == "DELETE" and path.startswith("/notes/"):
|
||||
return _delete_note(path[len("/notes/"):], req)
|
||||
|
||||
# POST /notes/move
|
||||
if method == "POST" and path == "/notes/move":
|
||||
return _move_note(req)
|
||||
|
||||
# ── Folder routes ──────────────────────────
|
||||
# GET /folders
|
||||
if method == "GET" and path == "/folders":
|
||||
return _list_folders()
|
||||
|
||||
# POST /folders
|
||||
if method == "POST" and path == "/folders":
|
||||
return _create_folder(req)
|
||||
|
||||
# PUT /folders/:id
|
||||
if method == "PUT" and path.startswith("/folders/"):
|
||||
return _update_folder(path[len("/folders/"):], req)
|
||||
|
||||
# DELETE /folders/:id
|
||||
if method == "DELETE" and path.startswith("/folders/"):
|
||||
return _delete_folder(path[len("/folders/"):])
|
||||
|
||||
return _resp(404, {"error": "not found"})
|
||||
|
||||
|
||||
@@ -200,6 +221,102 @@ def _delete_note(note_id, req):
|
||||
return _resp(200, {"archived": True})
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════
|
||||
# Move note between folders
|
||||
# ═══════════════════════════════════════════════
|
||||
|
||||
def _move_note(req):
|
||||
body = json.decode(req.get("body", "{}"))
|
||||
note_id = _str(body.get("note_id", ""))
|
||||
folder_id = _str(body.get("folder_id", ""))
|
||||
if not note_id:
|
||||
return _resp(400, {"error": "note_id required"})
|
||||
existing = db.query("notes", filters={"id": note_id}, limit=1)
|
||||
if not existing:
|
||||
return _resp(404, {"error": "note not found"})
|
||||
ok = db.update("notes", note_id, {"folder_id": folder_id, "updated_at": "now"})
|
||||
if not ok:
|
||||
return _resp(500, {"error": "move failed"})
|
||||
rows = db.query("notes", filters={"id": note_id}, limit=1)
|
||||
return _resp(200, rows[0] if rows else {})
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════
|
||||
# Folder CRUD
|
||||
# ═══════════════════════════════════════════════
|
||||
|
||||
def _list_folders():
|
||||
rows = db.query("folders", order="sort_order", limit=500)
|
||||
items = []
|
||||
for r in (rows or []):
|
||||
items.append({
|
||||
"id": r.get("id", ""),
|
||||
"name": r.get("name", ""),
|
||||
"parent_id": r.get("parent_id", ""),
|
||||
"sort_order": _int(r.get("sort_order", 0)),
|
||||
"creator_id": r.get("creator_id", ""),
|
||||
"created_at": r.get("created_at", ""),
|
||||
})
|
||||
return _resp(200, {"data": items})
|
||||
|
||||
|
||||
def _create_folder(req):
|
||||
body = json.decode(req.get("body", "{}"))
|
||||
user_id = req.get("user_id", "")
|
||||
name = _str(body.get("name", ""))
|
||||
if not name:
|
||||
return _resp(400, {"error": "name required"})
|
||||
row = db.insert("folders", {
|
||||
"name": name,
|
||||
"parent_id": _str(body.get("parent_id", "")),
|
||||
"creator_id": user_id,
|
||||
"sort_order": _int(body.get("sort_order", 0)),
|
||||
})
|
||||
return _resp(201, row)
|
||||
|
||||
|
||||
def _update_folder(folder_id, req):
|
||||
body = json.decode(req.get("body", "{}"))
|
||||
existing = db.query("folders", filters={"id": folder_id}, limit=1)
|
||||
if not existing:
|
||||
return _resp(404, {"error": "folder not found"})
|
||||
updates = {}
|
||||
for key in ["name", "parent_id"]:
|
||||
if key in body:
|
||||
updates[key] = _str(body[key])
|
||||
if "sort_order" in body:
|
||||
updates["sort_order"] = _int(body["sort_order"])
|
||||
if not updates:
|
||||
return _resp(200, existing[0])
|
||||
ok = db.update("folders", folder_id, updates)
|
||||
if not ok:
|
||||
return _resp(500, {"error": "update failed"})
|
||||
rows = db.query("folders", filters={"id": folder_id}, limit=1)
|
||||
return _resp(200, rows[0] if rows else {})
|
||||
|
||||
|
||||
def _delete_folder(folder_id):
|
||||
existing = db.query("folders", filters={"id": folder_id}, limit=1)
|
||||
if not existing:
|
||||
return _resp(404, {"error": "folder not found"})
|
||||
parent_id = _str(existing[0].get("parent_id", ""))
|
||||
|
||||
# reparent child folders to deleted folder's parent
|
||||
children = db.query("folders", filters={"parent_id": folder_id}, limit=500)
|
||||
for child in (children or []):
|
||||
db.update("folders", child["id"], {"parent_id": parent_id})
|
||||
|
||||
# orphan notes in this folder (move to unfiled)
|
||||
notes_in_folder = db.query("notes", filters={"folder_id": folder_id}, limit=1000)
|
||||
for n in (notes_in_folder or []):
|
||||
db.update("notes", n["id"], {"folder_id": ""})
|
||||
|
||||
ok = db.delete("folders", folder_id)
|
||||
if not ok:
|
||||
return _resp(500, {"error": "delete failed"})
|
||||
return _resp(200, {"deleted": True, "orphaned_notes": len(notes_in_folder or [])})
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════
|
||||
# Search
|
||||
# ═══════════════════════════════════════════════
|
||||
@@ -241,6 +358,7 @@ def _get_stats():
|
||||
total = 0
|
||||
pinned = 0
|
||||
archived = 0
|
||||
unfiled = 0
|
||||
for n in notes:
|
||||
if _int(n.get("archived", 0)) == 1:
|
||||
archived = archived + 1
|
||||
@@ -248,4 +366,8 @@ def _get_stats():
|
||||
total = total + 1
|
||||
if _int(n.get("pinned", 0)) == 1:
|
||||
pinned = pinned + 1
|
||||
return _resp(200, {"total": total, "pinned": pinned, "archived": archived})
|
||||
if not _str(n.get("folder_id", "")):
|
||||
unfiled = unfiled + 1
|
||||
all_folders = db.query("folders", limit=10000)
|
||||
folder_count = len(all_folders or [])
|
||||
return _resp(200, {"total": total, "pinned": pinned, "archived": archived, "unfiled": unfiled, "folders": folder_count})
|
||||
|
||||
Reference in New Issue
Block a user