"""Todo API server - lightweight todo management for nav page. Runs on port 9001, managed by Caddy proxy at /api/todos/* """ import json import os import uuid from datetime import datetime, timezone, timedelta from http.server import HTTPServer, BaseHTTPRequestHandler TODOS_FILE = "/var/www/nav/data/todos.json" tz = timezone(timedelta(hours=8)) def load_todos(): if os.path.exists(TODOS_FILE): with open(TODOS_FILE) as f: return json.load(f) return [] def save_todos(todos): os.makedirs(os.path.dirname(TODOS_FILE), exist_ok=True) with open(TODOS_FILE, "w") as f: json.dump(todos, f, ensure_ascii=False, indent=2) class TodoHandler(BaseHTTPRequestHandler): def _send(self, data, status=200): self.send_response(status) self.send_header("Content-Type", "application/json; charset=utf-8") self.send_header("Access-Control-Allow-Origin", "*") self.send_header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") self.send_header("Access-Control-Allow-Headers", "Content-Type") self.end_headers() self.wfile.write(json.dumps(data, ensure_ascii=False).encode("utf-8")) def do_OPTIONS(self): self._send({}) def do_GET(self): if self.path == "/api/todos": todos = load_todos() self._send({"todos": todos}) else: self._send({"error": "not found"}, 404) def do_POST(self): if self.path == "/api/todos": length = int(self.headers.get("Content-Length", 0)) body = json.loads(self.rfile.read(length).decode("utf-8")) todos = load_todos() now = datetime.now(tz).strftime("%Y-%m-%d %H:%M") todo = { "id": str(uuid.uuid4())[:8], "text": body.get("text", ""), "done": False, "source": body.get("source", "manual"), "priority": body.get("priority", "medium"), "created_at": now, } todos.insert(0, todo) save_todos(todos) self._send({"todo": todo}, 201) else: self._send({"error": "not found"}, 404) def do_PUT(self): parts = self.path.split("/") if len(parts) == 4 and parts[1] == "api" and parts[2] == "todos": tid = parts[3] length = int(self.headers.get("Content-Length", 0)) body = json.loads(self.rfile.read(length).decode("utf-8")) todos = load_todos() for t in todos: if t["id"] == tid: if "done" in body: t["done"] = body["done"] if "text" in body: t["text"] = body["text"] save_todos(todos) self._send({"todo": t}) return self._send({"error": "not found"}, 404) else: self._send({"error": "not found"}, 404) def do_DELETE(self): parts = self.path.split("/") if len(parts) == 4 and parts[1] == "api" and parts[2] == "todos": tid = parts[3] todos = load_todos() todos = [t for t in todos if t["id"] != tid] save_todos(todos) self._send({"deleted": tid}) else: self._send({"error": "not found"}, 404) def log_message(self, format, *args): pass # quiet if __name__ == "__main__": port = int(os.environ.get("PORT", 9001)) server = HTTPServer(("127.0.0.1", port), TodoHandler) print(f"Todo API running on :{port}") server.serve_forever()