From 35329cb70a86dbd697f8caba036ce4814610968f Mon Sep 17 00:00:00 2001 From: "Muhammad.haekal Rahmatullah" Date: Thu, 1 Oct 2026 00:27:59 -0400 Subject: [PATCH] Upload files to "/" --- go.mod | 5 ++ go.sum | 2 + haekal.go | 203 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 210 insertions(+) create mode 100644 go.mod create mode 100644 go.sum create mode 100644 haekal.go diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..dd14647 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module task-manager-api + +go 1.26.6 + +require github.com/google/uuid v1.6.0 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..7790d7c --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +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/haekal.go b/haekal.go new file mode 100644 index 0000000..a3a9cf3 --- /dev/null +++ b/haekal.go @@ -0,0 +1,203 @@ +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) + } +}