package controllers import ( "context" "database/sql" "encoding/json" "errors" "net/http" "strconv" "git.aiterp.net/lucifer/lucifer/internal/httperr" "git.aiterp.net/lucifer/lucifer/internal/respond" "git.aiterp.net/lucifer/lucifer/light" "git.aiterp.net/lucifer/lucifer/models" "github.com/gorilla/mux" ) // The BridgeController is a controller for all bridge things. type BridgeController struct { service *light.Service groups models.GroupRepository } // getBridges (`GET /`): Get all bridges func (c *BridgeController) getBridges(w http.ResponseWriter, r *http.Request) { bridges, err := c.service.Bridges(r.Context()) if err != nil { httperr.Respond(w, err) return } respond.Data(w, bridges) } // getBridge (`GET /:bridge_id`): Get bridge by ID func (c *BridgeController) getBridge(w http.ResponseWriter, r *http.Request) { idStr := mux.Vars(r)["bridge_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 } bridge, err := c.service.Bridge(r.Context(), int(id)) if err == sql.ErrNoRows { respond.Error(w, 404, "bridge_not_found", "The bridge cannot be found.") return } else if err != nil { httperr.Respond(w, err) return } respond.Data(w, bridge) } // postBridge (`POST /`): Post bridge func (c *BridgeController) postBridge(w http.ResponseWriter, r *http.Request) { postData := struct { Name string `json:"name"` Driver string `json:"driver"` Addr string `json:"addr"` }{} if err := json.NewDecoder(r.Body).Decode(&postData); err != nil || postData.Name == "" || postData.Driver == "" || postData.Addr == "" { respond.Error(w, http.StatusBadRequest, "invalid_json", "Input is not valid JSON.") return } bridge, err := c.service.DirectConnect(r.Context(), postData.Driver, postData.Addr, postData.Name) if err != nil { httperr.Respond(w, err) return } respond.Data(w, bridge) } // updateBridge (`PUT/PATCH /:bridge_id`): Update bridge by ID func (c *BridgeController) updateBridge(w http.ResponseWriter, r *http.Request) { idStr := mux.Vars(r)["bridge_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 } putData := struct { Name string }{} 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.Name == "" { respond.Error(w, http.StatusBadRequest, "invalid_name", "The name cannot be blank.") return } bridge, err := c.service.Bridge(r.Context(), int(id)) if err == sql.ErrNoRows { respond.Error(w, 404, "bridge_not_found", "The bridge cannot be found.") return } else if err != nil { httperr.Respond(w, err) return } bridge.Name = putData.Name err = c.service.UpdateBridge(r.Context(), bridge) if err != nil { httperr.Respond(w, err) return } respond.Data(w, bridge) } // postBridgeDiscover (`POST /:bridge_id/discover`): Delete bridge by ID func (c *BridgeController) postBridgeDiscover(w http.ResponseWriter, r *http.Request) { idStr := mux.Vars(r)["bridge_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 } bridge, err := c.service.Bridge(r.Context(), int(id)) if err == sql.ErrNoRows { respond.Error(w, 404, "bridge_not_found", "The bridge cannot be found.") return } else if err != nil { httperr.Respond(w, err) return } err = c.service.DiscoverLights(r.Context(), bridge) if err != nil { httperr.Respond(w, err) return } respond.Data(w, bridge) } // deleteBridge (`DELETE /:bridge_id`): Delete bridge by ID func (c *BridgeController) deleteBridge(w http.ResponseWriter, r *http.Request) { idStr := mux.Vars(r)["bridge_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 } bridge, err := c.service.Bridge(r.Context(), int(id)) if err == sql.ErrNoRows { respond.Error(w, 404, "bridge_not_found", "The bridge cannot be found.") return } else if err != nil { httperr.Respond(w, err) return } err = c.service.DeleteBridge(r.Context(), bridge) if err != nil { httperr.Respond(w, err) return } respond.Data(w, bridge) } // deleteBridgeLight (`DELETE /:bridge_id/light/:light_id`): Delete bridge by ID func (c *BridgeController) deleteBridgeLight(w http.ResponseWriter, r *http.Request) { idStr := mux.Vars(r)["bridge_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 } lightIDStr := mux.Vars(r)["light_id"] lightID, err := strconv.ParseInt(lightIDStr, 10, 32) if err != nil { respond.Error(w, http.StatusBadRequest, "invalid_id", "The id "+lightIDStr+" is not valid.") return } bridge, err := c.service.Bridge(r.Context(), int(id)) if err == sql.ErrNoRows { respond.Error(w, 404, "bridge_not_found", "The bridge cannot be found.") return } else if err != nil { httperr.Respond(w, err) return } light, err := c.service.Light(r.Context(), int(lightID)) if err != nil { httperr.Respond(w, err) return } if light.BridgeID != bridge.ID { respond.Error(w, 404, "bridge_not_found", "The bridge cannot be found.") return } err = c.service.DeleteBridgeLight(r.Context(), bridge, light) if err != nil { httperr.Respond(w, err) return } respond.Data(w, bridge) } // discoverBridges (`GET /discover/:driver`): Get all bridges func (c *BridgeController) discoverBridges(w http.ResponseWriter, r *http.Request) { newBridges, err := c.service.DiscoverBridges(r.Context(), mux.Vars(r)["driver_name"]) if err != nil { httperr.Respond(w, err) return } respond.Data(w, newBridges) } // Mount mounts the controller func (c *BridgeController) Mount(router *mux.Router, prefix string) { sub := router.PathPrefix(prefix).Subrouter() sub.Use(c.adminMiddleware()) sub.HandleFunc("/", c.getBridges).Methods("GET") sub.HandleFunc("/", c.postBridge).Methods("POST") sub.HandleFunc("/{bridge_id}", c.getBridge).Methods("GET") sub.HandleFunc("/{bridge_id}", c.updateBridge).Methods("PUT", "PATCH") sub.HandleFunc("/{bridge_id}", c.deleteBridge).Methods("DELETE") sub.HandleFunc("/{bridge_id}/light/{light_id}", c.deleteBridgeLight).Methods("DELETE") sub.HandleFunc("/{bridge_id}/discover", c.postBridgeDiscover).Methods("POST") sub.HandleFunc("/discover/{driver_name}", c.discoverBridges).Methods("GET") } func (c *BridgeController) adminMiddleware() mux.MiddlewareFunc { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { err := c.isAdmin(r.Context()) if err != nil { httperr.Respond(w, err) return } next.ServeHTTP(w, r) }) } } func (c *BridgeController) isAdmin(ctx context.Context) error { user := models.UserFromContext(ctx) lonelyLights, err := c.groups.FindByID(ctx, 0) if err != nil { return errors.New("Lonely Lights group could not be found") } if !lonelyLights.Permission(user.ID).Write { return httperr.ErrAccessDenied } return nil } // NewBridgeController makes a new bridge controller func NewBridgeController(service *light.Service, groups models.GroupRepository) *BridgeController { return &BridgeController{ service: service, groups: groups, } }