fs3/backend/main.go
rdev-worker c7f7f656c2
Some checks are pending
ci/woodpecker/push/woodpecker Pipeline is pending
build: Build a simple Go API server with: 1. GET /api/health endpoint 2. GET...
2026-01-31 07:47:53 +00:00

199 lines
4.9 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"`
Completed bool `json:"completed"`
CreatedAt time.Time `json:"createdAt"`
}
type TaskStore struct {
mu sync.RWMutex
tasks map[int]Task
nextID int
}
func NewTaskStore() *TaskStore {
return &TaskStore{
tasks: make(map[int]Task),
nextID: 1,
}
}
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(title, description string) Task {
s.mu.Lock()
defer s.mu.Unlock()
task := Task{
ID: s.nextID,
Title: title,
Description: description,
Completed: false,
CreatedAt: time.Now(),
}
s.tasks[s.nextID] = 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 jsonResponse(w http.ResponseWriter, status int, resp APIResponse) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(resp)
}
func main() {
store := NewTaskStore()
// Add some sample tasks
store.Create("Welcome to Task Manager", "This is your first task. Click on it to see details!")
store.Create("Add a new task", "Use the Add Task button to create new tasks")
store.Create("Stay organized", "Keep track of all your tasks in one place")
r := chi.NewRouter()
// Middleware
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
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,
}))
// Routes
r.Route("/api", func(r chi.Router) {
// Health check
r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
jsonResponse(w, http.StatusOK, APIResponse{Success: true, Data: "OK"})
})
// Hello World
r.Get("/hello", func(w http.ResponseWriter, r *http.Request) {
jsonResponse(w, http.StatusOK, APIResponse{Success: true, Data: "Hello World"})
})
r.Get("/tasks", func(w http.ResponseWriter, r *http.Request) {
tasks := store.GetAll()
jsonResponse(w, http.StatusOK, APIResponse{Success: true, Data: tasks})
})
r.Post("/tasks", func(w http.ResponseWriter, r *http.Request) {
var input struct {
Title string `json:"title"`
Description string `json:"description"`
}
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
jsonResponse(w, http.StatusBadRequest, APIResponse{Success: false, Error: "Invalid request body"})
return
}
if input.Title == "" {
jsonResponse(w, http.StatusBadRequest, APIResponse{Success: false, Error: "Title is required"})
return
}
task := store.Create(input.Title, input.Description)
jsonResponse(w, http.StatusCreated, APIResponse{Success: true, Data: task})
})
r.Get("/tasks/{id}", func(w http.ResponseWriter, r *http.Request) {
idStr := chi.URLParam(r, "id")
id, err := strconv.Atoi(idStr)
if err != nil {
jsonResponse(w, http.StatusBadRequest, APIResponse{Success: false, Error: "Invalid task ID"})
return
}
task, ok := store.Get(id)
if !ok {
jsonResponse(w, http.StatusNotFound, APIResponse{Success: false, Error: "Task not found"})
return
}
jsonResponse(w, http.StatusOK, APIResponse{Success: true, Data: task})
})
r.Delete("/tasks/{id}", func(w http.ResponseWriter, r *http.Request) {
idStr := chi.URLParam(r, "id")
id, err := strconv.Atoi(idStr)
if err != nil {
jsonResponse(w, http.StatusBadRequest, APIResponse{Success: false, Error: "Invalid task ID"})
return
}
if !store.Delete(id) {
jsonResponse(w, http.StatusNotFound, APIResponse{Success: false, Error: "Task not found"})
return
}
jsonResponse(w, http.StatusOK, APIResponse{Success: true, Data: "Task deleted"})
})
})
// Health check
r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
jsonResponse(w, http.StatusOK, APIResponse{Success: true, Data: "OK"})
})
port := ":8081"
log.Printf("Backend server starting on %s", port)
if err := http.ListenAndServe(port, r); err != nil {
log.Fatal(err)
}
}