package controllers import ( "database/sql" "encoding/json" "net/http" "strconv" "git.aiterp.net/lucifer/lucifer/light" "git.aiterp.net/lucifer/lucifer/internal/httperr" "git.aiterp.net/lucifer/lucifer/internal/respond" "git.aiterp.net/lucifer/lucifer/models" "github.com/gorilla/mux" "golang.org/x/sync/errgroup" ) // The LightController is a controller for light matters. type LightController struct { service *light.Service groups models.GroupRepository users models.UserRepository lights models.LightRepository } // getLights (`GET /:id`): Get user by id func (c *LightController) getLights(w http.ResponseWriter, r *http.Request) { user := models.UserFromContext(r.Context()) groups, err := c.groups.ListByUser(r.Context(), *user) if err != nil { httperr.Respond(w, err) return } allLights := make([]models.Light, 0, len(groups)*8) eg, egCtx := errgroup.WithContext(r.Context()) for i := range groups { group := groups[i] eg.Go(func() error { lights, err := c.lights.ListByGroup(egCtx, group) if err != nil { return err } allLights = append(allLights, lights...) return nil }) } if err := eg.Wait(); err != nil { httperr.Respond(w, err) return } respond.Data(w, allLights) } func (c *LightController) getLight(w http.ResponseWriter, r *http.Request) { _, light, err := c.findLight(r) if err != nil { httperr.Respond(w, err) return } respond.Data(w, light) } func (c *LightController) updateLight(w http.ResponseWriter, r *http.Request) { patch := struct { Color *string `json:"color,omitempty"` Brightness *int `json:"brightness,omitempty"` On *bool `json:"on,omitempty"` Name *string `json:"name,omitempty"` GroupID *int `json:"groupId,omitempty"` }{} if err := json.NewDecoder(r.Body).Decode(&patch); err != nil { respond.Error(w, http.StatusBadRequest, "invalid_json", "Input is not valid JSON.") return } group, light, err := c.findLight(r) if err != nil { httperr.Respond(w, err) return } user := models.UserFromContext(r.Context()) if !group.Permission(user.ID).Write { httperr.Respond(w, httperr.ErrAccessDenied) return } if patch.Color != nil { err := light.SetColor(*patch.Color) if err != nil { httperr.Respond(w, err) return } } if patch.Name != nil { if len(*patch.Name) == 0 { respond.Error(w, 400, "invalid_name", "The name is empty.") return } light.Name = *patch.Name } if patch.Brightness != nil { if *patch.Brightness < 0 || *patch.Brightness > 255 { respond.Error(w, 400, "invalid_brightness", "The brightness must be a value between 0-255 inclusive.") return } light.Brightness = uint8(*patch.Brightness) } if patch.On != nil { light.On = *patch.On } if patch.GroupID != nil && *patch.GroupID != light.GroupID { if !group.Permission(user.ID).Delete { respond.Error(w, 403, "cannot_move_out", "You are not permitted to delete lights from group.") return } // Anyone is allowed to move lights TO group 0 (Lonely Lights) as it's the closest thing there is // to deleting lights. if *patch.GroupID != 0 { targetGroup, err := c.groups.FindByID(r.Context(), *patch.GroupID) if err != nil { respond.Error(w, 404, "group_not_found", "The group could not be found.") return } if !targetGroup.Permission(user.ID).Create { respond.Error(w, 403, "cannot_move_in", "You are not permitted to create lights in target group.") return } } light.GroupID = *patch.GroupID } err = c.service.UpdateLight(r.Context(), light) if err != nil { httperr.Respond(w, err) return } respond.Data(w, light) } // Mount mounts the controller func (c *LightController) Mount(router *mux.Router, prefix string) { sub := router.PathPrefix(prefix).Subrouter() sub.HandleFunc("/", c.getLights).Methods("GET") sub.HandleFunc("/{id}", c.getLight).Methods("GET") sub.HandleFunc("/{id}", c.updateLight).Methods("PATCH", "PUT") } func (c *LightController) findLight(r *http.Request) (models.Group, models.Light, error) { user := models.UserFromContext(r.Context()) idStr := mux.Vars(r)["id"] id, err := strconv.ParseInt(idStr, 10, 32) if err != nil { return models.Group{}, models.Light{}, &httperr.Error{Status: http.StatusForbidden, Kind: "invalid_id", Message: "The light id " + idStr + " is not valid."} } light, err := c.lights.FindByID(r.Context(), int(id)) if err == sql.ErrNoRows { return models.Group{}, models.Light{}, httperr.NotFound("Light") } else if err != nil { return models.Group{}, models.Light{}, err } group, err := c.groups.FindByID(r.Context(), light.GroupID) if err == sql.ErrNoRows { return models.Group{}, models.Light{}, httperr.NotFound("Group") } else if err != nil { return models.Group{}, models.Light{}, err } if !group.Permission(user.ID).Read { return models.Group{}, models.Light{}, httperr.ErrAccessDenied } if !group.Permission(user.ID).Read { return models.Group{}, models.Light{}, &httperr.Error{Status: http.StatusForbidden, Kind: "permission_denied", Message: "Thou canst not see the light."} } return group, light, nil } // NewLightController creates a new LightController. func NewLightController(service *light.Service, groups models.GroupRepository, users models.UserRepository, lights models.LightRepository) *LightController { return &LightController{service: service, groups: groups, users: users, lights: lights} }