package handler import ( "net/http" "strings" "github.com/gin-gonic/gin" "github.com/yuxingu/digital-psychology/apps/api/internal/middleware" "github.com/yuxingu/digital-psychology/apps/api/internal/service/auth" "github.com/yuxingu/digital-psychology/apps/api/pkg/response" ) // AuthHandler serves /api/v1/auth/*. type AuthHandler struct { Svc *auth.Service } // Register mounts auth routes. Public register/login; me/logout need device (+ session). func (h *AuthHandler) Register(api *gin.RouterGroup) { g := api.Group("/auth") g.POST("/register", h.RegisterAccount) g.POST("/login", h.Login) g.POST("/logout", h.Logout) g.GET("/me", h.Me) g.PATCH("/me", h.PatchMe) g.POST("/me/avatar", h.UploadAvatar) } type authBody struct { Phone string `json:"phone"` Password string `json:"password"` Nickname string `json:"nickname"` } // RegisterAccount handles POST /auth/register (DeviceAuth required on group). func (h *AuthHandler) RegisterAccount(c *gin.Context) { userID, ok := middleware.UserIDFromContext(c) if !ok { response.Fail(c, http.StatusUnauthorized, 40100, "unauthorized") return } var body authBody if err := c.ShouldBindJSON(&body); err != nil { response.Fail(c, http.StatusBadRequest, 10000, "invalid request") return } deviceKey := c.GetHeader(middleware.DeviceKeyHeader) res, err := h.Svc.Register(c.Request.Context(), userID, deviceKey, body.Phone, body.Password, body.Nickname) if err != nil { if failTextCompliance(c, err) { return } response.Fail(c, http.StatusBadRequest, 40110, err.Error()) return } response.OK(c, res) } // Login handles POST /auth/login (open mode: any phone+password). func (h *AuthHandler) Login(c *gin.Context) { userID, ok := middleware.UserIDFromContext(c) if !ok { response.Fail(c, http.StatusUnauthorized, 40100, "unauthorized") return } var body authBody if err := c.ShouldBindJSON(&body); err != nil { response.Fail(c, http.StatusBadRequest, 10000, "invalid request") return } deviceKey := c.GetHeader(middleware.DeviceKeyHeader) res, err := h.Svc.Login(c.Request.Context(), userID, deviceKey, body.Phone, body.Password) if err != nil { response.Fail(c, http.StatusBadRequest, 40111, err.Error()) return } c.Set(string(middleware.UserIDKey), res.User.ID) response.OK(c, res) } // Logout handles POST /auth/logout. func (h *AuthHandler) Logout(c *gin.Context) { tok := bearerToken(c) deviceKey := c.GetHeader(middleware.DeviceKeyHeader) _ = h.Svc.Logout(c.Request.Context(), tok, deviceKey) response.OK(c, gin.H{"ok": true}) } // Me handles GET /auth/me. func (h *AuthHandler) Me(c *gin.Context) { userID, ok := middleware.UserIDFromContext(c) if !ok { response.Fail(c, http.StatusUnauthorized, 40100, "unauthorized") return } me, err := h.Svc.Me(c.Request.Context(), userID) if err != nil { response.Fail(c, http.StatusUnauthorized, 40112, "请先登录") return } response.OK(c, me) } type patchMeBody struct { Nickname string `json:"nickname"` } // PatchMe handles PATCH /auth/me (update nickname). func (h *AuthHandler) PatchMe(c *gin.Context) { userID, ok := middleware.UserIDFromContext(c) if !ok { response.Fail(c, http.StatusUnauthorized, 40100, "unauthorized") return } var body patchMeBody if err := c.ShouldBindJSON(&body); err != nil { response.Fail(c, http.StatusBadRequest, 10000, "invalid request") return } me, err := h.Svc.UpdateNickname(c.Request.Context(), userID, body.Nickname) if err != nil { if failTextCompliance(c, err) { return } response.Fail(c, http.StatusBadRequest, 40113, err.Error()) return } response.OK(c, me) } // UploadAvatar handles POST /auth/me/avatar (multipart file). func (h *AuthHandler) UploadAvatar(c *gin.Context) { userID, ok := middleware.UserIDFromContext(c) if !ok { response.Fail(c, http.StatusUnauthorized, 40100, "unauthorized") return } fh, err := c.FormFile("file") if err != nil { response.Fail(c, http.StatusBadRequest, 10000, "请选择图片") return } me, err := h.Svc.UpdateAvatar(c.Request.Context(), userID, fh) if err != nil { response.Fail(c, http.StatusBadRequest, 40114, err.Error()) return } response.OK(c, me) } func bearerToken(c *gin.Context) string { h := c.GetHeader("Authorization") if strings.HasPrefix(strings.ToLower(h), "bearer ") { return strings.TrimSpace(h[7:]) } return "" }