95 lines
2.8 KiB
Go
95 lines
2.8 KiB
Go
package handler
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/prabhupadasays/spsays-bff/internal/middleware"
|
|
"github.com/prabhupadasays/spsays-bff/internal/repository"
|
|
"github.com/prabhupadasays/spsays-bff/internal/service"
|
|
"github.com/prabhupadasays/spsays-bff/pkg/response"
|
|
)
|
|
|
|
// UserHandler exposes profile-edit + avatar-presign endpoints.
|
|
// All routes here sit behind middleware.RequireAuth.
|
|
type UserHandler struct {
|
|
svc *service.UserService
|
|
}
|
|
|
|
func NewUserHandler(svc *service.UserService) *UserHandler {
|
|
return &UserHandler{svc: svc}
|
|
}
|
|
|
|
// updateProfileReq accepts partial updates. Use pointers so the JSON decoder
|
|
// can tell "client explicitly sent null" from "field not present" — the
|
|
// service treats nil-name as "leave alone" and ""-name as an error.
|
|
type updateProfileReq struct {
|
|
Name *string `json:"name"`
|
|
AvatarURL *string `json:"avatar_url"`
|
|
Handle *string `json:"handle"`
|
|
}
|
|
|
|
// UpdateProfile patches name, avatar, and/or handle.
|
|
func (h *UserHandler) UpdateProfile(w http.ResponseWriter, r *http.Request) {
|
|
uid := middleware.UserID(r.Context())
|
|
|
|
var req updateProfileReq
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
response.Error(w, http.StatusBadRequest, "invalid JSON body")
|
|
return
|
|
}
|
|
|
|
u, err := h.svc.UpdateProfile(r.Context(), uid, req.Name, req.AvatarURL, req.Handle)
|
|
if err != nil {
|
|
if errors.Is(err, repository.ErrHandleTaken) {
|
|
response.Error(w, http.StatusConflict, "that handle is already in use")
|
|
return
|
|
}
|
|
response.Error(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
response.JSON(w, http.StatusOK, u)
|
|
}
|
|
|
|
type presignReq struct {
|
|
ContentType string `json:"content_type"`
|
|
}
|
|
|
|
type presignResp struct {
|
|
UploadURL string `json:"upload_url"`
|
|
PublicURL string `json:"public_url"`
|
|
}
|
|
|
|
// AvatarPresign returns a one-shot R2 PUT URL the browser uses to upload.
|
|
// The frontend then PATCHes /users/me with the returned public_url to
|
|
// persist the new avatar on the user row.
|
|
func (h *UserHandler) AvatarPresign(w http.ResponseWriter, r *http.Request) {
|
|
uid := middleware.UserID(r.Context())
|
|
|
|
var req presignReq
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
response.Error(w, http.StatusBadRequest, "invalid JSON body")
|
|
return
|
|
}
|
|
ct := strings.TrimSpace(req.ContentType)
|
|
if ct == "" {
|
|
response.Error(w, http.StatusBadRequest, "content_type is required")
|
|
return
|
|
}
|
|
|
|
upload, public, err := h.svc.AvatarPresign(r.Context(), uid, ct)
|
|
if err != nil {
|
|
// Differentiate "feature off" from "bad content type" so the frontend
|
|
// can show a useful message.
|
|
if strings.Contains(err.Error(), "not configured") {
|
|
response.Error(w, http.StatusServiceUnavailable, err.Error())
|
|
return
|
|
}
|
|
response.Error(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
response.JSON(w, http.StatusOK, presignResp{UploadURL: upload, PublicURL: public})
|
|
}
|