package api import ( "context" "errors" "log/slog" "net/http" "github.com/go-chi/chi/v5" "github.com/go-chi/render" "github.com/google/uuid" ) func Whoami(w http.ResponseWriter, r *http.Request) { slog.Debug("user: entering Whoami handler") user, ok := r.Context().Value(userKey{}).(*User) if !ok || user == nil { slog.Debug("user: anonymous user") w.Write([]byte("anonymous")) return } slog.Debug("user: returning username", "userid", user.ID, "username", user.Name) w.Write([]byte(user.Name)) } func LoginCtx(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { slog.Debug("user: entering LoginCtx middleware") userID, ok := r.Context().Value(userIDKey).(uuid.UUID) if !ok || userID == uuid.Nil { slog.Debug("user: no user ID provided, assuming anonymous user") next.ServeHTTP(w, r) return } slog.Debug("user: fetching user by ID", "user ID", userID) user, err := dbGetUser(userID.String()) if err != nil { slog.Error("user: failed to fetch user by ID", "user ID", userID, "error", err) render.Render(w, r, ErrNotFound) return } slog.Debug("user: successfully fetched user", "user ID", user.ID, "username", user.Name) ctx := context.WithValue(r.Context(), userKey{}, user) next.ServeHTTP(w, r.WithContext(ctx)) }) } func ListUsers(w http.ResponseWriter, r *http.Request) { slog.Debug("user: entering ListUsers handler") dbUsers, err := dbGetAllUsers() if err != nil { slog.Error("user: failed to fetch users", "error", err) render.Render(w, r, ErrInternal(err)) return } slog.Debug("user: successfully fetched users", "count", len(dbUsers)) if err := render.RenderList(w, r, NewUserListResponse(dbUsers)); err != nil { slog.Error("user: failed to render user list response", "error", err) render.Render(w, r, ErrInternal(err)) return } } func GetUser(w http.ResponseWriter, r *http.Request) { slog.Debug("user: entering GetUser handler") userID := chi.URLParam(r, "userID") parsed, err := uuid.Parse(userID) if err != nil { render.Render(w, r, ErrInvalidRequest(err)) return } user, err := dbGetUser(parsed.String()) if err != nil { if errors.Is(err, ErrUserNotFound) { render.Render(w, r, ErrNotFound) } else { slog.Error("user: failed to fetch user", "userid", parsed.String(), "error", err) render.Render(w, r, ErrInternal(err)) } return } slog.Debug("user: rendering user", "userid", user.ID, "username", user.Name) if err := render.Render(w, r, NewUserPayloadResponse(user)); err != nil { slog.Error("user: failed to render user", "userid", parsed.String(), "error", err) render.Render(w, r, ErrInternal(err)) } } func newUserID() uuid.UUID { return uuid.New() } func NewUser(w http.ResponseWriter, r *http.Request) { slog.Debug("user: entering NewUser handler") err := r.ParseMultipartForm(64 << 10) if err != nil { slog.Error("user: failed to parse multipartform", "error", err) http.Error(w, "Unable to parse form", http.StatusBadRequest) return } newUserName := r.FormValue("name") password := r.FormValue("password") if newUserName == "" || password == "" { slog.Error("user: username or password is empty") http.Error(w, "Username and password cannot be empty", http.StatusBadRequest) return } slog.Debug("user: hashing password for new user", "userName", newUserName) hashedPassword, err := hashPassword(password) if err != nil { slog.Error("user: failed to hash password", "error", err) http.Error(w, "Unable to hash password", http.StatusInternalServerError) return } newUser := User{ ID: newUserID(), Name: newUserName, Password: hashedPassword, } slog.Debug("user: adding new user to database", "userID", newUser.ID, "userName", newUser.Name) err = dbAddUser(&newUser) if err != nil { slog.Error("user: failed to add new user", "userID", newUser.ID, "userName", newUser.Name, "error", err) render.Render(w, r, ErrInternal(err)) return } slog.Debug("user: successfully added new user", "userID", newUser.ID, "userName", newUser.Name) render.Render(w, r, NewUserPayloadResponse(&newUser)) } type User struct { ID uuid.UUID `json:"id"` Name string `json:"name"` Password string `json:"-"` } type userKey struct{} type UserPayload struct { *User }