2023-08-05 09:32:52 +08:00
|
|
|
package v2
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2023-12-19 23:49:24 +08:00
|
|
|
"encoding/json"
|
2023-12-21 22:42:06 +08:00
|
|
|
"fmt"
|
2023-12-19 23:49:24 +08:00
|
|
|
"time"
|
2023-08-05 09:32:52 +08:00
|
|
|
|
|
|
|
"github.com/google/cel-go/cel"
|
|
|
|
"github.com/pkg/errors"
|
2023-12-17 09:53:22 +08:00
|
|
|
expr "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
2023-08-05 09:32:52 +08:00
|
|
|
"google.golang.org/grpc/codes"
|
|
|
|
"google.golang.org/grpc/status"
|
2023-12-19 23:49:24 +08:00
|
|
|
"google.golang.org/protobuf/types/known/timestamppb"
|
2023-09-17 22:55:13 +08:00
|
|
|
|
2023-12-19 23:49:24 +08:00
|
|
|
apiv1 "github.com/usememos/memos/api/v1"
|
|
|
|
"github.com/usememos/memos/plugin/gomark/parser"
|
|
|
|
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
|
2023-09-17 22:55:13 +08:00
|
|
|
apiv2pb "github.com/usememos/memos/proto/gen/api/v2"
|
|
|
|
"github.com/usememos/memos/store"
|
2023-08-05 09:32:52 +08:00
|
|
|
)
|
|
|
|
|
2023-10-27 09:07:35 +08:00
|
|
|
func (s *APIV2Service) CreateMemo(ctx context.Context, request *apiv2pb.CreateMemoRequest) (*apiv2pb.CreateMemoResponse, error) {
|
2023-10-01 14:44:10 +08:00
|
|
|
user, err := getCurrentUser(ctx, s.Store)
|
|
|
|
if err != nil {
|
|
|
|
return nil, status.Errorf(codes.Internal, "failed to get user")
|
|
|
|
}
|
|
|
|
if user == nil {
|
|
|
|
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
|
|
|
}
|
|
|
|
|
|
|
|
create := &store.Memo{
|
|
|
|
CreatorID: user.ID,
|
|
|
|
Content: request.Content,
|
2023-10-13 22:53:58 +08:00
|
|
|
Visibility: store.Visibility(request.Visibility.String()),
|
2023-10-01 14:44:10 +08:00
|
|
|
}
|
|
|
|
memo, err := s.Store.CreateMemo(ctx, create)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-12-19 23:49:24 +08:00
|
|
|
memoMessage, err := s.convertMemoFromStore(ctx, memo)
|
2023-12-17 09:53:22 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed to convert memo")
|
|
|
|
}
|
2023-10-01 14:44:10 +08:00
|
|
|
response := &apiv2pb.CreateMemoResponse{
|
2023-12-17 09:53:22 +08:00
|
|
|
Memo: memoMessage,
|
2023-10-01 14:44:10 +08:00
|
|
|
}
|
|
|
|
return response, nil
|
|
|
|
}
|
|
|
|
|
2023-10-27 09:07:35 +08:00
|
|
|
func (s *APIV2Service) ListMemos(ctx context.Context, request *apiv2pb.ListMemosRequest) (*apiv2pb.ListMemosResponse, error) {
|
2023-08-05 09:32:52 +08:00
|
|
|
memoFind := &store.FindMemo{}
|
2023-09-13 20:42:44 +08:00
|
|
|
if request.Filter != "" {
|
|
|
|
filter, err := parseListMemosFilter(request.Filter)
|
|
|
|
if err != nil {
|
|
|
|
return nil, status.Errorf(codes.InvalidArgument, "invalid filter: %v", err)
|
|
|
|
}
|
2023-12-22 00:31:29 +08:00
|
|
|
if len(filter.ContentSearch) > 0 {
|
|
|
|
memoFind.ContentSearch = filter.ContentSearch
|
2023-09-13 20:42:44 +08:00
|
|
|
}
|
2023-12-21 23:40:43 +08:00
|
|
|
if len(filter.Visibilities) > 0 {
|
|
|
|
memoFind.VisibilityList = filter.Visibilities
|
|
|
|
}
|
2023-12-22 09:09:03 +08:00
|
|
|
if filter.OrderByPinned {
|
|
|
|
memoFind.OrderByPinned = filter.OrderByPinned
|
|
|
|
}
|
2023-09-13 20:42:44 +08:00
|
|
|
if filter.CreatedTsBefore != nil {
|
|
|
|
memoFind.CreatedTsBefore = filter.CreatedTsBefore
|
|
|
|
}
|
|
|
|
if filter.CreatedTsAfter != nil {
|
|
|
|
memoFind.CreatedTsAfter = filter.CreatedTsAfter
|
|
|
|
}
|
2023-12-19 23:49:24 +08:00
|
|
|
if filter.Creator != nil {
|
|
|
|
username, err := ExtractUsernameFromName(*filter.Creator)
|
|
|
|
if err != nil {
|
|
|
|
return nil, status.Errorf(codes.InvalidArgument, "invalid creator name")
|
|
|
|
}
|
|
|
|
user, err := s.Store.GetUser(ctx, &store.FindUser{
|
|
|
|
Username: &username,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, status.Errorf(codes.Internal, "failed to get user")
|
|
|
|
}
|
|
|
|
if user == nil {
|
|
|
|
return nil, status.Errorf(codes.NotFound, "user not found")
|
|
|
|
}
|
|
|
|
memoFind.CreatorID = &user.ID
|
|
|
|
}
|
|
|
|
if filter.RowStatus != nil {
|
|
|
|
memoFind.RowStatus = filter.RowStatus
|
|
|
|
}
|
2023-09-13 20:42:44 +08:00
|
|
|
}
|
2023-12-19 23:49:24 +08:00
|
|
|
|
2023-09-14 20:16:17 +08:00
|
|
|
user, _ := getCurrentUser(ctx, s.Store)
|
2023-09-13 20:42:44 +08:00
|
|
|
// If the user is not authenticated, only public memos are visible.
|
2023-09-14 20:16:17 +08:00
|
|
|
if user == nil {
|
2023-09-13 20:42:44 +08:00
|
|
|
memoFind.VisibilityList = []store.Visibility{store.Public}
|
|
|
|
}
|
2023-12-19 23:49:24 +08:00
|
|
|
if user != nil && memoFind.CreatorID != nil && *memoFind.CreatorID != user.ID {
|
|
|
|
memoFind.VisibilityList = []store.Visibility{store.Public, store.Protected}
|
2023-10-23 21:32:58 +08:00
|
|
|
}
|
|
|
|
|
2023-12-21 23:40:43 +08:00
|
|
|
if request.Limit != 0 {
|
|
|
|
offset, limit := int(request.Offset), int(request.Limit)
|
2023-08-05 09:32:52 +08:00
|
|
|
memoFind.Offset = &offset
|
|
|
|
memoFind.Limit = &limit
|
|
|
|
}
|
|
|
|
memos, err := s.Store.ListMemos(ctx, memoFind)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
memoMessages := make([]*apiv2pb.Memo, len(memos))
|
|
|
|
for i, memo := range memos {
|
2023-12-19 23:49:24 +08:00
|
|
|
memoMessage, err := s.convertMemoFromStore(ctx, memo)
|
2023-12-17 09:53:22 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed to convert memo")
|
|
|
|
}
|
|
|
|
memoMessages[i] = memoMessage
|
2023-08-05 09:32:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
response := &apiv2pb.ListMemosResponse{
|
2023-09-13 20:42:44 +08:00
|
|
|
Memos: memoMessages,
|
2023-08-05 09:32:52 +08:00
|
|
|
}
|
|
|
|
return response, nil
|
|
|
|
}
|
|
|
|
|
2023-10-27 09:07:35 +08:00
|
|
|
func (s *APIV2Service) GetMemo(ctx context.Context, request *apiv2pb.GetMemoRequest) (*apiv2pb.GetMemoResponse, error) {
|
2023-08-05 19:51:32 +08:00
|
|
|
memo, err := s.Store.GetMemo(ctx, &store.FindMemo{
|
|
|
|
ID: &request.Id,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if memo == nil {
|
|
|
|
return nil, status.Errorf(codes.NotFound, "memo not found")
|
|
|
|
}
|
|
|
|
if memo.Visibility != store.Public {
|
2023-09-14 20:16:17 +08:00
|
|
|
user, err := getCurrentUser(ctx, s.Store)
|
|
|
|
if err != nil {
|
|
|
|
return nil, status.Errorf(codes.Internal, "failed to get user")
|
|
|
|
}
|
|
|
|
if user == nil {
|
|
|
|
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
2023-08-05 19:51:32 +08:00
|
|
|
}
|
2023-09-14 20:16:17 +08:00
|
|
|
if memo.Visibility == store.Private && memo.CreatorID != user.ID {
|
2023-08-05 19:51:32 +08:00
|
|
|
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-19 23:49:24 +08:00
|
|
|
memoMessage, err := s.convertMemoFromStore(ctx, memo)
|
2023-12-17 09:53:22 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed to convert memo")
|
|
|
|
}
|
2023-08-05 19:51:32 +08:00
|
|
|
response := &apiv2pb.GetMemoResponse{
|
2023-12-17 09:53:22 +08:00
|
|
|
Memo: memoMessage,
|
2023-08-05 19:51:32 +08:00
|
|
|
}
|
|
|
|
return response, nil
|
|
|
|
}
|
2023-08-05 09:32:52 +08:00
|
|
|
|
2023-12-20 23:14:15 +08:00
|
|
|
func (s *APIV2Service) UpdateMemo(ctx context.Context, request *apiv2pb.UpdateMemoRequest) (*apiv2pb.UpdateMemoResponse, error) {
|
|
|
|
if request.UpdateMask == nil || len(request.UpdateMask.Paths) == 0 {
|
|
|
|
return nil, status.Errorf(codes.InvalidArgument, "update mask is required")
|
|
|
|
}
|
|
|
|
|
|
|
|
memo, err := s.Store.GetMemo(ctx, &store.FindMemo{
|
|
|
|
ID: &request.Id,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if memo == nil {
|
|
|
|
return nil, status.Errorf(codes.NotFound, "memo not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
user, _ := getCurrentUser(ctx, s.Store)
|
|
|
|
if memo.CreatorID != user.ID {
|
|
|
|
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
|
|
|
}
|
|
|
|
|
2023-12-22 09:01:30 +08:00
|
|
|
currentTs := time.Now().Unix()
|
2023-12-20 23:14:15 +08:00
|
|
|
update := &store.UpdateMemo{
|
2023-12-22 09:01:30 +08:00
|
|
|
ID: request.Id,
|
|
|
|
UpdatedTs: ¤tTs,
|
2023-12-20 23:14:15 +08:00
|
|
|
}
|
|
|
|
for _, path := range request.UpdateMask.Paths {
|
|
|
|
if path == "content" {
|
|
|
|
update.Content = &request.Memo.Content
|
|
|
|
} else if path == "visibility" {
|
|
|
|
visibility := convertVisibilityToStore(request.Memo.Visibility)
|
|
|
|
update.Visibility = &visibility
|
|
|
|
} else if path == "row_status" {
|
|
|
|
rowStatus := convertRowStatusToStore(request.Memo.RowStatus)
|
|
|
|
println("rowStatus", rowStatus)
|
|
|
|
update.RowStatus = &rowStatus
|
2023-12-21 23:40:43 +08:00
|
|
|
} else if path == "created_ts" {
|
|
|
|
createdTs := request.Memo.CreateTime.AsTime().Unix()
|
|
|
|
update.CreatedTs = &createdTs
|
2023-12-22 00:31:29 +08:00
|
|
|
} else if path == "pinned" {
|
|
|
|
if _, err := s.Store.UpsertMemoOrganizer(ctx, &store.MemoOrganizer{
|
|
|
|
MemoID: request.Id,
|
|
|
|
UserID: user.ID,
|
|
|
|
Pinned: request.Memo.Pinned,
|
|
|
|
}); err != nil {
|
|
|
|
return nil, status.Errorf(codes.Internal, "failed to upsert memo organizer")
|
|
|
|
}
|
2023-12-20 23:14:15 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = s.Store.UpdateMemo(ctx, update); err != nil {
|
|
|
|
return nil, status.Errorf(codes.Internal, "failed to update memo")
|
|
|
|
}
|
|
|
|
|
|
|
|
memo, err = s.Store.GetMemo(ctx, &store.FindMemo{
|
|
|
|
ID: &request.Id,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed to get memo")
|
|
|
|
}
|
|
|
|
memoMessage, err := s.convertMemoFromStore(ctx, memo)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed to convert memo")
|
|
|
|
}
|
|
|
|
return &apiv2pb.UpdateMemoResponse{
|
|
|
|
Memo: memoMessage,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *APIV2Service) DeleteMemo(ctx context.Context, request *apiv2pb.DeleteMemoRequest) (*apiv2pb.DeleteMemoResponse, error) {
|
|
|
|
memo, err := s.Store.GetMemo(ctx, &store.FindMemo{
|
|
|
|
ID: &request.Id,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if memo == nil {
|
|
|
|
return nil, status.Errorf(codes.NotFound, "memo not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
user, _ := getCurrentUser(ctx, s.Store)
|
|
|
|
if memo.CreatorID != user.ID {
|
|
|
|
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = s.Store.DeleteMemo(ctx, &store.DeleteMemo{
|
|
|
|
ID: request.Id,
|
|
|
|
}); err != nil {
|
|
|
|
return nil, status.Errorf(codes.Internal, "failed to delete memo")
|
|
|
|
}
|
|
|
|
|
|
|
|
return &apiv2pb.DeleteMemoResponse{}, nil
|
|
|
|
}
|
|
|
|
|
2023-12-21 21:24:08 +08:00
|
|
|
func (s *APIV2Service) SetMemoResources(ctx context.Context, request *apiv2pb.SetMemoResourcesRequest) (*apiv2pb.SetMemoResourcesResponse, error) {
|
|
|
|
resources, err := s.Store.ListResources(ctx, &store.FindResource{MemoID: &request.Id})
|
|
|
|
if err != nil {
|
|
|
|
return nil, status.Errorf(codes.Internal, "failed to list resources")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete resources that are not in the request.
|
|
|
|
for _, resource := range resources {
|
|
|
|
found := false
|
|
|
|
for _, requestResource := range request.Resources {
|
|
|
|
if resource.ID == int32(requestResource.Id) {
|
|
|
|
found = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !found {
|
|
|
|
if err = s.Store.DeleteResource(ctx, &store.DeleteResource{
|
|
|
|
ID: int32(resource.ID),
|
|
|
|
MemoID: &request.Id,
|
|
|
|
}); err != nil {
|
|
|
|
return nil, status.Errorf(codes.Internal, "failed to delete resource")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update resources' memo_id in the request.
|
|
|
|
for _, resource := range request.Resources {
|
|
|
|
if _, err := s.Store.UpdateResource(ctx, &store.UpdateResource{
|
|
|
|
ID: resource.Id,
|
|
|
|
MemoID: &request.Id,
|
|
|
|
}); err != nil {
|
|
|
|
return nil, status.Errorf(codes.Internal, "failed to update resource")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return &apiv2pb.SetMemoResourcesResponse{}, nil
|
|
|
|
}
|
|
|
|
|
2023-12-20 23:46:04 +08:00
|
|
|
func (s *APIV2Service) ListMemoResources(ctx context.Context, request *apiv2pb.ListMemoResourcesRequest) (*apiv2pb.ListMemoResourcesResponse, error) {
|
|
|
|
resources, err := s.Store.ListResources(ctx, &store.FindResource{
|
|
|
|
MemoID: &request.Id,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, status.Errorf(codes.Internal, "failed to list resources")
|
|
|
|
}
|
|
|
|
|
|
|
|
response := &apiv2pb.ListMemoResourcesResponse{
|
|
|
|
Resources: []*apiv2pb.Resource{},
|
|
|
|
}
|
|
|
|
for _, resource := range resources {
|
|
|
|
response.Resources = append(response.Resources, s.convertResourceFromStore(ctx, resource))
|
|
|
|
}
|
|
|
|
return response, nil
|
|
|
|
}
|
|
|
|
|
2023-12-21 21:24:08 +08:00
|
|
|
func (s *APIV2Service) SetMemoRelations(ctx context.Context, request *apiv2pb.SetMemoRelationsRequest) (*apiv2pb.SetMemoRelationsResponse, error) {
|
2023-12-21 22:42:06 +08:00
|
|
|
referenceType := store.MemoRelationReference
|
|
|
|
// Delete all reference relations first.
|
|
|
|
if err := s.Store.DeleteMemoRelation(ctx, &store.DeleteMemoRelation{
|
|
|
|
MemoID: &request.Id,
|
|
|
|
Type: &referenceType,
|
|
|
|
}); err != nil {
|
2023-12-21 21:24:08 +08:00
|
|
|
return nil, status.Errorf(codes.Internal, "failed to delete memo relation")
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, relation := range request.Relations {
|
|
|
|
if _, err := s.Store.UpsertMemoRelation(ctx, &store.MemoRelation{
|
|
|
|
MemoID: request.Id,
|
|
|
|
RelatedMemoID: relation.RelatedMemoId,
|
|
|
|
Type: convertMemoRelationTypeToStore(relation.Type),
|
|
|
|
}); err != nil {
|
|
|
|
return nil, status.Errorf(codes.Internal, "failed to upsert memo relation")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return &apiv2pb.SetMemoRelationsResponse{}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *APIV2Service) ListMemoRelations(ctx context.Context, request *apiv2pb.ListMemoRelationsRequest) (*apiv2pb.ListMemoRelationsResponse, error) {
|
|
|
|
relationList := []*apiv2pb.MemoRelation{}
|
|
|
|
tempList, err := s.Store.ListMemoRelations(ctx, &store.FindMemoRelation{
|
|
|
|
MemoID: &request.Id,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
for _, relation := range tempList {
|
|
|
|
relationList = append(relationList, convertMemoRelationFromStore(relation))
|
|
|
|
}
|
|
|
|
tempList, err = s.Store.ListMemoRelations(ctx, &store.FindMemoRelation{
|
|
|
|
RelatedMemoID: &request.Id,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
for _, relation := range tempList {
|
|
|
|
relationList = append(relationList, convertMemoRelationFromStore(relation))
|
|
|
|
}
|
|
|
|
|
|
|
|
response := &apiv2pb.ListMemoRelationsResponse{
|
|
|
|
Relations: relationList,
|
|
|
|
}
|
|
|
|
return response, nil
|
|
|
|
}
|
|
|
|
|
2023-10-27 09:07:35 +08:00
|
|
|
func (s *APIV2Service) CreateMemoComment(ctx context.Context, request *apiv2pb.CreateMemoCommentRequest) (*apiv2pb.CreateMemoCommentResponse, error) {
|
2023-10-01 14:44:10 +08:00
|
|
|
// Create the comment memo first.
|
|
|
|
createMemoResponse, err := s.CreateMemo(ctx, request.Create)
|
|
|
|
if err != nil {
|
|
|
|
return nil, status.Errorf(codes.Internal, "failed to create memo")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Build the relation between the comment memo and the original memo.
|
|
|
|
memo := createMemoResponse.Memo
|
|
|
|
_, err = s.Store.UpsertMemoRelation(ctx, &store.MemoRelation{
|
|
|
|
MemoID: memo.Id,
|
|
|
|
RelatedMemoID: request.Id,
|
|
|
|
Type: store.MemoRelationComment,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, status.Errorf(codes.Internal, "failed to create memo relation")
|
|
|
|
}
|
|
|
|
|
|
|
|
response := &apiv2pb.CreateMemoCommentResponse{
|
|
|
|
Memo: memo,
|
|
|
|
}
|
|
|
|
return response, nil
|
|
|
|
}
|
|
|
|
|
2023-10-27 09:07:35 +08:00
|
|
|
func (s *APIV2Service) ListMemoComments(ctx context.Context, request *apiv2pb.ListMemoCommentsRequest) (*apiv2pb.ListMemoCommentsResponse, error) {
|
2023-10-01 14:44:10 +08:00
|
|
|
memoRelationComment := store.MemoRelationComment
|
|
|
|
memoRelations, err := s.Store.ListMemoRelations(ctx, &store.FindMemoRelation{
|
|
|
|
RelatedMemoID: &request.Id,
|
|
|
|
Type: &memoRelationComment,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, status.Errorf(codes.Internal, "failed to list memo relations")
|
|
|
|
}
|
|
|
|
|
|
|
|
var memos []*apiv2pb.Memo
|
|
|
|
for _, memoRelation := range memoRelations {
|
|
|
|
memo, err := s.Store.GetMemo(ctx, &store.FindMemo{
|
|
|
|
ID: &memoRelation.MemoID,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, status.Errorf(codes.Internal, "failed to get memo")
|
|
|
|
}
|
|
|
|
if memo != nil {
|
2023-12-19 23:49:24 +08:00
|
|
|
memoMessage, err := s.convertMemoFromStore(ctx, memo)
|
2023-12-17 09:53:22 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed to convert memo")
|
|
|
|
}
|
|
|
|
memos = append(memos, memoMessage)
|
2023-10-01 14:44:10 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
response := &apiv2pb.ListMemoCommentsResponse{
|
|
|
|
Memos: memos,
|
|
|
|
}
|
|
|
|
return response, nil
|
|
|
|
}
|
|
|
|
|
2023-12-19 23:49:24 +08:00
|
|
|
func (s *APIV2Service) convertMemoFromStore(ctx context.Context, memo *store.Memo) (*apiv2pb.Memo, error) {
|
|
|
|
rawNodes, err := parser.Parse(tokenizer.Tokenize(memo.Content))
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed to parse memo content")
|
|
|
|
}
|
|
|
|
displayTs := memo.CreatedTs
|
|
|
|
if displayWithUpdatedTs, err := s.getMemoDisplayWithUpdatedTsSettingValue(ctx); err == nil && displayWithUpdatedTs {
|
|
|
|
displayTs = memo.UpdatedTs
|
|
|
|
}
|
|
|
|
|
2023-12-21 22:42:06 +08:00
|
|
|
creator, err := s.Store.GetUser(ctx, &store.FindUser{ID: &memo.CreatorID})
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed to get creator")
|
|
|
|
}
|
|
|
|
|
2023-12-22 20:18:31 +08:00
|
|
|
listMemoRelationsResponse, err := s.ListMemoRelations(ctx, &apiv2pb.ListMemoRelationsRequest{Id: memo.ID})
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed to list memo relations")
|
|
|
|
}
|
|
|
|
|
|
|
|
listMemoResourcesResponse, err := s.ListMemoResources(ctx, &apiv2pb.ListMemoResourcesRequest{Id: memo.ID})
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed to list memo resources")
|
|
|
|
}
|
|
|
|
|
2023-12-19 23:49:24 +08:00
|
|
|
return &apiv2pb.Memo{
|
|
|
|
Id: int32(memo.ID),
|
|
|
|
RowStatus: convertRowStatusFromStore(memo.RowStatus),
|
2023-12-21 22:42:06 +08:00
|
|
|
Creator: fmt.Sprintf("%s%s", UserNamePrefix, creator.Username),
|
|
|
|
CreatorId: int32(memo.CreatorID),
|
2023-12-19 23:49:24 +08:00
|
|
|
CreateTime: timestamppb.New(time.Unix(memo.CreatedTs, 0)),
|
|
|
|
UpdateTime: timestamppb.New(time.Unix(memo.UpdatedTs, 0)),
|
|
|
|
DisplayTime: timestamppb.New(time.Unix(displayTs, 0)),
|
|
|
|
Content: memo.Content,
|
|
|
|
Nodes: convertFromASTNodes(rawNodes),
|
|
|
|
Visibility: convertVisibilityFromStore(memo.Visibility),
|
|
|
|
Pinned: memo.Pinned,
|
2023-12-22 20:18:31 +08:00
|
|
|
Relations: listMemoRelationsResponse.Relations,
|
|
|
|
Resources: listMemoResourcesResponse.Resources,
|
2023-12-19 23:49:24 +08:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *APIV2Service) getMemoDisplayWithUpdatedTsSettingValue(ctx context.Context) (bool, error) {
|
|
|
|
memoDisplayWithUpdatedTsSetting, err := s.Store.GetSystemSetting(ctx, &store.FindSystemSetting{
|
|
|
|
Name: apiv1.SystemSettingMemoDisplayWithUpdatedTsName.String(),
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return false, errors.Wrap(err, "failed to find system setting")
|
|
|
|
}
|
|
|
|
memoDisplayWithUpdatedTs := false
|
|
|
|
if memoDisplayWithUpdatedTsSetting != nil {
|
|
|
|
err = json.Unmarshal([]byte(memoDisplayWithUpdatedTsSetting.Value), &memoDisplayWithUpdatedTs)
|
|
|
|
if err != nil {
|
|
|
|
return false, errors.Wrap(err, "failed to unmarshal system setting value")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return memoDisplayWithUpdatedTs, nil
|
|
|
|
}
|
|
|
|
|
2023-12-21 21:24:08 +08:00
|
|
|
func convertMemoRelationFromStore(memoRelation *store.MemoRelation) *apiv2pb.MemoRelation {
|
|
|
|
return &apiv2pb.MemoRelation{
|
|
|
|
MemoId: memoRelation.MemoID,
|
|
|
|
RelatedMemoId: memoRelation.RelatedMemoID,
|
|
|
|
Type: convertMemoRelationTypeFromStore(memoRelation.Type),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func convertMemoRelationTypeFromStore(relationType store.MemoRelationType) apiv2pb.MemoRelation_Type {
|
|
|
|
switch relationType {
|
|
|
|
case store.MemoRelationReference:
|
|
|
|
return apiv2pb.MemoRelation_REFERENCE
|
|
|
|
case store.MemoRelationComment:
|
|
|
|
return apiv2pb.MemoRelation_COMMENT
|
|
|
|
default:
|
|
|
|
return apiv2pb.MemoRelation_TYPE_UNSPECIFIED
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func convertMemoRelationTypeToStore(relationType apiv2pb.MemoRelation_Type) store.MemoRelationType {
|
|
|
|
switch relationType {
|
|
|
|
case apiv2pb.MemoRelation_REFERENCE:
|
|
|
|
return store.MemoRelationReference
|
|
|
|
case apiv2pb.MemoRelation_COMMENT:
|
|
|
|
return store.MemoRelationComment
|
|
|
|
default:
|
|
|
|
return store.MemoRelationReference
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-19 23:49:24 +08:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-20 23:14:15 +08:00
|
|
|
func convertVisibilityToStore(visibility apiv2pb.Visibility) store.Visibility {
|
|
|
|
switch visibility {
|
|
|
|
case apiv2pb.Visibility_PRIVATE:
|
|
|
|
return store.Private
|
|
|
|
case apiv2pb.Visibility_PROTECTED:
|
|
|
|
return store.Protected
|
|
|
|
case apiv2pb.Visibility_PUBLIC:
|
|
|
|
return store.Public
|
|
|
|
default:
|
|
|
|
return store.Private
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-13 20:42:44 +08:00
|
|
|
// ListMemosFilterCELAttributes are the CEL attributes for ListMemosFilter.
|
|
|
|
var ListMemosFilterCELAttributes = []cel.EnvOption{
|
2023-12-22 09:09:03 +08:00
|
|
|
cel.Variable("content_search", cel.ListType(cel.StringType)),
|
2023-12-21 23:40:43 +08:00
|
|
|
cel.Variable("visibilities", cel.ListType(cel.StringType)),
|
2023-12-22 09:09:03 +08:00
|
|
|
cel.Variable("order_by_pinned", cel.BoolType),
|
2023-09-13 20:42:44 +08:00
|
|
|
cel.Variable("created_ts_before", cel.IntType),
|
|
|
|
cel.Variable("created_ts_after", cel.IntType),
|
2023-12-19 23:49:24 +08:00
|
|
|
cel.Variable("creator", cel.StringType),
|
|
|
|
cel.Variable("row_status", cel.StringType),
|
2023-09-13 20:42:44 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
type ListMemosFilter struct {
|
2023-12-22 00:31:29 +08:00
|
|
|
ContentSearch []string
|
2023-12-21 23:40:43 +08:00
|
|
|
Visibilities []store.Visibility
|
2023-12-22 09:09:03 +08:00
|
|
|
OrderByPinned bool
|
2023-09-13 20:42:44 +08:00
|
|
|
CreatedTsBefore *int64
|
|
|
|
CreatedTsAfter *int64
|
2023-12-19 23:49:24 +08:00
|
|
|
Creator *string
|
|
|
|
RowStatus *store.RowStatus
|
2023-09-13 20:42:44 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func parseListMemosFilter(expression string) (*ListMemosFilter, error) {
|
|
|
|
e, err := cel.NewEnv(ListMemosFilterCELAttributes...)
|
2023-08-05 09:32:52 +08:00
|
|
|
if err != nil {
|
2023-09-13 20:42:44 +08:00
|
|
|
return nil, err
|
2023-08-05 09:32:52 +08:00
|
|
|
}
|
2023-09-13 20:42:44 +08:00
|
|
|
ast, issues := e.Compile(expression)
|
2023-08-05 09:32:52 +08:00
|
|
|
if issues != nil {
|
2023-09-13 20:42:44 +08:00
|
|
|
return nil, errors.Errorf("found issue %v", issues)
|
2023-08-05 09:32:52 +08:00
|
|
|
}
|
2023-09-13 20:42:44 +08:00
|
|
|
filter := &ListMemosFilter{}
|
2023-10-13 22:53:02 +08:00
|
|
|
expr, err := cel.AstToParsedExpr(ast)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
callExpr := expr.GetExpr().GetCallExpr()
|
2023-09-13 20:42:44 +08:00
|
|
|
findField(callExpr, filter)
|
|
|
|
return filter, nil
|
|
|
|
}
|
|
|
|
|
2023-12-17 09:53:22 +08:00
|
|
|
func findField(callExpr *expr.Expr_Call, filter *ListMemosFilter) {
|
2023-09-13 20:42:44 +08:00
|
|
|
if len(callExpr.Args) == 2 {
|
|
|
|
idExpr := callExpr.Args[0].GetIdentExpr()
|
|
|
|
if idExpr != nil {
|
2023-12-22 00:31:29 +08:00
|
|
|
if idExpr.Name == "content_search" {
|
|
|
|
contentSearch := []string{}
|
|
|
|
for _, expr := range callExpr.Args[1].GetListExpr().GetElements() {
|
|
|
|
value := expr.GetConstExpr().GetStringValue()
|
|
|
|
contentSearch = append(contentSearch, value)
|
|
|
|
}
|
|
|
|
filter.ContentSearch = contentSearch
|
|
|
|
} else if idExpr.Name == "visibilities" {
|
2023-12-21 23:40:43 +08:00
|
|
|
visibilities := []store.Visibility{}
|
|
|
|
for _, expr := range callExpr.Args[1].GetListExpr().GetElements() {
|
|
|
|
value := expr.GetConstExpr().GetStringValue()
|
|
|
|
visibilities = append(visibilities, store.Visibility(value))
|
|
|
|
}
|
|
|
|
filter.Visibilities = visibilities
|
2023-12-22 09:09:03 +08:00
|
|
|
} else if idExpr.Name == "order_by_pinned" {
|
|
|
|
value := callExpr.Args[1].GetConstExpr().GetBoolValue()
|
|
|
|
filter.OrderByPinned = value
|
2023-12-22 00:31:29 +08:00
|
|
|
} else if idExpr.Name == "created_ts_before" {
|
2023-09-13 20:42:44 +08:00
|
|
|
createdTsBefore := callExpr.Args[1].GetConstExpr().GetInt64Value()
|
|
|
|
filter.CreatedTsBefore = &createdTsBefore
|
2023-12-22 00:31:29 +08:00
|
|
|
} else if idExpr.Name == "created_ts_after" {
|
2023-09-13 20:42:44 +08:00
|
|
|
createdTsAfter := callExpr.Args[1].GetConstExpr().GetInt64Value()
|
|
|
|
filter.CreatedTsAfter = &createdTsAfter
|
2023-12-22 00:31:29 +08:00
|
|
|
} else if idExpr.Name == "creator" {
|
2023-12-19 23:49:24 +08:00
|
|
|
creator := callExpr.Args[1].GetConstExpr().GetStringValue()
|
|
|
|
filter.Creator = &creator
|
2023-12-22 00:31:29 +08:00
|
|
|
} else if idExpr.Name == "row_status" {
|
2023-12-19 23:49:24 +08:00
|
|
|
rowStatus := store.RowStatus(callExpr.Args[1].GetConstExpr().GetStringValue())
|
|
|
|
filter.RowStatus = &rowStatus
|
|
|
|
}
|
2023-09-13 20:42:44 +08:00
|
|
|
return
|
|
|
|
}
|
2023-08-05 09:32:52 +08:00
|
|
|
}
|
2023-09-13 20:42:44 +08:00
|
|
|
for _, arg := range callExpr.Args {
|
|
|
|
callExpr := arg.GetCallExpr()
|
|
|
|
if callExpr != nil {
|
|
|
|
findField(callExpr, filter)
|
|
|
|
}
|
2023-08-05 09:32:52 +08:00
|
|
|
}
|
|
|
|
}
|