memo_service.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. package v2
  2. import (
  3. "context"
  4. "github.com/google/cel-go/cel"
  5. "github.com/pkg/errors"
  6. v1alpha1 "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
  7. "google.golang.org/grpc/codes"
  8. "google.golang.org/grpc/status"
  9. apiv2pb "github.com/usememos/memos/proto/gen/api/v2"
  10. "github.com/usememos/memos/store"
  11. )
  12. func (s *APIV2Service) CreateMemo(ctx context.Context, request *apiv2pb.CreateMemoRequest) (*apiv2pb.CreateMemoResponse, error) {
  13. user, err := getCurrentUser(ctx, s.Store)
  14. if err != nil {
  15. return nil, status.Errorf(codes.Internal, "failed to get user")
  16. }
  17. if user == nil {
  18. return nil, status.Errorf(codes.PermissionDenied, "permission denied")
  19. }
  20. create := &store.Memo{
  21. CreatorID: user.ID,
  22. Content: request.Content,
  23. Visibility: store.Visibility(request.Visibility.String()),
  24. }
  25. memo, err := s.Store.CreateMemo(ctx, create)
  26. if err != nil {
  27. return nil, err
  28. }
  29. response := &apiv2pb.CreateMemoResponse{
  30. Memo: convertMemoFromStore(memo),
  31. }
  32. return response, nil
  33. }
  34. func (s *APIV2Service) ListMemos(ctx context.Context, request *apiv2pb.ListMemosRequest) (*apiv2pb.ListMemosResponse, error) {
  35. memoFind := &store.FindMemo{}
  36. if request.Filter != "" {
  37. filter, err := parseListMemosFilter(request.Filter)
  38. if err != nil {
  39. return nil, status.Errorf(codes.InvalidArgument, "invalid filter: %v", err)
  40. }
  41. if filter.Visibility != nil {
  42. memoFind.VisibilityList = []store.Visibility{*filter.Visibility}
  43. }
  44. if filter.CreatedTsBefore != nil {
  45. memoFind.CreatedTsBefore = filter.CreatedTsBefore
  46. }
  47. if filter.CreatedTsAfter != nil {
  48. memoFind.CreatedTsAfter = filter.CreatedTsAfter
  49. }
  50. }
  51. user, _ := getCurrentUser(ctx, s.Store)
  52. // If the user is not authenticated, only public memos are visible.
  53. if user == nil {
  54. memoFind.VisibilityList = []store.Visibility{store.Public}
  55. }
  56. if request.CreatorId != nil {
  57. memoFind.CreatorID = request.CreatorId
  58. }
  59. // Remove the private memos from the list if the user is not the creator.
  60. if user != nil && request.CreatorId != nil && *request.CreatorId != user.ID {
  61. var filteredVisibility []store.Visibility
  62. for _, v := range memoFind.VisibilityList {
  63. if v != store.Private {
  64. filteredVisibility = append(filteredVisibility, v)
  65. }
  66. }
  67. memoFind.VisibilityList = filteredVisibility
  68. }
  69. if request.PageSize != 0 {
  70. offset := int(request.Page * request.PageSize)
  71. limit := int(request.PageSize)
  72. memoFind.Offset = &offset
  73. memoFind.Limit = &limit
  74. }
  75. memos, err := s.Store.ListMemos(ctx, memoFind)
  76. if err != nil {
  77. return nil, err
  78. }
  79. memoMessages := make([]*apiv2pb.Memo, len(memos))
  80. for i, memo := range memos {
  81. memoMessages[i] = convertMemoFromStore(memo)
  82. }
  83. response := &apiv2pb.ListMemosResponse{
  84. Memos: memoMessages,
  85. }
  86. return response, nil
  87. }
  88. func (s *APIV2Service) GetMemo(ctx context.Context, request *apiv2pb.GetMemoRequest) (*apiv2pb.GetMemoResponse, error) {
  89. memo, err := s.Store.GetMemo(ctx, &store.FindMemo{
  90. ID: &request.Id,
  91. })
  92. if err != nil {
  93. return nil, err
  94. }
  95. if memo == nil {
  96. return nil, status.Errorf(codes.NotFound, "memo not found")
  97. }
  98. if memo.Visibility != store.Public {
  99. user, err := getCurrentUser(ctx, s.Store)
  100. if err != nil {
  101. return nil, status.Errorf(codes.Internal, "failed to get user")
  102. }
  103. if user == nil {
  104. return nil, status.Errorf(codes.PermissionDenied, "permission denied")
  105. }
  106. if memo.Visibility == store.Private && memo.CreatorID != user.ID {
  107. return nil, status.Errorf(codes.PermissionDenied, "permission denied")
  108. }
  109. }
  110. response := &apiv2pb.GetMemoResponse{
  111. Memo: convertMemoFromStore(memo),
  112. }
  113. return response, nil
  114. }
  115. func (s *APIV2Service) CreateMemoComment(ctx context.Context, request *apiv2pb.CreateMemoCommentRequest) (*apiv2pb.CreateMemoCommentResponse, error) {
  116. // Create the comment memo first.
  117. createMemoResponse, err := s.CreateMemo(ctx, request.Create)
  118. if err != nil {
  119. return nil, status.Errorf(codes.Internal, "failed to create memo")
  120. }
  121. // Build the relation between the comment memo and the original memo.
  122. memo := createMemoResponse.Memo
  123. _, err = s.Store.UpsertMemoRelation(ctx, &store.MemoRelation{
  124. MemoID: memo.Id,
  125. RelatedMemoID: request.Id,
  126. Type: store.MemoRelationComment,
  127. })
  128. if err != nil {
  129. return nil, status.Errorf(codes.Internal, "failed to create memo relation")
  130. }
  131. response := &apiv2pb.CreateMemoCommentResponse{
  132. Memo: memo,
  133. }
  134. return response, nil
  135. }
  136. func (s *APIV2Service) ListMemoComments(ctx context.Context, request *apiv2pb.ListMemoCommentsRequest) (*apiv2pb.ListMemoCommentsResponse, error) {
  137. memoRelationComment := store.MemoRelationComment
  138. memoRelations, err := s.Store.ListMemoRelations(ctx, &store.FindMemoRelation{
  139. RelatedMemoID: &request.Id,
  140. Type: &memoRelationComment,
  141. })
  142. if err != nil {
  143. return nil, status.Errorf(codes.Internal, "failed to list memo relations")
  144. }
  145. var memos []*apiv2pb.Memo
  146. for _, memoRelation := range memoRelations {
  147. memo, err := s.Store.GetMemo(ctx, &store.FindMemo{
  148. ID: &memoRelation.MemoID,
  149. })
  150. if err != nil {
  151. return nil, status.Errorf(codes.Internal, "failed to get memo")
  152. }
  153. if memo != nil {
  154. memos = append(memos, convertMemoFromStore(memo))
  155. }
  156. }
  157. response := &apiv2pb.ListMemoCommentsResponse{
  158. Memos: memos,
  159. }
  160. return response, nil
  161. }
  162. // ListMemosFilterCELAttributes are the CEL attributes for ListMemosFilter.
  163. var ListMemosFilterCELAttributes = []cel.EnvOption{
  164. cel.Variable("visibility", cel.StringType),
  165. cel.Variable("created_ts_before", cel.IntType),
  166. cel.Variable("created_ts_after", cel.IntType),
  167. }
  168. type ListMemosFilter struct {
  169. Visibility *store.Visibility
  170. CreatedTsBefore *int64
  171. CreatedTsAfter *int64
  172. }
  173. func parseListMemosFilter(expression string) (*ListMemosFilter, error) {
  174. e, err := cel.NewEnv(ListMemosFilterCELAttributes...)
  175. if err != nil {
  176. return nil, err
  177. }
  178. ast, issues := e.Compile(expression)
  179. if issues != nil {
  180. return nil, errors.Errorf("found issue %v", issues)
  181. }
  182. filter := &ListMemosFilter{}
  183. expr, err := cel.AstToParsedExpr(ast)
  184. if err != nil {
  185. return nil, err
  186. }
  187. callExpr := expr.GetExpr().GetCallExpr()
  188. findField(callExpr, filter)
  189. return filter, nil
  190. }
  191. func findField(callExpr *v1alpha1.Expr_Call, filter *ListMemosFilter) {
  192. if len(callExpr.Args) == 2 {
  193. idExpr := callExpr.Args[0].GetIdentExpr()
  194. if idExpr != nil {
  195. if idExpr.Name == "visibility" {
  196. visibility := store.Visibility(callExpr.Args[1].GetConstExpr().GetStringValue())
  197. filter.Visibility = &visibility
  198. }
  199. if idExpr.Name == "created_ts_before" {
  200. createdTsBefore := callExpr.Args[1].GetConstExpr().GetInt64Value()
  201. filter.CreatedTsBefore = &createdTsBefore
  202. }
  203. if idExpr.Name == "created_ts_after" {
  204. createdTsAfter := callExpr.Args[1].GetConstExpr().GetInt64Value()
  205. filter.CreatedTsAfter = &createdTsAfter
  206. }
  207. return
  208. }
  209. }
  210. for _, arg := range callExpr.Args {
  211. callExpr := arg.GetCallExpr()
  212. if callExpr != nil {
  213. findField(callExpr, filter)
  214. }
  215. }
  216. }
  217. func convertMemoFromStore(memo *store.Memo) *apiv2pb.Memo {
  218. return &apiv2pb.Memo{
  219. Id: int32(memo.ID),
  220. RowStatus: convertRowStatusFromStore(memo.RowStatus),
  221. CreatedTs: memo.CreatedTs,
  222. UpdatedTs: memo.UpdatedTs,
  223. CreatorId: int32(memo.CreatorID),
  224. Content: memo.Content,
  225. Visibility: convertVisibilityFromStore(memo.Visibility),
  226. Pinned: memo.Pinned,
  227. }
  228. }
  229. func convertVisibilityFromStore(visibility store.Visibility) apiv2pb.Visibility {
  230. switch visibility {
  231. case store.Private:
  232. return apiv2pb.Visibility_PRIVATE
  233. case store.Protected:
  234. return apiv2pb.Visibility_PROTECTED
  235. case store.Public:
  236. return apiv2pb.Visibility_PUBLIC
  237. default:
  238. return apiv2pb.Visibility_VISIBILITY_UNSPECIFIED
  239. }
  240. }