db, handlers, and interfaces
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/dryrun Pipeline was successful
ci/woodpecker/push/publish-tag Pipeline was successful
ci/woodpecker/push/publish-latest Pipeline was successful

This commit is contained in:
Michael Thomson 2024-06-15 21:33:14 -04:00
parent 3d29dadaf3
commit 09a957ca4b
No known key found for this signature in database
12 changed files with 160 additions and 95 deletions

View File

@ -558,7 +558,37 @@ video {
position: static; position: static;
} }
.flex {
display: flex;
}
.flex-col {
flex-direction: column;
}
.content-center {
align-content: center;
}
.items-center {
align-items: center;
}
.border {
border-width: 1px;
}
.border-black {
--tw-border-opacity: 1;
border-color: rgb(0 0 0 / var(--tw-border-opacity));
}
.text-xl { .text-xl {
font-size: 1.25rem; font-size: 1.25rem;
line-height: 1.75rem; line-height: 1.75rem;
} }
.text-3xl {
font-size: 1.875rem;
line-height: 2.25rem;
}

View File

@ -1,19 +1,61 @@
package db package db
import "github.com/google/uuid" import (
"fmt"
type Todo struct { "github.com/google/uuid"
Id uuid.UUID "michaelthomson.dev/mthomson/go-todos-app/models"
Name string )
Done bool
}
type TodosStore struct { type TodosStore struct {
Todos []Todo Todos []models.Todo
}
func (ts *TodosStore) List() (todo []models.Todo, err error) {
return ts.Todos, nil
}
func (ts *TodosStore) Get(id uuid.UUID) (todo models.Todo, err error) {
index := -1
for i, todo := range ts.Todos {
if id == todo.Id {
index = i
}
}
if index == -1 {
return models.Todo{}, fmt.Errorf("Could not find todo by id %q", id)
}
ts.Todos = append(ts.Todos[:index], ts.Todos[index + 1:]...)
return ts.Todos[index], nil
}
func (ts *TodosStore) Add(todo models.Todo) (addedTodo models.Todo, err error) {
ts.Todos = append(ts.Todos, todo)
return todo, nil
}
func (ts *TodosStore) Delete(id uuid.UUID) (err error) {
index := -1
for i, todo := range ts.Todos {
if id == todo.Id {
index = i
}
}
if index == -1 {
return fmt.Errorf("Could not find todo by id %q", id)
}
ts.Todos = append(ts.Todos[:index], ts.Todos[index + 1:]...)
return nil
} }
func NewTodoStore() TodosStore { func NewTodoStore() TodosStore {
return TodosStore{ return TodosStore{
Todos: []Todo{}, Todos: []models.Todo{},
} }
} }

View File

