diff --git a/db/db.go b/db/db.go
new file mode 100644
index 0000000..e3ae1e5
--- /dev/null
+++ b/db/db.go
@@ -0,0 +1,19 @@
+package db
+
+import "github.com/google/uuid"
+
+type Todo struct {
+ Id uuid.UUID
+ Name string
+ Done bool
+}
+
+type TodosStore struct {
+ Todos []Todo
+}
+
+func NewTodoStore() TodosStore {
+ return TodosStore{
+ Todos: []Todo{},
+ }
+}
diff --git a/go.mod b/go.mod
index 00e9f8f..687b23c 100644
--- a/go.mod
+++ b/go.mod
@@ -3,3 +3,5 @@ module michaelthomson.dev/mthomson/go-todos-app
go 1.22.1
require github.com/a-h/templ v0.2.707
+
+require github.com/google/uuid v1.6.0 // indirect
diff --git a/go.sum b/go.sum
index ce5d368..8eeab4e 100644
--- a/go.sum
+++ b/go.sum
@@ -1,2 +1,6 @@
github.com/a-h/templ v0.2.707 h1:T1Gkd2ugbRglZ9rYw/VBchWOSZVKmetDbBkm4YubM7U=
github.com/a-h/templ v0.2.707/go.mod h1:5cqsugkq9IerRNucNsI4DEamdHPsoGMQy99DzydLhM8=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
diff --git a/handlers/homeHandler.go b/handlers/homeHandler.go
new file mode 100644
index 0000000..bf06845
--- /dev/null
+++ b/handlers/homeHandler.go
@@ -0,0 +1,41 @@
+package handlers
+
+import (
+ "net/http"
+
+ "michaelthomson.dev/mthomson/go-todos-app/services"
+ "michaelthomson.dev/mthomson/go-todos-app/templates/pages"
+)
+
+type HomeHandler struct {
+ ts services.TodoService
+}
+
+func NewHomeHandler(ts services.TodoService) HomeHandler {
+ return HomeHandler{ts: ts}
+}
+
+func (h *HomeHandler) Home(w http.ResponseWriter, r *http.Request) {
+ var err error
+
+ todos, err := h.ts.GetTodos()
+
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+
+ err = pages.Home(todos).Render(r.Context(), w)
+
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+func (h HomeHandler) Router() http.Handler {
+ router := http.NewServeMux()
+
+ router.HandleFunc("/", h.Home)
+
+ return router
+}
diff --git a/handlers/todoHandler.go b/handlers/todoHandler.go
new file mode 100644
index 0000000..dd9716f
--- /dev/null
+++ b/handlers/todoHandler.go
@@ -0,0 +1,61 @@
+package handlers
+
+import (
+ "net/http"
+
+ "github.com/google/uuid"
+ "michaelthomson.dev/mthomson/go-todos-app/services"
+ "michaelthomson.dev/mthomson/go-todos-app/templates/partials"
+)
+
+type TodoHandler struct {
+ ts services.TodoService
+}
+
+func NewTodoHandler(ts services.TodoService) TodoHandler {
+ return TodoHandler{ts: ts}
+}
+
+func (h TodoHandler) Create(w http.ResponseWriter, r *http.Request) {
+ var err error
+ err = r.ParseForm()
+
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ name := r.FormValue("name")
+
+ addedTodo, err := h.ts.AddTodo(name)
+
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ err = partials.Todo(addedTodo).Render(r.Context(), w)
+
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+func (h TodoHandler) Delete(w http.ResponseWriter, r *http.Request) {
+ var err error
+
+ id, err := uuid.Parse(r.PathValue("id"))
+
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+
+ err = h.ts.DeleteTodoById(id)
+
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
diff --git a/main.go b/main.go
index 36a146a..5d445c9 100644
--- a/main.go
+++ b/main.go
@@ -3,24 +3,28 @@ package main
import (
"log"
"net/http"
- "path/filepath"
- "github.com/a-h/templ"
- "michaelthomson.dev/mthomson/go-todos-app/views"
+ "michaelthomson.dev/mthomson/go-todos-app/db"
+ "michaelthomson.dev/mthomson/go-todos-app/handlers"
+ "michaelthomson.dev/mthomson/go-todos-app/services"
)
func main() {
+ todosStore := db.NewTodoStore()
+ ts := services.NewTodoService(&todosStore)
+
+ homeHandler := handlers.NewHomeHandler(*ts)
+ todoHandler := handlers.NewTodoHandler(*ts)
+
router := http.NewServeMux()
// Serve static files
- router.HandleFunc("GET /assets/", func(w http.ResponseWriter, r *http.Request) {
- filePath := r.URL.Path[len("/assets/"):]
- fullPath := filepath.Join(".", "assets", filePath)
- http.ServeFile(w, r, fullPath)
- })
+ fs := http.FileServer(http.Dir("./assets"))
+ router.Handle("GET /assets/", http.StripPrefix("/assets/", fs))
- home := views.Home()
- router.Handle("GET /", templ.Handler(home))
+ router.HandleFunc("GET /{$}", homeHandler.Home)
+ router.HandleFunc("POST /todos", todoHandler.Create)
+ router.HandleFunc("DELETE /todos/{id}", todoHandler.Delete)
server := http.Server{
Addr: "localhost:3000",
diff --git a/services/todoService.go b/services/todoService.go
new file mode 100644
index 0000000..6ced1ad
--- /dev/null
+++ b/services/todoService.go
@@ -0,0 +1,61 @@
+package services
+
+import (
+ "fmt"
+
+ "github.com/google/uuid"
+ "michaelthomson.dev/mthomson/go-todos-app/db"
+)
+
+type TodoService struct {
+ store *db.TodosStore
+}
+
+func NewTodoService(store *db.TodosStore) *TodoService {
+ return &TodoService{
+ store: store,
+ }
+}
+
+func (ts *TodoService) AddTodo(name string) (todo db.Todo, err error) {
+ addedTodo := db.Todo{
+ Id: uuid.New(),
+ Name: name,
+ Done: false,
+ }
+
+ ts.store.Todos = append(ts.store.Todos, addedTodo)
+
+ return addedTodo, nil
+}
+
+func (ts *TodoService) GetTodos() (todos []db.Todo, err error) {
+ return ts.store.Todos, nil
+}
+
+func (ts *TodoService) GetTodoById(id uuid.UUID) (todo db.Todo, err error) {
+ for _, todo := range ts.store.Todos {
+ if id == todo.Id {
+ return todo, nil
+ }
+ }
+
+ return db.Todo{}, fmt.Errorf("Could not find todo by id %q", id)
+}
+
+func (ts *TodoService) DeleteTodoById(id uuid.UUID) (err error) {
+ index := -1
+ for i, todo := range ts.store.Todos {
+ if id == todo.Id {
+ index = i
+ }
+ }
+
+ if index == -1 {
+ return fmt.Errorf("Could not find todo by id %q", id)
+ }
+
+ ts.store.Todos = append(ts.store.Todos[:index], ts.store.Todos[index + 1:]...)
+
+ return nil
+}
diff --git a/templates/pages/home.templ b/templates/pages/home.templ
new file mode 100644
index 0000000..629036d
--- /dev/null
+++ b/templates/pages/home.templ
@@ -0,0 +1,20 @@
+package pages
+
+import "michaelthomson.dev/mthomson/go-todos-app/templates/shared"
+import "michaelthomson.dev/mthomson/go-todos-app/db"
+import "michaelthomson.dev/mthomson/go-todos-app/templates/partials"
+
+templ Home(todos []db.Todo) {
+ @shared.Page("Todos") {
+
Todos
+
+
+ for _, todo := range todos {
+ @partials.Todo(todo)
+ }
+
+ }
+}
diff --git a/views/home_templ.go b/templates/pages/home_templ.go
similarity index 64%
rename from views/home_templ.go
rename to templates/pages/home_templ.go
index 6cd1e56..ef47fac 100644
--- a/views/home_templ.go
+++ b/templates/pages/home_templ.go
@@ -1,7 +1,7 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.663
-package views
+package pages
//lint:file-ignore SA4006 This context is only used if a nested component is present.
@@ -10,9 +10,11 @@ import "context"
import "io"
import "bytes"
-import "michaelthomson.dev/mthomson/go-todos-app/views/template"
+import "michaelthomson.dev/mthomson/go-todos-app/templates/shared"
+import "michaelthomson.dev/mthomson/go-todos-app/db"
+import "michaelthomson.dev/mthomson/go-todos-app/templates/partials"
-func Home() templ.Component {
+func Home(todos []db.Todo) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
@@ -31,7 +33,17 @@ func Home() templ.Component {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("Home page
")
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("Todos
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ for _, todo := range todos {
+ templ_7745c5c3_Err = partials.Todo(todo).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -40,7 +52,7 @@ func Home() templ.Component {
}
return templ_7745c5c3_Err
})
- templ_7745c5c3_Err = template.Base("Todos app").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
+ templ_7745c5c3_Err = shared.Page("Todos").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
diff --git a/templates/partials/todo.templ b/templates/partials/todo.templ
new file mode 100644
index 0000000..e72c038
--- /dev/null
+++ b/templates/partials/todo.templ
@@ -0,0 +1,14 @@
+package partials
+
+import "michaelthomson.dev/mthomson/go-todos-app/db"
+
+func todoId(todo db.Todo) string {
+ return "todo-" + todo.Id.String()
+}
+
+templ Todo(todo db.Todo) {
+
+ { todo.Id.String() }: { todo.Name }
+
+
+}
diff --git a/templates/partials/todo_templ.go b/templates/partials/todo_templ.go
new file mode 100644
index 0000000..35177dd
--- /dev/null
+++ b/templates/partials/todo_templ.go
@@ -0,0 +1,106 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.2.663
+package partials
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import "github.com/a-h/templ"
+import "context"
+import "io"
+import "bytes"
+
+import "michaelthomson.dev/mthomson/go-todos-app/db"
+
+func todoId(todo db.Todo) string {
+ return "todo-" + todo.Id.String()
+}
+
+func Todo(todo db.Todo) templ.Component {
+ return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
+ if !templ_7745c5c3_IsBuffer {
+ templ_7745c5c3_Buffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var1 == nil {
+ templ_7745c5c3_Var1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var3 string
+ templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(todo.Id.String())
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/partials/todo.templ`, Line: 11, Col: 22}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(": ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var4 string
+ templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(todo.Name)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/partials/todo.templ`, Line: 11, Col: 37}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if !templ_7745c5c3_IsBuffer {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
+ }
+ return templ_7745c5c3_Err
+ })
+}
diff --git a/views/template/template.templ b/templates/shared/page.templ
similarity index 90%
rename from views/template/template.templ
rename to templates/shared/page.templ
index 50cbc3f..31ad4e1 100644
--- a/views/template/template.templ
+++ b/templates/shared/page.templ
@@ -1,6 +1,6 @@
-package template
+package shared
-templ Base(title string) {
+templ Page(title string) {
diff --git a/views/template/template_templ.go b/templates/shared/page_templ.go
similarity index 92%
rename from views/template/template_templ.go
rename to templates/shared/page_templ.go
index fe9b2a9..d300e89 100644
--- a/views/template/template_templ.go
+++ b/templates/shared/page_templ.go
@@ -1,7 +1,7 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.663
-package template
+package shared
//lint:file-ignore SA4006 This context is only used if a nested component is present.
@@ -10,7 +10,7 @@ import "context"
import "io"
import "bytes"
-func Base(title string) templ.Component {
+func Page(title string) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
@@ -30,7 +30,7 @@ func Base(title string) templ.Component {
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/template/template.templ`, Line: 10, Col: 25}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/shared/page.templ`, Line: 10, Col: 25}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
diff --git a/views/home.templ b/views/home.templ
deleted file mode 100644
index 478457c..0000000
--- a/views/home.templ
+++ /dev/null
@@ -1,9 +0,0 @@
-package views
-
-import "michaelthomson.dev/mthomson/go-todos-app/views/template"
-
-templ Home() {
- @template.Base("Todos app") {
- Home page
- }
-}