From e644f4816c74ae53ae74c126e133c10e145ae2ff Mon Sep 17 00:00:00 2001 From: Gisle Aune Date: Wed, 6 Feb 2019 22:00:47 +0100 Subject: [PATCH] lucifer-server, controllers, sqlite, models: Added read only API for groups and stuff. --- cmd/lucifer-server/main.go | 2 + controllers/group-controller.go | 152 ++++++++++++++++++++++++++++ controllers/user-controller.go | 2 +- database/sqlite/group-repository.go | 63 +++++++++++- models/group.go | 19 +++- 5 files changed, 230 insertions(+), 8 deletions(-) create mode 100644 controllers/group-controller.go diff --git a/cmd/lucifer-server/main.go b/cmd/lucifer-server/main.go index c8484b4..c2da2f5 100644 --- a/cmd/lucifer-server/main.go +++ b/cmd/lucifer-server/main.go @@ -43,10 +43,12 @@ func main() { // Controllers userController := controllers.NewUserController(sqlite.UserRepository, sqlite.SessionRepository) + groupController := controllers.NewGroupController(sqlite.GroupRepository, sqlite.UserRepository, sqlite.LightRepository) // Router router := mux.NewRouter() router.Use(middlewares.Session(sqlite.SessionRepository)) + groupController.Mount(router, "/api/group/") userController.Mount(router, "/api/user/") // Background Tasks diff --git a/controllers/group-controller.go b/controllers/group-controller.go new file mode 100644 index 0000000..2759cde --- /dev/null +++ b/controllers/group-controller.go @@ -0,0 +1,152 @@ +package controllers + +import ( + "database/sql" + "fmt" + "log" + "net/http" + "strconv" + + "git.aiterp.net/lucifer/lucifer/internal/respond" + "git.aiterp.net/lucifer/lucifer/models" + "github.com/gorilla/mux" +) + +// The GroupController is a controller for all user inports. +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) { + session := models.SessionFromContext(r.Context()) + if session == nil { + respond.Error(w, http.StatusForbidden, "permission_denied", "You must log in") + return + } + + user, err := c.users.FindByID(r.Context(), session.UserID) + 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 + } + + respond.Data(w, groups) +} + +// 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) + 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 /: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) + 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/: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) + if err != nil { + respond.Error(w, http.StatusBadRequest, "invalid_id", "The group id "+groupIDStr+" is not valid.") + return + } + + 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 + } + + 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) +} + +// 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("/{id}", c.getGroup).Methods("GET") + sub.HandleFunc("/{id}/light/", c.getGroupLights).Methods("GET") + sub.HandleFunc("/{group_id}/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} +} diff --git a/controllers/user-controller.go b/controllers/user-controller.go index 990116d..277a0de 100644 --- a/controllers/user-controller.go +++ b/controllers/user-controller.go @@ -98,7 +98,7 @@ func (c *UserController) login(w http.ResponseWriter, r *http.Request) { if err := c.sessions.Insert(r.Context(), session); err != nil { log.Printf("Session create for user %s (%d) failed: %s", user.Name, user.ID, err) - respond.Error(w, http.StatusInternalServerError, "session_failure", "Failed to open session.") + respond.Error(w, http.StatusInternalServerError, "internal_error", "Failed to open session.") return } diff --git a/database/sqlite/group-repository.go b/database/sqlite/group-repository.go index 5b6e1aa..f60fa67 100644 --- a/database/sqlite/group-repository.go +++ b/database/sqlite/group-repository.go @@ -15,14 +15,36 @@ var GroupRepository = &groupRepository{} func (r *groupRepository) FindByID(ctx context.Context, id int) (models.Group, error) { group := models.Group{} - err := db.GetContext(ctx, &group, "SELECT * FROM group WHERE id=?", id) + err := db.GetContext(ctx, &group, "SELECT * FROM 'group' WHERE id=?", id) + if err != nil { + return models.Group{}, err + } + + err = db.SelectContext(ctx, &group.Permissions, "SELECT * FROM group_permission WHERE group_id=?", group.ID) + if err != nil { + return models.Group{}, err + } return group, err } func (r *groupRepository) FindByLight(ctx context.Context, light models.Light) (models.Group, error) { + const query = ` + SELECT g.id, g.name FROM 'light' l + JOIN 'group' g ON g.id = l.group_id + WHERE l.id = ? + ` + group := models.Group{} - err := db.GetContext(ctx, &group, "SELECT g.id, g.name FROM `light` l JOIN `group` g ON g.id = l.group_id WHERE l.id = ?", light.ID) + err := db.GetContext(ctx, &group, query, light.ID) + if err != nil { + return models.Group{}, err + } + + err = db.SelectContext(ctx, &group.Permissions, "SELECT * FROM group_permission WHERE group_id=?", group.ID) + if err != nil { + return models.Group{}, err + } return group, err } @@ -39,15 +61,46 @@ func (r *groupRepository) List(ctx context.Context) ([]models.Group, error) { group := &groups[i] eg.Go(func() error { - return db.SelectContext(egCtx, &group.Permissions, "SELECT * FROM group_permissions WHERE group_id=?", group.ID) + return db.SelectContext(egCtx, &group.Permissions, "SELECT * FROM group_permission WHERE group_id=?", group.ID) }) } - return groups, eg.Wait() + err = eg.Wait() + if err != nil { + return nil, err + } + + return groups, nil } func (r *groupRepository) ListByUser(ctx context.Context, user models.User) ([]models.Group, error) { - panic("not implemented") + const query = ` + SELECT g.id, g.name FROM group_permission AS p + JOIN 'group' AS g ON p.group_id=g.id + WHERE p.user_id=? AND p.read=true; + ` + + groups := make([]models.Group, 0, 16) + err := db.SelectContext(ctx, &groups, query, user.ID) + if err != nil { + return nil, err + } + + eg, egCtx := errgroup.WithContext(ctx) + for i := range groups { + group := &groups[i] + + eg.Go(func() error { + return db.SelectContext(egCtx, &group.Permissions, "SELECT * FROM group_permission WHERE group_id=?", group.ID) + }) + } + + err = eg.Wait() + if err != nil { + return nil, err + } + + return groups, nil } func (r *groupRepository) Insert(ctx context.Context, group models.Group) (models.Group, error) { diff --git a/models/group.go b/models/group.go index 53c7b34..c08c433 100644 --- a/models/group.go +++ b/models/group.go @@ -10,10 +10,25 @@ type Group struct { Permissions []GroupPermission `json:"permissions" db:"-"` } +// Permission gets the permissions for the user with id `userID` or returns +// a blank permission if none is found. +func (group *Group) Permission(userID int) GroupPermission { + for _, permission := range group.Permissions { + if permission.UserID == userID { + return permission + } + } + + return GroupPermission{ + GroupID: group.ID, + UserID: userID, + } +} + // A GroupPermission is a permission for a user in a group. type GroupPermission struct { - GroupID int `json:"group_id" db:"group_id"` - UserID int `json:"user_id" db:"user_id"` + GroupID int `json:"-" db:"group_id"` + UserID int `json:"userId" db:"user_id"` Read bool `json:"read" db:"read"` Write bool `json:"write" db:"write"` Create bool `json:"create" db:"create"`