The main server, and probably only repository in this org.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

203 lines
5.3 KiB

  1. package controllers
  2. import (
  3. "database/sql"
  4. "encoding/json"
  5. "net/http"
  6. "strconv"
  7. "git.aiterp.net/lucifer/lucifer/light"
  8. "git.aiterp.net/lucifer/lucifer/internal/httperr"
  9. "git.aiterp.net/lucifer/lucifer/internal/respond"
  10. "git.aiterp.net/lucifer/lucifer/models"
  11. "github.com/gorilla/mux"
  12. "golang.org/x/sync/errgroup"
  13. )
  14. // The LightController is a controller for light matters.
  15. type LightController struct {
  16. service *light.Service
  17. groups models.GroupRepository
  18. users models.UserRepository
  19. lights models.LightRepository
  20. }
  21. // getLights (`GET /:id`): Get user by id
  22. func (c *LightController) getLights(w http.ResponseWriter, r *http.Request) {
  23. user := models.UserFromContext(r.Context())
  24. groups, err := c.groups.ListByUser(r.Context(), *user)
  25. if err != nil {
  26. httperr.Respond(w, err)
  27. return
  28. }
  29. allLights := make([]models.Light, 0, len(groups)*8)
  30. eg, egCtx := errgroup.WithContext(r.Context())
  31. for i := range groups {
  32. group := groups[i]
  33. eg.Go(func() error {
  34. lights, err := c.lights.ListByGroup(egCtx, group)
  35. if err != nil {
  36. return err
  37. }
  38. allLights = append(allLights, lights...)
  39. return nil
  40. })
  41. }
  42. if err := eg.Wait(); err != nil {
  43. httperr.Respond(w, err)
  44. return
  45. }
  46. respond.Data(w, allLights)
  47. }
  48. func (c *LightController) getLight(w http.ResponseWriter, r *http.Request) {
  49. _, light, err := c.findLight(r)
  50. if err != nil {
  51. httperr.Respond(w, err)
  52. return
  53. }
  54. respond.Data(w, light)
  55. }
  56. func (c *LightController) updateLight(w http.ResponseWriter, r *http.Request) {
  57. patch := struct {
  58. Color *string `json:"color,omitempty"`
  59. Brightness *int `json:"brightness,omitempty"`
  60. On *bool `json:"on,omitempty"`
  61. Name *string `json:"name,omitempty"`
  62. GroupID *int `json:"groupId,omitempty"`
  63. }{}
  64. if err := json.NewDecoder(r.Body).Decode(&patch); err != nil {
  65. respond.Error(w, http.StatusBadRequest, "invalid_json", "Input is not valid JSON.")
  66. return
  67. }
  68. group, light, err := c.findLight(r)
  69. if err != nil {
  70. httperr.Respond(w, err)
  71. return
  72. }
  73. user := models.UserFromContext(r.Context())
  74. if !group.Permission(user.ID).Write {
  75. httperr.Respond(w, httperr.ErrAccessDenied)
  76. return
  77. }
  78. if patch.Color != nil {
  79. err := light.SetColor(*patch.Color)
  80. if err != nil {
  81. httperr.Respond(w, err)
  82. return
  83. }
  84. }
  85. if patch.Name != nil {
  86. if len(*patch.Name) == 0 {
  87. respond.Error(w, 400, "invalid_name", "The name is empty.")
  88. return
  89. }
  90. light.Name = *patch.Name
  91. }
  92. if patch.Brightness != nil {
  93. if *patch.Brightness < 0 || *patch.Brightness > 255 {
  94. respond.Error(w, 400, "invalid_brightness", "The brightness must be a value between 0-255 inclusive.")
  95. return
  96. }
  97. light.Brightness = uint8(*patch.Brightness)
  98. }
  99. if patch.On != nil {
  100. light.On = *patch.On
  101. }
  102. if patch.GroupID != nil && *patch.GroupID != light.GroupID {
  103. if !group.Permission(user.ID).Delete {
  104. respond.Error(w, 403, "cannot_move_out", "You are not permitted to delete lights from group.")
  105. return
  106. }
  107. // Anyone is allowed to move lights TO group 0 (Lonely Lights) as it's the closest thing there is
  108. // to deleting lights.
  109. if *patch.GroupID != 0 {
  110. targetGroup, err := c.groups.FindByID(r.Context(), *patch.GroupID)
  111. if err != nil {
  112. respond.Error(w, 404, "group_not_found", "The group could not be found.")
  113. return
  114. }
  115. if !targetGroup.Permission(user.ID).Create {
  116. respond.Error(w, 403, "cannot_move_in", "You are not permitted to create lights in target group.")
  117. return
  118. }
  119. }
  120. light.GroupID = *patch.GroupID
  121. }
  122. err = c.service.UpdateLight(r.Context(), light)
  123. if err != nil {
  124. httperr.Respond(w, err)
  125. return
  126. }
  127. respond.Data(w, light)
  128. }
  129. // Mount mounts the controller
  130. func (c *LightController) Mount(router *mux.Router, prefix string) {
  131. sub := router.PathPrefix(prefix).Subrouter()
  132. sub.HandleFunc("/", c.getLights).Methods("GET")
  133. sub.HandleFunc("/{id}", c.getLight).Methods("GET")
  134. sub.HandleFunc("/{id}", c.updateLight).Methods("PATCH", "PUT")
  135. }
  136. func (c *LightController) findLight(r *http.Request) (models.Group, models.Light, error) {
  137. user := models.UserFromContext(r.Context())
  138. idStr := mux.Vars(r)["id"]
  139. id, err := strconv.ParseInt(idStr, 10, 32)
  140. if err != nil {
  141. return models.Group{}, models.Light{}, &httperr.Error{Status: http.StatusForbidden, Kind: "invalid_id", Message: "The light id " + idStr + " is not valid."}
  142. }
  143. light, err := c.lights.FindByID(r.Context(), int(id))
  144. if err == sql.ErrNoRows {
  145. return models.Group{}, models.Light{}, httperr.NotFound("Light")
  146. } else if err != nil {
  147. return models.Group{}, models.Light{}, err
  148. }
  149. group, err := c.groups.FindByID(r.Context(), light.GroupID)
  150. if err == sql.ErrNoRows {
  151. return models.Group{}, models.Light{}, httperr.NotFound("Group")
  152. } else if err != nil {
  153. return models.Group{}, models.Light{}, err
  154. }
  155. if !group.Permission(user.ID).Read {
  156. return models.Group{}, models.Light{}, httperr.ErrAccessDenied
  157. }
  158. if !group.Permission(user.ID).Read {
  159. return models.Group{}, models.Light{}, &httperr.Error{Status: http.StatusForbidden, Kind: "permission_denied", Message: "Thou canst not see the light."}
  160. }
  161. return group, light, nil
  162. }
  163. // NewLightController creates a new LightController.
  164. func NewLightController(service *light.Service, groups models.GroupRepository, users models.UserRepository, lights models.LightRepository) *LightController {
  165. return &LightController{service: service, groups: groups, users: users, lights: lights}
  166. }