diff --git a/kipina-codebench/golden-examples/todo-go.md b/kipina-codebench/golden-examples/todo-go.md index fbd2b4f..b879334 100644 --- a/kipina-codebench/golden-examples/todo-go.md +++ b/kipina-codebench/golden-examples/todo-go.md @@ -71,7 +71,7 @@ type UpdateTodo struct { ## handlers.go -CRUD handlers as closures taking *sql.DB. INSERT/UPDATE use RETURNING, sql.ErrNoRows for 404. +CRUD handlers as closures taking *sql.DB. Key patterns: INSERT RETURNING, sql.ErrNoRows for 404, RowsAffected for delete. ```go package main @@ -81,67 +81,51 @@ import ( "encoding/json" "net/http" "strconv" - "github.com/go-chi/chi/v5" ) +// POST — decode JSON, defaults with nil-check, INSERT RETURNING, StatusCreated. func createTodo(db *sql.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var input CreateTodo if err := json.NewDecoder(r.Body).Decode(&input); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return + http.Error(w, err.Error(), http.StatusBadRequest); return } priority := int64(1) - if input.Priority != nil { - priority = *input.Priority - } + if input.Priority != nil { priority = *input.Priority } status := "pending" - if input.Status != nil { - status = *input.Status - } + if input.Status != nil { status = *input.Status } var todo Todo err := db.QueryRow( `INSERT INTO todos (title, description, due_date, priority, status) - VALUES (?, ?, ?, ?, ?) - RETURNING id, title, description, due_date, priority, status`, + VALUES (?, ?, ?, ?, ?) RETURNING id, title, description, due_date, priority, status`, input.Title, input.Description, input.DueDate, priority, status, ).Scan(&todo.ID, &todo.Title, &todo.Description, &todo.DueDate, &todo.Priority, &todo.Status) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } + if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError); return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(todo) } } +// GET list — db.Query + rows.Scan loop, empty slice not nil. func listTodos(db *sql.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { rows, err := db.Query("SELECT id, title, description, due_date, priority, status FROM todos") - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } + if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError); return } defer rows.Close() - var todos []Todo + todos := []Todo{} for rows.Next() { var t Todo - if err := rows.Scan(&t.ID, &t.Title, &t.Description, &t.DueDate, &t.Priority, &t.Status); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } + rows.Scan(&t.ID, &t.Title, &t.Description, &t.DueDate, &t.Priority, &t.Status) todos = append(todos, t) } - if todos == nil { - todos = []Todo{} - } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(todos) } } +// GET by id — QueryRow + sql.ErrNoRows → 404. func getTodo(db *sql.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) @@ -149,83 +133,51 @@ func getTodo(db *sql.DB) http.HandlerFunc { err := db.QueryRow( "SELECT id, title, description, due_date, priority, status FROM todos WHERE id = ?", id, ).Scan(&todo.ID, &todo.Title, &todo.Description, &todo.DueDate, &todo.Priority, &todo.Status) - if err == sql.ErrNoRows { - http.Error(w, "not found", http.StatusNotFound) - return - } - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } + if err == sql.ErrNoRows { http.Error(w, "not found", http.StatusNotFound); return } + if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError); return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(todo) } } +// PUT — fetch existing, merge with input nil-checks, UPDATE RETURNING. func updateTodo(db *sql.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) var existing Todo - err := db.QueryRow( - "SELECT id, title, description, due_date, priority, status FROM todos WHERE id = ?", id, + err := db.QueryRow("SELECT id, title, description, due_date, priority, status FROM todos WHERE id = ?", id, ).Scan(&existing.ID, &existing.Title, &existing.Description, &existing.DueDate, &existing.Priority, &existing.Status) - if err == sql.ErrNoRows { - http.Error(w, "not found", http.StatusNotFound) - return - } - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } + if err == sql.ErrNoRows { http.Error(w, "not found", http.StatusNotFound); return } + if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError); return } var input UpdateTodo if err := json.NewDecoder(r.Body).Decode(&input); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - if input.Title != nil { - existing.Title = *input.Title - } - if input.Description != nil { - existing.Description = input.Description - } - if input.DueDate != nil { - existing.DueDate = input.DueDate - } - if input.Priority != nil { - existing.Priority = *input.Priority - } - if input.Status != nil { - existing.Status = *input.Status + http.Error(w, err.Error(), http.StatusBadRequest); return } + if input.Title != nil { existing.Title = *input.Title } + if input.Description != nil { existing.Description = input.Description } + if input.DueDate != nil { existing.DueDate = input.DueDate } + if input.Priority != nil { existing.Priority = *input.Priority } + if input.Status != nil { existing.Status = *input.Status } var updated Todo err = db.QueryRow( - `UPDATE todos SET title = ?, description = ?, due_date = ?, priority = ?, status = ? - WHERE id = ? + `UPDATE todos SET title=?, description=?, due_date=?, priority=?, status=? WHERE id=? RETURNING id, title, description, due_date, priority, status`, existing.Title, existing.Description, existing.DueDate, existing.Priority, existing.Status, id, ).Scan(&updated.ID, &updated.Title, &updated.Description, &updated.DueDate, &updated.Priority, &updated.Status) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } + if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError); return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(updated) } } +// DELETE — Exec + RowsAffected == 0 → 404, else 204. func deleteTodo(db *sql.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) result, err := db.Exec("DELETE FROM todos WHERE id = ?", id) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } + if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError); return } rows, _ := result.RowsAffected() - if rows == 0 { - http.Error(w, "not found", http.StatusNotFound) - return - } + if rows == 0 { http.Error(w, "not found", http.StatusNotFound); return } w.WriteHeader(http.StatusNoContent) } }