Skip to content

Commit 8809505

Browse files
Update Web server file
1 parent cbb3766 commit 8809505

File tree

2 files changed

+22
-1
lines changed

2 files changed

+22
-1
lines changed

src/main/java/WebServer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
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}button.act{margin-left:8px;font-size:12px;padding:4px 8px}</style></head><body><h2>🧠 AI-Powered To-Do Manager (Web)</h2><div><input id=\"title\" placeholder=\"Add a task...\"/><button onclick=\"addTask()\">Add</button></div><p><button onclick=\"load()\">Refresh</button></p><ul id=\"list\"></ul><script>async function load(){const r=await fetch('/api/tasks');const d=await r.json();const ul=document.getElementById('list');ul.innerHTML='';d.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(i+1);li.appendChild(btnC);const btnD=document.createElement('button');btnD.className='act';btnD.textContent='Delete';btnD.onclick=()=>deleteTask(i+1);li.appendChild(btnD);ul.appendChild(li);});}async function addTask(){const v=document.getElementById('title').value.trim();if(!v)return;await fetch('/api/tasks',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({title:v})});document.getElementById('title').value='';load();}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
1+
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
22
String[] parts = path.split("/");if (parts.length < 4) {sendText(exchange, 404, "Not Found");return;}String idxStr = parts[3];int index;try {index = Integer.parseInt(idxStr);} catch (NumberFormatException e) {sendText(exchange, 400, "Invalid index");return;}String method = exchange.getRequestMethod().toUpperCase();boolean isCompletePath = parts.length >= 5 && "complete".equalsIgnoreCase(parts[4]);try {if ("DELETE".equals(method) && parts.length==4){boolean ok = taskManager.deleteTask(index);if (ok) {sendJson(exchange, 200, "{\"status\":\"deleted\"}");} else {sendJson(exchange, 400, "{\"error\":\"Delete failed\"}");}} else if ("PUT".equals(method) && isCompletePath){boolean ok = taskManager.completeTask(index);if (ok) {sendJson(exchange, 200, "{\"status\":\"completed\"}");} else {sendJson(exchange, 400, "{\"error\":\"Complete failed\"}");}} else {sendText(exchange, 405, "Method Not Allowed");}} catch (Exception e){sendJson(exchange, 500, "{\"error\":\""+escape(e.getMessage())+"\"}");}}private List<Task> getTasksSnapshot() {try {java.lang.reflect.Field f = TaskManager.class.getDeclaredField("tasks");f.setAccessible(true);@SuppressWarnings("unchecked")List<Task> list = (List<Task>) f.get(taskManager);return List.copyOf(list);} catch (Exception e) {return List.of();}}private String toJson(List<Task> tasks) {String items = tasks.stream().map(t -> "{" +"\"title\":\"" + escape(t.getTitle()) + "\"," +"\"category\":\"" + escape(t.getCategory()) + "\"," +"\"completed\":" + (t.isCompleted() ? "true" : "false") +"}").collect(Collectors.joining(","));return "[" + items + "]";}private String parseTitle(String body) {try {int i = body.indexOf("\"title\"");if (i < 0) return null;int c = body.indexOf(':', i);if (c < 0) return null;int q1 = body.indexOf('"', c+1);if (q1 < 0) return null;int q2 = body.indexOf('"', q1+1);if (q2 < 0) return null;return body.substring(q1+1, q2);} catch (Exception e) {return null;}}private void sendHtml(HttpExchange ex, int code, String html) throws IOException {byte[] b = html.getBytes(StandardCharsets.UTF_8);ex.getResponseHeaders().set("Content-Type", "text/html; charset=utf-8");ex.sendResponseHeaders(code, b.length);try (OutputStream os = ex.getResponseBody()) { os.write(b); }}private void sendJson(HttpExchange ex, int code, String json) throws IOException {byte[] b = json.getBytes(StandardCharsets.UTF_8);ex.getResponseHeaders().set("Content-Type", "application/json; charset=utf-8");ex.sendResponseHeaders(code, b.length);try (OutputStream os = ex.getResponseBody()) { os.write(b); }}private void sendText(HttpExchange ex, int code, String text) throws IOException {byte[] b = text.getBytes(StandardCharsets.UTF_8);ex.getResponseHeaders().set("Content-Type", "text/plain; charset=utf-8");ex.sendResponseHeaders(code, b.length);try (OutputStream os = ex.getResponseBody()) { os.write(b); }}private String escape(String s) { return s == null ? "" : s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n"); }}

tasks.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[
2+
{
3+
"title": "generate Ai features",
4+
"category": "General",
5+
"completed": true,
6+
"createdAt": "2025-08-05 16:47:09",
7+
"completedAt": "2025-08-05 16:50:02"
8+
},
9+
{
10+
"title": "Urgent office meeting at 2 am",
11+
"category": "Urgent",
12+
"completed": false,
13+
"createdAt": "2025-08-05 16:51:14"
14+
},
15+
{
16+
"title": "study time 9 - 10",
17+
"category": "General",
18+
"completed": false,
19+
"createdAt": "2025-08-05 17:08:23"
20+
}
21+
]

0 commit comments

Comments
 (0)