user_handler.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. package api
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "net/http"
  6. "strconv"
  7. "strings"
  8. "gorm.io/gorm"
  9. "github.com/go-chi/chi"
  10. "github.com/porter-dev/porter/internal/forms"
  11. "github.com/porter-dev/porter/internal/models"
  12. "github.com/porter-dev/porter/internal/repository"
  13. )
  14. // Enumeration of user API error codes, represented as int64
  15. const (
  16. ErrUserDecode ErrorCode = iota + 600
  17. ErrUserValidateFields
  18. ErrUserDataWrite
  19. ErrUserDataRead
  20. )
  21. // HandleCreateUser validates a user form entry, converts the user to a gorm
  22. // model, and saves the user to the database
  23. func (app *App) HandleCreateUser(w http.ResponseWriter, r *http.Request) {
  24. form := &forms.CreateUserForm{}
  25. user, err := app.writeUser(
  26. form,
  27. app.repo.User.CreateUser,
  28. w,
  29. r,
  30. doesUserExist,
  31. )
  32. if err == nil {
  33. app.logger.Info().Msgf("New user created: %d", user.ID)
  34. w.WriteHeader(http.StatusCreated)
  35. }
  36. }
  37. // HandleReadUser returns an externalized User (models.UserExternal)
  38. // based on an ID
  39. func (app *App) HandleReadUser(w http.ResponseWriter, r *http.Request) {
  40. id, err := strconv.ParseUint(chi.URLParam(r, "id"), 0, 64)
  41. if err != nil || id == 0 {
  42. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  43. return
  44. }
  45. user, err := app.repo.User.ReadUser(uint(id))
  46. if err != nil {
  47. app.handleErrorRead(err, ErrUserDataRead, w)
  48. return
  49. }
  50. extUser := user.Externalize()
  51. if err := json.NewEncoder(w).Encode(extUser); err != nil {
  52. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  53. return
  54. }
  55. w.WriteHeader(http.StatusOK)
  56. }
  57. // HandleUpdateUser validates an update user form entry, updates the user
  58. // in the database, and writes status accepted
  59. func (app *App) HandleUpdateUser(w http.ResponseWriter, r *http.Request) {
  60. id, err := strconv.ParseUint(chi.URLParam(r, "id"), 0, 64)
  61. if err != nil || id == 0 {
  62. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  63. return
  64. }
  65. form := &forms.UpdateUserForm{
  66. ID: uint(id),
  67. }
  68. user, err := app.writeUser(form, app.repo.User.UpdateUser, w, r)
  69. if err == nil {
  70. app.logger.Info().Msgf("User updated: %d", user.ID)
  71. w.WriteHeader(http.StatusAccepted)
  72. }
  73. }
  74. // HandleDeleteUser is majestic
  75. func (app *App) HandleDeleteUser(w http.ResponseWriter, r *http.Request) {
  76. id, err := strconv.ParseUint(chi.URLParam(r, "id"), 0, 64)
  77. if err != nil || id == 0 {
  78. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  79. return
  80. }
  81. // TODO -- HASH AND VERIFY PASSWORD BEFORE USER DELETION
  82. form := &forms.DeleteUserForm{
  83. ID: uint(id),
  84. Password: "testing",
  85. }
  86. user, err := app.writeUser(form, app.repo.User.DeleteUser, w, r)
  87. if err == nil {
  88. app.logger.Info().Msgf("User deleted: %d", user.ID)
  89. w.WriteHeader(http.StatusAccepted)
  90. }
  91. }
  92. // ------------------------ User handler helper functions ------------------------ //
  93. // writeUser will take a POST or PUT request to the /api/users endpoint and decode
  94. // the request into a forms.WriteUserForm model, convert it to a models.User, and
  95. // write to the database.
  96. func (app *App) writeUser(
  97. form forms.WriteUserForm,
  98. dbWrite repository.WriteUser,
  99. w http.ResponseWriter,
  100. r *http.Request,
  101. validators ...func(repo *repository.Repository, user *models.User) *HTTPError,
  102. ) (*models.User, error) {
  103. // decode from JSON to form value
  104. if err := json.NewDecoder(r.Body).Decode(form); err != nil {
  105. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  106. return nil, err
  107. }
  108. // validate the form
  109. if err := app.validator.Struct(form); err != nil {
  110. app.handleErrorFormValidation(err, ErrUserValidateFields, w)
  111. return nil, err
  112. }
  113. // convert the form to a user model -- WriteUserForm must implement ToUser
  114. userModel, err := form.ToUser()
  115. if err != nil {
  116. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  117. return nil, err
  118. }
  119. // Check any additional validators for any semantic errors
  120. // We have completed all syntax checks, so these will be sent
  121. // with http.StatusUnprocessableEntity (422), unless this is
  122. // an internal server error
  123. for _, validator := range validators {
  124. err := validator(app.repo, userModel)
  125. if err != nil {
  126. goErr := errors.New(strings.Join(err.Errors, ", "))
  127. if err.Code == 500 {
  128. app.sendExternalError(
  129. goErr,
  130. http.StatusInternalServerError,
  131. *err,
  132. w,
  133. )
  134. } else {
  135. app.sendExternalError(
  136. goErr,
  137. http.StatusUnprocessableEntity,
  138. *err,
  139. w,
  140. )
  141. }
  142. return nil, goErr
  143. }
  144. }
  145. // handle write to the database
  146. user, err := dbWrite(userModel)
  147. if err != nil {
  148. app.handleErrorDataWrite(err, ErrUserDataWrite, w)
  149. return nil, err
  150. }
  151. return user, nil
  152. }
  153. func doesUserExist(repo *repository.Repository, user *models.User) *HTTPError {
  154. user, err := repo.User.ReadUserByEmail(user.Email)
  155. if user != nil && err == nil {
  156. return &HTTPError{
  157. Code: ErrUserValidateFields,
  158. Errors: []string{
  159. "Email already taken",
  160. },
  161. }
  162. }
  163. if err != gorm.ErrRecordNotFound {
  164. return &ErrorDataRead
  165. }
  166. return nil
  167. }