package handlers import ( "net/http" "time" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" "github.com/rss2/backend/internal/config" "github.com/rss2/backend/internal/db" "github.com/rss2/backend/internal/models" "golang.org/x/crypto/bcrypt" ) var jwtSecret []byte func CheckFirstUser(c *gin.Context) { var count int err := db.GetPool().QueryRow(c.Request.Context(), "SELECT COUNT(*) FROM users").Scan(&count) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to check users"}) return } c.JSON(http.StatusOK, gin.H{"is_first_user": count == 0, "total_users": count}) } func InitAuth(secret string) { jwtSecret = []byte(secret) } type Claims struct { UserID int64 `json:"user_id"` Email string `json:"email"` Username string `json:"username"` IsAdmin bool `json:"is_admin"` jwt.RegisteredClaims } func Login(c *gin.Context) { var req models.LoginRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, models.ErrorResponse{Error: "Invalid request", Message: err.Error()}) return } var user models.User err := db.GetPool().QueryRow(c.Request.Context(), ` SELECT id, email, username, password_hash, is_admin, created_at, updated_at FROM users WHERE email = $1`, req.Email).Scan( &user.ID, &user.Email, &user.Username, &user.PasswordHash, &user.IsAdmin, &user.CreatedAt, &user.UpdatedAt, ) if err != nil { c.JSON(http.StatusUnauthorized, models.ErrorResponse{Error: "Invalid credentials"}) return } if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(req.Password)); err != nil { c.JSON(http.StatusUnauthorized, models.ErrorResponse{Error: "Invalid credentials"}) return } expirationTime := time.Now().Add(24 * time.Hour) claims := &Claims{ UserID: user.ID, Email: user.Email, Username: user.Username, IsAdmin: user.IsAdmin, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(expirationTime), IssuedAt: jwt.NewNumericDate(time.Now()), }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) tokenString, err := token.SignedString(jwtSecret) if err != nil { c.JSON(http.StatusInternalServerError, models.ErrorResponse{Error: "Failed to generate token"}) return } c.JSON(http.StatusOK, models.AuthResponse{ Token: tokenString, User: user, }) } func Register(c *gin.Context) { var req models.RegisterRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, models.ErrorResponse{Error: "Invalid request", Message: err.Error()}) return } hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) if err != nil { c.JSON(http.StatusInternalServerError, models.ErrorResponse{Error: "Failed to hash password"}) return } var userCount int db.GetPool().QueryRow(c.Request.Context(), "SELECT COUNT(*) FROM users").Scan(&userCount) isFirstUser := userCount == 0 var userID int64 err = db.GetPool().QueryRow(c.Request.Context(), ` INSERT INTO users (email, username, password_hash, is_admin, created_at, updated_at) VALUES ($1, $2, $3, $4, NOW(), NOW()) RETURNING id`, req.Email, req.Username, string(hashedPassword), isFirstUser, ).Scan(&userID) if err != nil { c.JSON(http.StatusInternalServerError, models.ErrorResponse{Error: "Failed to create user", Message: err.Error()}) return } var user models.User err = db.GetPool().QueryRow(c.Request.Context(), ` SELECT id, email, username, is_admin, created_at, updated_at FROM users WHERE id = $1`, userID).Scan( &user.ID, &user.Email, &user.Username, &user.IsAdmin, &user.CreatedAt, &user.UpdatedAt, ) if err != nil { c.JSON(http.StatusInternalServerError, models.ErrorResponse{Error: "Failed to fetch user"}) return } expirationTime := time.Now().Add(24 * time.Hour) claims := &Claims{ UserID: user.ID, Email: user.Email, Username: user.Username, IsAdmin: user.IsAdmin, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(expirationTime), IssuedAt: jwt.NewNumericDate(time.Now()), }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) tokenString, err := token.SignedString(jwtSecret) if err != nil { c.JSON(http.StatusInternalServerError, models.ErrorResponse{Error: "Failed to generate token"}) return } c.JSON(http.StatusCreated, models.AuthResponse{ Token: tokenString, User: user, IsFirstUser: isFirstUser, }) } func GetCurrentUser(c *gin.Context) { userVal, exists := c.Get("user") if !exists { c.JSON(http.StatusUnauthorized, models.ErrorResponse{Error: "Not authenticated"}) return } claims := userVal.(*Claims) var user models.User err := db.GetPool().QueryRow(c.Request.Context(), ` SELECT id, email, username, is_admin, created_at, updated_at FROM users WHERE id = $1`, claims.UserID).Scan( &user.ID, &user.Email, &user.Username, &user.IsAdmin, &user.CreatedAt, &user.UpdatedAt, ) if err != nil { c.JSON(http.StatusNotFound, models.ErrorResponse{Error: "User not found"}) return } c.JSON(http.StatusOK, user) } func init() { cfg := config.Load() InitAuth(cfg.SecretKey) }