Enhance user profile management and Gravatar integration
- Added email field to user profile API and frontend components for better user identification. - Implemented PATCH /map/api/me endpoint to update user email, enhancing user experience. - Introduced useGravatarUrl composable for generating Gravatar URLs based on user email. - Updated profile and layout components to display user avatars using Gravatar, improving visual consistency. - Enhanced development documentation to guide testing of navbar and profile features.
This commit is contained in:
@@ -33,7 +33,14 @@ func (h *Handlers) APIRouter(rw http.ResponseWriter, req *http.Request) {
|
||||
h.APILogout(rw, req)
|
||||
return
|
||||
case "me":
|
||||
h.APIMe(rw, req)
|
||||
switch req.Method {
|
||||
case http.MethodGet:
|
||||
h.APIMe(rw, req)
|
||||
case http.MethodPatch:
|
||||
h.APIMeUpdate(rw, req)
|
||||
default:
|
||||
JSONError(rw, http.StatusMethodNotAllowed, "method not allowed", "METHOD_NOT_ALLOWED")
|
||||
}
|
||||
return
|
||||
case "me/tokens":
|
||||
h.APIMeTokens(rw, req)
|
||||
|
||||
@@ -18,12 +18,17 @@ type meResponse struct {
|
||||
Auths []string `json:"auths"`
|
||||
Tokens []string `json:"tokens,omitempty"`
|
||||
Prefix string `json:"prefix,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
}
|
||||
|
||||
type passwordRequest struct {
|
||||
Pass string `json:"pass"`
|
||||
}
|
||||
|
||||
type meUpdateRequest struct {
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
// APILogin handles POST /map/api/login.
|
||||
func (h *Handlers) APILogin(rw http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != http.MethodPost {
|
||||
@@ -105,9 +110,36 @@ func (h *Handlers) APIMe(rw http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
out := meResponse{Username: s.Username, Auths: s.Auths}
|
||||
out.Tokens, out.Prefix = h.Auth.GetUserTokensAndPrefix(ctx, s.Username)
|
||||
if u := h.Auth.GetUserByUsername(ctx, s.Username); u != nil {
|
||||
out.Email = u.Email
|
||||
}
|
||||
JSON(rw, http.StatusOK, out)
|
||||
}
|
||||
|
||||
// APIMeUpdate handles PATCH /map/api/me (update current user email).
|
||||
func (h *Handlers) APIMeUpdate(rw http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != http.MethodPatch {
|
||||
JSONError(rw, http.StatusMethodNotAllowed, "method not allowed", "METHOD_NOT_ALLOWED")
|
||||
return
|
||||
}
|
||||
ctx := req.Context()
|
||||
s := h.Auth.GetSession(ctx, req)
|
||||
if s == nil {
|
||||
JSONError(rw, http.StatusUnauthorized, "Unauthorized", "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
var body meUpdateRequest
|
||||
if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
|
||||
JSONError(rw, http.StatusBadRequest, "bad request", "BAD_REQUEST")
|
||||
return
|
||||
}
|
||||
if err := h.Auth.SetUserEmail(ctx, s.Username, body.Email); err != nil {
|
||||
JSONError(rw, http.StatusInternalServerError, "internal error", "INTERNAL_ERROR")
|
||||
return
|
||||
}
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
// APIMeTokens handles POST /map/api/me/tokens.
|
||||
func (h *Handlers) APIMeTokens(rw http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != http.MethodPost {
|
||||
|
||||
@@ -287,6 +287,51 @@ func TestAPIMePassword(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIMeUpdate_UpdatesEmail(t *testing.T) {
|
||||
env := newTestEnv(t)
|
||||
env.createUser(t, "alice", "pass", app.Auths{app.AUTH_MAP})
|
||||
sid := env.loginSession(t, "alice")
|
||||
|
||||
patchBody := `{"email":"test@example.com"}`
|
||||
req := withSession(httptest.NewRequest(http.MethodPatch, "/map/api/me", strings.NewReader(patchBody)), sid)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rr := httptest.NewRecorder()
|
||||
env.h.APIMeUpdate(rr, req)
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d: %s", rr.Code, rr.Body.String())
|
||||
}
|
||||
|
||||
req2 := withSession(httptest.NewRequest(http.MethodGet, "/map/api/me", nil), sid)
|
||||
rr2 := httptest.NewRecorder()
|
||||
env.h.APIMe(rr2, req2)
|
||||
if rr2.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200 on GET /me, got %d", rr2.Code)
|
||||
}
|
||||
var meResp struct {
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
if err := json.NewDecoder(rr2.Body).Decode(&meResp); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if meResp.Email != "test@example.com" {
|
||||
t.Fatalf("expected email test@example.com, got %q", meResp.Email)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIRouter_Me_MethodNotAllowed(t *testing.T) {
|
||||
env := newTestEnv(t)
|
||||
env.createUser(t, "alice", "pass", app.Auths{app.AUTH_MAP})
|
||||
sid := env.loginSession(t, "alice")
|
||||
|
||||
req := withSession(httptest.NewRequest(http.MethodPost, "/map/api/me", nil), sid)
|
||||
rr := httptest.NewRecorder()
|
||||
env.h.APIRouter(rr, req)
|
||||
if rr.Code != http.StatusMethodNotAllowed {
|
||||
t.Fatalf("expected 405 for POST /me, got %d", rr.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminUsers_RequiresAdmin(t *testing.T) {
|
||||
env := newTestEnv(t)
|
||||
env.createUser(t, "alice", "pass", app.Auths{app.AUTH_MAP})
|
||||
|
||||
Reference in New Issue
Block a user