| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 |
- package handlers
- import (
- "errors"
- "net/http"
- "strings"
- "time"
- "github.com/celestia-trace/backend/config"
- "github.com/celestia-trace/backend/models"
- "github.com/gin-gonic/gin"
- "github.com/golang-jwt/jwt/v5"
- "golang.org/x/crypto/bcrypt"
- "gorm.io/gorm"
- )
- type AuthHandler struct {
- DB *gorm.DB
- Cfg *config.Config
- }
- func respondError(c *gin.Context, status int, code int, message string) {
- c.JSON(status, models.ErrorResponse{Code: code, Message: message})
- }
- func respondSuccess(c *gin.Context, data interface{}) {
- c.JSON(http.StatusOK, gin.H{"code": 0, "message": "success", "data": data})
- }
- func (h *AuthHandler) generateToken(userID string) (string, error) {
- token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
- "sub": userID,
- "exp": time.Now().Add(7 * 24 * time.Hour).Unix(),
- })
- return token.SignedString([]byte(h.Cfg.JWTSecret))
- }
- func isEmail(identifier string) bool {
- return strings.Contains(identifier, "@")
- }
- // Register handler
- func (h *AuthHandler) Register(c *gin.Context) {
- var req models.RegisterRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- respondError(c, http.StatusBadRequest, 400, "invalid input data")
- return
- }
- hash, err := bcrypt.GenerateFromPassword([]byte(req.Password), 10)
- if err != nil {
- respondError(c, http.StatusInternalServerError, 500, "failed to hash password")
- return
- }
- user := models.User{
- Username: req.Username,
- PasswordHash: string(hash),
- }
- if isEmail(req.Identifier) {
- user.Email = &req.Identifier
- } else {
- user.PhoneNumber = &req.Identifier
- }
- if err := h.DB.Create(&user).Error; err != nil {
- if strings.Contains(err.Error(), "duplicate key") || strings.Contains(err.Error(), "UNIQUE constraint") {
- respondError(c, http.StatusConflict, 409, "username or identifier already exists")
- return
- }
- respondError(c, http.StatusInternalServerError, 500, "failed to create user")
- return
- }
- token, err := h.generateToken(user.ID)
- if err != nil {
- respondError(c, http.StatusInternalServerError, 500, "failed to generate token")
- return
- }
- respondSuccess(c, models.AuthResponse{User: user, Token: token})
- }
- // Login handler
- func (h *AuthHandler) Login(c *gin.Context) {
- var req models.LoginRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- respondError(c, http.StatusBadRequest, 400, "invalid input data")
- return
- }
- var user models.User
- if err := h.DB.Where("username = ? OR email = ? OR phone_number = ?", req.Identifier, req.Identifier, req.Identifier).First(&user).Error; err != nil {
- if errors.Is(err, gorm.ErrRecordNotFound) {
- respondError(c, http.StatusUnauthorized, 401, "invalid credentials")
- return
- }
- respondError(c, http.StatusInternalServerError, 500, "database error")
- return
- }
- if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(req.Password)); err != nil {
- respondError(c, http.StatusUnauthorized, 401, "invalid credentials")
- return
- }
- token, err := h.generateToken(user.ID)
- if err != nil {
- respondError(c, http.StatusInternalServerError, 500, "failed to generate token")
- return
- }
- respondSuccess(c, models.AuthResponse{User: user, Token: token})
- }
- // Logout handler
- func (h *AuthHandler) Logout(c *gin.Context) {
- // Client side discards the token
- respondSuccess(c, nil)
- }
- // GetProfile handler
- func (h *AuthHandler) GetProfile(c *gin.Context) {
- userID := c.GetString("userID")
- var user models.User
- if err := h.DB.First(&user, "id = ?", userID).Error; err != nil {
- respondError(c, http.StatusNotFound, 404, "user not found")
- return
- }
- respondSuccess(c, user)
- }
- // UpdateProfile handler
- func (h *AuthHandler) UpdateProfile(c *gin.Context) {
- userID := c.GetString("userID")
- var req models.UpdateProfileRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- respondError(c, http.StatusBadRequest, 400, "invalid input data")
- return
- }
- var user models.User
- if err := h.DB.First(&user, "id = ?", userID).Error; err != nil {
- respondError(c, http.StatusNotFound, 404, "user not found")
- return
- }
- if req.Username != nil && len(*req.Username) >= 2 {
- user.Username = *req.Username
- }
- if req.Email != nil {
- user.Email = req.Email
- }
- if req.PhoneNumber != nil {
- user.PhoneNumber = req.PhoneNumber
- }
- if req.AvatarURL != nil {
- user.AvatarURL = req.AvatarURL
- }
- if err := h.DB.Save(&user).Error; err != nil {
- respondError(c, http.StatusInternalServerError, 500, "failed to update profile")
- return
- }
- respondSuccess(c, user)
- }
- // ChangePassword handler
- func (h *AuthHandler) ChangePassword(c *gin.Context) {
- userID := c.GetString("userID")
- var req models.ChangePasswordRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- respondError(c, http.StatusBadRequest, 400, "invalid input data")
- return
- }
- var user models.User
- if err := h.DB.First(&user, "id = ?", userID).Error; err != nil {
- respondError(c, http.StatusNotFound, 404, "user not found")
- return
- }
- if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(req.OldPassword)); err != nil {
- respondError(c, http.StatusUnauthorized, 401, "invalid old password")
- return
- }
- hash, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), 10)
- if err != nil {
- respondError(c, http.StatusInternalServerError, 500, "failed to hash new password")
- return
- }
- user.PasswordHash = string(hash)
- if err := h.DB.Save(&user).Error; err != nil {
- respondError(c, http.StatusInternalServerError, 500, "failed to change password")
- return
- }
- respondSuccess(c, nil)
- }
|