- Todo API server (:9001) with CRUD + priority support - Daily brief aggregator (trading + inspiration) - Todo extraction from inspiration digest markdown - DeepSeek balance checker for nav page
107 lines
3.6 KiB
Python
Executable File
107 lines
3.6 KiB
Python
Executable File
"""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()
|