From 96704162d8006f1d240cf1279656ef1dc08b0dac Mon Sep 17 00:00:00 2001 From: Steven Date: Tue, 20 Jun 2023 17:06:22 +0800 Subject: [PATCH] feat: migrate workspace to api v1 --- api/v1/auth.go | 1 + api/v1/user.go | 1 + api/v1/v1.go | 2 + api/v1/workspace.go | 189 ++++++++++++++++++++++++++ server/server.go | 1 - server/workspace.go | 229 ------------------------------- store/workspace.go | 318 +------------------------------------------- 7 files changed, 195 insertions(+), 546 deletions(-) create mode 100644 api/v1/workspace.go delete mode 100644 server/workspace.go diff --git a/api/v1/auth.go b/api/v1/auth.go index bbbb710..f6f81a5 100644 --- a/api/v1/auth.go +++ b/api/v1/auth.go @@ -7,6 +7,7 @@ import ( "github.com/boojack/shortify/server/auth" "github.com/boojack/shortify/store" + "github.com/labstack/echo/v4" "golang.org/x/crypto/bcrypt" ) diff --git a/api/v1/user.go b/api/v1/user.go index bcc832f..47f816b 100644 --- a/api/v1/user.go +++ b/api/v1/user.go @@ -8,6 +8,7 @@ import ( "strconv" "github.com/boojack/shortify/store" + "github.com/labstack/echo/v4" "golang.org/x/crypto/bcrypt" ) diff --git a/api/v1/v1.go b/api/v1/v1.go index a376ada..41e0043 100644 --- a/api/v1/v1.go +++ b/api/v1/v1.go @@ -3,6 +3,7 @@ package v1 import ( "github.com/boojack/shortify/server/profile" "github.com/boojack/shortify/store" + "github.com/labstack/echo/v4" ) @@ -21,4 +22,5 @@ func NewAPIV1Service(profile *profile.Profile, store *store.Store) *APIV1Service func (s *APIV1Service) Start(apiV1Group *echo.Group, secret string) { s.registerAuthRoutes(apiV1Group, secret) s.registerUserRoutes(apiV1Group) + s.registerWorkspaceRoutes(apiV1Group) } diff --git a/api/v1/workspace.go b/api/v1/workspace.go new file mode 100644 index 0000000..9e5662b --- /dev/null +++ b/api/v1/workspace.go @@ -0,0 +1,189 @@ +package v1 + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" + + "github.com/boojack/shortify/store" + + "github.com/labstack/echo/v4" +) + +type Workspace struct { + ID int `json:"id"` + + // Standard fields + CreatorID int `json:"creatorId"` + CreatedTs int64 `json:"createdTs"` + UpdatedTs int64 `json:"updatedTs"` + RowStatus RowStatus `json:"rowStatus"` + + // Domain specific fields + Name string `json:"name"` + Title string `json:"title"` + Description string `json:"description"` +} + +type CreateWorkspaceRequest struct { + Name string `json:"name"` + Title string `json:"title"` + Description string `json:"description"` +} + +type PatchWorkspaceRequest struct { + RowStatus *RowStatus `json:"rowStatus"` + Name *string `json:"name"` + Title *string `json:"title"` + Description *string `json:"description"` +} + +type WorkspaceFind struct { + ID *int `json:"id"` + + // Standard fields + RowStatus *RowStatus `json:"rowStatus"` + + // Domain specific fields + Name *string `json:"name"` + + // Related fields + MemberID *int +} + +type WorkspaceDelete struct { + ID int +} + +func (s *APIV1Service) registerWorkspaceRoutes(g *echo.Group) { + g.POST("/workspace", 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") + } + + create := &CreateWorkspaceRequest{} + if err := json.NewDecoder(c.Request().Body).Decode(create); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post workspace request").SetInternal(err) + } + if len(create.Name) > 24 { + return echo.NewHTTPError(http.StatusBadRequest, "Workspace name length should be less than 24") + } + + workspace, err := s.Store.CreateWorkspace(ctx, &store.Workspace{ + ResourceID: create.Name, + Title: create.Title, + Description: create.Description, + }) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create workspace").SetInternal(err) + } + + _, err = s.Store.UpsertWorkspaceUserV1(ctx, &store.WorkspaceUser{ + WorkspaceID: workspace.ID, + UserID: userID, + Role: store.RoleAdmin, + }) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create workspace user").SetInternal(err) + } + + return c.JSON(http.StatusOK, workspace) + }) + + g.GET("/workspace/:id", func(c echo.Context) error { + ctx := c.Request().Context() + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Malformatted workspace id").SetInternal(err) + } + + workspace, err := s.Store.GetWorkspace(ctx, &store.FindWorkspace{ + ID: &id, + }) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find workspace").SetInternal(err) + } + + return c.JSON(http.StatusOK, workspace) + }) + + g.PATCH("/workspace/:id", func(c echo.Context) error { + ctx := c.Request().Context() + userID, ok := c.Get(getUserIDContextKey()).(int) + if !ok { + return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session") + } + + workspaceID, err := strconv.Atoi(c.Param("id")) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("id"))).SetInternal(err) + } + workspaceUser, err := s.Store.GetWorkspaceUser(ctx, &store.FindWorkspaceUser{ + WorkspaceID: &workspaceID, + UserID: &userID, + }) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find workspace user").SetInternal(err) + } + if workspaceUser == nil { + return echo.NewHTTPError(http.StatusUnauthorized, "Current user is not a member of the workspace") + } + if workspaceUser.Role != store.RoleAdmin { + return echo.NewHTTPError(http.StatusUnauthorized, "Current user is not an admin of the workspace") + } + + patch := &PatchWorkspaceRequest{} + if err := json.NewDecoder(c.Request().Body).Decode(patch); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Malformatted patch workspace request").SetInternal(err) + } + + workspace, err := s.Store.UpdateWorkspace(ctx, &store.UpdateWorkspace{ + ID: workspaceID, + RowStatus: (*store.RowStatus)(patch.RowStatus), + ResourceID: patch.Name, + Title: patch.Title, + Description: patch.Description, + }) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to patch workspace").SetInternal(err) + } + + return c.JSON(http.StatusOK, workspace) + }) + + g.DELETE("/workspace/:id", func(c echo.Context) error { + ctx := c.Request().Context() + userID, ok := c.Get(getUserIDContextKey()).(int) + if !ok { + return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session") + } + workspaceID, err := strconv.Atoi(c.Param("id")) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("id"))).SetInternal(err) + } + workspaceUser, err := s.Store.GetWorkspaceUser(ctx, &store.FindWorkspaceUser{ + WorkspaceID: &workspaceID, + UserID: &userID, + }) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find workspace user").SetInternal(err) + } + if workspaceUser == nil { + return echo.NewHTTPError(http.StatusUnauthorized, "not workspace user") + } + if workspaceUser.Role != store.RoleAdmin { + return echo.NewHTTPError(http.StatusUnauthorized, "not workspace admin") + } + + if err := s.Store.DeleteWorkspace(ctx, &store.DeleteWorkspace{ + ID: workspaceID, + }); err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to delete user").SetInternal(err) + } + + return c.JSON(http.StatusOK, true) + }) +} diff --git a/server/server.go b/server/server.go index bc777f2..6b43a2f 100644 --- a/server/server.go +++ b/server/server.go @@ -65,7 +65,6 @@ func NewServer(profile *profile.Profile, store *store.Store) (*Server, error) { return JWTMiddleware(s, next, string(secret)) }) s.registerSystemRoutes(apiGroup) - s.registerWorkspaceRoutes(apiGroup) s.registerWorkspaceUserRoutes(apiGroup) s.registerShortcutRoutes(apiGroup) diff --git a/server/workspace.go b/server/workspace.go deleted file mode 100644 index 63d4850..0000000 --- a/server/workspace.go +++ /dev/null @@ -1,229 +0,0 @@ -package server - -import ( - "encoding/json" - "fmt" - "net/http" - "strconv" - - "github.com/boojack/shortify/api" - "github.com/boojack/shortify/internal/errorutil" - - "github.com/labstack/echo/v4" -) - -func (s *Server) registerWorkspaceRoutes(g *echo.Group) { - g.POST("/workspace", 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") - } - - workspaceCreate := &api.WorkspaceCreate{ - CreatorID: userID, - } - if err := json.NewDecoder(c.Request().Body).Decode(workspaceCreate); err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post workspace request").SetInternal(err) - } - if len(workspaceCreate.Name) > 20 { - return echo.NewHTTPError(http.StatusBadRequest, "Workspace name length should be less than 20") - } - - workspace, err := s.Store.CreateWorkspace(ctx, workspaceCreate) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create workspace").SetInternal(err) - } - - _, err = s.Store.UpsertWorkspaceUser(ctx, &api.WorkspaceUserUpsert{ - WorkspaceID: workspace.ID, - UserID: userID, - Role: api.RoleAdmin, - }) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create workspace user").SetInternal(err) - } - - if err := s.Store.ComposeWorkspaceUserListForWorkspace(ctx, workspace); err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to compose workspace user list").SetInternal(err) - } - - return c.JSON(http.StatusOK, composeResponse(workspace)) - }) - - g.GET("/workspace", 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") - } - - workspaceFind := &api.WorkspaceFind{ - MemberID: &userID, - } - workspaceList, err := s.Store.FindWordspaceList(ctx, workspaceFind) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch workspace list").SetInternal(err) - } - - for _, workspace := range workspaceList { - if err := s.Store.ComposeWorkspaceUserListForWorkspace(ctx, workspace); err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to compose workspace user list").SetInternal(err) - } - } - - return c.JSON(http.StatusOK, composeResponse(workspaceList)) - }) - - g.GET("/workspace/:id", func(c echo.Context) error { - ctx := c.Request().Context() - id, err := strconv.Atoi(c.Param("id")) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Malformatted workspace id").SetInternal(err) - } - - workspace, err := s.Store.FindWorkspace(ctx, &api.WorkspaceFind{ - ID: &id, - }) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find workspace").SetInternal(err) - } - - if err := s.Store.ComposeWorkspaceUserListForWorkspace(ctx, workspace); err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to compose workspace user list").SetInternal(err) - } - - return c.JSON(http.StatusOK, composeResponse(workspace)) - }) - - g.GET("/workspace/:workspaceName/shortcut/:shortcutName", func(c echo.Context) error { - ctx := c.Request().Context() - workspaceName := c.Param("workspaceName") - shortcutName := c.Param("shortcutName") - if workspaceName == "" || shortcutName == "" { - return echo.NewHTTPError(http.StatusBadRequest, "Missing workspace name or shortcut name") - } - - workspace, err := s.Store.FindWorkspace(ctx, &api.WorkspaceFind{ - Name: &workspaceName, - }) - if err != nil { - if errorutil.ErrorCode(err) == errorutil.NotFound { - return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("workspace not found by name %s", workspaceName)) - } - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to find workspace with name %s", workspaceName)).SetInternal(err) - } - - shortcut, err := s.Store.FindShortcut(ctx, &api.ShortcutFind{ - WorkspaceID: &workspace.ID, - Name: &shortcutName, - }) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to find shortcut with name %s", shortcutName)).SetInternal(err) - } - - if shortcut.Visibility != api.VisibilityPublic { - userID, ok := c.Get(getUserIDContextKey()).(int) - if !ok { - return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session") - } - - if shortcut.Visibility == api.VisibilityPrivite && shortcut.CreatorID != userID { - return echo.NewHTTPError(http.StatusUnauthorized, "not shortcut owner") - } - - workspaceUser, err := s.Store.FindWordspaceUser(ctx, &api.WorkspaceUserFind{ - WorkspaceID: &workspace.ID, - UserID: &userID, - }) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find workspace user").SetInternal(err) - } - if workspaceUser == nil { - return echo.NewHTTPError(http.StatusUnauthorized, "not workspace user") - } - } - - return c.JSON(http.StatusOK, composeResponse(shortcut)) - }) - - g.PATCH("/workspace/:id", func(c echo.Context) error { - ctx := c.Request().Context() - userID, ok := c.Get(getUserIDContextKey()).(int) - if !ok { - return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session") - } - - workspaceID, err := strconv.Atoi(c.Param("id")) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("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 workspaceUser == nil { - return echo.NewHTTPError(http.StatusUnauthorized, "not workspace user") - } - if workspaceUser.Role != api.RoleAdmin { - return echo.NewHTTPError(http.StatusUnauthorized, "not workspace admin") - } - - patch := &api.WorkspacePatch{ - ID: workspaceID, - } - if err := json.NewDecoder(c.Request().Body).Decode(patch); err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Malformatted patch workspace request").SetInternal(err) - } - - workspace, err := s.Store.PatchWorkspace(ctx, patch) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to patch workspace").SetInternal(err) - } - - if err := s.Store.ComposeWorkspaceUserListForWorkspace(ctx, workspace); err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to compose workspace user list").SetInternal(err) - } - - return c.JSON(http.StatusOK, composeResponse(workspace)) - }) - - g.DELETE("/workspace/:id", func(c echo.Context) error { - ctx := c.Request().Context() - userID, ok := c.Get(getUserIDContextKey()).(int) - if !ok { - return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session") - } - workspaceID, err := strconv.Atoi(c.Param("id")) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("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 workspaceUser == nil { - return echo.NewHTTPError(http.StatusUnauthorized, "not workspace user") - } - if workspaceUser.Role != api.RoleAdmin { - return echo.NewHTTPError(http.StatusUnauthorized, "not workspace admin") - } - - if err := s.Store.DeleteWorkspace(ctx, &api.WorkspaceDelete{ - ID: workspaceID, - }); err != nil { - if errorutil.ErrorCode(err) == errorutil.NotFound { - return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("workspace %d not found", workspaceID)) - } - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to delete user").SetInternal(err) - } - - return c.JSON(http.StatusOK, true) - }) -} diff --git a/store/workspace.go b/store/workspace.go index f3bd36d..3a8db11 100644 --- a/store/workspace.go +++ b/store/workspace.go @@ -6,9 +6,6 @@ import ( "errors" "fmt" "strings" - - "github.com/boojack/shortify/api" - "github.com/boojack/shortify/internal/errorutil" ) type Workspace struct { @@ -47,7 +44,7 @@ type DeleteWorkspace struct { ID int } -func (s *Store) CreateWorkspaceV1(ctx context.Context, create *Workspace) (*Workspace, error) { +func (s *Store) CreateWorkspace(ctx context.Context, create *Workspace) (*Workspace, error) { tx, err := s.db.BeginTx(ctx, nil) if err != nil { return nil, err @@ -178,7 +175,7 @@ func (s *Store) GetWorkspace(ctx context.Context, find *FindWorkspace) (*Workspa return list[0], nil } -func (s *Store) DeleteWorkspaceV1(ctx context.Context, delete *DeleteWorkspace) error { +func (s *Store) DeleteWorkspace(ctx context.Context, delete *DeleteWorkspace) error { tx, err := s.db.BeginTx(ctx, nil) if err != nil { return err @@ -255,314 +252,3 @@ func listWorkspaces(ctx context.Context, tx *sql.Tx, find *FindWorkspace) ([]*Wo return list, nil } - -// workspaceRaw is the store model for Workspace. -type workspaceRaw struct { - ID int - - // Standard fields - CreatorID int - CreatedTs int64 - UpdatedTs int64 - RowStatus api.RowStatus - - // Domain specific fields - Name string - Title string - Description string -} - -func (raw *workspaceRaw) toWorkspace() *api.Workspace { - return &api.Workspace{ - ID: raw.ID, - - CreatorID: raw.CreatorID, - CreatedTs: raw.CreatedTs, - UpdatedTs: raw.UpdatedTs, - RowStatus: raw.RowStatus, - - Name: raw.Name, - Title: raw.Title, - Description: raw.Description, - WorkspaceUserList: []*api.WorkspaceUser{}, - } -} - -func (s *Store) CreateWorkspace(ctx context.Context, create *api.WorkspaceCreate) (*api.Workspace, error) { - tx, err := s.db.BeginTx(ctx, nil) - if err != nil { - return nil, err - } - defer tx.Rollback() - - workspaceRaw, err := createWorkspace(ctx, tx, create) - if err != nil { - return nil, err - } - - if err := tx.Commit(); err != nil { - return nil, err - } - - s.workspaceCache.Store(workspaceRaw.ID, workspaceRaw) - workspace := workspaceRaw.toWorkspace() - return workspace, nil -} - -func (s *Store) PatchWorkspace(ctx context.Context, patch *api.WorkspacePatch) (*api.Workspace, error) { - tx, err := s.db.BeginTx(ctx, nil) - if err != nil { - return nil, err - } - defer tx.Rollback() - - workspaceRaw, err := patchWorkspace(ctx, tx, patch) - if err != nil { - return nil, err - } - - if err := tx.Commit(); err != nil { - return nil, err - } - - s.workspaceCache.Store(workspaceRaw.ID, workspaceRaw) - workspace := workspaceRaw.toWorkspace() - return workspace, nil -} - -func (s *Store) FindWordspaceList(ctx context.Context, find *api.WorkspaceFind) ([]*api.Workspace, error) { - tx, err := s.db.BeginTx(ctx, nil) - if err != nil { - return nil, err - } - defer tx.Rollback() - - workspaceRawList, err := findWorkspaceList(ctx, tx, find) - if err != nil { - return nil, err - } - - list := []*api.Workspace{} - for _, workspaceRaw := range workspaceRawList { - s.workspaceCache.Store(workspaceRaw.ID, workspaceRaw) - list = append(list, workspaceRaw.toWorkspace()) - } - - return list, nil -} - -func (s *Store) FindWorkspace(ctx context.Context, find *api.WorkspaceFind) (*api.Workspace, error) { - if find.ID != nil { - if cache, ok := s.workspaceCache.Load(*find.ID); ok { - return cache.(*workspaceRaw).toWorkspace(), nil - } - } - - tx, err := s.db.BeginTx(ctx, nil) - if err != nil { - return nil, err - } - defer tx.Rollback() - - list, err := findWorkspaceList(ctx, tx, find) - if err != nil { - return nil, err - } - - if len(list) == 0 { - return nil, &errorutil.Error{Code: errorutil.NotFound, Err: fmt.Errorf("not found workspace with filter %+v", find)} - } else if len(list) > 1 { - return nil, &errorutil.Error{Code: errorutil.Conflict, Err: fmt.Errorf("found %d workspaces with filter %+v, expect 1", len(list), find)} - } - - workspaceRaw := list[0] - s.workspaceCache.Store(workspaceRaw.ID, workspaceRaw) - workspace := workspaceRaw.toWorkspace() - return workspace, nil -} - -func (s *Store) DeleteWorkspace(ctx context.Context, delete *api.WorkspaceDelete) error { - tx, err := s.db.BeginTx(ctx, nil) - if err != nil { - return err - } - defer tx.Rollback() - - err = deleteWorkspace(ctx, tx, delete) - if err != nil { - return err - } - - if err := tx.Commit(); err != nil { - return err - } - - s.workspaceCache.Delete(delete.ID) - return nil -} - -func createWorkspace(ctx context.Context, tx *sql.Tx, create *api.WorkspaceCreate) (*workspaceRaw, error) { - query := ` - INSERT INTO workspace ( - creator_id, - name, - title, - description - ) - VALUES (?, ?, ?) - RETURNING id, creator_id, created_ts, updated_ts, row_status, name, title, description - ` - var workspaceRaw workspaceRaw - if err := tx.QueryRowContext(ctx, query, - create.CreatorID, - create.Name, - create.Title, - create.Description, - ).Scan( - &workspaceRaw.ID, - &workspaceRaw.CreatorID, - &workspaceRaw.CreatedTs, - &workspaceRaw.UpdatedTs, - &workspaceRaw.RowStatus, - &workspaceRaw.Name, - &workspaceRaw.Title, - &workspaceRaw.Description, - ); err != nil { - return nil, err - } - - return &workspaceRaw, nil -} - -func patchWorkspace(ctx context.Context, tx *sql.Tx, patch *api.WorkspacePatch) (*workspaceRaw, error) { - set, args := []string{}, []any{} - - if v := patch.RowStatus; v != nil { - set, args = append(set, "row_status = ?"), append(args, *v) - } - if v := patch.Name; v != nil { - set, args = append(set, "name = ?"), append(args, *v) - } - if v := patch.Title; v != nil { - set, args = append(set, "title = ?"), append(args, *v) - } - if v := patch.Description; v != nil { - set, args = append(set, "description = ?"), append(args, *v) - } - - args = append(args, patch.ID) - - query := ` - UPDATE workspace - SET ` + strings.Join(set, ", ") + ` - WHERE id = ? - RETURNING id, creator_id, created_ts, updated_ts, row_status, name, title, description - ` - row, err := tx.QueryContext(ctx, query, args...) - if err != nil { - return nil, err - } - defer row.Close() - - if row.Next() { - var workspaceRaw workspaceRaw - if err := row.Scan( - &workspaceRaw.ID, - &workspaceRaw.CreatorID, - &workspaceRaw.CreatedTs, - &workspaceRaw.UpdatedTs, - &workspaceRaw.RowStatus, - &workspaceRaw.Name, - &workspaceRaw.Title, - &workspaceRaw.Description, - ); err != nil { - return nil, err - } - - if err := row.Err(); err != nil { - return nil, err - } - - return &workspaceRaw, nil - } - - return nil, &errorutil.Error{Code: errorutil.NotFound, Err: fmt.Errorf("workspace ID not found: %d", patch.ID)} -} - -func findWorkspaceList(ctx context.Context, tx *sql.Tx, find *api.WorkspaceFind) ([]*workspaceRaw, error) { - where, args := []string{"1 = 1"}, []any{} - - if v := find.ID; v != nil { - where, args = append(where, "id = ?"), append(args, *v) - } - if v := find.RowStatus; v != nil { - where, args = append(where, "row_status = ?"), append(args, *v) - } - if v := find.Name; v != nil { - 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) - } - - query := ` - SELECT - id, - creator_id, - created_ts, - updated_ts, - row_status, - name, - title, - description - FROM workspace - WHERE ` + strings.Join(where, " AND ") + ` - ORDER BY created_ts DESC, row_status DESC - ` - rows, err := tx.QueryContext(ctx, query, args...) - if err != nil { - return nil, err - } - defer rows.Close() - - workspaceRawList := make([]*workspaceRaw, 0) - for rows.Next() { - var workspaceRaw workspaceRaw - if err := rows.Scan( - &workspaceRaw.ID, - &workspaceRaw.CreatorID, - &workspaceRaw.CreatedTs, - &workspaceRaw.UpdatedTs, - &workspaceRaw.RowStatus, - &workspaceRaw.Name, - &workspaceRaw.Title, - &workspaceRaw.Description, - ); err != nil { - return nil, err - } - - workspaceRawList = append(workspaceRawList, &workspaceRaw) - } - - if err := rows.Err(); err != nil { - return nil, err - } - - return workspaceRawList, nil -} - -func deleteWorkspace(ctx context.Context, tx *sql.Tx, delete *api.WorkspaceDelete) error { - result, err := tx.ExecContext(ctx, ` - DELETE FROM workspace WHERE id = ? - `, delete.ID) - if err != nil { - return err - } - - rows, _ := result.RowsAffected() - if rows == 0 { - return &errorutil.Error{Code: errorutil.NotFound, Err: fmt.Errorf("workspace ID not found: %d", delete.ID)} - } - - return nil -}