diff --git a/api/v1/shortcut.go b/api/v1/shortcut.go index af73de7..c27be1d 100644 --- a/api/v1/shortcut.go +++ b/api/v1/shortcut.go @@ -39,12 +39,11 @@ type Shortcut struct { ID int `json:"id"` // Standard fields - CreatorID int `json:"creatorId"` - Creator *User `json:"creator"` - CreatedTs int64 `json:"createdTs"` - UpdatedTs int64 `json:"updatedTs"` - WorkspaceID int `json:"workspaceId"` - RowStatus RowStatus `json:"rowStatus"` + CreatorID int `json:"creatorId"` + Creator *User `json:"creator"` + CreatedTs int64 `json:"createdTs"` + UpdatedTs int64 `json:"updatedTs"` + RowStatus RowStatus `json:"rowStatus"` // Domain specific fields Name string `json:"name"` @@ -54,7 +53,6 @@ type Shortcut struct { } type CreateShortcutRequest struct { - WorkspaceID int `json:"workspaceId"` Name string `json:"name"` Link string `json:"link"` Description string `json:"description"` @@ -83,7 +81,6 @@ func (s *APIV1Service) registerShortcutRoutes(g *echo.Group) { shortcut, err := s.Store.CreateShortcut(ctx, &store.Shortcut{ CreatorID: userID, - WorkspaceID: create.WorkspaceID, Name: create.Name, Link: create.Link, Description: create.Description, @@ -113,17 +110,11 @@ func (s *APIV1Service) registerShortcutRoutes(g *echo.Group) { if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find shortcut").SetInternal(err) } - - workspaceUser, err := s.Store.GetWorkspaceUser(ctx, &store.FindWorkspaceUser{ - UserID: &userID, - WorkspaceID: &shortcut.WorkspaceID, - }) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find workspace user").SetInternal(err) + if shortcut == nil { + return echo.NewHTTPError(http.StatusNotFound, "Shortcut not found") } - - if shortcut.CreatorID != userID && workspaceUser.Role != store.RoleAdmin { - return echo.NewHTTPError(http.StatusForbidden, "Forbidden to patch shortcut") + if shortcut.CreatorID != userID { + return echo.NewHTTPError(http.StatusForbidden, "Shortcut does not belong to user") } patch := &PatchShortcutRequest{} @@ -153,9 +144,6 @@ func (s *APIV1Service) registerShortcutRoutes(g *echo.Group) { } find := &store.FindShortcut{} - if workspaceID, err := strconv.Atoi(c.QueryParam("workspaceId")); err == nil { - find.WorkspaceID = &workspaceID - } if name := c.QueryParam("name"); name != "" { find.Name = &name } diff --git a/api/v1/v1.go b/api/v1/v1.go index d172015..3931059 100644 --- a/api/v1/v1.go +++ b/api/v1/v1.go @@ -23,7 +23,5 @@ func (s *APIV1Service) Start(apiV1Group *echo.Group, secret string) { s.registerSystemRoutes(apiV1Group) s.registerAuthRoutes(apiV1Group, secret) s.registerUserRoutes(apiV1Group) - s.registerWorkspaceRoutes(apiV1Group) - s.registerWorkspaceUserRoutes(apiV1Group) s.registerShortcutRoutes(apiV1Group) } diff --git a/api/v1/workspace.go b/api/v1/workspace.go deleted file mode 100644 index db5b42c..0000000 --- a/api/v1/workspace.go +++ /dev/null @@ -1,189 +0,0 @@ -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.UpsertWorkspaceUser(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/api/v1/workspace_user.go b/api/v1/workspace_user.go deleted file mode 100644 index 9785213..0000000 --- a/api/v1/workspace_user.go +++ /dev/null @@ -1,213 +0,0 @@ -package v1 - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "strconv" - - "github.com/boojack/shortify/store" - - "github.com/labstack/echo/v4" -) - -type WorkspaceUser struct { - WorkspaceID int `json:"workspaceId"` - UserID int `json:"userId"` - Role Role `json:"role"` - - // Related fields - Username string `json:"username"` - Nickname string `json:"nickname"` -} - -type UpsertWorkspaceUserRequest struct { - WorkspaceID int `json:"workspaceId"` - UserID int `json:"userId"` - Role Role `json:"role"` -} - -func (s *APIV1Service) 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.GetWorkspaceUser(ctx, &store.FindWorkspaceUser{ - WorkspaceID: &workspaceID, - UserID: &userID, - }) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find workspace user").SetInternal(err) - } - if currentWorkspaceUser.Role != store.RoleAdmin { - return echo.NewHTTPError(http.StatusForbidden, "Access forbidden to add workspace user").SetInternal(err) - } - - upsert := &UpsertWorkspaceUserRequest{ - WorkspaceID: workspaceID, - } - if err := json.NewDecoder(c.Request().Body).Decode(upsert); err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post workspace user request").SetInternal(err) - } - - workspaceUser, err := s.Store.UpsertWorkspaceUser(ctx, &store.WorkspaceUser{ - WorkspaceID: upsert.WorkspaceID, - UserID: upsert.UserID, - Role: convertRoleToStore(upsert.Role), - }) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert workspace user").SetInternal(err) - } - - composedWorkspaceUser, err := s.composeWorkspaceUser(ctx, workspaceUser) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to compose workspace user").SetInternal(err) - } - - return c.JSON(http.StatusOK, composedWorkspaceUser) - }) - - 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.ListWorkspaceUsers(ctx, &store.FindWorkspaceUser{ - WorkspaceID: &workspaceID, - }) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find workspace user list").SetInternal(err) - } - - composedList := make([]*WorkspaceUser, 0, len(workspaceUserList)) - for _, workspaceUser := range workspaceUserList { - composedWorkspaceUser, err := s.composeWorkspaceUser(ctx, workspaceUser) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to compose workspace user").SetInternal(err) - } - composedList = append(composedList, composedWorkspaceUser) - } - - return c.JSON(http.StatusOK, composedList) - }) - - 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.GetWorkspaceUser(ctx, &store.FindWorkspaceUser{ - WorkspaceID: &workspaceID, - UserID: &userID, - }) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find workspace user").SetInternal(err) - } - - composedWorkspaceUser, err := s.composeWorkspaceUser(ctx, workspaceUser) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to compose workspace user").SetInternal(err) - } - - return c.JSON(http.StatusOK, composedWorkspaceUser) - }) - - g.DELETE("/workspace/:workspaceId/user/:userId", func(c echo.Context) error { - ctx := c.Request().Context() - currentUserID, 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) - } - - userID, err := strconv.Atoi(c.Param("userId")) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Malformatted user id").SetInternal(err) - } - - currentWorkspaceUser, err := s.Store.GetWorkspaceUser(ctx, &store.FindWorkspaceUser{ - WorkspaceID: &workspaceID, - UserID: ¤tUserID, - }) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find workspace user").SetInternal(err) - } - if currentUserID != userID && currentWorkspaceUser.Role != store.RoleAdmin { - return echo.NewHTTPError(http.StatusForbidden, "Access forbidden to delete workspace user").SetInternal(err) - } - - if err := s.Store.DeleteWorkspaceUser(ctx, &store.DeleteWorkspaceUser{ - WorkspaceID: workspaceID, - UserID: userID, - }); err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to delete workspace user").SetInternal(err) - } - - return c.JSON(http.StatusOK, true) - }) -} - -func convertRoleToStore(role Role) store.Role { - switch role { - case RoleAdmin: - return store.RoleAdmin - case RoleUser: - return store.RoleUser - default: - return store.RoleUser - } -} - -func convertRoleFromStore(role store.Role) Role { - switch role { - case store.RoleAdmin: - return RoleAdmin - case store.RoleUser: - return RoleUser - default: - return RoleUser - } -} - -func (s *APIV1Service) composeWorkspaceUser(ctx context.Context, workspaceUser *store.WorkspaceUser) (*WorkspaceUser, error) { - user, err := s.Store.GetUser(ctx, &store.FindUser{ - ID: &workspaceUser.UserID, - }) - if err != nil { - return nil, err - } - if user == nil { - return nil, fmt.Errorf("Failed to find user %d", workspaceUser.UserID) - } - - composedWorkspaceUser := &WorkspaceUser{ - WorkspaceID: workspaceUser.WorkspaceID, - UserID: workspaceUser.UserID, - Role: convertRoleFromStore(workspaceUser.Role), - Username: user.Username, - Nickname: user.Nickname, - } - - return composedWorkspaceUser, nil -} diff --git a/store/db/migration/dev/LATEST__SCHEMA.sql b/store/db/migration/dev/LATEST__SCHEMA.sql index 5eec13e..94f21e5 100644 --- a/store/db/migration/dev/LATEST__SCHEMA.sql +++ b/store/db/migration/dev/LATEST__SCHEMA.sql @@ -4,28 +4,10 @@ CREATE TABLE migration_history ( created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')) ); --- workspace -CREATE TABLE workspace ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')), - updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')), - row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL', - resource_id TEXT NOT NULL UNIQUE, - title TEXT NOT NULL, - description TEXT NOT NULL DEFAULT '' -); - -INSERT INTO - sqlite_sequence (name, seq) -VALUES - ('workspace', 1); - -- workspace_setting CREATE TABLE workspace_setting ( - workspace_id INTEGER NOT NULL, - key TEXT NOT NULL, - value TEXT NOT NULL, - UNIQUE(workspace_id, key) + key TEXT NOT NULL UNIQUE, + value TEXT NOT NULL ); -- user @@ -41,11 +23,6 @@ CREATE TABLE user ( role TEXT NOT NULL CHECK (role IN ('ADMIN', 'USER')) DEFAULT 'USER' ); -INSERT INTO - sqlite_sequence (name, seq) -VALUES - ('user', 10); - -- user_setting CREATE TABLE user_setting ( user_id INTEGER NOT NULL, @@ -54,14 +31,6 @@ CREATE TABLE user_setting ( UNIQUE(user_id, key) ); --- workspace_user -CREATE TABLE workspace_user ( - workspace_id INTEGER NOT NULL, - user_id INTEGER NOT NULL, - role TEXT NOT NULL CHECK (role IN ('ADMIN', 'USER')) DEFAULT 'USER', - UNIQUE(workspace_id, user_id) -); - -- shortcut CREATE TABLE shortcut ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -69,15 +38,8 @@ CREATE TABLE shortcut ( created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')), updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')), row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL', - workspace_id INTEGER NOT NULL, - name TEXT NOT NULL, + name TEXT NOT NULL UNIQUE, link TEXT NOT NULL, description TEXT NOT NULL DEFAULT '', - visibility TEXT NOT NULL CHECK (visibility IN ('PRIVATE', 'WORKSPACE', 'PUBLIC')) DEFAULT 'PRIVATE', - UNIQUE(workspace_id, name) + visibility TEXT NOT NULL CHECK (visibility IN ('PRIVATE', 'WORKSPACE', 'PUBLIC')) DEFAULT 'PRIVATE' ); - -INSERT INTO - sqlite_sequence (name, seq) -VALUES - ('shortcut', 100); diff --git a/store/db/migration/prod/LATEST__SCHEMA.sql b/store/db/migration/prod/LATEST__SCHEMA.sql index 5eec13e..94f21e5 100644 --- a/store/db/migration/prod/LATEST__SCHEMA.sql +++ b/store/db/migration/prod/LATEST__SCHEMA.sql @@ -4,28 +4,10 @@ CREATE TABLE migration_history ( created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')) ); --- workspace -CREATE TABLE workspace ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')), - updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')), - row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL', - resource_id TEXT NOT NULL UNIQUE, - title TEXT NOT NULL, - description TEXT NOT NULL DEFAULT '' -); - -INSERT INTO - sqlite_sequence (name, seq) -VALUES - ('workspace', 1); - -- workspace_setting CREATE TABLE workspace_setting ( - workspace_id INTEGER NOT NULL, - key TEXT NOT NULL, - value TEXT NOT NULL, - UNIQUE(workspace_id, key) + key TEXT NOT NULL UNIQUE, + value TEXT NOT NULL ); -- user @@ -41,11 +23,6 @@ CREATE TABLE user ( role TEXT NOT NULL CHECK (role IN ('ADMIN', 'USER')) DEFAULT 'USER' ); -INSERT INTO - sqlite_sequence (name, seq) -VALUES - ('user', 10); - -- user_setting CREATE TABLE user_setting ( user_id INTEGER NOT NULL, @@ -54,14 +31,6 @@ CREATE TABLE user_setting ( UNIQUE(user_id, key) ); --- workspace_user -CREATE TABLE workspace_user ( - workspace_id INTEGER NOT NULL, - user_id INTEGER NOT NULL, - role TEXT NOT NULL CHECK (role IN ('ADMIN', 'USER')) DEFAULT 'USER', - UNIQUE(workspace_id, user_id) -); - -- shortcut CREATE TABLE shortcut ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -69,15 +38,8 @@ CREATE TABLE shortcut ( created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')), updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')), row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL', - workspace_id INTEGER NOT NULL, - name TEXT NOT NULL, + name TEXT NOT NULL UNIQUE, link TEXT NOT NULL, description TEXT NOT NULL DEFAULT '', - visibility TEXT NOT NULL CHECK (visibility IN ('PRIVATE', 'WORKSPACE', 'PUBLIC')) DEFAULT 'PRIVATE', - UNIQUE(workspace_id, name) + visibility TEXT NOT NULL CHECK (visibility IN ('PRIVATE', 'WORKSPACE', 'PUBLIC')) DEFAULT 'PRIVATE' ); - -INSERT INTO - sqlite_sequence (name, seq) -VALUES - ('shortcut', 100); diff --git a/store/shortcut.go b/store/shortcut.go index db04c48..bbe9af8 100644 --- a/store/shortcut.go +++ b/store/shortcut.go @@ -41,7 +41,6 @@ type Shortcut struct { RowStatus RowStatus // Domain specific fields - WorkspaceID int Name string Link string Description string @@ -62,7 +61,6 @@ type FindShortcut struct { ID *int CreatorID *int RowStatus *RowStatus - WorkspaceID *int Name *string VisibilityList []Visibility } @@ -78,9 +76,9 @@ func (s *Store) CreateShortcut(ctx context.Context, create *Shortcut) (*Shortcut } defer tx.Rollback() - set := []string{"creator_id", "workspace_id", "name", "link", "description", "visibility"} - args := []any{create.CreatorID, create.WorkspaceID, create.Name, create.Link, create.Description, create.Visibility} - placeholder := []string{"?", "?", "?", "?", "?", "?"} + set := []string{"creator_id", "name", "link", "description", "visibility"} + args := []any{create.CreatorID, create.Name, create.Link, create.Description, create.Visibility} + placeholder := []string{"?", "?", "?", "?", "?"} query := ` INSERT INTO shortcut ( @@ -140,7 +138,7 @@ func (s *Store) UpdateShortcut(ctx context.Context, update *UpdateShortcut) (*Sh ` + strings.Join(set, ", ") + ` WHERE id = ? - RETURNING id, creator_id, created_ts, updated_ts, workspace_id, row_status, name, link, description, visibility + RETURNING id, creator_id, created_ts, updated_ts, row_status, name, link, description, visibility ` var shortcut Shortcut if err := tx.QueryRowContext(ctx, query, args...).Scan( @@ -148,7 +146,6 @@ func (s *Store) UpdateShortcut(ctx context.Context, update *UpdateShortcut) (*Sh &shortcut.CreatorID, &shortcut.CreatedTs, &shortcut.UpdatedTs, - &shortcut.WorkspaceID, &shortcut.RowStatus, &shortcut.Name, &shortcut.Link, @@ -239,9 +236,6 @@ func listShortcuts(ctx context.Context, tx *sql.Tx, find *FindShortcut) ([]*Shor if v := find.RowStatus; v != nil { where, args = append(where, "row_status = ?"), append(args, *v) } - if v := find.WorkspaceID; v != nil { - where, args = append(where, "workspace_id = ?"), append(args, *v) - } if v := find.Name; v != nil { where, args = append(where, "name = ?"), append(args, *v) } @@ -261,7 +255,6 @@ func listShortcuts(ctx context.Context, tx *sql.Tx, find *FindShortcut) ([]*Shor created_ts, updated_ts, row_status, - workspace_id, name, link, description, @@ -284,7 +277,6 @@ func listShortcuts(ctx context.Context, tx *sql.Tx, find *FindShortcut) ([]*Shor &shortcut.CreatorID, &shortcut.CreatedTs, &shortcut.UpdatedTs, - &shortcut.WorkspaceID, &shortcut.RowStatus, &shortcut.Name, &shortcut.Link, diff --git a/store/user.go b/store/user.go index 8a16932..d843947 100644 --- a/store/user.go +++ b/store/user.go @@ -7,6 +7,16 @@ import ( "strings" ) +// Role is the type of a role. +type Role string + +const ( + // RoleAdmin is the ADMIN role. + RoleAdmin Role = "ADMIN" + // RoleUser is the USER role. + RoleUser Role = "USER" +) + type User struct { ID int diff --git a/store/user_setting.go b/store/user_setting.go deleted file mode 100644 index 72440ea..0000000 --- a/store/user_setting.go +++ /dev/null @@ -1 +0,0 @@ -package store diff --git a/store/workspace.go b/store/workspace.go deleted file mode 100644 index 3a8db11..0000000 --- a/store/workspace.go +++ /dev/null @@ -1,254 +0,0 @@ -package store - -import ( - "context" - "database/sql" - "errors" - "fmt" - "strings" -) - -type Workspace struct { - ID int - - // Standard fields - CreatedTs int64 - UpdatedTs int64 - RowStatus RowStatus - - // Domain specific fields - ResourceID string - Title string - Description string -} - -type UpdateWorkspace struct { - ID int - - // Standard fields - RowStatus *RowStatus - - // Domain specific fields - ResourceID *string - Title *string - Description *string -} - -type FindWorkspace struct { - ID *int - RowStatus *RowStatus - ResourceID *string -} - -type DeleteWorkspace struct { - ID int -} - -func (s *Store) CreateWorkspace(ctx context.Context, create *Workspace) (*Workspace, error) { - tx, err := s.db.BeginTx(ctx, nil) - if err != nil { - return nil, err - } - defer tx.Rollback() - - query := ` - INSERT INTO workspace ( - resource_id, - title, - description - ) - VALUES (?, ?, ?) - RETURNING id, created_ts, updated_ts, row_status - ` - if err := tx.QueryRowContext(ctx, query, - create.ResourceID, - create.Title, - create.Description, - ).Scan( - &create.ID, - &create.CreatedTs, - &create.UpdatedTs, - &create.RowStatus, - ); err != nil { - return nil, err - } - - if err := tx.Commit(); err != nil { - return nil, err - } - - workspace := create - return workspace, nil -} - -func (s *Store) UpdateWorkspace(ctx context.Context, update *UpdateWorkspace) (*Workspace, error) { - tx, err := s.db.BeginTx(ctx, nil) - if err != nil { - return nil, err - } - defer tx.Rollback() - - set, args := []string{}, []any{} - - if v := update.RowStatus; v != nil { - set, args = append(set, "row_status = ?"), append(args, *v) - } - if v := update.ResourceID; v != nil { - set, args = append(set, "resource_id = ?"), append(args, *v) - } - if v := update.Title; v != nil { - set, args = append(set, "title = ?"), append(args, *v) - } - if v := update.Description; v != nil { - set, args = append(set, "description = ?"), append(args, *v) - } - args = append(args, update.ID) - - query := ` - UPDATE workspace - SET ` + strings.Join(set, ", ") + ` - WHERE id = ? - RETURNING id, created_ts, updated_ts, row_status, resource_id, title, description - ` - row, err := tx.QueryContext(ctx, query, args...) - if err != nil { - return nil, err - } - defer row.Close() - - if !row.Next() { - return nil, errors.New(fmt.Sprint("workspace ID not found: ", update.ID)) - } - workspace := &Workspace{} - if err := row.Scan( - &workspace.ID, - &workspace.CreatedTs, - &workspace.UpdatedTs, - &workspace.RowStatus, - &workspace.ResourceID, - &workspace.Title, - &workspace.Description, - ); err != nil { - return nil, err - } - - if err := row.Err(); err != nil { - return nil, err - } - if err := tx.Commit(); err != nil { - return nil, err - } - - return workspace, nil -} - -func (s *Store) ListWorkspaces(ctx context.Context, find *FindWorkspace) ([]*Workspace, error) { - tx, err := s.db.BeginTx(ctx, nil) - if err != nil { - return nil, err - } - defer tx.Rollback() - - list, err := listWorkspaces(ctx, tx, find) - if err != nil { - return nil, err - } - - return list, nil -} - -func (s *Store) GetWorkspace(ctx context.Context, find *FindWorkspace) (*Workspace, error) { - tx, err := s.db.BeginTx(ctx, nil) - if err != nil { - return nil, err - } - defer tx.Rollback() - - list, err := listWorkspaces(ctx, tx, find) - if err != nil { - return nil, err - } - - if len(list) == 0 { - return nil, nil - } - return list[0], nil -} - -func (s *Store) DeleteWorkspace(ctx context.Context, delete *DeleteWorkspace) error { - tx, err := s.db.BeginTx(ctx, nil) - if err != nil { - return err - } - defer tx.Rollback() - - if _, err := tx.ExecContext(ctx, ` - DELETE FROM workspace WHERE id = ? - `, delete.ID); err != nil { - return err - } - - if err := tx.Commit(); err != nil { - // do nothing here to prevent linter warning. - return err - } - - return nil -} - -func listWorkspaces(ctx context.Context, tx *sql.Tx, find *FindWorkspace) ([]*Workspace, 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.ResourceID; v != nil { - where, args = append(where, "resource_id = ?"), append(args, *v) - } - - query := ` - SELECT - id, - created_ts, - updated_ts, - row_status, - resource_id, - 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() - - list := make([]*Workspace, 0) - for rows.Next() { - var workspace Workspace - if err := rows.Scan( - &workspace.ID, - &workspace.CreatedTs, - &workspace.UpdatedTs, - &workspace.RowStatus, - &workspace.ResourceID, - &workspace.Title, - &workspace.Description, - ); err != nil { - return nil, err - } - - list = append(list, &workspace) - } - - if err := rows.Err(); err != nil { - return nil, err - } - - return list, nil -} diff --git a/store/workspace_setting.go b/store/workspace_setting.go new file mode 100644 index 0000000..f420dad --- /dev/null +++ b/store/workspace_setting.go @@ -0,0 +1,116 @@ +package store + +import ( + "context" + "database/sql" + "strings" +) + +type WorkspaceSetting struct { + Key string + Value string +} + +type FindWorkspaceSetting struct { + Key string +} + +func (s *Store) UpsertWorkspaceSetting(ctx context.Context, upsert *WorkspaceSetting) (*WorkspaceSetting, error) { + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return nil, err + } + defer tx.Rollback() + + query := ` + INSERT INTO workspace_setting ( + key, + value + ) + VALUES (?, ?) + ON CONFLICT(key) DO UPDATE + SET value = EXCLUDED.value + ` + if _, err := tx.ExecContext(ctx, query, upsert.Key, upsert.Value); err != nil { + return nil, err + } + + if err := tx.Commit(); err != nil { + return nil, err + } + + workspaceSetting := upsert + return workspaceSetting, nil +} + +func (s *Store) ListWorkspaceSettings(ctx context.Context, find *FindWorkspaceSetting) ([]*WorkspaceSetting, error) { + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return nil, err + } + defer tx.Rollback() + + list, err := listWorkspaceSettings(ctx, tx, find) + if err != nil { + return nil, err + } + + if err := tx.Commit(); err != nil { + return nil, err + } + + return list, nil +} + +func (s *Store) GetWorkspaceSetting(ctx context.Context, find *FindWorkspaceSetting) (*WorkspaceSetting, error) { + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return nil, err + } + defer tx.Rollback() + + list, err := listWorkspaceSettings(ctx, tx, find) + if err != nil { + return nil, err + } + + if len(list) == 0 { + return nil, nil + } + + workspaceSetting := list[0] + return workspaceSetting, nil +} + +func listWorkspaceSettings(ctx context.Context, tx *sql.Tx, find *FindWorkspaceSetting) ([]*WorkspaceSetting, error) { + where, args := []string{"1 = 1"}, []any{} + if find.Key != "" { + where, args = append(where, "key = ?"), append(args, find.Key) + } + + query := ` + SELECT + key, + value + FROM workspace_setting + WHERE ` + strings.Join(where, " AND ") + + rows, err := tx.QueryContext(ctx, query, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + list := []*WorkspaceSetting{} + for rows.Next() { + workspaceSetting := &WorkspaceSetting{} + if err := rows.Scan( + &workspaceSetting.Key, + &workspaceSetting.Value, + ); err != nil { + return nil, err + } + list = append(list, workspaceSetting) + } + return list, nil +} diff --git a/store/workspace_user.go b/store/workspace_user.go deleted file mode 100644 index 39f9fbc..0000000 --- a/store/workspace_user.go +++ /dev/null @@ -1,172 +0,0 @@ -package store - -import ( - "context" - "database/sql" - "strings" -) - -// Role is the type of a role. -type Role string - -const ( - // RoleAdmin is the ADMIN role. - RoleAdmin Role = "ADMIN" - // RoleUser is the USER role. - RoleUser Role = "USER" -) - -type WorkspaceUser struct { - WorkspaceID int - UserID int - Role Role -} - -type FindWorkspaceUser struct { - WorkspaceID *int - UserID *int - Role *Role -} - -type DeleteWorkspaceUser struct { - WorkspaceID int - UserID int -} - -func (s *Store) UpsertWorkspaceUser(ctx context.Context, upsert *WorkspaceUser) (*WorkspaceUser, error) { - tx, err := s.db.BeginTx(ctx, nil) - if err != nil { - return nil, err - } - defer tx.Rollback() - - set := []string{"workspace_id", "user_id", "role"} - args := []any{upsert.WorkspaceID, upsert.UserID, upsert.Role} - placeholder := []string{"?", "?", "?"} - - query := ` - INSERT INTO workspace_user ( - ` + strings.Join(set, ", ") + ` - ) - VALUES (` + strings.Join(placeholder, ",") + `) - ON CONFLICT(workspace_id, user_id) DO UPDATE - SET - role = EXCLUDED.role - ` - if _, err := tx.ExecContext(ctx, query, args...); err != nil { - return nil, err - } - - if err := tx.Commit(); err != nil { - return nil, err - } - - workspaceUser := upsert - return workspaceUser, nil -} - -func (s *Store) ListWorkspaceUsers(ctx context.Context, find *FindWorkspaceUser) ([]*WorkspaceUser, error) { - tx, err := s.db.BeginTx(ctx, nil) - if err != nil { - return nil, err - } - defer tx.Rollback() - - list, err := listWorkspaceUsers(ctx, tx, find) - if err != nil { - return nil, err - } - - if err := tx.Commit(); err != nil { - return nil, err - } - - return list, nil -} - -func (s *Store) GetWorkspaceUser(ctx context.Context, find *FindWorkspaceUser) (*WorkspaceUser, error) { - tx, err := s.db.BeginTx(ctx, nil) - if err != nil { - return nil, err - } - defer tx.Rollback() - - list, err := listWorkspaceUsers(ctx, tx, find) - if err != nil { - return nil, err - } - - if len(list) == 0 { - return nil, nil - } - workspaceUser := list[0] - return workspaceUser, nil -} - -func (s *Store) DeleteWorkspaceUser(ctx context.Context, delete *DeleteWorkspaceUser) error { - tx, err := s.db.BeginTx(ctx, nil) - if err != nil { - return err - } - defer tx.Rollback() - - if _, err := tx.ExecContext(ctx, ` - DELETE FROM workspace_user WHERE workspace_id = ? AND user_id = ? - `, delete.WorkspaceID, delete.UserID); err != nil { - return err - } - - if err := tx.Commit(); err != nil { - // do nothing here to prevent linter warning. - return err - } - - return nil -} - -func listWorkspaceUsers(ctx context.Context, tx *sql.Tx, find *FindWorkspaceUser) ([]*WorkspaceUser, error) { - where, args := []string{"1 = 1"}, []any{} - - if v := find.WorkspaceID; v != nil { - where, args = append(where, "workspace_id = ?"), append(args, *v) - } - if v := find.UserID; v != nil { - where, args = append(where, "user_id = ?"), append(args, *v) - } - if v := find.Role; v != nil { - where, args = append(where, "role = ?"), append(args, *v) - } - - query := ` - SELECT - workspace_id, - user_id, - role - FROM workspace_user - WHERE ` + strings.Join(where, " AND ") - rows, err := tx.QueryContext(ctx, query, args...) - if err != nil { - return nil, err - } - defer rows.Close() - - list := make([]*WorkspaceUser, 0) - for rows.Next() { - var workspaceUser WorkspaceUser - if err := rows.Scan( - &workspaceUser.WorkspaceID, - &workspaceUser.UserID, - &workspaceUser.Role, - ); err != nil { - return nil, err - } - - list = append(list, &workspaceUser) - } - - if err := rows.Err(); err != nil { - return nil, err - } - - return list, nil -}