update
This commit is contained in:
91
backend/app/main.py
Normal file
91
backend/app/main.py
Normal file
@@ -0,0 +1,91 @@
|
||||
import logging
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from fastapi import FastAPI, Header, HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
||||
from . import db
|
||||
from .config import COLLECTIONS, SYNC_TOKEN
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
|
||||
log = logging.getLogger("app")
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(_app):
|
||||
if not SYNC_TOKEN:
|
||||
log.warning("SYNC_TOKEN ist leer, jede Anfrage wird abgelehnt.")
|
||||
db.init()
|
||||
yield
|
||||
|
||||
|
||||
app = FastAPI(title="Desktop Sync", lifespan=lifespan)
|
||||
|
||||
|
||||
def check_token(authorization: str):
|
||||
if not SYNC_TOKEN or authorization != f"Bearer {SYNC_TOKEN}":
|
||||
raise HTTPException(status_code=401, detail="Token fehlt oder ist falsch")
|
||||
|
||||
|
||||
class SyncIn(BaseModel):
|
||||
since: int = 0
|
||||
events: list[dict] = []
|
||||
todos: list[dict] = []
|
||||
notes: list[dict] = []
|
||||
|
||||
|
||||
@app.get("/api/health")
|
||||
def health():
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@app.post("/api/sync")
|
||||
def sync(body: SyncIn, authorization: str = Header(default="")):
|
||||
"""Nimmt Änderungen des Clients an und gibt alles zurück, was sich seit seinem Stand geändert hat.
|
||||
|
||||
Konflikte: der Eintrag mit dem neueren updated gewinnt. Verliert der Client, bekommt er den
|
||||
Server-Stand zurück. Die Antwort enthält auch die gerade angenommenen Einträge; der Client
|
||||
erkennt sie am gleichen updated und überspringt sie.
|
||||
"""
|
||||
check_token(authorization)
|
||||
with db.connect() as conn:
|
||||
accepted = 0
|
||||
rejected = {collection: [] for collection in COLLECTIONS}
|
||||
for collection in COLLECTIONS:
|
||||
for item in getattr(body, collection):
|
||||
ok, stored = db.merge(conn, collection, item)
|
||||
if ok:
|
||||
accepted += 1
|
||||
else:
|
||||
rejected[collection].append(stored)
|
||||
out = {}
|
||||
for collection in COLLECTIONS:
|
||||
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]
|
||||
out["cursor"] = db.cursor(conn)
|
||||
if accepted:
|
||||
log.info("sync: %d Einträge übernommen", accepted)
|
||||
return out
|
||||
|
||||
|
||||
# Lesezugriff für andere Anwendungen
|
||||
@app.get("/api/events")
|
||||
def events(authorization: str = Header(default="")):
|
||||
check_token(authorization)
|
||||
with db.connect() as conn:
|
||||
return db.alive(conn, "events")
|
||||
|
||||
|
||||
@app.get("/api/todos")
|
||||
def todos(authorization: str = Header(default="")):
|
||||
check_token(authorization)
|
||||
with db.connect() as conn:
|
||||
return db.alive(conn, "todos")
|
||||
|
||||
|
||||
@app.get("/api/notes")
|
||||
def notes(authorization: str = Header(default="")):
|
||||
check_token(authorization)
|
||||
with db.connect() as conn:
|
||||
return db.alive(conn, "notes")
|
||||
Reference in New Issue
Block a user