chore: remove workspace and workspace tables

This commit is contained in:
Steven 2023-06-21 08:32:57 +08:00
parent c3ce03ffe5
commit 895cd8e38c
12 changed files with 147 additions and 948 deletions

View File

@ -39,12 +39,11 @@ type Shortcut struct {
ID int `json:"id"` ID int `json:"id"`
// Standard fields // Standard fields
CreatorID int `json:"creatorId"` CreatorID int `json:"creatorId"`
Creator *User `json:"creator"` Creator *User `json:"creator"`
CreatedTs int64 `json:"createdTs"` CreatedTs int64 `json:"createdTs"`
UpdatedTs int64 `json:"updatedTs"` UpdatedTs int64 `json:"updatedTs"`
WorkspaceID int `json:"workspaceId"` RowStatus RowStatus `json:"rowStatus"`
RowStatus RowStatus `json:"rowStatus"`
// Domain specific fields // Domain specific fields
Name string `json:"name"` Name string `json:"name"`
@ -54,7 +53,6 @@ type Shortcut struct {
} }
type CreateShortcutRequest struct { type CreateShortcutRequest struct {
WorkspaceID int `json:"workspaceId"`
Name string `json:"name"` Name string `json:"name"`
Link string `json:"link"` Link string `json:"link"`
Description string `json:"description"` Description string `json:"description"`
@ -83,7 +81,6 @@ func (s *APIV1Service) registerShortcutRoutes(g *echo.Group) {
shortcut, err := s.Store.CreateShortcut(ctx, &store.Shortcut{ shortcut, err := s.Store.CreateShortcut(ctx, &store.Shortcut{
CreatorID: userID, CreatorID: userID,
WorkspaceID: create.WorkspaceID,
Name: create.Name, Name: create.Name,
Link: create.Link, Link: create.Link,
Description: create.Description, Description: create.Description,
@ -113,17 +110,11 @@ func (s *APIV1Service) registerShortcutRoutes(g *echo.Group) {
if err != nil { if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find shortcut").SetInternal(err) return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find shortcut").SetInternal(err)
} }
if shortcut == nil {
workspaceUser, err := s.Store.GetWorkspaceUser(ctx, &store.FindWorkspaceUser{ return echo.NewHTTPError(http.StatusNotFound, "Shortcut not found")
UserID: &userID,
WorkspaceID: &shortcut.WorkspaceID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find workspace user").SetInternal(err)
} }
if shortcut.CreatorID != userID {
if shortcut.CreatorID != userID && workspaceUser.Role != store.RoleAdmin { return echo.NewHTTPError(http.StatusForbidden, "Shortcut does not belong to user")
return echo.NewHTTPError(http.StatusForbidden, "Forbidden to patch shortcut")
} }
patch := &PatchShortcutRequest{} patch := &PatchShortcutRequest{}
@ -153,9 +144,6 @@ func (s *APIV1Service) registerShortcutRoutes(g *echo.Group) {
} }
find := &store.FindShortcut{} find := &store.FindShortcut{}
if workspaceID, err := strconv.Atoi(c.QueryParam("workspaceId")); err == nil {
find.WorkspaceID = &workspaceID
}
if name := c.QueryParam("name"); name != "" { if name := c.QueryParam("name"); name != "" {
find.Name = &name find.Name = &name
} }

View File

@ -23,7 +23,5 @@ func (s *APIV1Service) Start(apiV1Group *echo.Group, secret string) {
s.registerSystemRoutes(apiV1Group) s.registerSystemRoutes(apiV1Group)
s.registerAuthRoutes(apiV1Group, secret) s.registerAuthRoutes(apiV1Group, secret)
s.registerUserRoutes(apiV1Group) s.registerUserRoutes(apiV1Group)
s.registerWorkspaceRoutes(apiV1Group)
s.registerWorkspaceUserRoutes(apiV1Group)
s.registerShortcutRoutes(apiV1Group) s.registerShortcutRoutes(apiV1Group)
} }

View File

@ -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)
})
}

View File

@ -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: &currentUserID,
})
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
}

View File

