package controllers import ( "database/sql" "encoding/json" "fmt" "log" "net/http" "strconv" "git.aiterp.net/lucifer/lucifer/internal/httperr" "git.aiterp.net/lucifer/lucifer/internal/respond" "git.aiterp.net/lucifer/lucifer/models" "github.com/gorilla/mux" ) // The GroupController is a controller for all group stuff. type GroupController struct { groups models.GroupRepository users models.UserRepository lights models.LightRepository } // getGroups (`GET /:id`): Get user by id func (c *GroupController) getGroups(w http.ResponseWriter, r *http.Request) { user := models.UserFromContext(r.Context()) groups, err := c.groups.ListByUser(r.Context(), *user) if err != nil { log.Printf("Getting groups for user %s (%d) failed: %s", user.Name, user.ID, err) respond.Error(w, http.StatusInternalServerError, "internal_error", "Failed to get groups.") return } respond.Data(w, groups) } // getGroup (`GET /:group_id`): Get user by id func (c *GroupController) getGroup(w http.ResponseWriter, r *http.Request) { session := models.SessionFromContext(r.Context()) idStr := mux.Vars(r)["group_id"] id, err := strconv.ParseInt(idStr, 10, 32) if err != nil { respond.Error(w, http.StatusBadRequest, "invalid_id", "The id"+idStr+"is not valid.") return } group, err := c.groups.FindByID(r.Context(), int(id)) if err != nil || !group.Permission(session.UserID).Read { respond.Error(w, http.StatusNotFound, "not_found", "The group cannot be found or you are not authorized to view it.") return } respond.Data(w, group) } // getGroupLights (`GET /:group_id/light/`): Get user by id func (c *GroupController) getGroupLights(w http.ResponseWriter, r *http.Request) { session := models.SessionFromContext(r.Context()) idStr := mux.Vars(r)["group_id"] id, err := strconv.ParseInt(idStr, 10, 32) if err != nil { respond.Error(w, http.StatusBadRequest, "invalid_id", "The id "+idStr+" is not valid.") return } group, err := c.groups.FindByID(r.Context(), int(id)) if err != nil || !group.Permission(session.UserID).Read { respond.Error(w, http.StatusNotFound, "group_not_found", "The group cannot be found or you are not authorized to view it.") return } lights, err := c.lights.ListByGroup(r.Context(), group) if err != nil && err != sql.ErrNoRows { log.Printf("Getting lights for group %s (%d) failed: %s", group.Name, group.ID, err) respond.Error(w, http.StatusInternalServerError, "internal_error", "Failed to get groups.") return } respond.Data(w, lights) } // getGroupLight (`GET /:group_id/light/:light_id`): Get user by id func (c *GroupController) getGroupLight(w http.ResponseWriter, r *http.Request) { session := models.SessionFromContext(r.Context()) groupIDStr := mux.Vars(r)["group_id"] groupID, err := strconv.ParseInt(groupIDStr, 10, 32) if err != nil { respond.Error(w, http.StatusBadRequest, "invalid_id", "The group id "+groupIDStr+" is not valid.") return } idStr := mux.Vars(r)["light_id"] id, err := strconv.ParseInt(idStr, 10, 32) if err != nil { respond.Error(w, http.StatusBadRequest, "invalid_id", "The light id "+idStr+" is not valid.") return } group, err := c.groups.FindByID(r.Context(), int(groupID)) if err != nil || !group.Permission(session.UserID).Read { respond.Error(w, http.StatusNotFound, "group_not_found", "The group cannot be found or you are not authorized to view it.") return } light, err := c.lights.FindByID(r.Context(), int(id)) if err != nil || light.GroupID != group.ID { fmt.Println(light, id) respond.Error(w, http.StatusNotFound, "light_not_found", "The light cannot be found in this group.") return } respond.Data(w, light) } // postGroup (`POST /`): Create a group. func (c *GroupController) postGroup(w http.ResponseWriter, r *http.Request) { user := models.UserFromContext(r.Context()) postData := struct { Name string }{} if err := json.NewDecoder(r.Body).Decode(&postData); err != nil { respond.Error(w, http.StatusBadRequest, "invalid_json", "Input is not valid JSON.") return } if postData.Name == "" { respond.Error(w, http.StatusBadRequest, "invalid_name", "The name cannot be blank.") return } group, err := c.groups.Insert(r.Context(), models.Group{Name: postData.Name}) if err != nil { httperr.Respond(w, err) return } permission := models.GroupPermission{ GroupID: group.ID, UserID: user.ID, Read: true, Write: true, Delete: true, Create: true, Manage: true, } err = c.groups.UpdatePermissions(r.Context(), permission) if err != nil { httperr.Respond(w, err) return } group.Permissions = []models.GroupPermission{permission} respond.Data(w, group) } // updateGroup (`PUT/PATCH /:group_id`): Create a group. func (c *GroupController) updateGroup(w http.ResponseWriter, r *http.Request) { user := models.UserFromContext(r.Context()) groupIDStr := mux.Vars(r)["group_id"] groupID, err := strconv.ParseInt(groupIDStr, 10, 32) if err != nil { respond.Error(w, http.StatusBadRequest, "invalid_id", "The group id "+groupIDStr+" is not valid.") return } postData := struct { Name string }{} if err := json.NewDecoder(r.Body).Decode(&postData); err != nil { respond.Error(w, http.StatusBadRequest, "invalid_json", "Input is not valid JSON.") return } if postData.Name == "" { respond.Error(w, http.StatusBadRequest, "invalid_name", "The name cannot be blank.") return } group, err := c.groups.FindByID(r.Context(), int(groupID)) if err != nil { respond.Error(w, http.StatusNotFound, "group_not_found", "The group cannot be found or you are not authorized to view it.") return } else if !group.Permission(user.ID).Manage { respond.Error(w, http.StatusNotFound, "permission_denied", "Your transgression will be remembered.") return } group.Name = postData.Name err = c.groups.Update(r.Context(), group) if err != nil { httperr.Respond(w, err) return } respond.Data(w, group) } // updateGroupPermission (`PUT/PATCH /:group_id/permission/:user_id`): Update a user's permission on a group. func (c *GroupController) updateGroupPermission(w http.ResponseWriter, r *http.Request) { user := models.UserFromContext(r.Context()) groupIDStr := mux.Vars(r)["group_id"] groupID, err := strconv.ParseInt(groupIDStr, 10, 32) if err != nil { respond.Error(w, http.StatusBadRequest, "invalid_id", "The group id "+groupIDStr+" is not valid.") return } userIDStr := mux.Vars(r)["user_id"] userID, err := strconv.ParseInt(userIDStr, 10, 32) if err != nil { respond.Error(w, http.StatusBadRequest, "invalid_id", "The user id "+userIDStr+" is not valid.") return } putData := struct { Read *bool `json:"read"` Write *bool `json:"write"` Create *bool `json:"create"` Delete *bool `json:"delete"` Manage *bool `json:"manage"` }{} if err := json.NewDecoder(r.Body).Decode(&putData); err != nil { respond.Error(w, http.StatusBadRequest, "invalid_json", "Input is not valid JSON.") return } if putData.Manage != nil { respond.Error(w, http.StatusBadRequest, "cannot_change_manage", "You cannot change the manage permission.") return } group, err := c.groups.FindByID(r.Context(), int(groupID)) if err != nil { respond.Error(w, http.StatusNotFound, "group_not_found", "The group could not be found.") return } else if !group.Permission(user.ID).Manage { respond.Error(w, http.StatusForbidden, "permission_denied", "This transgression has been dispatched to the North Pole for review.") return } user2, err := c.users.FindByID(r.Context(), int(userID)) if err == sql.ErrNoRows { respond.Error(w, http.StatusNotFound, "user_not_Found", "The user could not be found.") return } else if err != nil { httperr.Respond(w, err) return } permissions := group.Permission(user2.ID) if putData.Read != nil { permissions.Read = *putData.Read } if putData.Write != nil { permissions.Write = *putData.Write } if putData.Create != nil { permissions.Create = *putData.Create } if putData.Delete != nil { permissions.Delete = *putData.Delete } err = c.groups.UpdatePermissions(r.Context(), permissions) if err != nil { httperr.Respond(w, err) return } respond.Data(w, permissions) } // deleteGroup (`DELETE /:group_id`): Delete a group. func (c *GroupController) deleteGroup(w http.ResponseWriter, r *http.Request) { user := models.UserFromContext(r.Context()) groupIDStr := mux.Vars(r)["group_id"] groupID, err := strconv.ParseInt(groupIDStr, 10, 32) if err != nil { respond.Error(w, http.StatusBadRequest, "invalid_id", "The group id "+groupIDStr+" is not valid.") return } group, err := c.groups.FindByID(r.Context(), int(groupID)) if err != nil { respond.Error(w, http.StatusNotFound, "group_not_found", "The group cannot be found.") return } else if !group.Permission(user.ID).Manage { respond.Error(w, http.StatusNotFound, "permission_denied", "Your transgression will be remembered.") return } lights, err := c.lights.ListByGroup(r.Context(), group) if err != nil && err != sql.ErrNoRows { httperr.Respond(w, err) return } for _, light := range lights { light.GroupID = 0 err := c.lights.Update(r.Context(), light) if err != nil { httperr.Respond(w, err) return } } err = c.groups.Remove(r.Context(), group) if err != nil { httperr.Respond(w, err) return } respond.Data(w, group) } // Mount mounts the controller func (c *GroupController) Mount(router *mux.Router, prefix string) { sub := router.PathPrefix(prefix).Subrouter() sub.HandleFunc("/", c.getGroups).Methods("GET") sub.HandleFunc("/", c.postGroup).Methods("POST") sub.HandleFunc("/{group_id}", c.getGroup).Methods("GET") sub.HandleFunc("/{group_id}", c.updateGroup).Methods("PUT", "PATCH") sub.HandleFunc("/{group_id}", c.deleteGroup).Methods("DELETE") sub.HandleFunc("/{group_id}/permission/{user_id}", c.updateGroupPermission).Methods("PUT", "PATCH") sub.HandleFunc("/{group_id}/light/", c.getGroupLights).Methods("GET") sub.HandleFunc("/{group_id}/light/{light_id}", c.getGroupLight).Methods("GET") } // NewGroupController creates a new GroupController. func NewGroupController(groups models.GroupRepository, users models.UserRepository, lights models.LightRepository) *GroupController { return &GroupController{groups: groups, users: users, lights: lights} }