+import com.sun.net.httpserver.HttpExchange;import com.sun.net.httpserver.HttpHandler;import com.sun.net.httpserver.HttpServer;import java.io.IOException;import java.io.OutputStream;import java.net.InetSocketAddress;import java.nio.charset.StandardCharsets;import java.util.List;import java.util.stream.Collectors;public class WebServer {private final TaskManager taskManager;private final int port;private HttpServer server;public WebServer(TaskManager taskManager, int port) throws IOException {this.taskManager = taskManager;this.port = port;this.server = HttpServer.create(new InetSocketAddress(port), 0);registerRoutes();}private void registerRoutes() {server.createContext("/", this::handleIndex);server.createContext("/api/tasks", this::handleTasks);server.createContext("/api/tasks/", this::handleTaskByIndex);}public void start() {server.start();System.out.println("✅ Web server started at http://localhost:" + port + "/");}private void handleIndex(HttpExchange exchange) throws IOException {if (!"GET".equalsIgnoreCase(exchange.getRequestMethod())) {sendText(exchange, 405, "Method Not Allowed");return;}String html = "<!doctype html><html><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><title>AI To-Do</title><style>body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;margin:20px}input,button{font-size:16px;padding:8px}ul{padding-left:18px}li{margin:6px 0;display:flex;align-items:center;gap:6px}button.act{margin-left:8px;font-size:12px;padding:4px 8px}.muted{color:#666}.row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.filters{margin:10px 0;display:flex;gap:8px;align-items:center}.badge{display:inline-block;padding:2px 6px;border-radius:10px;background:#eee;font-size:12px}.error{color:#b00020;margin-left:8px}</style></head><body><h2>🧠 AI-Powered To-Do Manager (Web)</h2><div class=\"row\"><input id=\"title\" placeholder=\"Add a task...\"/><button id=\"addBtn\" onclick=\"addTask()\">Add</button><span id=\"error\" class=\"error\"></span></div><div class=\"filters\"><div><strong>Filter:</strong> <button id=\"fAll\" onclick=\"setFilter('all')\">All</button><button id=\"fPending\" onclick=\"setFilter('pending')\">Pending</button><button id=\"fCompleted\" onclick=\"setFilter('completed')\">Completed</button></div><div class=\"muted\">Counts: <span class=\"badge\">All <span id=\"cAll\">0</span></span> <span class=\"badge\">Pending <span id=\"cPending\">0</span></span> <span class=\"badge\">Completed <span id=\"cCompleted\">0</span></span></div></div><p><button onclick=\"load()\">Refresh</button></p><ul id=\"list\"></ul><script>let currentFilter='all';function setFilter(f){currentFilter=f;highlightFilter();render(window.__tasks||[]);}function highlightFilter(){['fAll','fPending','fCompleted'].forEach(id=>{const el=document.getElementById(id);el.style.fontWeight='normal';el.style.textDecoration='none';});if(currentFilter==='all'){document.getElementById('fAll').style.fontWeight='bold';}else if(currentFilter==='pending'){document.getElementById('fPending').style.fontWeight='bold';}else{document.getElementById('fCompleted').style.fontWeight='bold';}}async function load(){const r=await fetch('/api/tasks');const d=await r.json();window.__tasks=d;render(d);}function computeCounts(tasks){const all=tasks.length;const completed=tasks.filter(t=>t.completed).length;const pending=all-completed;return {all,pending,completed};}function render(tasks){const {all,pending,completed}=computeCounts(tasks);document.getElementById('cAll').textContent=all;document.getElementById('cPending').textContent=pending;document.getElementById('cCompleted').textContent=completed;const ul=document.getElementById('list');ul.innerHTML='';let view=tasks;if(currentFilter==='pending'){view=tasks.filter(t=>!t.completed);}else if(currentFilter==='completed'){view=tasks.filter(t=>t.completed);}view.forEach((t,i)=>{const li=document.createElement('li');const text=document.createElement('span');text.textContent=(i+1)+'. '+t.title+' ['+t.category+'] '+(t.completed?'✅':'');li.appendChild(text);const btnC=document.createElement('button');btnC.className='act';btnC.textContent='Complete';btnC.disabled=t.completed;btnC.onclick=()=>completeTask(indexFromOriginal(tasks,t));li.appendChild(btnC);const btnD=document.createElement('button');btnD.className='act';btnD.textContent='Delete';btnD.onclick=()=>deleteTask(indexFromOriginal(tasks,t));li.appendChild(btnD);ul.appendChild(li);});highlightFilter();}function indexFromOriginal(allTasks, task){const idx=allTasks.indexOf(task);return (idx>=0?idx:window.__tasks.indexOf(task))+1;}async function addTask(){const input=document.getElementById('title');const btn=document.getElementById('addBtn');const err=document.getElementById('error');err.textContent='';const v=input.value.trim();if(!v){err.textContent='Title required';return;}btn.disabled=true;btn.textContent='Adding...';try{const r=await fetch('/api/tasks',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({title:v})});if(!r.ok){const e=await r.text();err.textContent='Failed: '+e;return;}input.value='';await load();}catch(e){err.textContent='Network error';}finally{btn.disabled=false;btn.textContent='Add';}}async function completeTask(idx){const r=await fetch('/api/tasks/'+idx+'/complete',{method:'PUT'});if(r.ok){load();}else{const e=await r.text();alert('Failed to complete: '+e);} }async function deleteTask(idx){const r=await fetch('/api/tasks/'+idx,{method:'DELETE'});if(r.ok){load();}else{const e=await r.text();alert('Failed to delete: '+e);} }load();</script></body></html>";sendHtml(exchange, 200, html);}private void handleTasks(HttpExchange exchange) throws IOException {switch (exchange.getRequestMethod()) {case "GET":List<Task> tasks = getTasksSnapshot();String json = toJson(tasks);sendJson(exchange, 200, json);break;case "POST":String body = new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8);String title = parseTitle(body);if (title == null || title.isBlank()) {sendJson(exchange, 400, "{\"error\":\"Title is required\"}");return;}try {taskManager.addTask(title);sendJson(exchange, 201, "{\"status\":\"created\"}");} catch (Exception e) {sendJson(exchange, 500, "{\"error\":\"" + escape(e.getMessage()) + "\"}");}break;default:sendText(exchange, 405, "Method Not Allowed");}}private void handleTaskByIndex(HttpExchange exchange) throws IOException {String path = exchange.getRequestURI().getPath(); // expected /api/tasks/{index} or /api/tasks/{index}/complete
0 commit comments