@ -4,28 +4,10 @@ CREATE TABLE migration_history (
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')) 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 -- workspace_setting
CREATE TABLE workspace_setting ( CREATE TABLE workspace_setting (
workspace_id INTEGER NOT NULL, key TEXT NOT NULL UNIQUE,
key TEXT NOT NULL, value TEXT NOT NULL
value TEXT NOT NULL,
UNIQUE(workspace_id, key)
); );
-- user -- user
@ -41,11 +23,6 @@ CREATE TABLE user (
role TEXT NOT NULL CHECK (role IN ('ADMIN', 'USER')) DEFAULT 'USER' role TEXT NOT NULL CHECK (role IN ('ADMIN', 'USER')) DEFAULT 'USER'
); );
INSERT INTO
sqlite_sequence (name, seq)
VALUES
('user', 10);
-- user_setting -- user_setting
CREATE TABLE user_setting ( CREATE TABLE user_setting (
user_id INTEGER NOT NULL, user_id INTEGER NOT NULL,
@ -54,14 +31,6 @@ CREATE TABLE user_setting (
UNIQUE(user_id, key) 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 -- shortcut
CREATE TABLE shortcut ( CREATE TABLE shortcut (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
@ -69,15 +38,8 @@ CREATE TABLE shortcut (
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')), created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
updated_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', row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
workspace_id INTEGER NOT NULL, name TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
link TEXT NOT NULL, link TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '', description TEXT NOT NULL DEFAULT '',
visibility TEXT NOT NULL CHECK (visibility IN ('PRIVATE', 'WORKSPACE', 'PUBLIC')) DEFAULT 'PRIVATE', visibility TEXT NOT NULL CHECK (visibility IN ('PRIVATE', 'WORKSPACE', 'PUBLIC')) DEFAULT 'PRIVATE'
UNIQUE(workspace_id, name)
); );
INSERT INTO
sqlite_sequence (name, seq)
VALUES
('shortcut', 100);

View File

@ -4,28 +4,10 @@ CREATE TABLE migration_history (
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')) 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 -- workspace_setting
CREATE TABLE workspace_setting ( CREATE TABLE workspace_setting (
workspace_id INTEGER NOT NULL, key TEXT NOT NULL UNIQUE,
key TEXT NOT NULL, value TEXT NOT NULL
value TEXT NOT NULL,
UNIQUE(workspace_id, key)
); );
-- user -- user
@ -41,11 +23,6 @@ CREATE TABLE user (
role TEXT NOT NULL CHECK (role IN ('ADMIN', 'USER')) DEFAULT 'USER' role TEXT NOT NULL CHECK (role IN ('ADMIN', 'USER')) DEFAULT 'USER'
); );
INSERT INTO
sqlite_sequence (name, seq)
VALUES
('user', 10);
-- user_setting -- user_setting
CREATE TABLE user_setting ( CREATE TABLE user_setting (
user_id INTEGER NOT NULL, user_id INTEGER NOT NULL,
@ -54,14 +31,6 @@ CREATE TABLE user_setting (
UNIQUE(user_id, key) 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 -- shortcut
CREATE TABLE shortcut ( CREATE TABLE shortcut (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
@ -69,15 +38,8 @@ CREATE TABLE shortcut (
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')), created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
updated_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', row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
workspace_id INTEGER NOT NULL, name TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
link TEXT NOT NULL, link TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '', description TEXT NOT NULL DEFAULT '',
visibility TEXT NOT NULL CHECK (visibility IN ('PRIVATE', 'WORKSPACE', 'PUBLIC')) DEFAULT 'PRIVATE', visibility TEXT NOT NULL CHECK (visibility IN ('PRIVATE', 'WORKSPACE', 'PUBLIC')) DEFAULT 'PRIVATE'
UNIQUE(workspace_id, name)
); );
INSERT INTO
sqlite_sequence (name, seq)
VALUES
('shortcut', 100);

View File

@ -41,7 +41,6 @@ type Shortcut struct {
RowStatus RowStatus RowStatus RowStatus
// Domain specific fields // Domain specific fields
WorkspaceID int
Name string Name string
Link string Link string
Description string Description string
@ -62,7 +61,6 @@ type FindShortcut struct {
ID *int ID *int
CreatorID *int CreatorID *int
RowStatus *RowStatus RowStatus *RowStatus
WorkspaceID *int
Name *string Name *string
VisibilityList []Visibility VisibilityList []Visibility
} }
@ -78,9 +76,9 @@ func (s *Store) CreateShortcut(ctx context.Context, create *Shortcut) (*Shortcut
} }
defer tx.Rollback() defer tx.Rollback()
set := []string{"creator_id", "workspace_id", "name", "link", "description", "visibility"} set := []string{"creator_id", "name", "link", "description", "visibility"}
args := []any{create.CreatorID, create.WorkspaceID, create.Name, create.Link, create.Description, create.Visibility} args := []any{create.CreatorID, create.Name, create.Link, create.Description, create.Visibility}
placeholder := []string{"?", "?", "?", "?", "?", "?"} placeholder := []string{"?", "?", "?", "?", "?"}
query := ` query := `
INSERT INTO shortcut ( INSERT INTO shortcut (
@ -140,7 +138,7 @@ func (s *Store) UpdateShortcut(ctx context.Context, update *UpdateShortcut) (*Sh
` + strings.Join(set, ", ") + ` ` + strings.Join(set, ", ") + `
WHERE WHERE
id = ? 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 var shortcut Shortcut
if err := tx.QueryRowContext(ctx, query, args...).Scan( 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.CreatorID,
&shortcut.CreatedTs, &shortcut.CreatedTs,
&shortcut.UpdatedTs, &shortcut.UpdatedTs,
&shortcut.WorkspaceID,
&shortcut.RowStatus, &shortcut.RowStatus,
&shortcut.Name, &shortcut.Name,
&shortcut.Link, &shortcut.Link,
@ -239,9 +236,6 @@ func listShortcuts(ctx context.Context, tx *sql.Tx, find *FindShortcut) ([]*Shor
if v := find.RowStatus; v != nil { if v := find.RowStatus; v != nil {
where, args = append(where, "row_status = ?"), append(args, *v) 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 { if v := find.Name; v != nil {
where, args = append(where, "name = ?"), append(args, *v) 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, created_ts,
updated_ts, updated_ts,
row_status, row_status,
workspace_id,
name, name,
link, link,
description, description,
@ -284,7 +277,6 @@ func listShortcuts(ctx context.Context, tx *sql.Tx, find *FindShortcut) ([]*Shor
&shortcut.CreatorID, &shortcut.CreatorID,
&shortcut.CreatedTs, &shortcut.CreatedTs,
&shortcut.UpdatedTs, &shortcut.UpdatedTs,
&shortcut.WorkspaceID,
&shortcut.RowStatus, &shortcut.RowStatus,
&shortcut.Name, &shortcut.Name,
&shortcut.Link, &shortcut.Link,

View File

@ -7,6 +7,16 @@ import (
"strings" "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 { type User struct {
ID int ID int

View File

@ -1 +0,0 @@
package store

View File

@ -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
}

116
store/workspace_setting.go Normal file
View File

@ -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
}

View File

@ -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
}