feat: migrate api to v1 package

This commit is contained in:
Steven 2023-06-20 18:02:53 +08:00
parent 44ef82fb4a
commit 92d50eabf3
17 changed files with 244 additions and 799 deletions

View File

@ -1,21 +0,0 @@
package api
// RowStatus is the status for a row.
type RowStatus string
const (
// Normal is the status for a normal row.
Normal RowStatus = "NORMAL"
// Archived is the status for an archived row.
Archived RowStatus = "ARCHIVED"
)
func (e RowStatus) String() string {
switch e {
case Normal:
return "NORMAL"
case Archived:
return "ARCHIVED"
}
return ""
}

View File

@ -1,12 +0,0 @@
package api
type Signup struct {
Email string `json:"email"`
DisplayName string `json:"displayName"`
Password string `json:"password"`
}
type Signin struct {
Email string `json:"email"`
Password string `json:"password"`
}

View File

@ -1,91 +0,0 @@
package api
// Visibility is the type of a visibility.
type Visibility string
const (
// VisibilityPublic is the PUBLIC visibility.
VisibilityPublic Visibility = "PUBLIC"
// VisibilityWorkspace is the WORKSPACE visibility.
VisibilityWorkspace Visibility = "WORKSPACE"
// VisibilityPrivite is the PRIVATE visibility.
VisibilityPrivite Visibility = "PRIVATE"
)
func (e Visibility) String() string {
switch e {
case VisibilityPublic:
return "PUBLIC"
case VisibilityWorkspace:
return "WORKSPACE"
case VisibilityPrivite:
return "PRIVATE"
}
return "PRIVATE"
}
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"`
// Domain specific fields
Name string `json:"name"`
Link string `json:"link"`
Description string `json:"description"`
Visibility Visibility `json:"visibility"`
}
type ShortcutCreate struct {
// Standard fields
CreatorID int
WorkspaceID int `json:"workspaceId"`
// Domain specific fields
Name string `json:"name"`
Link string `json:"link"`
Description string `json:"description"`
Visibility Visibility `json:"visibility"`
}
type ShortcutPatch struct {
ID int
// Standard fields
RowStatus *RowStatus `json:"rowStatus"`
// Domain specific fields
Name *string `json:"name"`
Link *string `json:"link"`
Description *string `json:"description"`
Visibility *Visibility `json:"visibility"`
}
type ShortcutFind struct {
ID *int
// Standard fields
CreatorID *int `json:"creatorId"`
WorkspaceID *int `json:"workspaceId"`
// Domain specific fields
Name *string
Link *string
Description *string
MemberID *int
VisibilityList []Visibility
}
type ShortcutDelete struct {
ID *int
// Standard fields
CreatorID *int
WorkspaceID *int
}

View File

@ -1,46 +0,0 @@
package api
// 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"
)
func (e Role) String() string {
switch e {
case RoleAdmin:
return "ADMIN"
case RoleUser:
return "USER"
}
return "USER"
}
type User struct {
ID int `json:"id"`
// Standard fields
CreatedTs int64 `json:"createdTs"`
UpdatedTs int64 `json:"updatedTs"`
RowStatus RowStatus `json:"rowStatus"`
// Domain specific fields
Email string `json:"email"`
DisplayName string `json:"displayName"`
PasswordHash string `json:"-"`
OpenID string `json:"openId"`
Role Role `json:"role"`
}
type UserCreate struct {
Email string `json:"email"`
DisplayName string `json:"displayName"`
Password string `json:"password"`
PasswordHash string `json:"-"`
OpenID string `json:"-"`
Role Role `json:"-"`
}

View File

