package main import ( "encoding/json" "fmt" "net/http" "strings" "sync" "github.com/google/uuid" ) type Task struct { ID string `json:"id"` Name string `json:"name"` } type TaskStore struct { mu sync.RWMutex tasks map[string]Task } func NewTaskStore() *TaskStore { return &TaskStore{ tasks: make(map[string]Task), } } func (s *TaskStore) Create(name string) Task { s.mu.Lock() defer s.mu.Unlock() id := uuid.New().String() task := Task{ ID: id, Name: name, } s.tasks[id] = task return task } func (s *TaskStore) GetAll() []Task { s.mu.RLock() defer s.mu.RUnlock() // Guarantee an initialized, non-nil slice to serialize as [] instead of null taskList := make([]Task, 0, len(s.tasks)) for _, task := range s.tasks { taskList = append(taskList, task) } return taskList } func (s *TaskStore) GetByID(id string) (Task, bool) { s.mu.RLock() defer s.mu.RUnlock() task, exists := s.tasks[id] return task, exists } func (s *TaskStore) Update(id string, name string) (Task, bool) { s.mu.Lock() defer s.mu.Unlock() if _, exists := s.tasks[id]; !exists { return Task{}, false } task := Task{ ID: id, Name: name, } s.tasks[id] = task return task, true } func (s *TaskStore) Delete(id string) bool { s.mu.Lock() defer s.mu.Unlock() if _, exists := s.tasks[id]; !exists { return false } delete(s.tasks, id) return true } type Server struct { store *TaskStore } func writeJSONResponse(w http.ResponseWriter, statusCode int, data interface{}) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(statusCode) if data != nil { json.NewEncoder(w).Encode(data) } } func writeJSONError(w http.ResponseWriter, statusCode int, message string) { writeJSONResponse(w, statusCode, map[string]string{"error": message}) } // Handler for /tasks (POST, GET) func (s *Server) handleTasks(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: tasks := s.store.GetAll() writeJSONResponse(w, http.StatusOK, tasks) case http.MethodPost: var req struct { Name string `json:"name"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeJSONError(w, http.StatusBadRequest, "Invalid JSON body") return } if strings.TrimSpace(req.Name) == "" { writeJSONError(w, http.StatusBadRequest, "Name field is required and cannot be empty") return } task := s.store.Create(req.Name) writeJSONResponse(w, http.StatusCreated, task) default: writeJSONError(w, http.StatusMethodNotAllowed, "Method not allowed") } } // Handler for /tasks/{id} (GET, PUT, DELETE) func (s *Server) handleTaskByID(w http.ResponseWriter, r *http.Request) { id := strings.TrimPrefix(r.URL.Path, "/tasks/") if id == "" || strings.Contains(id, "/") { writeJSONError(w, http.StatusBadRequest, "Invalid ID path") return } switch r.Method { case http.MethodGet: task, found := s.store.GetByID(id) if !found { writeJSONError(w, http.StatusNotFound, "Task not found") return } writeJSONResponse(w, http.StatusOK, task) case http.MethodPut: var req struct { Name string `json:"name"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeJSONError(w, http.StatusBadRequest, "Invalid JSON body") return } if strings.TrimSpace(req.Name) == "" { writeJSONError(w, http.StatusBadRequest, "Name field is required and cannot be empty") return } task, updated := s.store.Update(id, req.Name) if !updated { writeJSONError(w, http.StatusNotFound, "Task not found") return } writeJSONResponse(w, http.StatusOK, task) case http.MethodDelete: deleted := s.store.Delete(id) if !deleted { writeJSONError(w, http.StatusNotFound, "Task not found") return } // Must output exactly 204 with no body w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusNoContent) default: writeJSONError(w, http.StatusMethodNotAllowed, "Method not allowed") } } func main() { server := &Server{ store: NewTaskStore(), } mux := http.NewServeMux() mux.HandleFunc("/tasks", server.handleTasks) mux.HandleFunc("/tasks/", server.handleTaskByID) fmt.Println("Server running on port 8080...") if err := http.ListenAndServe(":8080", mux); err != nil { panic(err) } }