package handlers import ( "net/http" "time" db "zardzul/music-index/sqlc" "zardzul/music-index/utils" "zardzul/music-index/repository" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgtype" "golang.org/x/crypto/bcrypt" ) type UserHandler struct { repo repository.UserRepository jwtSecret string jwtIssuer string jwtTTL time.Duration } func NewUserHandler(repo repository.UserRepository, jwtSecret string, jwtIssuer string, jwtTTL time.Duration) *UserHandler { return &UserHandler{ repo: repo, jwtSecret: jwtSecret, jwtIssuer: jwtIssuer, jwtTTL: jwtTTL, } } type CreateUserRequest struct { UserName string `json:"user_name" binding:"required"` UserMail string `json:"user_mail" binding:"required,email"` Password string `json:"password" binding:"required,min=12"` } type MessageResponse struct { Message string `json:"message"` } type CreateUserResponse struct { Message string `json:"message"` ID string `json:"id"` } type UsernameResponse struct { UserName string `json:"user_name"` } type ErrorResponse struct { Error string `json:"error"` } // CreateUser godoc // @Summary Create a user // @Tags user // @Accept json // @Produce json // @Param payload body CreateUserRequest true "User payload" // @Success 201 {object} CreateUserResponse // @Failure 400 {object} ErrorResponse // @Failure 500 {object} ErrorResponse // @Router /users/create [post] func (h *UserHandler) CreateUser(c *gin.Context) { var req CreateUserRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } var pgID pgtype.UUID if err := pgID.Scan(uuid.New().String()); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate user id"}) return } hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to hash password"}) return } id, err := h.repo.CreateUser(c.Request.Context(), db.CreateUserParams{ ID: pgID, UserName: req.UserName, UserMail: req.UserMail, Password: string(hashedPassword), }) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, gin.H{ "message": "user created", "id": id.String(), }) } // GetUsernameByID godoc // @Summary Get username by user ID // @Tags user // @Produce json // @Param id path string true "User ID (UUID)" // @Success 200 {object} UsernameResponse // @Failure 400 {object} ErrorResponse // @Failure 500 {object} ErrorResponse // @Router /users/{id} [get] func (h *UserHandler) GetUsernameByID(c *gin.Context) { idParam := c.Param("id") var pgID pgtype.UUID if err := pgID.Scan(idParam); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid user id"}) return } username, err := h.repo.GetUsernameByID(c.Request.Context(), pgID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "user_name": username, }) } type LoginRequest struct { UserMail string `json:"user_mail" binding:"required,email"` Password string `json:"password" binding:"required"` } type LoginResponse struct { Token string `json:"token"` } // Login godoc // @Summary Log in with email and password // @Tags user // @Accept json // @Produce json // @Param payload body LoginRequest true "Login payload" // @Success 200 {object} LoginResponse // @Failure 400 {object} ErrorResponse // @Failure 401 {object} ErrorResponse // @Failure 500 {object} ErrorResponse // @Router /users/login [post] func (h *UserHandler) Login(c *gin.Context) { var req LoginRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, ErrorResponse{Error: err.Error()}) return } user, err := h.repo.GetUserAuthByEmail(c.Request.Context(), req.UserMail) if err != nil { c.JSON(http.StatusUnauthorized, ErrorResponse{Error: "invalid credentials"}) return } if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password)); err != nil { c.JSON(http.StatusUnauthorized, ErrorResponse{Error: "invalid credentials"}) return } token, err := utils.GenerateToken( h.jwtSecret, h.jwtIssuer, user.ID.String(), user.UserName, user.UserMail, h.jwtTTL, ) if err != nil { c.JSON(http.StatusInternalServerError, ErrorResponse{Error: "failed to create token"}) return } c.JSON(http.StatusOK, LoginResponse{Token: token}) }