update
This commit is contained in:
@@ -9,4 +9,6 @@ DB_PATH = Path(os.getenv("DB_PATH", DATA_DIR / "desktop.db"))
|
||||
# Jeder Client schickt diesen Token als "Authorization: Bearer <Token>"
|
||||
SYNC_TOKEN = os.getenv("SYNC_TOKEN", "")
|
||||
|
||||
COLLECTIONS = ("events", "todos", "notes", "folders")
|
||||
# files: Dateien der PCs (Claude-Sitzungen), kommen nur zurück, wenn der Client sie anfragt
|
||||
COLLECTIONS = ("events", "todos", "notes", "folders", "files")
|
||||
BASE_COLLECTIONS = COLLECTIONS[:-1]
|
||||
|
||||
@@ -5,7 +5,7 @@ from fastapi import FastAPI, Header, HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
||||
from . import db
|
||||
from .config import COLLECTIONS, SYNC_TOKEN
|
||||
from .config import BASE_COLLECTIONS, COLLECTIONS, SYNC_TOKEN
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
|
||||
log = logging.getLogger("app")
|
||||
@@ -29,10 +29,18 @@ def check_token(authorization: str):
|
||||
|
||||
class SyncIn(BaseModel):
|
||||
since: int = 0
|
||||
events: list[dict] = []
|
||||
todos: list[dict] = []
|
||||
notes: list[dict] = []
|
||||
folders: list[dict] = []
|
||||
events: list[dict] | None = None
|
||||
todos: list[dict] | None = None
|
||||
notes: list[dict] | None = None
|
||||
folders: list[dict] | None = None
|
||||
files: list[dict] | None = None
|
||||
|
||||
# Zurück kommen die Sammlungen der Anfrage. Ohne Sammlungen die vier Basis-Sammlungen,
|
||||
# Dateien nur auf ausdrückliche Anfrage, sonst zieht das Handy Sitzungen mit.
|
||||
@property
|
||||
def requested(self):
|
||||
named = [c for c in COLLECTIONS if getattr(self, c) is not None]
|
||||
return named or list(BASE_COLLECTIONS)
|
||||
|
||||
|
||||
@app.get("/api/health")
|
||||
@@ -53,14 +61,14 @@ def sync(body: SyncIn, authorization: str = Header(default="")):
|
||||
accepted = 0
|
||||
rejected = {collection: [] for collection in COLLECTIONS}
|
||||
for collection in COLLECTIONS:
|
||||
for item in getattr(body, collection):
|
||||
for item in getattr(body, collection) or []:
|
||||
ok, stored = db.merge(conn, collection, item)
|
||||
if ok:
|
||||
accepted += 1
|
||||
else:
|
||||
rejected[collection].append(stored)
|
||||
out = {}
|
||||
for collection in COLLECTIONS:
|
||||
for collection in body.requested:
|
||||
changed = db.since(conn, collection, body.since)
|
||||
ids = {item["id"] for item in changed}
|
||||
out[collection] = changed + [item for item in rejected[collection] if item["id"] not in ids]
|
||||
|
||||
@@ -90,3 +90,18 @@ def test_seit_cursor_liefert_nur_neues(client):
|
||||
sync(client, events=[{"id": "e2", "updated": "2026-09-19T10:00:00.000Z", "title": "b"}])
|
||||
r = sync(client, since=1)
|
||||
assert [e["id"] for e in r["events"]] == ["e2"]
|
||||
|
||||
|
||||
def test_dateien_nur_auf_anfrage(client):
|
||||
sync(client, files=[{"id": "-home-x/s1.jsonl", "updated": "2026-09-22T10:00:00.000Z", "content": "zeile"}])
|
||||
# Handy und Extension fragen ohne files: nichts davon in der Antwort
|
||||
r = sync(client, events=[], todos=[], notes=[], folders=[])
|
||||
assert "files" not in r and r["events"] == []
|
||||
assert "files" not in sync(client)
|
||||
# Nur Dateien angefragt: nur Dateien zurück, Stand zählt trotzdem
|
||||
r = sync(client, files=[])
|
||||
assert [f["id"] for f in r["files"]] == ["-home-x/s1.jsonl"] and "events" not in r
|
||||
assert r["cursor"] == 1
|
||||
# Tombstone gewinnt mit neuerem updated
|
||||
r = sync(client, files=[{"id": "-home-x/s1.jsonl", "updated": "2026-09-22T11:00:00.000Z", "deleted": True}])
|
||||
assert r["files"][-1]["deleted"] is True
|
||||
|
||||
Reference in New Issue
Block a user