Upload files to "/"
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
module task-manager-api
|
||||||
|
|
||||||
|
go 1.26.6
|
||||||
|
|
||||||
|
require github.com/google/uuid v1.6.0 // indirect
|
||||||
@@ -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=
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user