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