Commit 925e9788 authored by Johnny's avatar Johnny

feat: support pinned factor

parent 90119c1a
...@@ -12,6 +12,7 @@ var MemoFilterCELAttributes = []cel.EnvOption{ ...@@ -12,6 +12,7 @@ var MemoFilterCELAttributes = []cel.EnvOption{
// As the built-in timestamp type is deprecated, we use string type for now. // As the built-in timestamp type is deprecated, we use string type for now.
// e.g., "2021-01-01T00:00:00Z" // e.g., "2021-01-01T00:00:00Z"
cel.Variable("create_time", cel.StringType), cel.Variable("create_time", cel.StringType),
cel.Variable("pinned", cel.BoolType),
cel.Variable("tag", cel.StringType), cel.Variable("tag", cel.StringType),
cel.Variable("update_time", cel.StringType), cel.Variable("update_time", cel.StringType),
cel.Variable("visibility", cel.StringType), cel.Variable("visibility", cel.StringType),
......
...@@ -211,6 +211,9 @@ message UserStats { ...@@ -211,6 +211,9 @@ message UserStats {
// Format: "tag1": 1, "tag2": 2 // Format: "tag1": 1, "tag2": 2
map<string, int32> tag_count = 4; map<string, int32> tag_count = 4;
// The pinned memos of the user.
repeated string pinned_memos = 5;
message MemoTypeStats { message MemoTypeStats {
int32 link_count = 1; int32 link_count = 1;
int32 code_count = 2; int32 code_count = 2;
......
This diff is collapsed.
...@@ -3272,6 +3272,11 @@ definitions: ...@@ -3272,6 +3272,11 @@ definitions:
title: |- title: |-
The count of tags. The count of tags.
Format: "tag1": 1, "tag2": 2 Format: "tag1": 1, "tag2": 2
pinnedMemos:
type: array
items:
type: string
description: The pinned memos of the user.
v1Visibility: v1Visibility:
type: string type: string
enum: enum:
......
...@@ -108,7 +108,6 @@ func (s *APIV1Service) ListMemos(ctx context.Context, request *v1pb.ListMemosReq ...@@ -108,7 +108,6 @@ func (s *APIV1Service) ListMemos(ctx context.Context, request *v1pb.ListMemosReq
return nil, status.Errorf(codes.InvalidArgument, "invalid parent: %v", err) return nil, status.Errorf(codes.InvalidArgument, "invalid parent: %v", err)
} }
memoFind.CreatorID = &userID memoFind.CreatorID = &userID
memoFind.OrderByPinned = true
} }
if request.State == v1pb.State_ARCHIVED { if request.State == v1pb.State_ARCHIVED {
state := store.Archived state := store.Archived
......
...@@ -62,6 +62,9 @@ func (s *APIV1Service) ListAllUserStats(ctx context.Context, _ *v1pb.ListAllUser ...@@ -62,6 +62,9 @@ func (s *APIV1Service) ListAllUserStats(ctx context.Context, _ *v1pb.ListAllUser
for _, tag := range memo.Payload.Tags { for _, tag := range memo.Payload.Tags {
userStats.TagCount[tag]++ userStats.TagCount[tag]++
} }
if memo.Pinned {
userStats.PinnedMemos = append(userStats.PinnedMemos, fmt.Sprintf("%s%s", MemoNamePrefix, memo.UID))
}
if memo.Payload.Property.GetHasLink() { if memo.Payload.Property.GetHasLink() {
userStats.MemoTypeStats.LinkCount++ userStats.MemoTypeStats.LinkCount++
} }
...@@ -140,6 +143,9 @@ func (s *APIV1Service) GetUserStats(ctx context.Context, request *v1pb.GetUserSt ...@@ -140,6 +143,9 @@ func (s *APIV1Service) GetUserStats(ctx context.Context, request *v1pb.GetUserSt
for _, tag := range memo.Payload.Tags { for _, tag := range memo.Payload.Tags {
userStats.TagCount[tag]++ userStats.TagCount[tag]++
} }
if memo.Pinned {
userStats.PinnedMemos = append(userStats.PinnedMemos, fmt.Sprintf("%s%s", MemoNamePrefix, memo.UID))
}
if memo.Payload.Property.GetHasLink() { if memo.Payload.Property.GetHasLink() {
userStats.MemoTypeStats.LinkCount++ userStats.MemoTypeStats.LinkCount++
} }
......
...@@ -87,6 +87,9 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo ...@@ -87,6 +87,9 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo
} }
where = append(where, fmt.Sprintf("`memo`.`visibility` in (%s)", strings.Join(placeholder, ","))) where = append(where, fmt.Sprintf("`memo`.`visibility` in (%s)", strings.Join(placeholder, ",")))
} }
if v := find.Pinned; v != nil {
where, args = append(where, "`memo`.`pinned` = ?"), append(args, *v)
}
if v := find.PayloadFind; v != nil { if v := find.PayloadFind; v != nil {
if v.Raw != nil { if v.Raw != nil {
where, args = append(where, "`memo`.`payload` = ?"), append(args, *v.Raw) where, args = append(where, "`memo`.`payload` = ?"), append(args, *v.Raw)
...@@ -131,14 +134,11 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo ...@@ -131,14 +134,11 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo
having = append(having, "`parent_id` IS NULL") having = append(having, "`parent_id` IS NULL")
} }
orders := []string{}
if find.OrderByPinned {
orders = append(orders, "`pinned` DESC")
}
order := "DESC" order := "DESC"
if find.OrderByTimeAsc { if find.OrderByTimeAsc {
order = "ASC" order = "ASC"
} }
orders := []string{}
if find.OrderByUpdatedTs { if find.OrderByUpdatedTs {
orders = append(orders, "`updated_ts` "+order) orders = append(orders, "`updated_ts` "+order)
} else { } else {
......
...@@ -188,6 +188,16 @@ func (d *DB) ConvertExprToSQL(ctx *filter.ConvertContext, expr *exprv1.Expr) err ...@@ -188,6 +188,16 @@ func (d *DB) ConvertExprToSQL(ctx *filter.ConvertContext, expr *exprv1.Expr) err
} }
ctx.Args = append(ctx.Args, fmt.Sprintf("%%%s%%", arg)) ctx.Args = append(ctx.Args, fmt.Sprintf("%%%s%%", arg))
} }
} else if v, ok := expr.ExprKind.(*exprv1.Expr_IdentExpr); ok {
identifier := v.IdentExpr.GetName()
if !slices.Contains([]string{"pinned"}, identifier) {
return errors.Errorf("invalid identifier for %s", identifier)
}
if identifier == "pinned" {
if _, err := ctx.Buffer.WriteString("`memo`.`pinned` IS TRUE"); err != nil {
return err
}
}
} }
return nil return nil
} }
...@@ -49,6 +49,16 @@ func TestConvertExprToSQL(t *testing.T) { ...@@ -49,6 +49,16 @@ func TestConvertExprToSQL(t *testing.T) {
want: "(JSON_CONTAINS(JSON_EXTRACT(`memo`.`payload`, '$.tags'), ?) OR `memo`.`content` LIKE ?)", want: "(JSON_CONTAINS(JSON_EXTRACT(`memo`.`payload`, '$.tags'), ?) OR `memo`.`content` LIKE ?)",
args: []any{"tag1", "%hello%"}, args: []any{"tag1", "%hello%"},
}, },
{
filter: `1`,
want: "",
args: []any{},
},
{
filter: `pinned`,
want: "`memo`.`pinned` IS TRUE",
args: []any{},
},
} }
for _, tt := range tests { for _, tt := range tests {
......
...@@ -78,6 +78,9 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo ...@@ -78,6 +78,9 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo
} }
where = append(where, fmt.Sprintf("memo.visibility in (%s)", strings.Join(holders, ", "))) where = append(where, fmt.Sprintf("memo.visibility in (%s)", strings.Join(holders, ", ")))
} }
if v := find.Pinned; v != nil {
where, args = append(where, "memo.pinned = "+placeholder(len(args)+1)), append(args, *v)
}
if v := find.PayloadFind; v != nil { if v := find.PayloadFind; v != nil {
if v.Raw != nil { if v.Raw != nil {
where, args = append(where, "memo.payload = "+placeholder(len(args)+1)), append(args, *v.Raw) where, args = append(where, "memo.payload = "+placeholder(len(args)+1)), append(args, *v.Raw)
...@@ -123,14 +126,11 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo ...@@ -123,14 +126,11 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo
where = append(where, "memo_relation.related_memo_id IS NULL") where = append(where, "memo_relation.related_memo_id IS NULL")
} }
orders := []string{}
if find.OrderByPinned {
orders = append(orders, "pinned DESC")
}
order := "DESC" order := "DESC"
if find.OrderByTimeAsc { if find.OrderByTimeAsc {
order = "ASC" order = "ASC"
} }
orders := []string{}
if find.OrderByUpdatedTs { if find.OrderByUpdatedTs {
orders = append(orders, "updated_ts "+order) orders = append(orders, "updated_ts "+order)
} else { } else {
......
...@@ -188,6 +188,16 @@ func (d *DB) ConvertExprToSQL(ctx *filter.ConvertContext, expr *exprv1.Expr) err ...@@ -188,6 +188,16 @@ func (d *DB) ConvertExprToSQL(ctx *filter.ConvertContext, expr *exprv1.Expr) err
} }
ctx.Args = append(ctx.Args, fmt.Sprintf("%%%s%%", arg)) ctx.Args = append(ctx.Args, fmt.Sprintf("%%%s%%", arg))
} }
} else if v, ok := expr.ExprKind.(*exprv1.Expr_IdentExpr); ok {
identifier := v.IdentExpr.GetName()
if !slices.Contains([]string{"pinned"}, identifier) {
return errors.Errorf("invalid identifier %s", identifier)
}
if identifier == "pinned" {
if _, err := ctx.Buffer.WriteString("memo.pinned IS TRUE"); err != nil {
return err
}
}
} }
return nil return nil
} }
...@@ -49,6 +49,16 @@ func TestRestoreExprToSQL(t *testing.T) { ...@@ -49,6 +49,16 @@ func TestRestoreExprToSQL(t *testing.T) {
want: "(memo.payload->'tags' @> $1::jsonb OR memo.content ILIKE $2)", want: "(memo.payload->'tags' @> $1::jsonb OR memo.content ILIKE $2)",
args: []any{[]any{"tag1"}, "%hello%"}, args: []any{[]any{"tag1"}, "%hello%"},
}, },
{
filter: `1`,
want: "",
args: []any{},
},
{
filter: `pinned`,
want: "memo.pinned IS TRUE",
args: []any{},
},
} }
for _, tt := range tests { for _, tt := range tests {
......
...@@ -79,6 +79,9 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo ...@@ -79,6 +79,9 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo
} }
where = append(where, fmt.Sprintf("`memo`.`visibility` IN (%s)", strings.Join(placeholder, ","))) where = append(where, fmt.Sprintf("`memo`.`visibility` IN (%s)", strings.Join(placeholder, ",")))
} }
if v := find.Pinned; v != nil {
where, args = append(where, "`memo`.`pinned` = ?"), append(args, *v)
}
if v := find.PayloadFind; v != nil { if v := find.PayloadFind; v != nil {
if v.Raw != nil { if v.Raw != nil {
where, args = append(where, "`memo`.`payload` = ?"), append(args, *v.Raw) where, args = append(where, "`memo`.`payload` = ?"), append(args, *v.Raw)
...@@ -123,14 +126,11 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo ...@@ -123,14 +126,11 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo
where = append(where, "`parent_id` IS NULL") where = append(where, "`parent_id` IS NULL")
} }
orderBy := []string{}
if find.OrderByPinned {
orderBy = append(orderBy, "`pinned` DESC")
}
order := "DESC" order := "DESC"
if find.OrderByTimeAsc { if find.OrderByTimeAsc {
order = "ASC" order = "ASC"
} }
orderBy := []string{}
if find.OrderByUpdatedTs { if find.OrderByUpdatedTs {
orderBy = append(orderBy, "`updated_ts` "+order) orderBy = append(orderBy, "`updated_ts` "+order)
} else { } else {
......
...@@ -188,6 +188,16 @@ func (d *DB) ConvertExprToSQL(ctx *filter.ConvertContext, expr *exprv1.Expr) err ...@@ -188,6 +188,16 @@ func (d *DB) ConvertExprToSQL(ctx *filter.ConvertContext, expr *exprv1.Expr) err
} }
ctx.Args = append(ctx.Args, fmt.Sprintf("%%%s%%", arg)) ctx.Args = append(ctx.Args, fmt.Sprintf("%%%s%%", arg))
} }
} else if v, ok := expr.ExprKind.(*exprv1.Expr_IdentExpr); ok {
identifier := v.IdentExpr.GetName()
if !slices.Contains([]string{"pinned"}, identifier) {
return errors.Errorf("invalid identifier %s", identifier)
}
if identifier == "pinned" {
if _, err := ctx.Buffer.WriteString("`memo`.`pinned` IS TRUE"); err != nil {
return err
}
}
} }
return nil return nil
} }
...@@ -59,6 +59,16 @@ func TestConvertExprToSQL(t *testing.T) { ...@@ -59,6 +59,16 @@ func TestConvertExprToSQL(t *testing.T) {
want: "", want: "",
args: []any{}, args: []any{},
}, },
{
filter: `pinned`,
want: "`memo`.`pinned` IS TRUE",
args: []any{},
},
{
filter: `!pinned`,
want: "NOT (`memo`.`pinned` IS TRUE)",
args: []any{},
},
} }
for _, tt := range tests { for _, tt := range tests {
......
...@@ -70,6 +70,7 @@ type FindMemo struct { ...@@ -70,6 +70,7 @@ type FindMemo struct {
// Domain specific fields // Domain specific fields
ContentSearch []string ContentSearch []string
VisibilityList []Visibility VisibilityList []Visibility
Pinned *bool
PayloadFind *FindMemoPayload PayloadFind *FindMemoPayload
ExcludeContent bool ExcludeContent bool
ExcludeComments bool ExcludeComments bool
...@@ -81,7 +82,6 @@ type FindMemo struct { ...@@ -81,7 +82,6 @@ type FindMemo struct {
// Ordering // Ordering
OrderByUpdatedTs bool OrderByUpdatedTs bool
OrderByPinned bool
OrderByTimeAsc bool OrderByTimeAsc bool
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment