feat(auth): add user profile, phone login, and account deletion

This commit is contained in:
iu
2026-10-12 14:26:32 +03:00
parent 1cb9aaf198
commit 86aff695df
46 changed files with 2893 additions and 134 deletions
+87 -10
View File
@@ -18,7 +18,8 @@ type Handler struct {
func NewHandler(svc *Service) *Handler { return &Handler{svc: svc} }
type registerRequest struct {
Email string `json:"email" binding:"required"`
Email string `json:"email"`
Phone string `json:"phone"`
Password string `json:"password" binding:"required"`
Name string `json:"name" binding:"required"`
}
@@ -26,34 +27,34 @@ type registerRequest struct {
func (h *Handler) Register(c *gin.Context) {
var req registerRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.RespondError(c, httpx.BadRequest("invalid_request", "email, password and name are required"))
httpx.RespondError(c, httpx.BadRequest("invalid_request", "name and password are required"))
return
}
u, pair, err := h.svc.Register(c.Request.Context(), req.Email, req.Password, req.Name)
u, pair, err := h.svc.Register(c.Request.Context(), req.Email, req.Phone, req.Password, req.Name)
if err != nil {
httpx.RespondError(c, err)
return
}
c.JSON(http.StatusCreated, gin.H{"user": u.Public(), "tokens": pair})
c.JSON(http.StatusCreated, gin.H{"user": u.Self(), "tokens": pair})
}
type loginRequest struct {
Email string `json:"email" binding:"required"`
Password string `json:"password" binding:"required"`
Identifier string `json:"identifier" binding:"required"`
Password string `json:"password" binding:"required"`
}
func (h *Handler) Login(c *gin.Context) {
var req loginRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.RespondError(c, httpx.BadRequest("invalid_request", "email and password are required"))
httpx.RespondError(c, httpx.BadRequest("invalid_request", "identifier and password are required"))
return
}
u, pair, err := h.svc.Login(c.Request.Context(), req.Email, req.Password)
u, pair, err := h.svc.Login(c.Request.Context(), req.Identifier, req.Password)
if err != nil {
httpx.RespondError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"user": u.Public(), "tokens": pair})
c.JSON(http.StatusOK, gin.H{"user": u.Self(), "tokens": pair})
}
type refreshRequest struct {
@@ -96,7 +97,83 @@ func (h *Handler) Me(c *gin.Context) {
if prefs == nil {
prefs = map[string]any{}
}
c.JSON(http.StatusOK, gin.H{"user": u.Public(), "preferences": prefs})
c.JSON(http.StatusOK, gin.H{"user": u.Self(), "preferences": prefs})
}
type updateMeRequest struct {
Name string `json:"name"`
}
// UpdateMe changes the display name of the authenticated user.
func (h *Handler) UpdateMe(c *gin.Context) {
var req updateMeRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.RespondError(c, httpx.BadRequest("invalid_request", "name is required"))
return
}
u, err := h.svc.UpdateName(c.Request.Context(), UserID(c), req.Name)
if err != nil {
httpx.RespondError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"user": u.Self()})
}
type changePasswordRequest struct {
CurrentPassword string `json:"current_password" binding:"required"`
NewPassword string `json:"new_password" binding:"required"`
}
// ChangePassword verifies the current password and revokes all refresh tokens.
func (h *Handler) ChangePassword(c *gin.Context) {
var req changePasswordRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.RespondError(c, httpx.BadRequest("invalid_request", "current_password and new_password are required"))
return
}
if err := h.svc.ChangePassword(c.Request.Context(), UserID(c), req.CurrentPassword, req.NewPassword); err != nil {
httpx.RespondError(c, err)
return
}
c.Status(http.StatusNoContent)
}
type addIdentifierRequest struct {
Email string `json:"email"`
Phone string `json:"phone"`
}
// AddIdentifier adds the one missing login identifier.
func (h *Handler) AddIdentifier(c *gin.Context) {
var req addIdentifierRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.RespondError(c, httpx.BadRequest("invalid_request", "email or phone is required"))
return
}
u, err := h.svc.AddIdentifier(c.Request.Context(), UserID(c), req.Email, req.Phone)
if err != nil {
httpx.RespondError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"user": u.Self()})
}
type deleteMeRequest struct {
Password string `json:"password" binding:"required"`
}
// DeleteMe deletes the authenticated account and all cascading content.
func (h *Handler) DeleteMe(c *gin.Context) {
var req deleteMeRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.RespondError(c, httpx.BadRequest("invalid_request", "password is required"))
return
}
if err := h.svc.DeleteAccount(c.Request.Context(), UserID(c), req.Password); err != nil {
httpx.RespondError(c, err)
return
}
c.Status(http.StatusNoContent)
}
const (