From 9495f5ec9f5bfd5eed2ec77fb1fb1a9549023c11 Mon Sep 17 00:00:00 2001 From: Exsors Date: Tue, 1 Sep 2026 14:43:06 +0700 Subject: [PATCH] first commit --- go.mod | 3 + main.go | 183 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 186 insertions(+) create mode 100644 go.mod create mode 100644 main.go diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..88779ec --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module task-manager + +go 1.21 \ No newline at end of file diff --git a/main.go b/main.go new file mode 100644 index 0000000..be0f48e --- /dev/null +++ b/main.go @@ -0,0 +1,183 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" + "strings" + "sync" + "sync/atomic" +) + +type Task struct { + ID string `json:"id"` + Name string `json:"name"` +} + +type createUpdateRequest struct { + Name string `json:"name"` +} + +var ( + store = make(map[string]Task) + mu sync.RWMutex + idSeq int64 +) + +func main() { + mux := http.NewServeMux() + + mux.HandleFunc("/tasks", handleTasks) // GET list + POST create + mux.HandleFunc("/tasks/", handleTaskByID) // GET/PUT/DELETE by id + + fmt.Println("Server running on http://localhost:8080") + if err := http.ListenAndServe(":8080", mux); err != nil { + panic(err) + } +} + +func handleTasks(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet: + listTasks(w, r) + case http.MethodPost: + createTask(w, r) + default: + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + } +} + +func handleTaskByID(w http.ResponseWriter, r *http.Request) { + // Path: /tasks/{id} + id := strings.TrimPrefix(r.URL.Path, "/tasks/") + if id == "" || strings.Contains(id, "/") { + writeJSONError(w, "Invalid ID", http.StatusBadRequest) + return + } + + switch r.Method { + case http.MethodGet: + getTask(w, r, id) + case http.MethodPut: + updateTask(w, r, id) + case http.MethodDelete: + deleteTask(w, r, id) + default: + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + } +} + +// ---------- Handlers ---------- + +func listTasks(w http.ResponseWriter, r *http.Request) { + mu.RLock() + defer mu.RUnlock() + + // PENTING: selalu kembalikan slice kosong, bukan nil + tasks := make([]Task, 0, len(store)) + for _, t := range store { + tasks = append(tasks, t) + } + + writeJSON(w, tasks, http.StatusOK) +} + +func createTask(w http.ResponseWriter, r *http.Request) { + var req createUpdateRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + writeJSONError(w, "Invalid JSON body", http.StatusBadRequest) + return + } + + if strings.TrimSpace(req.Name) == "" { + writeJSONError(w, "name is required and cannot be empty", http.StatusBadRequest) + return + } + + // Generate ID (server-side only) + newID := strconv.FormatInt(atomic.AddInt64(&idSeq, 1), 10) + + task := Task{ + ID: newID, + Name: req.Name, + } + + mu.Lock() + store[newID] = task + mu.Unlock() + + writeJSON(w, task, http.StatusCreated) +} + +func getTask(w http.ResponseWriter, r *http.Request, id string) { + mu.RLock() + task, exists := store[id] + mu.RUnlock() + + if !exists { + writeJSONError(w, "Task not found", http.StatusNotFound) + return + } + + writeJSON(w, task, http.StatusOK) +} + +func updateTask(w http.ResponseWriter, r *http.Request, id string) { + var req createUpdateRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + writeJSONError(w, "Invalid JSON body", http.StatusBadRequest) + return + } + + if strings.TrimSpace(req.Name) == "" { + writeJSONError(w, "name is required and cannot be empty", http.StatusBadRequest) + return + } + + mu.Lock() + defer mu.Unlock() + + task, exists := store[id] + if !exists { + writeJSONError(w, "Task not found", http.StatusNotFound) + return + } + + // Full replacement of name, ID remains the same + task.Name = req.Name + store[id] = task + + writeJSON(w, task, http.StatusOK) +} + +func deleteTask(w http.ResponseWriter, r *http.Request, id string) { + mu.Lock() + defer mu.Unlock() + + if _, exists := store[id]; !exists { + writeJSONError(w, "Task not found", http.StatusNotFound) + return + } + + delete(store, id) + + // 204 No Content — JANGAN tulis body apapun + w.WriteHeader(http.StatusNoContent) +} + +// ---------- Helper ---------- + +func writeJSON(w http.ResponseWriter, data interface{}, status int) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(status) + json.NewEncoder(w).Encode(data) +} + +func writeJSONError(w http.ResponseWriter, message string, status int) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(status) + json.NewEncoder(w).Encode(map[string]string{ + "error": message, + }) +}