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:
Beast
2026-06-14 13:06:35 +08:00
parent 901efb872e
commit 745ce62322
4 changed files with 344 additions and 0 deletions

106
scripts/todo_server.py Executable file
View 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()