@ -1,69 +0,0 @@
package api
import (
"encoding/json"
"fmt"
)
type UserSettingKey string
const (
// UserSettingLocaleKey is the key type for user locale.
UserSettingLocaleKey UserSettingKey = "locale"
)
// String returns the string format of UserSettingKey type.
func (key UserSettingKey) String() string {
if key == UserSettingLocaleKey {
return "locale"
}
return ""
}
var (
UserSettingLocaleValue = []string{"en", "zh"}
)
type UserSetting struct {
UserID int
Key UserSettingKey `json:"key"`
// Value is a JSON string with basic value
Value string `json:"value"`
}
type UserSettingUpsert struct {
UserID int
Key UserSettingKey `json:"key"`
Value string `json:"value"`
}
func (upsert UserSettingUpsert) Validate() error {
if upsert.Key == UserSettingLocaleKey {
localeValue := "en"
err := json.Unmarshal([]byte(upsert.Value), &localeValue)
if err != nil {
return fmt.Errorf("failed to unmarshal user setting locale value")
}
invalid := true
for _, value := range UserSettingLocaleValue {
if localeValue == value {
invalid = false
break
}
}
if invalid {
return fmt.Errorf("invalid user setting locale value")
}
} else {
return fmt.Errorf("invalid user setting key")
}
return nil
}
type UserSettingFind struct {
UserID int
Key *UserSettingKey `json:"key"`
}

227
api/v1/shortcut.go Normal file
View File

@ -0,0 +1,227 @@
package v1
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"github.com/boojack/shortify/store"
"github.com/labstack/echo/v4"
)
// Visibility is the type of a visibility.
type Visibility string
const (
// VisibilityPublic is the PUBLIC visibility.
VisibilityPublic Visibility = "PUBLIC"
// VisibilityWorkspace is the WORKSPACE visibility.
VisibilityWorkspace Visibility = "WORKSPACE"
// VisibilityPrivate is the PRIVATE visibility.
VisibilityPrivate Visibility = "PRIVATE"
)
func (e Visibility) String() string {
switch e {
case VisibilityPublic:
return "PUBLIC"
case VisibilityWorkspace:
return "WORKSPACE"
case VisibilityPrivate:
return "PRIVATE"
}
return "PRIVATE"
}
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"`
// Domain specific fields
Name string `json:"name"`
Link string `json:"link"`
Description string `json:"description"`
Visibility Visibility `json:"visibility"`
}
type CreateShortcutRequest struct {
WorkspaceID int `json:"workspaceId"`
Name string `json:"name"`
Link string `json:"link"`
Description string `json:"description"`
Visibility Visibility `json:"visibility"`
}
type PatchShortcutRequest struct {
RowStatus *RowStatus `json:"rowStatus"`
Name *string `json:"name"`
Link *string `json:"link"`
Description *string `json:"description"`
Visibility *Visibility `json:"visibility"`
}
func (s *APIV1Service) registerShortcutRoutes(g *echo.Group) {
g.POST("/shortcut", 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")
}
create := &CreateShortcutRequest{}
if err := json.NewDecoder(c.Request().Body).Decode(create); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post shortcut request").SetInternal(err)
}
shortcut, err := s.Store.CreateShortcut(ctx, &store.Shortcut{
CreatorID: userID,
WorkspaceID: create.WorkspaceID,
Name: create.Name,
Link: create.Link,
Description: create.Description,
Visibility: convertVisibilityToStore(create.Visibility),
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create shortcut").SetInternal(err)
}
return c.JSON(http.StatusOK, shortcut)
})
g.PATCH("/shortcut/:shortcutId", func(c echo.Context) error {
ctx := c.Request().Context()
shortcutID, err := strconv.Atoi(c.Param("shortcutId"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("shortcutId"))).SetInternal(err)
}
userID, ok := c.Get(getUserIDContextKey()).(int)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
}
shortcut, err := s.Store.GetShortcut(ctx, &store.FindShortcut{
ID: &shortcutID,
})
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.CreatorID != userID && workspaceUser.Role != store.RoleAdmin {
return echo.NewHTTPError(http.StatusForbidden, "Forbidden to patch shortcut")
}
patch := &PatchShortcutRequest{}
if err := json.NewDecoder(c.Request().Body).Decode(patch); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted patch shortcut request").SetInternal(err)
}
shortcut, err = s.Store.UpdateShortcut(ctx, &store.UpdateShortcut{
ID: shortcutID,
RowStatus: (*store.RowStatus)(patch.RowStatus),
Name: patch.Name,
Link: patch.Link,
Visibility: (*store.Visibility)(patch.Visibility),
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to patch shortcut").SetInternal(err)
}
return c.JSON(http.StatusOK, shortcut)
})
g.GET("/shortcut", 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")
}
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
}
list := []*store.Shortcut{}
find.VisibilityList = []store.Visibility{store.VisibilityWorkspace, store.VisibilityPublic}
visibleShortcutList, err := s.Store.ListShortcuts(ctx, find)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch shortcut list").SetInternal(err)
}
list = append(list, visibleShortcutList...)
find.VisibilityList = []store.Visibility{store.VisibilityPrivate}
find.CreatorID = &userID
privateShortcutList, err := s.Store.ListShortcuts(ctx, find)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch private shortcut list").SetInternal(err)
}
list = append(list, privateShortcutList...)
return c.JSON(http.StatusOK, list)
})
g.GET("/shortcut/:id", func(c echo.Context) error {
ctx := c.Request().Context()
shortcutID, 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)
}
shortcut, err := s.Store.GetShortcut(ctx, &store.FindShortcut{
ID: &shortcutID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to fetch shortcut by ID %d", shortcutID)).SetInternal(err)
}
return c.JSON(http.StatusOK, shortcut)
})
g.DELETE("/shortcut/:id", func(c echo.Context) error {
ctx := c.Request().Context()
shortcutID, 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)
}
if err := s.Store.DeleteShortcut(ctx, &store.DeleteShortcut{
ID: shortcutID,
}); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to delete shortcut").SetInternal(err)
}
return c.JSON(http.StatusOK, true)
})
}
func convertVisibilityToStore(visibility Visibility) store.Visibility {
switch visibility {
case VisibilityPrivate:
return store.VisibilityPrivate
case VisibilityWorkspace:
return store.VisibilityWorkspace
case VisibilityPublic:
return store.VisibilityPublic
default:
return store.VisibilityPrivate
}
}

