resource_service.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. package v2
  2. import (
  3. "context"
  4. "net/url"
  5. "time"
  6. "github.com/lithammer/shortuuid/v4"
  7. "google.golang.org/grpc/codes"
  8. "google.golang.org/grpc/status"
  9. "google.golang.org/protobuf/types/known/timestamppb"
  10. apiv2pb "github.com/usememos/memos/proto/gen/api/v2"
  11. "github.com/usememos/memos/server/service/metric"
  12. "github.com/usememos/memos/store"
  13. )
  14. func (s *APIV2Service) CreateResource(ctx context.Context, request *apiv2pb.CreateResourceRequest) (*apiv2pb.CreateResourceResponse, error) {
  15. user, err := getCurrentUser(ctx, s.Store)
  16. if err != nil {
  17. return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err)
  18. }
  19. if request.ExternalLink != "" {
  20. // Only allow those external links scheme with http/https
  21. linkURL, err := url.Parse(request.ExternalLink)
  22. if err != nil {
  23. return nil, status.Errorf(codes.InvalidArgument, "invalid external link: %v", err)
  24. }
  25. if linkURL.Scheme != "http" && linkURL.Scheme != "https" {
  26. return nil, status.Errorf(codes.InvalidArgument, "invalid external link scheme: %v", linkURL.Scheme)
  27. }
  28. }
  29. create := &store.Resource{
  30. ResourceName: shortuuid.New(),
  31. CreatorID: user.ID,
  32. Filename: request.Filename,
  33. ExternalLink: request.ExternalLink,
  34. Type: request.Type,
  35. }
  36. if request.MemoId != nil {
  37. create.MemoID = request.MemoId
  38. }
  39. resource, err := s.Store.CreateResource(ctx, create)
  40. if err != nil {
  41. return nil, status.Errorf(codes.Internal, "failed to create resource: %v", err)
  42. }
  43. metric.Enqueue("resource create")
  44. return &apiv2pb.CreateResourceResponse{
  45. Resource: s.convertResourceFromStore(ctx, resource),
  46. }, nil
  47. }
  48. func (s *APIV2Service) ListResources(ctx context.Context, _ *apiv2pb.ListResourcesRequest) (*apiv2pb.ListResourcesResponse, error) {
  49. user, err := getCurrentUser(ctx, s.Store)
  50. if err != nil {
  51. return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err)
  52. }
  53. resources, err := s.Store.ListResources(ctx, &store.FindResource{
  54. CreatorID: &user.ID,
  55. })
  56. if err != nil {
  57. return nil, status.Errorf(codes.Internal, "failed to list resources: %v", err)
  58. }
  59. response := &apiv2pb.ListResourcesResponse{}
  60. for _, resource := range resources {
  61. response.Resources = append(response.Resources, s.convertResourceFromStore(ctx, resource))
  62. }
  63. return response, nil
  64. }
  65. func (s *APIV2Service) GetResource(ctx context.Context, request *apiv2pb.GetResourceRequest) (*apiv2pb.GetResourceResponse, error) {
  66. resource, err := s.Store.GetResource(ctx, &store.FindResource{
  67. ID: &request.Id,
  68. })
  69. if err != nil {
  70. return nil, status.Errorf(codes.Internal, "failed to get resource: %v", err)
  71. }
  72. if resource == nil {
  73. return nil, status.Errorf(codes.NotFound, "resource not found")
  74. }
  75. return &apiv2pb.GetResourceResponse{
  76. Resource: s.convertResourceFromStore(ctx, resource),
  77. }, nil
  78. }
  79. func (s *APIV2Service) GetResourceByName(ctx context.Context, request *apiv2pb.GetResourceByNameRequest) (*apiv2pb.GetResourceByNameResponse, error) {
  80. resource, err := s.Store.GetResource(ctx, &store.FindResource{
  81. ResourceName: &request.Name,
  82. })
  83. if err != nil {
  84. return nil, status.Errorf(codes.Internal, "failed to get resource: %v", err)
  85. }
  86. if resource == nil {
  87. return nil, status.Errorf(codes.NotFound, "resource not found")
  88. }
  89. return &apiv2pb.GetResourceByNameResponse{
  90. Resource: s.convertResourceFromStore(ctx, resource),
  91. }, nil
  92. }
  93. func (s *APIV2Service) UpdateResource(ctx context.Context, request *apiv2pb.UpdateResourceRequest) (*apiv2pb.UpdateResourceResponse, error) {
  94. if request.UpdateMask == nil || len(request.UpdateMask.Paths) == 0 {
  95. return nil, status.Errorf(codes.InvalidArgument, "update mask is required")
  96. }
  97. currentTs := time.Now().Unix()
  98. update := &store.UpdateResource{
  99. ID: request.Resource.Id,
  100. UpdatedTs: &currentTs,
  101. }
  102. for _, field := range request.UpdateMask.Paths {
  103. if field == "filename" {
  104. update.Filename = &request.Resource.Filename
  105. } else if field == "memo_id" {
  106. update.MemoID = request.Resource.MemoId
  107. }
  108. }
  109. resource, err := s.Store.UpdateResource(ctx, update)
  110. if err != nil {
  111. return nil, status.Errorf(codes.Internal, "failed to update resource: %v", err)
  112. }
  113. return &apiv2pb.UpdateResourceResponse{
  114. Resource: s.convertResourceFromStore(ctx, resource),
  115. }, nil
  116. }
  117. func (s *APIV2Service) DeleteResource(ctx context.Context, request *apiv2pb.DeleteResourceRequest) (*apiv2pb.DeleteResourceResponse, error) {
  118. user, err := getCurrentUser(ctx, s.Store)
  119. if err != nil {
  120. return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err)
  121. }
  122. resource, err := s.Store.GetResource(ctx, &store.FindResource{
  123. ID: &request.Id,
  124. CreatorID: &user.ID,
  125. })
  126. if err != nil {
  127. return nil, status.Errorf(codes.Internal, "failed to find resource: %v", err)
  128. }
  129. if resource == nil {
  130. return nil, status.Errorf(codes.NotFound, "resource not found")
  131. }
  132. // Delete the resource from the database.
  133. if err := s.Store.DeleteResource(ctx, &store.DeleteResource{
  134. ID: resource.ID,
  135. }); err != nil {
  136. return nil, status.Errorf(codes.Internal, "failed to delete resource: %v", err)
  137. }
  138. return &apiv2pb.DeleteResourceResponse{}, nil
  139. }
  140. func (s *APIV2Service) convertResourceFromStore(ctx context.Context, resource *store.Resource) *apiv2pb.Resource {
  141. var memoID *int32
  142. if resource.MemoID != nil {
  143. memo, _ := s.Store.GetMemo(ctx, &store.FindMemo{
  144. ID: resource.MemoID,
  145. })
  146. if memo != nil {
  147. memoID = &memo.ID
  148. }
  149. }
  150. return &apiv2pb.Resource{
  151. Id: resource.ID,
  152. Name: resource.ResourceName,
  153. CreateTime: timestamppb.New(time.Unix(resource.CreatedTs, 0)),
  154. Filename: resource.Filename,
  155. ExternalLink: resource.ExternalLink,
  156. Type: resource.Type,
  157. Size: resource.Size,
  158. MemoId: memoID,
  159. }
  160. }