From c0699f159ea7a5843c331ae0a628fd0e2b6fe01f Mon Sep 17 00:00:00 2001 From: Steven Date: Wed, 21 Sep 2022 23:26:05 +0800 Subject: [PATCH] feat: register workspace user api --- api/workspace_user.go | 17 +++-- server/server.go | 1 + server/workspace_user.go | 159 +++++++++++++++++++++++++++++++++++++++ store/shortcut.go | 2 +- store/workspace.go | 2 +- store/workspace_user.go | 37 ++++++--- 6 files changed, 197 insertions(+), 21 deletions(-) create mode 100644 server/workspace_user.go diff --git a/api/workspace_user.go b/api/workspace_user.go index 1622824..23fc74e 100644 --- a/api/workspace_user.go +++ b/api/workspace_user.go @@ -23,24 +23,25 @@ func (e Role) String() string { type WorkspaceUser struct { WorkspaceID int `json:"workspaceId"` UserID int `json:"userId"` + User *User `json:"user"` Role Role `json:"role"` CreatedTs int64 `json:"createdTs"` UpdatedTs int64 `json:"updatedTs"` } type WorkspaceUserUpsert struct { - WorkspaceID int `json:"workspaceId"` - UserID int `json:"userId"` - Role Role `json:"role"` - UpdatedTs int64 `json:"updatedTs"` + WorkspaceID int `json:"workspaceId"` + UserID int `json:"userId"` + Role Role `json:"role"` + UpdatedTs *int64 `json:"updatedTs"` } type WorkspaceUserFind struct { - WorkspaceID *int `json:"workspaceId"` - UserID *int `json:"userId"` + WorkspaceID *int + UserID *int } type WorkspaceUserDelete struct { - WorkspaceID int `json:"workspaceId"` - UserID int `json:"userId"` + WorkspaceID int + UserID int } diff --git a/server/server.go b/server/server.go index ab10e0c..edc8687 100644 --- a/server/server.go +++ b/server/server.go @@ -64,6 +64,7 @@ func NewServer(profile *profile.Profile) *Server { s.registerAuthRoutes(apiGroup) s.registerUserRoutes(apiGroup) s.registerWorkspaceRoutes(apiGroup) + s.registerWorkspaceUserRoutes(apiGroup) s.registerShortcutRoutes(apiGroup) return s diff --git a/server/workspace_user.go b/server/workspace_user.go new file mode 100644 index 0000000..48f3a8f --- /dev/null +++ b/server/workspace_user.go @@ -0,0 +1,159 @@ +package server + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" + + "github.com/boojack/corgi/api" + "github.com/boojack/corgi/common" + + "github.com/labstack/echo/v4" +) + +func (s *Server) registerWorkspaceUserRoutes(g *echo.Group) { + g.POST("/workspace/:id/user", func(c echo.Context) error { + ctx := c.Request().Context() + userID, ok := c.Get(getUserIDContextKey()).(int) + if !ok { + return echo.NewHTTPError(http.StatusUnauthorized, "Missing auth session") + } + + workspaceID, err := strconv.Atoi(c.Param("id")) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Malformatted workspace id").SetInternal(err) + } + + currentWorkspaceUser, err := s.Store.FindWordspaceUser(ctx, &api.WorkspaceUserFind{ + WorkspaceID: &workspaceID, + UserID: &userID, + }) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find workspace user").SetInternal(err) + } + if currentWorkspaceUser.Role != api.RoleAdmin { + return echo.NewHTTPError(http.StatusForbidden, "Access forbidden to add workspace user").SetInternal(err) + } + + workspaceUserUpsert := &api.WorkspaceUserUpsert{ + WorkspaceID: workspaceID, + } + if err := json.NewDecoder(c.Request().Body).Decode(workspaceUserUpsert); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post workspace user request").SetInternal(err) + } + + workspaceUser, err := s.Store.UpsertWorkspaceUser(ctx, workspaceUserUpsert) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert workspace user").SetInternal(err) + } + if err := s.Store.ComposeWorkspaceUser(ctx, workspaceUser); err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to compose workspace user").SetInternal(err) + } + + c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8) + if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(workspaceUser)); err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to encode workspace user response").SetInternal(err) + } + return nil + }) + + g.GET("/workspace/:id/user", func(c echo.Context) error { + ctx := c.Request().Context() + workspaceID, err := strconv.Atoi(c.Param("id")) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Malformatted workspace id").SetInternal(err) + } + + workspaceUserList, err := s.Store.FindWordspaceUserList(ctx, &api.WorkspaceUserFind{ + WorkspaceID: &workspaceID, + }) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find workspace user list").SetInternal(err) + } + + for _, workspaceUser := range workspaceUserList { + if err := s.Store.ComposeWorkspaceUser(ctx, workspaceUser); err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to compose workspace user").SetInternal(err) + } + } + + c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8) + if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(workspaceUserList)); err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to encode workspace user list response").SetInternal(err) + } + return nil + }) + + g.GET("/workspace/:workspaceId/user/:userId", func(c echo.Context) error { + ctx := c.Request().Context() + workspaceID, err := strconv.Atoi(c.Param("workspaceId")) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Malformatted workspace id").SetInternal(err) + } + userID, err := strconv.Atoi(c.Param("userId")) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Malformatted user id").SetInternal(err) + } + + workspaceUser, err := s.Store.FindWordspaceUser(ctx, &api.WorkspaceUserFind{ + WorkspaceID: &workspaceID, + UserID: &userID, + }) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find workspace user").SetInternal(err) + } + + if err := s.Store.ComposeWorkspaceUser(ctx, workspaceUser); err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to compose workspace user").SetInternal(err) + } + + c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8) + if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(workspaceUser)); err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to encode workspace user response").SetInternal(err) + } + return nil + }) + + g.DELETE("/workspace/:workspaceId/user/:userId", func(c echo.Context) error { + ctx := c.Request().Context() + userID, ok := c.Get(getUserIDContextKey()).(int) + if !ok { + return echo.NewHTTPError(http.StatusUnauthorized, "Missing auth session") + } + + workspaceID, err := strconv.Atoi(c.Param("workspaceId")) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Malformatted workspace id").SetInternal(err) + } + + currentWorkspaceUser, err := s.Store.FindWordspaceUser(ctx, &api.WorkspaceUserFind{ + WorkspaceID: &workspaceID, + UserID: &userID, + }) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find workspace user").SetInternal(err) + } + if currentWorkspaceUser.Role != api.RoleAdmin { + return echo.NewHTTPError(http.StatusForbidden, "Access forbidden to add workspace user").SetInternal(err) + } + + userID, err = strconv.Atoi(c.Param("userId")) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Malformatted user id").SetInternal(err) + } + + workspaceUserDelete := &api.WorkspaceUserDelete{ + WorkspaceID: workspaceID, + UserID: userID, + } + if err := s.Store.DeleteWorkspaceUser(ctx, workspaceUserDelete); err != nil { + if common.ErrorCode(err) == common.NotFound { + return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Workspace user not found with workspace id %d and user id %d", workspaceID, userID)) + } + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to delete workspace user").SetInternal(err) + } + + return c.JSON(http.StatusOK, true) + }) +} diff --git a/store/shortcut.go b/store/shortcut.go index e9a2ab0..209b9a5 100644 --- a/store/shortcut.go +++ b/store/shortcut.go @@ -292,7 +292,7 @@ func findShortcutList(ctx context.Context, tx *sql.Tx, find *api.ShortcutFind) ( where = append(where, fmt.Sprintf("visibility in (%s)", strings.Join(list, ","))) } if v := find.MemberID; v != nil { - where, args = append(where, "workspace_id IN (SELECT workspace_id FROM workspace_user WHERE user_id = ? )"), append(args, *v) + where, args = append(where, "workspace_id IN (SELECT workspace_id FROM workspace_user WHERE user_id = ?)"), append(args, *v) } rows, err := tx.QueryContext(ctx, ` diff --git a/store/workspace.go b/store/workspace.go index 639a4a7..f565835 100644 --- a/store/workspace.go +++ b/store/workspace.go @@ -262,7 +262,7 @@ func findWorkspaceList(ctx context.Context, tx *sql.Tx, find *api.WorkspaceFind) where, args = append(where, "name = ?"), append(args, *v) } if v := find.MemberID; v != nil { - where, args = append(where, "id IN (SELECT workspace_id FROM workspace_user WHERE user_id = ? )"), append(args, *v) + where, args = append(where, "id IN (SELECT workspace_id FROM workspace_user WHERE user_id = ?)"), append(args, *v) } query := ` diff --git a/store/workspace_user.go b/store/workspace_user.go index ed323a6..308e51d 100644 --- a/store/workspace_user.go +++ b/store/workspace_user.go @@ -29,6 +29,21 @@ func (raw *workspaceUserRaw) toWorkspaceUser() *api.WorkspaceUser { } } +func (s *Store) ComposeWorkspaceUser(ctx context.Context, workspaceUser *api.WorkspaceUser) error { + user, err := s.FindUser(ctx, &api.UserFind{ + ID: &workspaceUser.UserID, + }) + if err != nil { + return err + } + + user.OpenID = "" + user.UserSettingList = nil + workspaceUser.User = user + + return nil +} + func (s *Store) UpsertWorkspaceUser(ctx context.Context, upsert *api.WorkspaceUserUpsert) (*api.WorkspaceUser, error) { tx, err := s.db.BeginTx(ctx, nil) if err != nil { @@ -112,14 +127,19 @@ func (s *Store) DeleteWorkspaceUser(ctx context.Context, delete *api.WorkspaceUs } func upsertWorkspaceUser(ctx context.Context, tx *sql.Tx, upsert *api.WorkspaceUserUpsert) (*workspaceUserRaw, error) { + set := []string{"workspace_id", "user_id", "role"} + args := []interface{}{upsert.WorkspaceID, upsert.UserID, upsert.Role} + placeholder := []string{"?", "?", "?"} + + if v := upsert.UpdatedTs; v != nil { + set, args, placeholder = append(set, "updated_ts"), append(args, *v), append(placeholder, "?") + } + query := ` INSERT INTO workspace_user ( - workspace_id, - user_id, - role, - updated_ts + ` + strings.Join(set, ", ") + ` ) - VALUES (?, ?, ?, ?) + VALUES (` + strings.Join(placeholder, ",") + `) ON CONFLICT(workspace_id, user_id) DO UPDATE SET role = EXCLUDED.role, @@ -127,12 +147,7 @@ func upsertWorkspaceUser(ctx context.Context, tx *sql.Tx, upsert *api.WorkspaceU RETURNING workspace_id, user_id, role, created_ts, updated_ts ` var workspaceUserRaw workspaceUserRaw - if err := tx.QueryRowContext(ctx, query, - upsert.WorkspaceID, - upsert.UserID, - upsert.Role, - upsert.UpdatedTs, - ).Scan( + if err := tx.QueryRowContext(ctx, query, args...).Scan( &workspaceUserRaw.WorkspaceID, &workspaceUserRaw.UserID, &workspaceUserRaw.Role,