123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- package handlers
- import (
- "fmt"
- "net/http"
- "git.linuxforward.com/byop/byop-engine/dbstore"
- "git.linuxforward.com/byop/byop-engine/models"
- "github.com/gin-gonic/gin"
- "github.com/go-playground/validator/v10"
- "golang.org/x/crypto/bcrypt"
- )
- // UserHandler handles user-related operations
- type UserHandler struct {
- Store *dbstore.SQLiteStore
- Validate *validator.Validate
- }
- // NewUserHandler creates a new UserHandler
- func NewUserHandler(store *dbstore.SQLiteStore) *UserHandler {
- return &UserHandler{
- Store: store,
- Validate: validator.New(),
- }
- }
- // RegisterUserRoutes registers routes for user operations
- func (h *UserHandler) RegisterUserRoutes(rg *gin.RouterGroup) {
- rg.POST("/", h.CreateUser)
- rg.GET("/:id", h.GetUser)
- rg.PUT("/:id", h.UpdateUser)
- rg.DELETE("/:id", h.DeleteUser)
- rg.GET("/", h.ListUsers)
- rg.GET("/:id/deployments", h.GetUserDeployments)
- }
- // CreateUserInput defines the input for creating a user
- type CreateUserInput struct {
- Email string `json:"email" validate:"required,email"`
- Password string `json:"password" validate:"required,min=8"`
- Name string `json:"name" validate:"required,min=2"`
- Role string `json:"role" validate:"omitempty,oneof=user admin editor"`
- Active *bool `json:"active"`
- }
- // CreateUser creates a new user
- func (h *UserHandler) CreateUser(c *gin.Context) {
- ctx := c.Request.Context()
- var input CreateUserInput
- if err := c.ShouldBindJSON(&input); err != nil {
- appErr := models.NewErrValidation("invalid_user_input_format", map[string]string{"body": "Invalid request body"}, err)
- models.RespondWithError(c, appErr)
- return
- }
- if err := h.Validate.StructCtx(ctx, input); err != nil {
- errors := models.ExtractValidationErrors(err)
- appErr := models.NewErrValidation("user_validation_failed", errors, err)
- models.RespondWithError(c, appErr)
- return
- }
- hashedPassword, err := bcrypt.GenerateFromPassword([]byte(input.Password), bcrypt.DefaultCost)
- if err != nil {
- appErr := models.NewErrInternalServer("password_hash_failed", fmt.Errorf("Failed to hash password: %w", err))
- models.RespondWithError(c, appErr)
- return
- }
- userRole := models.RoleUser
- if input.Role != "" {
- userRole = input.Role
- }
- userActive := true
- if input.Active != nil {
- userActive = *input.Active
- }
- user := models.User{
- Email: input.Email,
- Password: string(hashedPassword),
- Name: input.Name,
- Role: userRole,
- Active: userActive,
- }
- err = h.Store.CreateUser(ctx, &user)
- if err != nil {
- if models.IsErrConflict(err) {
- models.RespondWithError(c, err)
- return
- }
- appErr := models.NewErrInternalServer("failed_to_create_user", fmt.Errorf("Failed to create user: %w", err))
- models.RespondWithError(c, appErr)
- return
- }
- // GORM automatically sets the ID after creation
- // Clear the password before sending the response
- createdUser := user
- createdUser.Password = ""
- c.JSON(http.StatusCreated, createdUser)
- }
- // GetUser retrieves a user by ID
- func (h *UserHandler) GetUser(c *gin.Context) {
- ctx := c.Request.Context()
- id, err := parseUintID(c, "id")
- if err != nil {
- models.RespondWithError(c, err)
- return
- }
- user, err := h.Store.GetUserByID(ctx, id)
- if err != nil {
- models.RespondWithError(c, err)
- return
- }
- user.Password = ""
- c.JSON(http.StatusOK, user)
- }
- // UpdateUserInput defines the input for updating a user
- type UpdateUserInput struct {
- Email *string `json:"email,omitempty" validate:"omitempty,email"`
- Password *string `json:"password,omitempty" validate:"omitempty,min=8"`
- Name *string `json:"name,omitempty" validate:"omitempty,min=2"`
- Role *string `json:"role,omitempty" validate:"omitempty,oneof=user admin editor"`
- Active *bool `json:"active,omitempty"`
- }
- // UpdateUser updates an existing user
- func (h *UserHandler) UpdateUser(c *gin.Context) {
- ctx := c.Request.Context()
- id, err := parseUintID(c, "id")
- if err != nil {
- models.RespondWithError(c, err)
- return
- }
- var input UpdateUserInput
- if err := c.ShouldBindJSON(&input); err != nil {
- appErr := models.NewErrValidation("invalid_update_user_input_format", map[string]string{"body": "Invalid request body"}, err)
- models.RespondWithError(c, appErr)
- return
- }
- if err := h.Validate.StructCtx(ctx, input); err != nil {
- errors := models.ExtractValidationErrors(err)
- appErr := models.NewErrValidation("update_user_validation_failed", errors, err)
- models.RespondWithError(c, appErr)
- return
- }
- user, err := h.Store.GetUserByID(ctx, id)
- if err != nil {
- models.RespondWithError(c, err)
- return
- }
- updated := false
- if input.Email != nil {
- user.Email = *input.Email
- updated = true
- }
- if input.Password != nil {
- hashedPassword, err := bcrypt.GenerateFromPassword([]byte(*input.Password), bcrypt.DefaultCost)
- if err != nil {
- appErr := models.NewErrInternalServer("update_password_hash_failed", fmt.Errorf("Failed to hash new password: %w", err))
- models.RespondWithError(c, appErr)
- return
- }
- user.Password = string(hashedPassword)
- updated = true
- }
- if input.Name != nil {
- user.Name = *input.Name
- updated = true
- }
- if input.Role != nil {
- user.Role = *input.Role
- updated = true
- }
- if input.Active != nil {
- user.Active = *input.Active
- updated = true
- }
- if !updated {
- user.Password = ""
- c.JSON(http.StatusOK, user)
- return
- }
- if err := h.Store.UpdateUser(ctx, user); err != nil {
- models.RespondWithError(c, err)
- return
- }
- user.Password = ""
- c.JSON(http.StatusOK, user)
- }
- // DeleteUser deletes a user by ID
- func (h *UserHandler) DeleteUser(c *gin.Context) {
- ctx := c.Request.Context()
- id, err := parseUintID(c, "id")
- if err != nil {
- models.RespondWithError(c, err)
- return
- }
- if err := h.Store.DeleteUser(ctx, id); err != nil {
- models.RespondWithError(c, err)
- return
- }
- c.Status(http.StatusNoContent)
- }
- // ListUsers retrieves all users
- func (h *UserHandler) ListUsers(c *gin.Context) {
- ctx := c.Request.Context()
- users, err := h.Store.GetUsers(ctx)
- if err != nil {
- appErr := models.NewErrInternalServer("failed_to_list_users", fmt.Errorf("Failed to list users: %w", err))
- models.RespondWithError(c, appErr)
- return
- }
- for i := range users {
- users[i].Password = ""
- }
- c.JSON(http.StatusOK, users)
- }
- // GetUserDeployments returns all deployments for a specific user
- func (h *UserHandler) GetUserDeployments(c *gin.Context) {
- ctx := c.Request.Context()
- userID, err := parseUintID(c, "id")
- if err != nil {
- models.RespondWithError(c, err)
- return
- }
- _, err = h.Store.GetUserByID(ctx, userID)
- if err != nil {
- models.RespondWithError(c, err)
- return
- }
- deployments, err := h.Store.GetDeploymentsByUserID(ctx, userID)
- if err != nil {
- appErr := models.NewErrInternalServer("failed_to_get_user_deployments", fmt.Errorf("Failed to get deployments for user %d: %w", userID, err))
- models.RespondWithError(c, appErr)
- return
- }
- if deployments == nil {
- deployments = []*models.Deployment{}
- }
- c.JSON(http.StatusOK, deployments)
- }
|