View File

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

View File

@ -81,7 +81,7 @@ func (s *APIV1Service) registerWorkspaceRoutes(g *echo.Group) {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create workspace").SetInternal(err)
}
_, err = s.Store.UpsertWorkspaceUserV1(ctx, &store.WorkspaceUser{
_, err = s.Store.UpsertWorkspaceUser(ctx, &store.WorkspaceUser{
WorkspaceID: workspace.ID,
UserID: userID,
Role: store.RoleAdmin,

View File

@ -59,7 +59,7 @@ func (s *APIV1Service) registerWorkspaceUserRoutes(g *echo.Group) {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post workspace user request").SetInternal(err)
}
workspaceUser, err := s.Store.UpsertWorkspaceUserV1(ctx, &store.WorkspaceUser{
workspaceUser, err := s.Store.UpsertWorkspaceUser(ctx, &store.WorkspaceUser{
WorkspaceID: upsert.WorkspaceID,
UserID: upsert.UserID,
Role: convertRoleToStore(upsert.Role),
@ -157,7 +157,7 @@ func (s *APIV1Service) registerWorkspaceUserRoutes(g *echo.Group) {
return echo.NewHTTPError(http.StatusForbidden, "Access forbidden to delete workspace user").SetInternal(err)
}
if err := s.Store.DeleteWorkspaceUserV1(ctx, &store.DeleteWorkspaceUser{
if err := s.Store.DeleteWorkspaceUser(ctx, &store.DeleteWorkspaceUser{
WorkspaceID: workspaceID,
UserID: userID,
}); err != nil {

View File

@ -6,16 +6,6 @@ import (
"github.com/labstack/echo/v4"
)
func composeResponse(data any) any {
type R struct {
Data any `json:"data"`
}
return R{
Data: data,
}
}
// hasPrefixes returns true if the string s has any of the given prefixes.
func hasPrefixes(src string, prefixes ...string) bool {
for _, prefix := range prefixes {

View File

@ -60,15 +60,9 @@ func NewServer(profile *profile.Profile, store *store.Store) (*Server, error) {
}
e.Use(session.Middleware(sessions.NewCookieStore([]byte(secret))))
apiGroup := e.Group("/api")
apiGroup.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return JWTMiddleware(s, next, string(secret))
})
s.registerShortcutRoutes(apiGroup)
// Register API v1 routes.
apiV1Service := apiv1.NewAPIV1Service(profile, store)
apiV1Group := apiGroup.Group("/api/v1")
apiV1Group := e.Group("/api/v1")
apiV1Group.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return JWTMiddleware(s, next, string(secret))
})

View File

@ -1,186 +0,0 @@
package server
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"github.com/boojack/shortify/api"
"github.com/boojack/shortify/store"
"github.com/labstack/echo/v4"
)
func (s *Server) registerShortcutRoutes(g *echo.Group) {
g.POST("/shortcut", 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")
}
shortcutCreate := &api.ShortcutCreate{
CreatorID: userID,
}
if err := json.NewDecoder(c.Request().Body).Decode(shortcutCreate); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post shortcut request").SetInternal(err)
}
existingShortcut, err := s.Store.FindShortcut(ctx, &api.ShortcutFind{
Name: &shortcutCreate.Name,
WorkspaceID: &shortcutCreate.WorkspaceID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find shortcut").SetInternal(err)
}
if existingShortcut != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Shortcut with name %s already exists", shortcutCreate.Name))
}
shortcut, err := s.Store.CreateShortcut(ctx, shortcutCreate)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create shortcut").SetInternal(err)
}
if err := s.Store.ComposeShortcut(ctx, shortcut); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to compose shortcut").SetInternal(err)
}
return c.JSON(http.StatusOK, composeResponse(shortcut))
})
g.PATCH("/shortcut/:shortcutId", func(c echo.Context) error {
ctx := c.Request().Context()
shortcutID, err := strconv.Atoi(c.Param("shortcutId"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("shortcutId"))).SetInternal(err)
}
userID, ok := c.Get(getUserIDContextKey()).(int)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
}
shortcut, err := s.Store.FindShortcut(ctx, &api.ShortcutFind{
ID: &shortcutID,
})
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.CreatorID != userID && workspaceUser.Role != store.RoleAdmin {
return echo.NewHTTPError(http.StatusForbidden, "Forbidden to patch shortcut")
}
shortcutPatch := &api.ShortcutPatch{
ID: shortcutID,
}
if err := json.NewDecoder(c.Request().Body).Decode(shortcutPatch); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted patch shortcut request").SetInternal(err)
}
shortcut, err = s.Store.PatchShortcut(ctx, shortcutPatch)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to patch shortcut").SetInternal(err)
}
if err := s.Store.ComposeShortcut(ctx, shortcut); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to compose shortcut").SetInternal(err)
}
return c.JSON(http.StatusOK, composeResponse(shortcut))
})
g.GET("/shortcut", 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")
}
shortcutFind := &api.ShortcutFind{}
if workspaceID, err := strconv.Atoi(c.QueryParam("workspaceId")); err == nil {
shortcutFind.WorkspaceID = &workspaceID
}
if name := c.QueryParam("name"); name != "" {
shortcutFind.Name = &name
}
if link := c.QueryParam("link"); link != "" {
shortcutFind.Link = &link
}
list := []*api.Shortcut{}
if shortcutFind.WorkspaceID == nil {
shortcutFind.MemberID = &userID
}
shortcutFind.VisibilityList = []api.Visibility{api.VisibilityWorkspace, api.VisibilityPublic}
visibleShortcutList, err := s.Store.FindShortcutList(ctx, shortcutFind)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch shortcut list").SetInternal(err)
}
list = append(list, visibleShortcutList...)
shortcutFind.VisibilityList = []api.Visibility{api.VisibilityPrivite}
shortcutFind.CreatorID = &userID
privateShortcutList, err := s.Store.FindShortcutList(ctx, shortcutFind)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch private shortcut list").SetInternal(err)
}
list = append(list, privateShortcutList...)
for _, shortcut := range list {
if err := s.Store.ComposeShortcut(ctx, shortcut); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to compose shortcut").SetInternal(err)
}
}
return c.JSON(http.StatusOK, composeResponse(list))
})
g.GET("/shortcut/:id", func(c echo.Context) error {
ctx := c.Request().Context()
shortcutID, 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)
}
shortcutFind := &api.ShortcutFind{
ID: &shortcutID,
}
shortcut, err := s.Store.FindShortcut(ctx, shortcutFind)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to fetch shortcut by ID %d", *shortcutFind.ID)).SetInternal(err)
}
if err := s.Store.ComposeShortcut(ctx, shortcut); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to compose shortcut").SetInternal(err)
}
return c.JSON(http.StatusOK, composeResponse(shortcut))
})
g.DELETE("/shortcut/:id", func(c echo.Context) error {
ctx := c.Request().Context()
shortcutID, 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)
}
shortcutDelete := &api.ShortcutDelete{
ID: &shortcutID,
}
if err := s.Store.DeleteShortcut(ctx, shortcutDelete); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to delete shortcut").SetInternal(err)
}
return c.JSON(http.StatusOK, true)
})
}

