225 lines
5.4 KiB
Go
225 lines
5.4 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"log"
|
|
"net/http"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/go-chi/chi/v5/middleware"
|
|
"github.com/go-chi/cors"
|
|
)
|
|
|
|
type Task struct {
|
|
ID int `json:"id"`
|
|
Title string `json:"title"`
|
|
Description string `json:"description"`
|
|
Status string `json:"status"`
|
|
Priority string `json:"priority"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
UpdatedAt time.Time `json:"updatedAt"`
|
|
}
|
|
|
|
type TaskStore struct {
|
|
mu sync.RWMutex
|
|
tasks map[int]Task
|
|
nextID int
|
|
}
|
|
|
|
func NewTaskStore() *TaskStore {
|
|
store := &TaskStore{
|
|
tasks: make(map[int]Task),
|
|
nextID: 1,
|
|
}
|
|
// Add some sample tasks
|
|
now := time.Now()
|
|
store.tasks[1] = Task{
|
|
ID: 1,
|
|
Title: "Set up development environment",
|
|
Description: "Install all necessary tools and dependencies for the project",
|
|
Status: "completed",
|
|
Priority: "high",
|
|
CreatedAt: now.Add(-48 * time.Hour),
|
|
UpdatedAt: now.Add(-24 * time.Hour),
|
|
}
|
|
store.tasks[2] = Task{
|
|
ID: 2,
|
|
Title: "Design database schema",
|
|
Description: "Create the initial database schema for task management",
|
|
Status: "in_progress",
|
|
Priority: "high",
|
|
CreatedAt: now.Add(-24 * time.Hour),
|
|
UpdatedAt: now,
|
|
}
|
|
store.tasks[3] = Task{
|
|
ID: 3,
|
|
Title: "Implement user authentication",
|
|
Description: "Add JWT-based authentication system",
|
|
Status: "pending",
|
|
Priority: "medium",
|
|
CreatedAt: now.Add(-12 * time.Hour),
|
|
UpdatedAt: now.Add(-12 * time.Hour),
|
|
}
|
|
store.nextID = 4
|
|
return store
|
|
}
|
|
|
|
func (s *TaskStore) GetAll() []Task {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
tasks := make([]Task, 0, len(s.tasks))
|
|
for _, task := range s.tasks {
|
|
tasks = append(tasks, task)
|
|
}
|
|
return tasks
|
|
}
|
|
|
|
func (s *TaskStore) Get(id int) (Task, bool) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
task, ok := s.tasks[id]
|
|
return task, ok
|
|
}
|
|
|
|
func (s *TaskStore) Create(task Task) Task {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
task.ID = s.nextID
|
|
task.CreatedAt = time.Now()
|
|
task.UpdatedAt = time.Now()
|
|
if task.Status == "" {
|
|
task.Status = "pending"
|
|
}
|
|
if task.Priority == "" {
|
|
task.Priority = "medium"
|
|
}
|
|
s.tasks[task.ID] = task
|
|
s.nextID++
|
|
return task
|
|
}
|
|
|
|
func (s *TaskStore) Delete(id int) bool {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
if _, ok := s.tasks[id]; ok {
|
|
delete(s.tasks, id)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
type APIResponse struct {
|
|
Success bool `json:"success"`
|
|
Data interface{} `json:"data,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
func respondJSON(w http.ResponseWriter, status int, payload interface{}) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(status)
|
|
json.NewEncoder(w).Encode(payload)
|
|
}
|
|
|
|
func main() {
|
|
store := NewTaskStore()
|
|
|
|
r := chi.NewRouter()
|
|
|
|
// Middleware
|
|
r.Use(middleware.Logger)
|
|
r.Use(middleware.Recoverer)
|
|
r.Use(middleware.RequestID)
|
|
r.Use(cors.Handler(cors.Options{
|
|
AllowedOrigins: []string{"http://localhost:3000", "http://localhost:8080", "*"},
|
|
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
|
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
|
|
ExposedHeaders: []string{"Link"},
|
|
AllowCredentials: true,
|
|
MaxAge: 300,
|
|
}))
|
|
|
|
// Health check
|
|
r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
|
|
respondJSON(w, http.StatusOK, APIResponse{Success: true, Data: "healthy"})
|
|
})
|
|
|
|
// API routes
|
|
r.Route("/api", func(r chi.Router) {
|
|
r.Route("/tasks", func(r chi.Router) {
|
|
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
|
tasks := store.GetAll()
|
|
respondJSON(w, http.StatusOK, APIResponse{Success: true, Data: tasks})
|
|
})
|
|
|
|
r.Post("/", func(w http.ResponseWriter, r *http.Request) {
|
|
var task Task
|
|
if err := json.NewDecoder(r.Body).Decode(&task); err != nil {
|
|
respondJSON(w, http.StatusBadRequest, APIResponse{
|
|
Success: false,
|
|
Error: "Invalid request body",
|
|
})
|
|
return
|
|
}
|
|
if task.Title == "" {
|
|
respondJSON(w, http.StatusBadRequest, APIResponse{
|
|
Success: false,
|
|
Error: "Title is required",
|
|
})
|
|
return
|
|
}
|
|
created := store.Create(task)
|
|
respondJSON(w, http.StatusCreated, APIResponse{Success: true, Data: created})
|
|
})
|
|
|
|
r.Get("/{id}", func(w http.ResponseWriter, r *http.Request) {
|
|
idStr := chi.URLParam(r, "id")
|
|
id, err := strconv.Atoi(idStr)
|
|
if err != nil {
|
|
respondJSON(w, http.StatusBadRequest, APIResponse{
|
|
Success: false,
|
|
Error: "Invalid task ID",
|
|
})
|
|
return
|
|
}
|
|
task, ok := store.Get(id)
|
|
if !ok {
|
|
respondJSON(w, http.StatusNotFound, APIResponse{
|
|
Success: false,
|
|
Error: "Task not found",
|
|
})
|
|
return
|
|
}
|
|
respondJSON(w, http.StatusOK, APIResponse{Success: true, Data: task})
|
|
})
|
|
|
|
r.Delete("/{id}", func(w http.ResponseWriter, r *http.Request) {
|
|
idStr := chi.URLParam(r, "id")
|
|
id, err := strconv.Atoi(idStr)
|
|
if err != nil {
|
|
respondJSON(w, http.StatusBadRequest, APIResponse{
|
|
Success: false,
|
|
Error: "Invalid task ID",
|
|
})
|
|
return
|
|
}
|
|
if !store.Delete(id) {
|
|
respondJSON(w, http.StatusNotFound, APIResponse{
|
|
Success: false,
|
|
Error: "Task not found",
|
|
})
|
|
return
|
|
}
|
|
respondJSON(w, http.StatusOK, APIResponse{Success: true, Data: "Task deleted"})
|
|
})
|
|
})
|
|
})
|
|
|
|
log.Println("Starting server on :8080")
|
|
if err := http.ListenAndServe(":8080", r); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|