package api import ( "context" "log/slog" "net/http" "github.com/go-chi/chi/v5" "github.com/go-chi/render" "github.com/google/uuid" ) func UserCtx(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { slog.Debug("user: entering UserCtx middleware") var user *User var err error if userID := chi.URLParam(r, "userID"); userID != "" { slog.Debug("user: fetching user", "userID", userID) user, err = dbGetUser(userID) } else { slog.Error("user: userID not found in URL parameters") render.Render(w, r, ErrNotFound) return } if err != nil { slog.Error("user: failed to fetch user", "userID", user.ID, "error", err) render.Render(w, r, ErrNotFound) return } slog.Debug("user: successfully fetched user", "userID", user.ID) ctx := context.WithValue(r.Context(), userKey{}, user) next.ServeHTTP(w, r.WithContext(ctx)) }) } 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 user name", "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") username, ok := r.Context().Value(usernameKey).(string) if !ok || username == "" { slog.Debug("user: no username provided, assuming anonymous user") next.ServeHTTP(w, r) return } slog.Debug("user: fetching user by username", "username", username) user, err := dbGetUserByName(username) if err != nil { slog.Error("user: failed to fetch user by username", "username", username, "error", err) render.Render(w, r, ErrNotFound) return } slog.Debug("user: successfully fetched user", "userID", user.ID, "username", user.Name) ctx := context.WithValue(r.Context(), userKey{}, user) next.ServeHTTP(w, r.WithContext(ctx)) }) } func GetUser(w http.ResponseWriter, r *http.Request) { slog.Debug("user: entering GetUser handler") user, ok := r.Context().Value(userKey{}).(*User) if !ok || user == nil { slog.Error("user: user not found in context") render.Render(w, r, ErrNotFound) 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 response", "userID", user.ID, "error", err) render.Render(w, r, ErrRender(err)) return } } 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, ErrRender(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, ErrRender(err)) return } } 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 multipart form", "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, ErrRender(err)) return } slog.Debug("user: successfully added new user", "userID", newUser.ID, "userName", newUser.Name) render.Render(w, r, NewUserPayloadResponse(&newUser)) } type userKey struct{} type User struct { ID uuid.UUID `json:"id"` Name string `json:"name"` Password string `json:"-"` } type UserPayload struct { *User }