diff --git a/cmd/lucifer-server/main.go b/cmd/lucifer-server/main.go index 39c771f..005ca1b 100644 --- a/cmd/lucifer-server/main.go +++ b/cmd/lucifer-server/main.go @@ -44,12 +44,14 @@ func main() { // Controllers userController := controllers.NewUserController(sqlite.UserRepository, sqlite.SessionRepository) groupController := controllers.NewGroupController(sqlite.GroupRepository, sqlite.UserRepository, sqlite.LightRepository) + lightController := controllers.NewLightController(sqlite.GroupRepository, sqlite.UserRepository, sqlite.LightRepository) // Router router := mux.NewRouter() router.Use(middlewares.Session(sqlite.SessionRepository, sqlite.UserRepository)) groupController.Mount(router, "/api/group/") userController.Mount(router, "/api/user/") + lightController.Mount(router, "/api/light/") // Background Tasks go lightService.SyncLoop(context.TODO()) diff --git a/controllers/group-controller.go b/controllers/group-controller.go index 2759cde..97ca3e6 100644 --- a/controllers/group-controller.go +++ b/controllers/group-controller.go @@ -21,20 +21,10 @@ type GroupController struct { // getGroups (`GET /:id`): Get user by id func (c *GroupController) getGroups(w http.ResponseWriter, r *http.Request) { - session := models.SessionFromContext(r.Context()) - if session == nil { - respond.Error(w, http.StatusForbidden, "permission_denied", "You must log in") - return - } + user := models.UserFromContext(r.Context()) - user, err := c.users.FindByID(r.Context(), session.UserID) + groups, err := c.groups.ListByUser(r.Context(), *user) if err != nil { - respond.Error(w, http.StatusForbidden, "permission_denied", "You must log in") - return - } - - groups, err := c.groups.ListByUser(r.Context(), user) - if err != nil && err != sql.ErrNoRows { 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 @@ -46,10 +36,6 @@ func (c *GroupController) getGroups(w http.ResponseWriter, r *http.Request) { // getGroup (`GET /:id`): Get user by id func (c *GroupController) getGroup(w http.ResponseWriter, r *http.Request) { session := models.SessionFromContext(r.Context()) - if session == nil { - respond.Error(w, http.StatusForbidden, "permission_denied", "You must log in") - return - } idStr := mux.Vars(r)["id"] id, err := strconv.ParseInt(idStr, 10, 32) @@ -70,10 +56,6 @@ func (c *GroupController) getGroup(w http.ResponseWriter, r *http.Request) { // getGroupLights (`GET /:id/light/`): Get user by id func (c *GroupController) getGroupLights(w http.ResponseWriter, r *http.Request) { session := models.SessionFromContext(r.Context()) - if session == nil { - respond.Error(w, http.StatusForbidden, "permission_denied", "You must log in") - return - } idStr := mux.Vars(r)["id"] id, err := strconv.ParseInt(idStr, 10, 32) @@ -101,10 +83,6 @@ func (c *GroupController) getGroupLights(w http.ResponseWriter, r *http.Request) // getGroupLight (`GET /:group_id/light/:id`): Get user by id func (c *GroupController) getGroupLight(w http.ResponseWriter, r *http.Request) { session := models.SessionFromContext(r.Context()) - if session == nil { - respond.Error(w, http.StatusForbidden, "permission_denied", "You must log in") - return - } groupIDStr := mux.Vars(r)["group_id"] groupID, err := strconv.ParseInt(groupIDStr, 10, 32) diff --git a/controllers/light-controller.go b/controllers/light-controller.go new file mode 100644 index 0000000..1012f3c --- /dev/null +++ b/controllers/light-controller.go @@ -0,0 +1,106 @@ +package controllers + +import ( + "database/sql" + "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" + "golang.org/x/sync/errgroup" +) + +// The LightController is a controller for /api/light/. +type LightController struct { + 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) { + user := models.UserFromContext(r.Context()) + + idStr := mux.Vars(r)["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 + } + + light, err := c.lights.FindByID(r.Context(), int(id)) + if err == sql.ErrNoRows { + httperr.Respond(w, httperr.NotFound("Light")) + return + } else if err != nil { + httperr.Respond(w, err) + return + } + + group, err := c.groups.FindByID(r.Context(), light.GroupID) + if err == sql.ErrNoRows { + httperr.Respond(w, httperr.NotFound("Group")) + return + } else if err != nil { + httperr.Respond(w, err) + return + } + + if !group.Permission(user.ID).Read { + respond.Error(w, http.StatusForbidden, "permission_denied", "You do not have permission to see this light.") + 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") +} + +// NewLightController creates a new LightController. +func NewLightController(groups models.GroupRepository, users models.UserRepository, lights models.LightRepository) *LightController { + return &LightController{groups: groups, users: users, lights: lights} +} diff --git a/database/sqlite/group-repository.go b/database/sqlite/group-repository.go index f60fa67..0028217 100644 --- a/database/sqlite/group-repository.go +++ b/database/sqlite/group-repository.go @@ -2,6 +2,7 @@ package sqlite import ( "context" + "database/sql" "golang.org/x/sync/errgroup" @@ -37,16 +38,16 @@ func (r *groupRepository) FindByLight(ctx context.Context, light models.Light) ( group := models.Group{} err := db.GetContext(ctx, &group, query, light.ID) - if err != nil { + if err != nil && err != sql.ErrNoRows { return models.Group{}, err } err = db.SelectContext(ctx, &group.Permissions, "SELECT * FROM group_permission WHERE group_id=?", group.ID) - if err != nil { + if err != nil && err != sql.ErrNoRows { return models.Group{}, err } - return group, err + return group, nil } func (r *groupRepository) List(ctx context.Context) ([]models.Group, error) { @@ -66,7 +67,7 @@ func (r *groupRepository) List(ctx context.Context) ([]models.Group, error) { } err = eg.Wait() - if err != nil { + if err != nil && err != sql.ErrNoRows { return nil, err } @@ -82,7 +83,7 @@ func (r *groupRepository) ListByUser(ctx context.Context, user models.User) ([]m groups := make([]models.Group, 0, 16) err := db.SelectContext(ctx, &groups, query, user.ID) - if err != nil { + if err != nil && err != sql.ErrNoRows { return nil, err } @@ -91,7 +92,12 @@ func (r *groupRepository) ListByUser(ctx context.Context, user models.User) ([]m group := &groups[i] eg.Go(func() error { - return db.SelectContext(egCtx, &group.Permissions, "SELECT * FROM group_permission WHERE group_id=?", group.ID) + err := db.SelectContext(egCtx, &group.Permissions, "SELECT * FROM group_permission WHERE group_id=?", group.ID) + if err != nil && err != sql.ErrNoRows { + return err + } + + return nil }) } diff --git a/database/sqlite/light-repository.go b/database/sqlite/light-repository.go index e84a5e0..5a44bb8 100644 --- a/database/sqlite/light-repository.go +++ b/database/sqlite/light-repository.go @@ -2,6 +2,7 @@ package sqlite import ( "context" + "database/sql" "git.aiterp.net/lucifer/lucifer/models" ) @@ -43,6 +44,10 @@ func (r *lightRepository) ListByGroup(ctx context.Context, group models.Group) ( lights := make([]models.Light, 0, 64) err := db.SelectContext(ctx, &lights, "SELECT * FROM light WHERE group_id=?", group.ID) + if err == sql.ErrNoRows { + err = nil + } + return lights, err }