diff --git a/api/v1/redirector.go b/api/v1/redirector.go index f75bf59..ce659dd 100644 --- a/api/v1/redirector.go +++ b/api/v1/redirector.go @@ -8,6 +8,7 @@ import ( "net/url" "strings" + storepb "github.com/boojack/slash/proto/gen/store" "github.com/boojack/slash/store" "github.com/labstack/echo/v4" "github.com/pkg/errors" @@ -30,12 +31,12 @@ func (s *APIV1Service) registerRedirectorRoutes(g *echo.Group) { if shortcut == nil { return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("not found shortcut with name: %s", shortcutName)) } - if shortcut.Visibility != store.VisibilityPublic { + if shortcut.Visibility != storepb.Visibility_PUBLIC { userID, ok := c.Get(UserIDContextKey).(int32) if !ok { return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized") } - if shortcut.Visibility == store.VisibilityPrivate && shortcut.CreatorID != userID { + if shortcut.Visibility == storepb.Visibility_PRIVATE && shortcut.CreatorId != userID { return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized") } } @@ -48,9 +49,9 @@ func (s *APIV1Service) registerRedirectorRoutes(g *echo.Group) { }) } -func redirectToShortcut(c echo.Context, shortcut *store.Shortcut) error { +func redirectToShortcut(c echo.Context, shortcut *storepb.Shortcut) error { isValidURL := isValidURLString(shortcut.Link) - if shortcut.OpenGraphMetadata == nil || (shortcut.OpenGraphMetadata.Title == "" && shortcut.OpenGraphMetadata.Description == "" && shortcut.OpenGraphMetadata.Image == "") { + if shortcut.OgMetadata == nil || (shortcut.OgMetadata.Title == "" && shortcut.OgMetadata.Description == "" && shortcut.OgMetadata.Image == "") { if isValidURL { return c.Redirect(http.StatusSeeOther, shortcut.Link) } @@ -59,16 +60,16 @@ func redirectToShortcut(c echo.Context, shortcut *store.Shortcut) error { htmlTemplate := `%s%s` metadataList := []string{ - fmt.Sprintf(`%s`, shortcut.OpenGraphMetadata.Title), - fmt.Sprintf(``, shortcut.OpenGraphMetadata.Description), - fmt.Sprintf(``, shortcut.OpenGraphMetadata.Title), - fmt.Sprintf(``, shortcut.OpenGraphMetadata.Description), - fmt.Sprintf(``, shortcut.OpenGraphMetadata.Image), + fmt.Sprintf(`%s`, shortcut.OgMetadata.Title), + fmt.Sprintf(``, shortcut.OgMetadata.Description), + fmt.Sprintf(``, shortcut.OgMetadata.Title), + fmt.Sprintf(``, shortcut.OgMetadata.Description), + fmt.Sprintf(``, shortcut.OgMetadata.Image), ``, // Twitter related metadata. - fmt.Sprintf(``, shortcut.OpenGraphMetadata.Title), - fmt.Sprintf(``, shortcut.OpenGraphMetadata.Description), - fmt.Sprintf(``, shortcut.OpenGraphMetadata.Image), + fmt.Sprintf(``, shortcut.OgMetadata.Title), + fmt.Sprintf(``, shortcut.OgMetadata.Description), + fmt.Sprintf(``, shortcut.OgMetadata.Image), ``, } if isValidURL { @@ -84,9 +85,9 @@ func redirectToShortcut(c echo.Context, shortcut *store.Shortcut) error { return c.HTML(http.StatusOK, htmlString) } -func (s *APIV1Service) createShortcutViewActivity(c echo.Context, shortcut *store.Shortcut) error { +func (s *APIV1Service) createShortcutViewActivity(c echo.Context, shortcut *storepb.Shortcut) error { payload := &ActivityShorcutViewPayload{ - ShortcutID: shortcut.ID, + ShortcutID: shortcut.Id, IP: c.RealIP(), Referer: c.Request().Referer(), UserAgent: c.Request().UserAgent(), diff --git a/api/v1/shortcut.go b/api/v1/shortcut.go index eeb1c56..e67a938 100644 --- a/api/v1/shortcut.go +++ b/api/v1/shortcut.go @@ -90,15 +90,15 @@ func (s *APIV1Service) registerShortcutRoutes(g *echo.Group) { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("malformatted post shortcut request, err: %s", err)).SetInternal(err) } - shortcut, err := s.Store.CreateShortcut(ctx, &store.Shortcut{ - CreatorID: userID, + shortcut, err := s.Store.CreateShortcut(ctx, &storepb.Shortcut{ + CreatorId: userID, Name: strings.ToLower(create.Name), Link: create.Link, Title: create.Title, Description: create.Description, - Visibility: store.Visibility(create.Visibility.String()), - Tag: strings.Join(create.Tags, " "), - OpenGraphMetadata: &store.OpenGraphMetadata{ + Visibility: convertVisibilityToStorepb(create.Visibility), + Tags: create.Tags, + OgMetadata: &storepb.OpenGraphMetadata{ Title: create.OpenGraphMetadata.Title, Description: create.OpenGraphMetadata.Description, Image: create.OpenGraphMetadata.Image, @@ -112,7 +112,7 @@ func (s *APIV1Service) registerShortcutRoutes(g *echo.Group) { return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to create shortcut activity, err: %s", err)).SetInternal(err) } - shortcutMessage, err := s.composeShortcut(ctx, convertShortcutFromStore(shortcut)) + shortcutMessage, err := s.composeShortcut(ctx, convertShortcutFromStorepb(shortcut)) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to compose shortcut, err: %s", err)).SetInternal(err) } @@ -145,7 +145,7 @@ func (s *APIV1Service) registerShortcutRoutes(g *echo.Group) { if shortcut == nil { return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("not found shortcut with id: %d", shortcutID)) } - if shortcut.CreatorID != userID && currentUser.Role != store.RoleAdmin { + if shortcut.CreatorId != userID && currentUser.Role != store.RoleAdmin { return echo.NewHTTPError(http.StatusForbidden, "unauthorized to update shortcut") } @@ -187,7 +187,7 @@ func (s *APIV1Service) registerShortcutRoutes(g *echo.Group) { return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to patch shortcut, err: %s", err)).SetInternal(err) } - shortcutMessage, err := s.composeShortcut(ctx, convertShortcutFromStore(shortcut)) + shortcutMessage, err := s.composeShortcut(ctx, convertShortcutFromStorepb(shortcut)) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to compose shortcut, err: %s", err)).SetInternal(err) } @@ -208,7 +208,7 @@ func (s *APIV1Service) registerShortcutRoutes(g *echo.Group) { list := []*storepb.Shortcut{} find.VisibilityList = []store.Visibility{store.VisibilityWorkspace, store.VisibilityPublic} - visibleShortcutList, err := s.Store.ListShortcutsV1(ctx, find) + visibleShortcutList, err := s.Store.ListShortcuts(ctx, find) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to fetch shortcut list, err: %s", err)).SetInternal(err) } @@ -216,7 +216,7 @@ func (s *APIV1Service) registerShortcutRoutes(g *echo.Group) { find.VisibilityList = []store.Visibility{store.VisibilityPrivate} find.CreatorID = &userID - privateShortcutList, err := s.Store.ListShortcutsV1(ctx, find) + privateShortcutList, err := s.Store.ListShortcuts(ctx, find) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to fetch private shortcut list, err: %s", err)).SetInternal(err) } @@ -250,7 +250,7 @@ func (s *APIV1Service) registerShortcutRoutes(g *echo.Group) { return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("not found shortcut with id: %d", shortcutID)) } - shortcutMessage, err := s.composeShortcut(ctx, convertShortcutFromStore(shortcut)) + shortcutMessage, err := s.composeShortcut(ctx, convertShortcutFromStorepb(shortcut)) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to compose shortcut, err: %s", err)).SetInternal(err) } @@ -283,7 +283,7 @@ func (s *APIV1Service) registerShortcutRoutes(g *echo.Group) { if shortcut == nil { return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("not found shortcut with id: %d", shortcutID)) } - if shortcut.CreatorID != userID && currentUser.Role != store.RoleAdmin { + if shortcut.CreatorId != userID && currentUser.Role != store.RoleAdmin { return echo.NewHTTPError(http.StatusForbidden, "Unauthorized to delete shortcut") } @@ -324,32 +324,6 @@ func (s *APIV1Service) composeShortcut(ctx context.Context, shortcut *Shortcut) return shortcut, nil } -func convertShortcutFromStore(shortcut *store.Shortcut) *Shortcut { - tags := []string{} - if shortcut.Tag != "" { - tags = append(tags, strings.Split(shortcut.Tag, " ")...) - } - - return &Shortcut{ - ID: shortcut.ID, - CreatedTs: shortcut.CreatedTs, - UpdatedTs: shortcut.UpdatedTs, - CreatorID: shortcut.CreatorID, - Name: shortcut.Name, - Link: shortcut.Link, - Title: shortcut.Title, - Description: shortcut.Description, - Visibility: Visibility(shortcut.Visibility), - RowStatus: RowStatus(shortcut.RowStatus), - Tags: tags, - OpenGraphMetadata: &OpenGraphMetadata{ - Title: shortcut.OpenGraphMetadata.Title, - Description: shortcut.OpenGraphMetadata.Description, - Image: shortcut.OpenGraphMetadata.Image, - }, - } -} - func convertShortcutFromStorepb(shortcut *storepb.Shortcut) *Shortcut { return &Shortcut{ ID: shortcut.Id, @@ -371,16 +345,27 @@ func convertShortcutFromStorepb(shortcut *storepb.Shortcut) *Shortcut { } } -func (s *APIV1Service) createShortcutCreateActivity(ctx context.Context, shortcut *store.Shortcut) error { +func convertVisibilityToStorepb(visibility Visibility) storepb.Visibility { + switch visibility { + case VisibilityPublic: + return storepb.Visibility_PUBLIC + case VisibilityPrivate: + return storepb.Visibility_PRIVATE + default: + return storepb.Visibility_PUBLIC + } +} + +func (s *APIV1Service) createShortcutCreateActivity(ctx context.Context, shortcut *storepb.Shortcut) error { payload := &ActivityShorcutCreatePayload{ - ShortcutID: shortcut.ID, + ShortcutID: shortcut.Id, } payloadStr, err := json.Marshal(payload) if err != nil { return errors.Wrap(err, "Failed to marshal activity payload") } activity := &store.Activity{ - CreatorID: shortcut.CreatorID, + CreatorID: shortcut.CreatorId, Type: store.ActivityShortcutCreate, Level: store.ActivityInfo, Payload: string(payloadStr), diff --git a/store/common.go b/store/common.go index fcda0d8..be8749f 100644 --- a/store/common.go +++ b/store/common.go @@ -24,7 +24,7 @@ func (e RowStatus) String() string { return "" } -func convertStorepbRowStatus(status string) storepb.RowStatus { +func convertRowStatusStringToStorepb(status string) storepb.RowStatus { switch status { case "NORMAL": return storepb.RowStatus_NORMAL diff --git a/store/shortcut.go b/store/shortcut.go index f2f935c..725632e 100644 --- a/store/shortcut.go +++ b/store/shortcut.go @@ -41,25 +41,6 @@ type OpenGraphMetadata struct { Image string `json:"image"` } -type Shortcut struct { - ID int32 - - // Standard fields - CreatorID int32 - CreatedTs int64 - UpdatedTs int64 - RowStatus RowStatus - - // Domain specific fields - Name string - Link string - Title string - Description string - Visibility Visibility - Tag string - OpenGraphMetadata *OpenGraphMetadata -} - type UpdateShortcut struct { ID int32 @@ -86,40 +67,7 @@ type DeleteShortcut struct { ID int32 } -func (s *Store) CreateShortcut(ctx context.Context, create *Shortcut) (*Shortcut, error) { - set := []string{"creator_id", "name", "link", "title", "description", "visibility", "tag"} - args := []any{create.CreatorID, create.Name, create.Link, create.Title, create.Description, create.Visibility, create.Tag} - placeholder := []string{"?", "?", "?", "?", "?", "?", "?"} - if create.OpenGraphMetadata != nil { - set = append(set, "og_metadata") - openGraphMetadataBytes, err := json.Marshal(create.OpenGraphMetadata) - if err != nil { - return nil, err - } - args = append(args, string(openGraphMetadataBytes)) - placeholder = append(placeholder, "?") - } - - stmt := ` - INSERT INTO shortcut ( - ` + strings.Join(set, ", ") + ` - ) - VALUES (` + strings.Join(placeholder, ",") + `) - RETURNING id, created_ts, updated_ts, row_status - ` - if err := s.db.QueryRowContext(ctx, stmt, args...).Scan( - &create.ID, - &create.CreatedTs, - &create.UpdatedTs, - &create.RowStatus, - ); err != nil { - return nil, err - } - - return create, nil -} - -func (s *Store) CreateShortcutV1(ctx context.Context, create *storepb.Shortcut) (*storepb.Shortcut, error) { +func (s *Store) CreateShortcut(ctx context.Context, create *storepb.Shortcut) (*storepb.Shortcut, error) { set := []string{"creator_id", "name", "link", "title", "description", "visibility", "tag"} args := []any{create.CreatorId, create.Name, create.Link, create.Title, create.Description, create.Visibility.String(), strings.Join(create.Tags, " ")} placeholder := []string{"?", "?", "?", "?", "?", "?", "?"} @@ -149,12 +97,13 @@ func (s *Store) CreateShortcutV1(ctx context.Context, create *storepb.Shortcut) ); err != nil { return nil, err } - create.RowStatus = convertStorepbRowStatus(rowStatus) - - return create, nil + create.RowStatus = convertRowStatusStringToStorepb(rowStatus) + shortcut := create + s.shortcutCache.Store(shortcut.Id, shortcut) + return shortcut, nil } -func (s *Store) UpdateShortcut(ctx context.Context, update *UpdateShortcut) (*Shortcut, error) { +func (s *Store) UpdateShortcut(ctx context.Context, update *UpdateShortcut) (*storepb.Shortcut, error) { set, args := []string{}, []any{} if update.RowStatus != nil { set, args = append(set, "row_status = ?"), append(args, update.RowStatus.String()) @@ -197,125 +146,36 @@ func (s *Store) UpdateShortcut(ctx context.Context, update *UpdateShortcut) (*Sh id = ? RETURNING id, creator_id, created_ts, updated_ts, row_status, name, link, title, description, visibility, tag, og_metadata ` - shortcut := &Shortcut{} - openGraphMetadataString := "" + shortcut := &storepb.Shortcut{} + var rowStatus, visibility, tags, openGraphMetadataString string if err := s.db.QueryRowContext(ctx, stmt, args...).Scan( - &shortcut.ID, - &shortcut.CreatorID, + &shortcut.Id, + &shortcut.CreatorId, &shortcut.CreatedTs, &shortcut.UpdatedTs, - &shortcut.RowStatus, + &rowStatus, &shortcut.Name, &shortcut.Link, &shortcut.Title, &shortcut.Description, - &shortcut.Visibility, - &shortcut.Tag, + &visibility, + &tags, &openGraphMetadataString, ); err != nil { return nil, err } - if openGraphMetadataString != "" { - shortcut.OpenGraphMetadata = &OpenGraphMetadata{} - if err := json.Unmarshal([]byte(openGraphMetadataString), shortcut.OpenGraphMetadata); err != nil { - return nil, err - } + shortcut.RowStatus = convertRowStatusStringToStorepb(rowStatus) + shortcut.Visibility = convertVisibilityStringToStorepb(visibility) + var ogMetadata storepb.OpenGraphMetadata + if err := protojson.Unmarshal([]byte(openGraphMetadataString), &ogMetadata); err != nil { + return nil, err } - - s.shortcutCache.Store(shortcut.ID, shortcut) + shortcut.OgMetadata = &ogMetadata + s.shortcutCache.Store(shortcut.Id, shortcut) return shortcut, nil } -func (s *Store) ListShortcuts(ctx context.Context, find *FindShortcut) ([]*Shortcut, 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.RowStatus; v != nil { - where, args = append(where, "row_status = ?"), append(args, *v) - } - if v := find.Name; v != nil { - where, args = append(where, "name = ?"), append(args, *v) - } - if v := find.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.Tag; v != nil { - where, args = append(where, "tag LIKE ?"), append(args, "%"+*v+"%") - } - - rows, err := s.db.QueryContext(ctx, ` - SELECT - id, - creator_id, - created_ts, - updated_ts, - row_status, - name, - link, - title, - description, - visibility, - tag, - og_metadata - FROM shortcut - WHERE `+strings.Join(where, " AND ")+` - ORDER BY created_ts DESC`, - args..., - ) - if err != nil { - return nil, err - } - defer rows.Close() - - list := make([]*Shortcut, 0) - for rows.Next() { - shortcut := &Shortcut{} - openGraphMetadataString := "" - if err := rows.Scan( - &shortcut.ID, - &shortcut.CreatorID, - &shortcut.CreatedTs, - &shortcut.UpdatedTs, - &shortcut.RowStatus, - &shortcut.Name, - &shortcut.Link, - &shortcut.Title, - &shortcut.Description, - &shortcut.Visibility, - &shortcut.Tag, - &openGraphMetadataString, - ); err != nil { - return nil, err - } - if openGraphMetadataString != "" { - shortcut.OpenGraphMetadata = &OpenGraphMetadata{} - if err := json.Unmarshal([]byte(openGraphMetadataString), shortcut.OpenGraphMetadata); err != nil { - return nil, err - } - } - list = append(list, shortcut) - } - - if err := rows.Err(); err != nil { - return nil, err - } - - for _, shortcut := range list { - s.shortcutCache.Store(shortcut.ID, shortcut) - } - return list, nil -} - -func (s *Store) ListShortcutsV1(ctx context.Context, find *FindShortcut) ([]*storepb.Shortcut, error) { +func (s *Store) ListShortcuts(ctx context.Context, find *FindShortcut) ([]*storepb.Shortcut, error) { where, args := []string{"1 = 1"}, []any{} if v := find.ID; v != nil { where, args = append(where, "id = ?"), append(args, *v) @@ -385,7 +245,7 @@ func (s *Store) ListShortcutsV1(ctx context.Context, find *FindShortcut) ([]*sto ); err != nil { return nil, err } - shortcut.RowStatus = convertStorepbRowStatus(rowStatus) + shortcut.RowStatus = convertRowStatusStringToStorepb(rowStatus) shortcut.Visibility = storepb.Visibility(storepb.Visibility_value[visibility]) shortcut.Tags = strings.Split(tags, " ") var ogMetadata storepb.OpenGraphMetadata @@ -399,13 +259,16 @@ func (s *Store) ListShortcutsV1(ctx context.Context, find *FindShortcut) ([]*sto if err := rows.Err(); err != nil { return nil, err } + for _, shortcut := range list { + s.shortcutCache.Store(shortcut.Id, shortcut) + } return list, nil } -func (s *Store) GetShortcut(ctx context.Context, find *FindShortcut) (*Shortcut, error) { +func (s *Store) GetShortcut(ctx context.Context, find *FindShortcut) (*storepb.Shortcut, error) { if find.ID != nil { if cache, ok := s.shortcutCache.Load(*find.ID); ok { - return cache.(*Shortcut), nil + return cache.(*storepb.Shortcut), nil } } @@ -419,21 +282,7 @@ func (s *Store) GetShortcut(ctx context.Context, find *FindShortcut) (*Shortcut, } shortcut := shortcuts[0] - s.shortcutCache.Store(shortcut.ID, shortcut) - return shortcut, nil -} - -func (s *Store) GetShortcutV1(ctx context.Context, find *FindShortcut) (*storepb.Shortcut, error) { - shortcuts, err := s.ListShortcutsV1(ctx, find) - if err != nil { - return nil, err - } - - if len(shortcuts) == 0 { - return nil, nil - } - - shortcut := shortcuts[0] + s.shortcutCache.Store(shortcut.Id, shortcut) return shortcut, nil } @@ -465,3 +314,7 @@ func vacuumShortcut(ctx context.Context, tx *sql.Tx) error { return nil } + +func convertVisibilityStringToStorepb(visibility string) storepb.Visibility { + return storepb.Visibility(storepb.Visibility_value[visibility]) +} diff --git a/test/store/shortcut_test.go b/test/store/shortcut_test.go index b093cba..bd6e49e 100644 --- a/test/store/shortcut_test.go +++ b/test/store/shortcut_test.go @@ -14,7 +14,7 @@ func TestShortcutStore(t *testing.T) { ts := NewTestingStore(ctx, t) user, err := createTestingAdminUser(ctx, ts) require.NoError(t, err) - shortcut, err := ts.CreateShortcutV1(ctx, &storepb.Shortcut{ + shortcut, err := ts.CreateShortcut(ctx, &storepb.Shortcut{ CreatorId: user.ID, Name: "test", Link: "https://test.link", @@ -24,7 +24,7 @@ func TestShortcutStore(t *testing.T) { OgMetadata: &storepb.OpenGraphMetadata{}, }) require.NoError(t, err) - shortcuts, err := ts.ListShortcutsV1(ctx, &store.FindShortcut{ + shortcuts, err := ts.ListShortcuts(ctx, &store.FindShortcut{ CreatorID: &user.ID, }) require.NoError(t, err) @@ -38,7 +38,7 @@ func TestShortcutStore(t *testing.T) { require.NoError(t, err) require.Equal(t, newLink, updatedShortcut.Link) tag := "test" - shortcut, err = ts.GetShortcutV1(ctx, &store.FindShortcut{ + shortcut, err = ts.GetShortcut(ctx, &store.FindShortcut{ Tag: &tag, }) require.NoError(t, err) @@ -46,7 +46,7 @@ func TestShortcutStore(t *testing.T) { ID: shortcut.Id, }) require.NoError(t, err) - shortcuts, err = ts.ListShortcutsV1(ctx, &store.FindShortcut{ + shortcuts, err = ts.ListShortcuts(ctx, &store.FindShortcut{ CreatorID: &user.ID, }) require.NoError(t, err) diff --git a/test/store/user_test.go b/test/store/user_test.go index be028ad..e8d0871 100644 --- a/test/store/user_test.go +++ b/test/store/user_test.go @@ -4,8 +4,8 @@ import ( "context" "testing" + storepb "github.com/boojack/slash/proto/gen/store" "github.com/boojack/slash/store" - "github.com/stretchr/testify/require" "golang.org/x/crypto/bcrypt" ) @@ -26,11 +26,11 @@ func TestUserStore(t *testing.T) { Nickname: &userPatchNickname, }) require.NoError(t, err) - _, err = ts.CreateShortcut(ctx, &store.Shortcut{ - CreatorID: user.ID, + _, err = ts.CreateShortcut(ctx, &storepb.Shortcut{ + CreatorId: user.ID, Name: "test_shortcut", Link: "https://www.google.com", - Visibility: store.VisibilityPublic, + Visibility: storepb.Visibility_PUBLIC, }) require.NoError(t, err) require.Equal(t, userPatchNickname, user.Nickname) @@ -41,7 +41,7 @@ func TestUserStore(t *testing.T) { users, err = ts.ListUsers(ctx, &store.FindUser{}) require.NoError(t, err) require.Equal(t, 0, len(users)) - shortcuts, err := ts.ListShortcutsV1(ctx, &store.FindShortcut{}) + shortcuts, err := ts.ListShortcuts(ctx, &store.FindShortcut{}) require.NoError(t, err) require.Equal(t, 0, len(shortcuts)) }