150 lines
2.9 KiB
Go
150 lines
2.9 KiB
Go
|
|
package main
|
||
|
|
|
||
|
|
import (
|
||
|
|
"encoding/json"
|
||
|
|
"errors"
|
||
|
|
"fmt"
|
||
|
|
"io"
|
||
|
|
"log"
|
||
|
|
"net/http"
|
||
|
|
"os"
|
||
|
|
"strings"
|
||
|
|
"sync"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
type User struct {
|
||
|
|
ID int `json:"id"`
|
||
|
|
Name string `json:"name"`
|
||
|
|
Email string `json:"email"`
|
||
|
|
Active bool `json:"active"`
|
||
|
|
CreatedAt time.Time `json:"created_at"`
|
||
|
|
}
|
||
|
|
|
||
|
|
type UserService struct {
|
||
|
|
mu sync.RWMutex
|
||
|
|
users map[int]*User
|
||
|
|
}
|
||
|
|
|
||
|
|
func NewUserService() *UserService {
|
||
|
|
return &UserService{
|
||
|
|
users: make(map[int]*User),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *UserService) Add(user *User) error {
|
||
|
|
s.mu.Lock()
|
||
|
|
defer s.mu.Unlock()
|
||
|
|
|
||
|
|
if user.Name == "" {
|
||
|
|
return errors.New("name is required")
|
||
|
|
}
|
||
|
|
if !strings.Contains(user.Email, "@") {
|
||
|
|
return errors.New("invalid email")
|
||
|
|
}
|
||
|
|
if _, exists := s.users[user.ID]; exists {
|
||
|
|
return errors.New("user already exists")
|
||
|
|
}
|
||
|
|
s.users[user.ID] = user
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *UserService) FindByID(id int) (*User, error) {
|
||
|
|
s.mu.RLock()
|
||
|
|
defer s.mu.RUnlock()
|
||
|
|
|
||
|
|
user, ok := s.users[id]
|
||
|
|
if !ok {
|
||
|
|
return nil, fmt.Errorf("user %d not found", id)
|
||
|
|
}
|
||
|
|
return user, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *UserService) FindByEmail(email string) *User {
|
||
|
|
s.mu.RLock()
|
||
|
|
defer s.mu.RUnlock()
|
||
|
|
|
||
|
|
for _, u := range s.users {
|
||
|
|
if u.Email == email {
|
||
|
|
return u
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *UserService) All() []*User {
|
||
|
|
s.mu.RLock()
|
||
|
|
defer s.mu.RUnlock()
|
||
|
|
|
||
|
|
result := make([]*User, 0, len(s.users))
|
||
|
|
for _, u := range s.users {
|
||
|
|
result = append(result, u)
|
||
|
|
}
|
||
|
|
return result
|
||
|
|
}
|
||
|
|
|
||
|
|
func loggingMiddleware(next http.Handler) http.Handler {
|
||
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
start := time.Now()
|
||
|
|
log.Printf("%s %s %s", r.Method, r.URL.Path, r.RemoteAddr)
|
||
|
|
next.ServeHTTP(w, r)
|
||
|
|
log.Printf("%s completed in %v", r.URL.Path, time.Since(start))
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func handleUsers(svc *UserService) http.HandlerFunc {
|
||
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
w.Header().Set("Content-Type", "application/json")
|
||
|
|
|
||
|
|
switch r.Method {
|
||
|
|
case http.MethodGet:
|
||
|
|
users := svc.All()
|
||
|
|
json.NewEncoder(w).Encode(users)
|
||
|
|
|
||
|
|
case http.MethodPost:
|
||
|
|
var user User
|
||
|
|
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
|
||
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
if err := svc.Add(&user); err != nil {
|
||
|
|
http.Error(w, err.Error(), http.StatusConflict)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
w.WriteHeader(http.StatusCreated)
|
||
|
|
json.NewEncoder(w).Encode(user)
|
||
|
|
|
||
|
|
default:
|
||
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func main() {
|
||
|
|
svc := NewUserService()
|
||
|
|
svc.Add(&User{
|
||
|
|
ID: 1,
|
||
|
|
Name: "Alice",
|
||
|
|
Email: "alice@example.com",
|
||
|
|
Active: true,
|
||
|
|
})
|
||
|
|
|
||
|
|
mux := http.NewServeMux()
|
||
|
|
mux.HandleFunc("/users", handleUsers(svc))
|
||
|
|
|
||
|
|
port := os.Getenv("PORT")
|
||
|
|
if port == "" {
|
||
|
|
port = "8080"
|
||
|
|
}
|
||
|
|
|
||
|
|
server := &http.Server{
|
||
|
|
Addr: ":" + port,
|
||
|
|
Handler: loggingMiddleware(mux),
|
||
|
|
ReadTimeout: 10 * time.Second,
|
||
|
|
WriteTimeout: 10 * time.Second,
|
||
|
|
}
|
||
|
|
|
||
|
|
log.Printf("Server starting on :%s", port)
|
||
|
|
log.Fatal(server.ListenAndServe())
|
||
|
|
}
|