feat: todo API server, daily brief gen, extract todos, balance checker
- 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
This commit is contained in:
106
scripts/todo_server.py
Executable file
106
scripts/todo_server.py
Executable file
@ -0,0 +1,106 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user