From 2e73eae391a43caeb6f4390b0c39184522f1470c Mon Sep 17 00:00:00 2001 From: Andy Gayton Date: Fri, 24 Jan 2025 17:07:12 -0500 Subject: [PATCH] examples: add a todo app (#43) --- examples/todo-app/README.md | 41 ++++++++++++++++ examples/todo-app/handler.nu | 56 ++++++++++++++++++++++ examples/todo-app/index.html | 92 ++++++++++++++++++++++++++++++++++++ 3 files changed, 189 insertions(+) create mode 100644 examples/todo-app/README.md create mode 100644 examples/todo-app/handler.nu create mode 100644 examples/todo-app/index.html diff --git a/examples/todo-app/README.md b/examples/todo-app/README.md new file mode 100644 index 0000000..1d89dbf --- /dev/null +++ b/examples/todo-app/README.md @@ -0,0 +1,41 @@ +# Todo App Example + +A simple todo list application demonstrating how to build a web application using cross-stream's [built-in HTTP server](https://cablehead.github.io/xs/reference/http-server/). + +image + +## Features + +- Add new todos +- Toggle todo completion status +- Persistent storage using cross-stream's event store + +## Structure + +- `handler.nu` - Event handler for processing HTTP requests and managing todos +- `index.html` - Frontend interface with styling and JavaScript + +## Running the App + +1. Start cross-stream with HTTP server enabled: +```bash +xs serve ./store --http :5007 +``` + +2. Load the handler and template: +```nushell +# Install minijinja-cli first: https://github.com/mitsuhiko/minijinja +cat handler.nu | .append todo.handler.register +cat index.html | .append index.html +``` + +3. Visit: http://localhost:5007 in your browser + +## Event Structure + +The todo state is rebuilt from two event types: + +- `todo` - Contains the text content of new todos +- `todo.toggle` - Records completion status changes + +The handler aggregates these events to maintain the current state of all todos, with each todo having a unique ID, text content, and completion status. diff --git a/examples/todo-app/handler.nu b/examples/todo-app/handler.nu new file mode 100644 index 0000000..91011a3 --- /dev/null +++ b/examples/todo-app/handler.nu @@ -0,0 +1,56 @@ +{ + process: {|frame| + if $frame.topic != "http.request" { return } + + match $frame.meta { + {uri: "/" method: "GET"} => { + # Get todos + let todos = .cat | where topic =~ "todo" | reduce --fold {} {|f acc| + match $f.topic { + "todo" => ($acc | insert $f.id {text: (.cas $f.hash) done: false}) + todo.toggle => ($acc | update ($f.meta.id) { $in | update done { not ($in) } }) + _ => $acc + } + } + + # Render template + {todos: $todos} | to json -r | minijinja-cli -f json -t (.head index.html | .cas $in.hash) '' - | .append http.response --meta { + request_id: $frame.id + status: 200 + headers: { + Content-Type: "text/html" + } + } + } + + {uri: "/" method: "POST"} => { + # Get the todo content and store it + .cas $frame.hash | url split-query | transpose -rdl | get todo | .append todo + + # Redirect to GET / + .append http.response --meta { + request_id: $frame.id + status: 303 + headers: { + Location: "/" + } + } + } + + {uri: "/toggle" method: "POST"} => { + .cas $frame.hash | from json | .append todo.toggle --meta $in + "OK" | .append http.response --meta {request_id: $frame.id status: 200 headers: {Content-Type: "text/plain"}} + } + + _ => { + "not found :/" | .append http.response --meta { + request_id: $frame.id + status: 404 + headers: { + Content-Type: "text/html" + } + } + } + } + } +} diff --git a/examples/todo-app/index.html b/examples/todo-app/index.html new file mode 100644 index 0000000..2e2ec4a --- /dev/null +++ b/examples/todo-app/index.html @@ -0,0 +1,92 @@ + + + + Todo List + + + +

Todos

+ +
+ {% for id, todo in todos | items %} +
+ + {{ todo.text }} +
+ {% else %} +

No todos yet. Add one below!

+ {% endfor %} +
+ +
+ + +
+ + + +