@ -8,10 +8,10 @@ import (
) )
type HomeHandler struct { type HomeHandler struct {
ts services.TodoService ts *services.TodoService
} }
func NewHomeHandler(ts services.TodoService) HomeHandler { func NewHomeHandler(ts *services.TodoService) HomeHandler {
return HomeHandler{ts: ts} return HomeHandler{ts: ts}
} }
@ -32,7 +32,7 @@ func (h *HomeHandler) Home(w http.ResponseWriter, r *http.Request) {
} }
} }
func (h HomeHandler) Router() http.Handler { func (h *HomeHandler) Router() http.Handler {
router := http.NewServeMux() router := http.NewServeMux()
router.HandleFunc("/", h.Home) router.HandleFunc("/", h.Home)

View File

@ -9,14 +9,14 @@ import (
) )
type TodoHandler struct { type TodoHandler struct {
ts services.TodoService ts *services.TodoService
} }
func NewTodoHandler(ts services.TodoService) TodoHandler { func NewTodoHandler(ts *services.TodoService) TodoHandler {
return TodoHandler{ts: ts} return TodoHandler{ts: ts}
} }
func (h TodoHandler) Create(w http.ResponseWriter, r *http.Request) { func (h *TodoHandler) Create(w http.ResponseWriter, r *http.Request) {
var err error var err error
err = r.ParseForm() err = r.ParseForm()
@ -30,7 +30,7 @@ func (h TodoHandler) Create(w http.ResponseWriter, r *http.Request) {
addedTodo, err := h.ts.AddTodo(name) addedTodo, err := h.ts.AddTodo(name)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusBadRequest)
return return
} }
@ -42,7 +42,7 @@ func (h TodoHandler) Create(w http.ResponseWriter, r *http.Request) {
} }
} }
func (h TodoHandler) Delete(w http.ResponseWriter, r *http.Request) { func (h *TodoHandler) Delete(w http.ResponseWriter, r *http.Request) {
var err error var err error
id, err := uuid.Parse(r.PathValue("id")) id, err := uuid.Parse(r.PathValue("id"))

View File

@ -13,8 +13,8 @@ func main() {
todosStore := db.NewTodoStore() todosStore := db.NewTodoStore()
ts := services.NewTodoService(&todosStore) ts := services.NewTodoService(&todosStore)
homeHandler := handlers.NewHomeHandler(*ts) homeHandler := handlers.NewHomeHandler(ts)
todoHandler := handlers.NewTodoHandler(*ts) todoHandler := handlers.NewTodoHandler(ts)
router := http.NewServeMux() router := http.NewServeMux()

18
models/todo.go Normal file
View File

@ -0,0 +1,18 @@
package models
import "github.com/google/uuid"
type Todo struct {
Id uuid.UUID
Name string
Done bool
}
func NewTodo(name string, done bool) Todo {
return Todo{
Id: uuid.New(),
Name: name,
Done: done,
}
}

View File

@ -4,58 +4,41 @@ import (
"fmt" "fmt"
"github.com/google/uuid" "github.com/google/uuid"
"michaelthomson.dev/mthomson/go-todos-app/db" "michaelthomson.dev/mthomson/go-todos-app/models"
) )
type Db interface {
List() (todo []models.Todo, err error)
Get(id uuid.UUID) (todo models.Todo, err error)
Add(todo models.Todo) (addedTodo models.Todo, err error)
Delete(id uuid.UUID) (err error)
}
type TodoService struct { type TodoService struct {
store *db.TodosStore db Db
} }
func NewTodoService(store *db.TodosStore) *TodoService { func NewTodoService(db Db) *TodoService {
return &TodoService{ return &TodoService{
store: store, db: db,
} }
} }
func (ts *TodoService) AddTodo(name string) (todo db.Todo, err error) { func (ts *TodoService) AddTodo(name string) (todo models.Todo, err error) {
addedTodo := db.Todo{ if name == "" {
Id: uuid.New(), return models.Todo{}, fmt.Errorf("Must provide a name")
Name: name,
Done: false,
} }
return ts.db.Add(models.NewTodo(name, false))
ts.store.Todos = append(ts.store.Todos, addedTodo)
return addedTodo, nil
} }
func (ts *TodoService) GetTodos() (todos []db.Todo, err error) { func (ts *TodoService) GetTodos() (todos []models.Todo, err error) {
return ts.store.Todos, nil return ts.db.List()
} }
func (ts *TodoService) GetTodoById(id uuid.UUID) (todo db.Todo, err error) { func (ts *TodoService) GetTodoById(id uuid.UUID) (todo models.Todo, err error) {
for _, todo := range ts.store.Todos { return ts.db.Get(id)
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) { func (ts *TodoService) DeleteTodoById(id uuid.UUID) (err error) {
index := -1 return ts.db.Delete(id)
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
} }

View File

@ -0,0 +1,3 @@
package services
type DbMock struct {}

View File

@ -1,14 +1,15 @@
package pages package pages
import "michaelthomson.dev/mthomson/go-todos-app/templates/shared" 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" import "michaelthomson.dev/mthomson/go-todos-app/templates/partials"
import "michaelthomson.dev/mthomson/go-todos-app/models"
templ Home(todos []db.Todo) { templ Home(todos []models.Todo) {
@shared.Page("Todos") { @shared.Page("Todos") {
<div class="text-xl">Todos</div> <div class="flex content-center items-center flex-col">
<h1 class="text-3xl">Todos</h1>
<form hx-post="/todos" hx-target="#todos" hx-swap="beforeend"> <form hx-post="/todos" hx-target="#todos" hx-swap="beforeend">
<input name="name" type="text" /> <input name="name" type="text" class="border border-black" />
<button type="submit">Submit</button> <button type="submit">Submit</button>
</form> </form>
<div id="todos"> <div id="todos">
@ -16,5 +17,6 @@ templ Home(todos []db.Todo) {
@partials.Todo(todo) @partials.Todo(todo)
} }
</div> </div>
</div>
} }
} }

View File

@ -11,10 +11,10 @@ import "io"
import "bytes" import "bytes"
import "michaelthomson.dev/mthomson/go-todos-app/templates/shared" 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" import "michaelthomson.dev/mthomson/go-todos-app/templates/partials"
import "michaelthomson.dev/mthomson/go-todos-app/models"
func Home(todos []db.Todo) templ.Component { func Home(todos []models.Todo) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { 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) templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer { if !templ_7745c5c3_IsBuffer {
@ -33,7 +33,7 @@ func Home(todos []db.Todo) templ.Component {
templ_7745c5c3_Buffer = templ.GetBuffer() templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"text-xl\">Todos</div><form hx-post=\"/todos\" hx-target=\"#todos\" hx-swap=\"beforeend\"><input name=\"name\" type=\"text\"> <button type=\"submit\">Submit</button></form><div id=\"todos\">") _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"flex content-center items-center flex-col\"><h1 class=\"text-3xl\">Todos</h1><form hx-post=\"/todos\" hx-target=\"#todos\" hx-swap=\"beforeend\"><input name=\"name\" type=\"text\" class=\"border border-black\"> <button type=\"submit\">Submit</button></form><div id=\"todos\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@ -43,7 +43,7 @@ func Home(todos []db.Todo) templ.Component {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>") _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }

View File

@ -1,14 +1,14 @@
package partials package partials
import "michaelthomson.dev/mthomson/go-todos-app/db" import "michaelthomson.dev/mthomson/go-todos-app/models"
func todoId(todo db.Todo) string { func todoId(todo models.Todo) string {
return "todo-" + todo.Id.String() return "todo-" + todo.Id.String()
} }
templ Todo(todo db.Todo) { templ Todo(todo models.Todo) {
<div id={ todoId(todo) }> <div id={ todoId(todo) }>
{ todo.Id.String() }: { todo.Name } { todo.Name }
<button hx-delete={ "/todos/" + todo.Id.String() } hx-target={ "#" + todoId(todo) } hx-swap="delete">Delete</button> <button hx-delete={ "/todos/" + todo.Id.String() } hx-target={ "#" + todoId(todo) } hx-swap="delete">Delete</button>
</div> </div>
} }

View File

@ -10,13 +10,13 @@ import "context"
import "io" import "io"
import "bytes" import "bytes"
import "michaelthomson.dev/mthomson/go-todos-app/db" import "michaelthomson.dev/mthomson/go-todos-app/models"
func todoId(todo db.Todo) string { func todoId(todo models.Todo) string {
return "todo-" + todo.Id.String() return "todo-" + todo.Id.String()
} }
func Todo(todo db.Todo) templ.Component { func Todo(todo models.Todo) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { 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) templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer { if !templ_7745c5c3_IsBuffer {
@ -47,37 +47,24 @@ func Todo(todo db.Todo) templ.Component {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var3 string var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(todo.Id.String()) templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(todo.Name)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/partials/todo.templ`, Line: 11, Col: 22} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/partials/todo.templ`, Line: 11, Col: 15}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err 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(" <button hx-delete=\"") _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" <button hx-delete=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var5 string var templ_7745c5c3_Var4 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs("/todos/" + todo.Id.String()) templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs("/todos/" + todo.Id.String())
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/partials/todo.templ`, Line: 12, Col: 52} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/partials/todo.templ`, Line: 12, Col: 52}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@ -85,12 +72,12 @@ func Todo(todo db.Todo) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var6 string var templ_7745c5c3_Var5 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs("#" + todoId(todo)) templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs("#" + todoId(todo))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/partials/todo.templ`, Line: 12, Col: 85} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/partials/todo.templ`, Line: 12, Col: 85}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }