mgmt_api.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. package http
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "log/slog"
  7. "net/http"
  8. "os"
  9. "github.com/google/uuid"
  10. "github.com/mk6i/retro-aim-server/config"
  11. "github.com/mk6i/retro-aim-server/state"
  12. )
  13. type userWithPassword struct {
  14. state.User
  15. Password string `json:"password,omitempty"`
  16. }
  17. type userSession struct {
  18. ScreenName string `json:"screen_name"`
  19. }
  20. type onlineUsers struct {
  21. Count int `json:"count"`
  22. Sessions []userSession `json:"sessions"`
  23. }
  24. type UserManager interface {
  25. AllUsers() ([]state.User, error)
  26. InsertUser(u state.User) error
  27. SetUserPassword(u state.User) error
  28. }
  29. type SessionRetriever interface {
  30. AllSessions() []*state.Session
  31. }
  32. func StartManagementAPI(userManager UserManager, sessionRetriever SessionRetriever, logger *slog.Logger) {
  33. mux := http.NewServeMux()
  34. newUser := func() state.User {
  35. return state.User{AuthKey: uuid.New().String()}
  36. }
  37. mux.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) {
  38. userHandler(w, r, userManager, newUser, logger)
  39. })
  40. mux.HandleFunc("/user/password", func(w http.ResponseWriter, r *http.Request) {
  41. userPasswordHandler(w, r, userManager, newUser, logger)
  42. })
  43. mux.HandleFunc("/session", func(w http.ResponseWriter, r *http.Request) {
  44. sessionHandler(w, r, sessionRetriever)
  45. })
  46. //todo make port configurable
  47. addr := config.Address("", 8080)
  48. logger.Info("starting management API server", "addr", addr)
  49. if err := http.ListenAndServe(addr, mux); err != nil {
  50. logger.Error("unable to bind management API address address", "err", err.Error())
  51. os.Exit(1)
  52. }
  53. }
  54. func userHandler(
  55. w http.ResponseWriter,
  56. r *http.Request,
  57. userManager UserManager,
  58. newUser func() state.User,
  59. logger *slog.Logger,
  60. ) {
  61. switch r.Method {
  62. case http.MethodGet:
  63. getUserHandler(w, r, userManager, logger)
  64. case http.MethodPost:
  65. postUserHandler(w, r, userManager, newUser, logger)
  66. default:
  67. http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
  68. }
  69. }
  70. func userPasswordHandler(
  71. w http.ResponseWriter,
  72. r *http.Request,
  73. userManager UserManager,
  74. userFactory func() state.User,
  75. logger *slog.Logger,
  76. ) {
  77. switch r.Method {
  78. case http.MethodPut:
  79. putUserPasswordHandler(w, r, userManager, userFactory, logger)
  80. default:
  81. http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
  82. }
  83. }
  84. // putUserPasswordHandler handles the PUT /user/password endpoint.
  85. func putUserPasswordHandler(
  86. w http.ResponseWriter,
  87. r *http.Request,
  88. userManager UserManager,
  89. newUser func() state.User,
  90. logger *slog.Logger,
  91. ) {
  92. user := userWithPassword{
  93. User: newUser(),
  94. }
  95. if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
  96. http.Error(w, "malformed input", http.StatusBadRequest)
  97. return
  98. }
  99. if err := user.HashPassword(user.Password); err != nil {
  100. logger.Error("error hashing user password in PUT /user/password", "err", err.Error())
  101. http.Error(w, "internal server error", http.StatusInternalServerError)
  102. return
  103. }
  104. if err := userManager.SetUserPassword(user.User); err != nil {
  105. switch {
  106. case errors.Is(err, state.ErrNoUser):
  107. http.Error(w, "user does not exist", http.StatusNotFound)
  108. return
  109. case err != nil:
  110. logger.Error("error updating user password PUT /user/password", "err", err.Error())
  111. http.Error(w, "internal server error", http.StatusInternalServerError)
  112. return
  113. }
  114. }
  115. w.WriteHeader(http.StatusNoContent)
  116. }
  117. // sessionHandler handles GET /session
  118. func sessionHandler(w http.ResponseWriter, r *http.Request, sessionRetriever SessionRetriever) {
  119. w.Header().Set("Content-Type", "application/json")
  120. if r.Method != http.MethodGet {
  121. http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
  122. return
  123. }
  124. allUsers := sessionRetriever.AllSessions()
  125. ou := onlineUsers{
  126. Count: len(allUsers),
  127. Sessions: make([]userSession, 0),
  128. }
  129. for _, s := range allUsers {
  130. ou.Sessions = append(ou.Sessions, userSession{
  131. ScreenName: s.ScreenName(),
  132. })
  133. }
  134. if err := json.NewEncoder(w).Encode(ou); err != nil {
  135. http.Error(w, err.Error(), http.StatusInternalServerError)
  136. return
  137. }
  138. }
  139. // getUserHandler handles the GET /user endpoint.
  140. func getUserHandler(w http.ResponseWriter, _ *http.Request, userManager UserManager, logger *slog.Logger) {
  141. w.Header().Set("Content-Type", "application/json")
  142. users, err := userManager.AllUsers()
  143. if err != nil {
  144. logger.Error("error in GET /user", "err", err.Error())
  145. http.Error(w, "internal server error", http.StatusInternalServerError)
  146. return
  147. }
  148. if err := json.NewEncoder(w).Encode(users); err != nil {
  149. http.Error(w, err.Error(), http.StatusInternalServerError)
  150. return
  151. }
  152. }
  153. // postUserHandler handles the POST /user endpoint.
  154. func postUserHandler(
  155. w http.ResponseWriter,
  156. r *http.Request,
  157. userManager UserManager,
  158. newUser func() state.User,
  159. logger *slog.Logger,
  160. ) {
  161. user := userWithPassword{
  162. User: newUser(),
  163. }
  164. if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
  165. http.Error(w, "malformed input", http.StatusBadRequest)
  166. return
  167. }
  168. if err := user.HashPassword(user.Password); err != nil {
  169. logger.Error("error hashing user password in POST /user", "err", err.Error())
  170. http.Error(w, "internal server error", http.StatusInternalServerError)
  171. return
  172. }
  173. err := userManager.InsertUser(user.User)
  174. switch {
  175. case errors.Is(err, state.ErrDupUser):
  176. http.Error(w, "user already exists", http.StatusConflict)
  177. return
  178. case err != nil:
  179. logger.Error("error inserting user POST /user", "err", err.Error())
  180. http.Error(w, "internal server error", http.StatusInternalServerError)
  181. return
  182. }
  183. w.WriteHeader(http.StatusCreated)
  184. fmt.Fprintln(w, "User account created successfully.")
  185. }