"""Local notes starter. Run python3 server.py and visit http://127.0.0.1:8765.""" import json import sqlite3 from http.server import BaseHTTPRequestHandler, HTTPServer from pathlib import Path class Notes: def __init__(self, path): self.path = path with sqlite3.connect(path) as db: db.execute("CREATE TABLE IF NOT EXISTS notes(id INTEGER PRIMARY KEY, body TEXT NOT NULL)") def list(self): with sqlite3.connect(self.path) as db: return [{"id": row[0], "body": row[1]} for row in db.execute("SELECT id,body FROM notes ORDER BY id DESC")] def add(self, body): if not isinstance(body, str) or not body.strip() or len(body) > 2000: raise ValueError("A note must contain 1–2000 characters") with sqlite3.connect(self.path) as db: cursor = db.execute("INSERT INTO notes(body) VALUES (?)", (body.strip(),)) return {"id": cursor.lastrowid, "body": body.strip()} def delete(self, note_id): with sqlite3.connect(self.path) as db: return db.execute("DELETE FROM notes WHERE id=?", (note_id,)).rowcount > 0 class Handler(BaseHTTPRequestHandler): def send(self, status, payload, content_type="application/json"): data = payload.encode() if isinstance(payload, str) else json.dumps(payload).encode() self.send_response(status) self.send_header("Content-Type", content_type) self.send_header("Content-Length", str(len(data))) self.send_header("X-Content-Type-Options", "nosniff") self.send_header("Cache-Control", "no-store") self.end_headers() self.wfile.write(data) def valid_host(self): return self.headers.get("Host") == "127.0.0.1:8765" def do_GET(self): if not self.valid_host(): return self.send(403, {"error": "Use http://127.0.0.1:8765"}) if self.path == "/": return self.send(200, Path(__file__).with_name("index.html").read_text(), "text/html; charset=utf-8") if self.path == "/notes": return self.send(200, self.server.notes.list()) self.send(404, {"error": "Not found"}) def do_POST(self): if not self.valid_host() or self.headers.get("Origin") not in (None, "http://127.0.0.1:8765"): return self.send(403, {"error": "Origin not allowed"}) if self.path != "/notes": return self.send(404, {"error": "Not found"}) if self.headers.get("Content-Type") != "application/json": return self.send(415, {"error": "Use application/json"}) try: size = int(self.headers.get("Content-Length", "0")) if not 0 < size <= 10000: raise ValueError("Invalid request size") value = json.loads(self.rfile.read(size)) if not isinstance(value, dict): raise ValueError("Expected an object") self.send(201, self.server.notes.add(value.get("body"))) except (ValueError, UnicodeDecodeError) as error: self.send(400, {"error": str(error)}) def do_DELETE(self): if not self.valid_host() or self.headers.get("Origin") not in (None, "http://127.0.0.1:8765"): return self.send(403, {"error": "Origin not allowed"}) try: if not self.path.startswith("/notes/"): raise ValueError() found = self.server.notes.delete(int(self.path.removeprefix("/notes/"))) self.send(200 if found else 404, {"deleted": found}) except ValueError: self.send(404, {"error": "Not found"}) if __name__ == "__main__": server = HTTPServer(("127.0.0.1", 8765), Handler) server.notes = Notes(str(Path(__file__).with_name("notes.sqlite3"))) print("Open http://127.0.0.1:8765 — Ctrl+C to stop") try: server.serve_forever() except KeyboardInterrupt: server.server_close()