package auth import ( "errors" "net/http" "strings" "github.com/gin-gonic/gin" "golang.org/x/crypto/bcrypt" "gorm.io/gorm" "social-raiting.nekiiinkognito.ru/internal/models" ) type Handler struct { DB *gorm.DB JWTSecret string } func NewHandler(db *gorm.DB, jwtSecret string) Handler { return Handler{ DB: db, JWTSecret: jwtSecret, } } func (h Handler) Register(ctx *gin.Context) { var req RegisterRequest if err := ctx.ShouldBindJSON(&req); err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } email := strings.ToLower(strings.TrimSpace(req.Email)) passwordHash, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) if err != nil { ctx.JSON(http.StatusInternalServerError, gin.H{"error": "failed to hash password"}) return } user := models.User{ Email: email, PasswordHash: string(passwordHash), IsAdmin: req.IsAdmin, } if err := h.DB.Create(&user).Error; err != nil { if isDuplicateEmailError(err) { ctx.JSON(http.StatusConflict, gin.H{"error": "email is already registered"}) return } ctx.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create user"}) return } ctx.JSON(http.StatusCreated, UserResponse{User: user}) } func (h Handler) Login(ctx *gin.Context) { var req LoginRequest if err := ctx.ShouldBindJSON(&req); err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } var user models.User if err := h.DB.Where("email = ?", strings.ToLower(strings.TrimSpace(req.Email))).First(&user).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { ctx.JSON(http.StatusUnauthorized, gin.H{"error": "invalid credentials"}) return } ctx.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load user"}) return } if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(req.Password)); err != nil { ctx.JSON(http.StatusUnauthorized, gin.H{"error": "invalid credentials"}) return } token, err := GenerateToken(user.ID, h.JWTSecret, TokenOptions{}) if err != nil { ctx.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create token"}) return } ctx.JSON(http.StatusOK, AuthResponse{ Token: token, User: user, }) } func (h Handler) Me(ctx *gin.Context) { userID, exists := ctx.Get("userID") if !exists { ctx.JSON(http.StatusUnauthorized, gin.H{"error": "missing user context"}) return } var user models.User if err := h.DB.First(&user, "id = ?", userID).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { ctx.JSON(http.StatusNotFound, gin.H{"error": "user not found"}) return } ctx.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load user"}) return } ctx.JSON(http.StatusOK, UserResponse{User: user}) } func isDuplicateEmailError(err error) bool { if err == nil { return false } errText := strings.ToLower(err.Error()) return strings.Contains(errText, "duplicate") || strings.Contains(errText, "1062") }