M6: export — download all your notes as a zip
Data portability / no lock-in (task 1907, export half). GET /api/notes/export streams a zip of the caller's notes: a machine-readable notes.json (notes + labels + items + reminders + attachment refs), a human-readable Markdown file per note (frontmatter + body / checklist), and the attachment media. Sidebar 'Export' link (same-origin GET, session cookie) downloads it. _slugify unit-tested. Import (Google Keep Takeout) is the follow-up increment of this task. Pure backend + small frontend; no migration. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
This commit is contained in:
@@ -5,6 +5,7 @@ from thoughtsync.models.note import NOTE_COLORS, Note
|
||||
from thoughtsync.notes import (
|
||||
_escape_like,
|
||||
_parse_iso_dt,
|
||||
_slugify,
|
||||
derive_display_title,
|
||||
is_empty_note,
|
||||
normalize_color,
|
||||
@@ -196,6 +197,20 @@ async def test_reminders_requires_auth(app):
|
||||
assert resp.status_code == 401
|
||||
|
||||
|
||||
def test_slugify():
|
||||
assert _slugify("My Great Note!") == "my-great-note"
|
||||
assert _slugify(" spaced / weird __name ") == "spaced-weird-name"
|
||||
assert _slugify("") == "note" # empty falls back
|
||||
assert _slugify("!!!") == "note" # all punctuation strips to empty → fallback
|
||||
assert len(_slugify("x" * 100)) == 60
|
||||
|
||||
|
||||
async def test_export_requires_auth(app):
|
||||
client = app.test_client()
|
||||
resp = await client.get("/api/notes/export")
|
||||
assert resp.status_code == 401
|
||||
|
||||
|
||||
async def test_list_revisions_requires_auth(app):
|
||||
client = app.test_client()
|
||||
resp = await client.get("/api/notes/00000000-0000-0000-0000-000000000000/revisions")
|
||||
|
||||
Reference in New Issue
Block a user