package todos
import (
"fmt"
"sync"
"github.com/go-mizu/mizu"
)
// Todo represents a todo item.
type Todo struct {
ID string `json:"id"`
Title string `json:"title"`
Completed bool `json:"completed"`
}
// Store is an in-memory todo store.
type Store struct {
mu sync.RWMutex
todos map[string]*Todo
nextID int
}
// NewStore creates a new todo store.
func NewStore() *Store {
return &Store{
todos: make(map[string]*Todo),
}
}
// CreateInput is the input for creating a todo.
type CreateInput struct {
Title string `json:"title"`
}
// UpdateInput is the input for updating a todo.
type UpdateInput struct {
Title *string `json:"title"`
Completed *bool `json:"completed"`
}
// List returns a handler that lists all todos.
func List(store *Store) mizu.Handler {
return func(c *mizu.Ctx) error {
store.mu.RLock()
defer store.mu.RUnlock()
todos := make([]*Todo, 0, len(store.todos))
for _, t := range store.todos {
todos = append(todos, t)
}
return c.JSON(200, todos)
}
}
// Create returns a handler that creates a todo.
func Create(store *Store) mizu.Handler {
return func(c *mizu.Ctx) error {
var input CreateInput
if err := c.Bind(&input); err != nil {
return err
}
if input.Title == "" {
return c.JSON(400, map[string]string{"error": "title is required"})
}
store.mu.Lock()
defer store.mu.Unlock()
store.nextID++
todo := &Todo{
ID: fmt.Sprintf("%d", store.nextID),
Title: input.Title,
Completed: false,
}
store.todos[todo.ID] = todo
return c.JSON(201, todo)
}
}
// Get returns a handler that gets a todo by ID.
func Get(store *Store) mizu.Handler {
return func(c *mizu.Ctx) error {
id := c.Param("id")
store.mu.RLock()
defer store.mu.RUnlock()
todo, ok := store.todos[id]
if !ok {
return c.JSON(404, map[string]string{"error": "todo not found"})
}
return c.JSON(200, todo)
}
}
// Update returns a handler that updates a todo.
func Update(store *Store) mizu.Handler {
return func(c *mizu.Ctx) error {
id := c.Param("id")
var input UpdateInput
if err := c.Bind(&input); err != nil {
return err
}
store.mu.Lock()
defer store.mu.Unlock()
todo, ok := store.todos[id]
if !ok {
return c.JSON(404, map[string]string{"error": "todo not found"})
}
if input.Title != nil {
todo.Title = *input.Title
}
if input.Completed != nil {
todo.Completed = *input.Completed
}
return c.JSON(200, todo)
}
}
// Delete returns a handler that deletes a todo.
func Delete(store *Store) mizu.Handler {
return func(c *mizu.Ctx) error {
id := c.Param("id")
store.mu.Lock()
defer store.mu.Unlock()
if _, ok := store.todos[id]; !ok {
return c.JSON(404, map[string]string{"error": "todo not found"})
}
delete(store.todos, id)
return c.JSON(200, map[string]string{"message": "deleted"})
}
}