View File

@ -73,7 +73,8 @@ CREATE TABLE shortcut (
name TEXT NOT NULL,
link TEXT NOT NULL,
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

View File

@ -73,7 +73,8 @@ CREATE TABLE shortcut (
name TEXT NOT NULL,
link TEXT NOT NULL,
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

View File

@ -5,9 +5,6 @@ import (
"database/sql"
"fmt"
"strings"
"github.com/boojack/shortify/api"
"github.com/boojack/shortify/internal/errorutil"
)
// Visibility is the type of a visibility.
@ -18,8 +15,8 @@ const (
VisibilityPublic Visibility = "PUBLIC"
// VisibilityWorkspace is the WORKSPACE visibility.
VisibilityWorkspace Visibility = "WORKSPACE"
// VisibilityPrivite is the PRIVATE visibility.
VisibilityPrivite Visibility = "PRIVATE"
// VisibilityPrivate is the PRIVATE visibility.
VisibilityPrivate Visibility = "PRIVATE"
)
func (e Visibility) String() string {
@ -28,7 +25,7 @@ func (e Visibility) String() string {
return "PUBLIC"
case VisibilityWorkspace:
return "WORKSPACE"
case VisibilityPrivite:
case VisibilityPrivate:
return "PRIVATE"
}
return "PRIVATE"
@ -74,7 +71,7 @@ type DeleteShortcut struct {
ID int
}
func (s *Store) CreateShortcutV1(ctx context.Context, create *Shortcut) (*Shortcut, error) {
func (s *Store) CreateShortcut(ctx context.Context, create *Shortcut) (*Shortcut, error) {
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return nil, err
@ -108,7 +105,7 @@ func (s *Store) CreateShortcutV1(ctx context.Context, create *Shortcut) (*Shortc
return create, nil
}
func (s *Store) UpdateShortcutV1(ctx context.Context, update *UpdateShortcut) (*Shortcut, error) {
func (s *Store) UpdateShortcut(ctx context.Context, update *UpdateShortcut) (*Shortcut, error) {
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return nil, err
@ -209,7 +206,7 @@ func (s *Store) GetShortcut(ctx context.Context, find *FindShortcut) (*Shortcut,
return shortcuts[0], nil
}
func (s *Store) DeleteShortcutV1(ctx context.Context, delete *DeleteShortcut) error {
func (s *Store) DeleteShortcut(ctx context.Context, delete *DeleteShortcut) error {
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return err
@ -306,343 +303,3 @@ func listShortcuts(ctx context.Context, tx *sql.Tx, find *FindShortcut) ([]*Shor
return list, nil
}
// shortcutRaw is the store model for an Shortcut.
// Fields have exactly the same meanings as Shortcut.
type shortcutRaw struct {
ID int
// Standard fields
CreatorID int
CreatedTs int64
UpdatedTs int64
WorkspaceID int
RowStatus api.RowStatus
// Domain specific fields
Name string
Link string
Description string
Visibility api.Visibility
}
func (raw *shortcutRaw) toShortcut() *api.Shortcut {
return &api.Shortcut{
ID: raw.ID,
CreatorID: raw.CreatorID,
CreatedTs: raw.CreatedTs,
UpdatedTs: raw.UpdatedTs,
WorkspaceID: raw.WorkspaceID,
RowStatus: raw.RowStatus,
Name: raw.Name,
Link: raw.Link,
Description: raw.Description,
Visibility: raw.Visibility,
}
}
func (*Store) ComposeShortcut(_ context.Context, _ *api.Shortcut) error {
// TODO: implement this.
return nil
}
func (s *Store) CreateShortcut(ctx context.Context, create *api.ShortcutCreate) (*api.Shortcut, error) {
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return nil, err
}
defer tx.Rollback()
shortcutRaw, err := createShortcut(ctx, tx, create)
if err != nil {
return nil, err
}
if err := tx.Commit(); err != nil {
return nil, err
}
s.shortcutCache.Store(shortcutRaw.ID, shortcutRaw)
shortcut := shortcutRaw.toShortcut()
return shortcut, nil
}
func (s *Store) PatchShortcut(ctx context.Context, patch *api.ShortcutPatch) (*api.Shortcut, error) {
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return nil, err
}
defer tx.Rollback()
shortcutRaw, err := patchShortcut(ctx, tx, patch)
if err != nil {
return nil, err
}
if err := tx.Commit(); err != nil {
return nil, err
}
s.shortcutCache.Store(shortcutRaw.ID, shortcutRaw)
shortcut := shortcutRaw.toShortcut()
return shortcut, nil
}
func (s *Store) FindShortcutList(ctx context.Context, find *api.ShortcutFind) ([]*api.Shortcut, error) {
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return nil, err
}
defer tx.Rollback()
shortcutRawList, err := findShortcutList(ctx, tx, find)
if err != nil {
return nil, err
}
list := []*api.Shortcut{}
for _, shortcutRaw := range shortcutRawList {
s.shortcutCache.Store(shortcutRaw.ID, shortcutRaw)
list = append(list, shortcutRaw.toShortcut())
}
return list, nil
}
func (s *Store) FindShortcut(ctx context.Context, find *api.ShortcutFind) (*api.Shortcut, error) {
if find.ID != nil {
if cache, ok := s.shortcutCache.Load(*find.ID); ok {
return cache.(*shortcutRaw).toShortcut(), nil
}
}
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return nil, err
}
defer tx.Rollback()
list, err := findShortcutList(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")}
}
shortcutRaw := list[0]
s.shortcutCache.Store(shortcutRaw.ID, shortcutRaw)
shortcut := shortcutRaw.toShortcut()
return shortcut, nil
}
func (s *Store) DeleteShortcut(ctx context.Context, delete *api.ShortcutDelete) error {
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
err = deleteShortcut(ctx, tx, delete)
if err != nil {
return err
}
if err := tx.Commit(); err != nil {
return err
}
if delete.ID != nil {
s.shortcutCache.Delete(*delete.ID)
}
return nil
}
func createShortcut(ctx context.Context, tx *sql.Tx, create *api.ShortcutCreate) (*shortcutRaw, error) {
query := `
INSERT INTO shortcut (
creator_id,
workspace_id,
name,
link,
description,
visibility
)
VALUES (?, ?, ?, ?, ?, ?)
RETURNING id, creator_id, created_ts, updated_ts, workspace_id, row_status, name, link, description, visibility
`
var shortcutRaw shortcutRaw
if err := tx.QueryRowContext(ctx, query, create.CreatorID, create.WorkspaceID, create.Name, create.Link, create.Description, create.Visibility).Scan(
&shortcutRaw.ID,
&shortcutRaw.CreatorID,
&shortcutRaw.CreatedTs,
&shortcutRaw.UpdatedTs,
&shortcutRaw.WorkspaceID,
&shortcutRaw.RowStatus,
&shortcutRaw.Name,
&shortcutRaw.Link,
&shortcutRaw.Description,
&shortcutRaw.Visibility,
); err != nil {
return nil, err
}
return &shortcutRaw, nil
}
func patchShortcut(ctx context.Context, tx *sql.Tx, patch *api.ShortcutPatch) (*shortcutRaw, error) {
set, args := []string{}, []any{}
if v := patch.Name; v != nil {
set, args = append(set, "name = ?"), append(args, *v)
}
if v := patch.Link; v != nil {
set, args = append(set, "link = ?"), append(args, *v)
}
if v := patch.Description; v != nil {
set, args = append(set, "description = ?"), append(args, *v)
}
if v := patch.Visibility; v != nil {
set, args = append(set, "visibility = ?"), append(args, *v)
}
args = append(args, patch.ID)
query := `
UPDATE shortcut
SET ` + strings.Join(set, ", ") + `
WHERE id = ?
RETURNING id, creator_id, created_ts, updated_ts, workspace_id, row_status, name, link, description, visibility
`
var shortcutRaw shortcutRaw
if err := tx.QueryRowContext(ctx, query, args...).Scan(
&shortcutRaw.ID,
&shortcutRaw.CreatorID,
&shortcutRaw.CreatedTs,
&shortcutRaw.UpdatedTs,
&shortcutRaw.WorkspaceID,
&shortcutRaw.RowStatus,
&shortcutRaw.Name,
&shortcutRaw.Link,
&shortcutRaw.Description,
&shortcutRaw.Visibility,
); err != nil {
return nil, err
}
return &shortcutRaw, nil
}
func findShortcutList(ctx context.Context, tx *sql.Tx, find *api.ShortcutFind) ([]*shortcutRaw, error) {
where, args := []string{"1 = 1"}, []any{}
if v := find.ID; v != nil {
where, args = append(where, "id = ?"), append(args, *v)
}
if v := find.CreatorID; v != nil {
where, args = append(where, "creator_id = ?"), 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)
}
if v := find.Link; v != nil {
where, args = append(where, "link = ?"), append(args, *v)
}
if v := find.Description; v != nil {
where, args = append(where, "description = ?"), append(args, *v)
}
if v := find.VisibilityList; len(v) != 0 {
list := []string{}
for _, visibility := range v {
list = append(list, fmt.Sprintf("$%d", len(args)+1))
args = append(args, visibility)
}
where = append(where, fmt.Sprintf("visibility in (%s)", strings.Join(list, ",")))
}
if v := find.MemberID; v != nil {
where, args = append(where, "workspace_id IN (SELECT workspace_id FROM workspace_user WHERE user_id = ?)"), append(args, *v)
}
rows, err := tx.QueryContext(ctx, `
SELECT
id,
creator_id,
created_ts,
updated_ts,
workspace_id,
row_status,
name,
link,
description,
visibility
FROM shortcut
WHERE `+strings.Join(where, " AND ")+`
ORDER BY created_ts DESC`,
args...,
)
if err != nil {
return nil, err
}
defer rows.Close()
shortcutRawList := make([]*shortcutRaw, 0)
for rows.Next() {
var shortcutRaw shortcutRaw
if err := rows.Scan(
&shortcutRaw.ID,
&shortcutRaw.CreatorID,
&shortcutRaw.CreatedTs,
&shortcutRaw.UpdatedTs,
&shortcutRaw.WorkspaceID,
&shortcutRaw.RowStatus,
&shortcutRaw.Name,
&shortcutRaw.Link,
&shortcutRaw.Description,
&shortcutRaw.Visibility,
); err != nil {
return nil, err
}
shortcutRawList = append(shortcutRawList, &shortcutRaw)
}
if err := rows.Err(); err != nil {
return nil, err
}
return shortcutRawList, nil
}
func deleteShortcut(ctx context.Context, tx *sql.Tx, delete *api.ShortcutDelete) error {
where, args := []string{"1 = 1"}, []any{}
if v := delete.ID; v != nil {
where, args = append(where, "id = ?"), append(args, *v)
}
if v := delete.CreatorID; v != nil {
where, args = append(where, "creator_id = ?"), append(args, *v)
}
if v := delete.WorkspaceID; v != nil {
where, args = append(where, "workspace_id = ?"), append(args, *v)
}
result, err := tx.ExecContext(ctx, `
DELETE FROM shortcut WHERE `+strings.Join(where, " AND "), args...)
if err != nil {
return err
}
rows, _ := result.RowsAffected()
if rows == 0 {
return &errorutil.Error{Code: errorutil.NotFound, Err: fmt.Errorf("not found")}
}
return nil
}

View File

@ -14,7 +14,6 @@ type Store struct {
userCache sync.Map // map[int]*userRaw
workspaceCache sync.Map // map[int]*workspaceRaw
shortcutCache sync.Map // map[int]*shortcutRaw
}
// New creates a new instance of Store.

View File

@ -33,7 +33,7 @@ type DeleteWorkspaceUser struct {
UserID int
}
func (s *Store) UpsertWorkspaceUserV1(ctx context.Context, upsert *WorkspaceUser) (*WorkspaceUser, error) {
func (s *Store) UpsertWorkspaceUser(ctx context.Context, upsert *WorkspaceUser) (*WorkspaceUser, error) {
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return nil, err
@ -103,7 +103,7 @@ func (s *Store) GetWorkspaceUser(ctx context.Context, find *FindWorkspaceUser) (
return workspaceUser, nil
}
func (s *Store) DeleteWorkspaceUserV1(ctx context.Context, delete *DeleteWorkspaceUser) error {
func (s *Store) DeleteWorkspaceUser(ctx context.Context, delete *DeleteWorkspaceUser) error {
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return err