package v2 import ( "context" "github.com/google/cel-go/cel" "github.com/pkg/errors" apiv2pb "github.com/usememos/memos/proto/gen/api/v2" "github.com/usememos/memos/store" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) type MemoService struct { apiv2pb.UnimplementedMemoServiceServer Store *store.Store } // NewMemoService creates a new MemoService. func NewMemoService(store *store.Store) *MemoService { return &MemoService{ Store: store, } } func (s *MemoService) ListMemos(ctx context.Context, request *apiv2pb.ListMemosRequest) (*apiv2pb.ListMemosResponse, error) { memoFind := &store.FindMemo{} if request.PageSize != 0 { offset := int(request.Page * request.PageSize) limit := int(request.PageSize) memoFind.Offset = &offset memoFind.Limit = &limit } if request.Filter != "" { visibilityString, err := getVisibilityFilter(request.Filter) if err != nil { return nil, status.Errorf(codes.InvalidArgument, "invalid filter: %v", err) } memoFind.VisibilityList = []store.Visibility{store.Visibility(visibilityString)} } memos, err := s.Store.ListMemos(ctx, memoFind) if err != nil { return nil, err } memoMessages := make([]*apiv2pb.Memo, len(memos)) for i, memo := range memos { memoMessages[i] = convertMemoFromStore(memo) } response := &apiv2pb.ListMemosResponse{ Memos: memoMessages, } return response, nil } const visibilityFilterExample = `visibility == "PRIVATE"` // getVisibilityFilter will parse the simple filter such as `visibility = "PRIVATE"` to "PRIVATE" . func getVisibilityFilter(filter string) (string, error) { formatInvalidErr := errors.Errorf("invalid filter %q, example %q", filter, visibilityFilterExample) e, err := cel.NewEnv(cel.Variable("visibility", cel.StringType)) if err != nil { return "", err } ast, issues := e.Compile(filter) if issues != nil { return "", status.Errorf(codes.InvalidArgument, issues.String()) } expr := ast.Expr() if expr == nil { return "", formatInvalidErr } callExpr := expr.GetCallExpr() if callExpr == nil { return "", formatInvalidErr } if callExpr.Function != "_==_" { return "", formatInvalidErr } if len(callExpr.Args) != 2 { return "", formatInvalidErr } if callExpr.Args[0].GetIdentExpr() == nil || callExpr.Args[0].GetIdentExpr().Name != "visibility" { return "", formatInvalidErr } constExpr := callExpr.Args[1].GetConstExpr() if constExpr == nil { return "", formatInvalidErr } return constExpr.GetStringValue(), nil } func convertMemoFromStore(memo *store.Memo) *apiv2pb.Memo { return &apiv2pb.Memo{ Id: int32(memo.ID), RowStatus: convertRowStatusFromStore(memo.RowStatus), CreatedTs: memo.CreatedTs, UpdatedTs: memo.UpdatedTs, CreatorId: int32(memo.CreatorID), Content: memo.Content, Visibility: convertVisibilityFromStore(memo.Visibility), Pinned: memo.Pinned, } } func convertVisibilityFromStore(visibility store.Visibility) apiv2pb.Visibility { switch visibility { case store.Private: return apiv2pb.Visibility_PRIVATE case store.Protected: return apiv2pb.Visibility_PROTECTED case store.Public: return apiv2pb.Visibility_PUBLIC default: return apiv2pb.Visibility_VISIBILITY_UNSPECIFIED } }