From 53090a727305505eace44e252f319f4fd59b14a4 Mon Sep 17 00:00:00 2001 From: Steven Date: Wed, 27 Sep 2023 08:09:30 +0800 Subject: [PATCH] chore: show unused resources in dashboard --- api/v2/resource_service.go | 26 ++- proto/api/v2/resource_service.proto | 11 + proto/gen/api/v2/README.md | 28 +++ proto/gen/api/v2/resource_service.pb.go | 233 ++++++++++++++----- proto/gen/api/v2/resource_service.pb.gw.go | 103 ++++++++ proto/gen/api/v2/resource_service_grpc.pb.go | 39 +++- web/src/pages/Resources.tsx | 54 ++++- 7 files changed, 436 insertions(+), 58 deletions(-) diff --git a/api/v2/resource_service.go b/api/v2/resource_service.go index 58d485d2..9daf0fb1 100644 --- a/api/v2/resource_service.go +++ b/api/v2/resource_service.go @@ -31,8 +31,7 @@ func (s *ResourceService) ListResources(ctx context.Context, _ *apiv2pb.ListReso return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err) } resources, err := s.Store.ListResources(ctx, &store.FindResource{ - CreatorID: &user.ID, - HasRelatedMemo: true, + CreatorID: &user.ID, }) if err != nil { return nil, status.Errorf(codes.Internal, "failed to list resources: %v", err) @@ -45,6 +44,29 @@ func (s *ResourceService) ListResources(ctx context.Context, _ *apiv2pb.ListReso return response, nil } +func (s *ResourceService) DeleteResource(ctx context.Context, request *apiv2pb.DeleteResourceRequest) (*apiv2pb.DeleteResourceResponse, error) { + user, err := getCurrentUser(ctx, s.Store) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err) + } + resource, err := s.Store.GetResource(ctx, &store.FindResource{ + ID: &request.Id, + CreatorID: &user.ID, + }) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to find resource: %v", err) + } + if resource == nil { + return nil, status.Errorf(codes.NotFound, "resource not found") + } + if err := s.Store.DeleteResource(ctx, &store.DeleteResource{ + ID: resource.ID, + }); err != nil { + return nil, status.Errorf(codes.Internal, "failed to delete resource: %v", err) + } + return &apiv2pb.DeleteResourceResponse{}, nil +} + func convertResourceFromStore(resource *store.Resource) *apiv2pb.Resource { return &apiv2pb.Resource{ Id: resource.ID, diff --git a/proto/api/v2/resource_service.proto b/proto/api/v2/resource_service.proto index cd45b9bf..7500acd6 100644 --- a/proto/api/v2/resource_service.proto +++ b/proto/api/v2/resource_service.proto @@ -3,6 +3,7 @@ syntax = "proto3"; package memos.api.v2; import "google/api/annotations.proto"; +import "google/api/client.proto"; import "google/protobuf/timestamp.proto"; option go_package = "gen/api/v2"; @@ -11,6 +12,10 @@ service ResourceService { rpc ListResources(ListResourcesRequest) returns (ListResourcesResponse) { option (google.api.http) = {get: "/api/v2/resources"}; } + rpc DeleteResource(DeleteResourceRequest) returns (DeleteResourceResponse) { + option (google.api.http) = {get: "/api/v2/resources/{id}"}; + option (google.api.method_signature) = "id"; + } } message Resource { @@ -28,3 +33,9 @@ message ListResourcesRequest {} message ListResourcesResponse { repeated Resource resources = 1; } + +message DeleteResourceRequest { + int32 id = 1; +} + +message DeleteResourceResponse {} diff --git a/proto/gen/api/v2/README.md b/proto/gen/api/v2/README.md index a7498140..d9e1a233 100644 --- a/proto/gen/api/v2/README.md +++ b/proto/gen/api/v2/README.md @@ -18,6 +18,8 @@ - [MemoService](#memos-api-v2-MemoService) - [api/v2/resource_service.proto](#api_v2_resource_service-proto) + - [DeleteResourceRequest](#memos-api-v2-DeleteResourceRequest) + - [DeleteResourceResponse](#memos-api-v2-DeleteResourceResponse) - [ListResourcesRequest](#memos-api-v2-ListResourcesRequest) - [ListResourcesResponse](#memos-api-v2-ListResourcesResponse) - [Resource](#memos-api-v2-Resource) @@ -223,6 +225,31 @@ + + +### DeleteResourceRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| id | [int32](#int32) | | | + + + + + + + + +### DeleteResourceResponse + + + + + + + ### ListResourcesRequest @@ -283,6 +310,7 @@ | Method Name | Request Type | Response Type | Description | | ----------- | ------------ | ------------- | ------------| | ListResources | [ListResourcesRequest](#memos-api-v2-ListResourcesRequest) | [ListResourcesResponse](#memos-api-v2-ListResourcesResponse) | | +| DeleteResource | [DeleteResourceRequest](#memos-api-v2-DeleteResourceRequest) | [DeleteResourceResponse](#memos-api-v2-DeleteResourceResponse) | | diff --git a/proto/gen/api/v2/resource_service.pb.go b/proto/gen/api/v2/resource_service.pb.go index fe998bbb..817409b8 100644 --- a/proto/gen/api/v2/resource_service.pb.go +++ b/proto/gen/api/v2/resource_service.pb.go @@ -202,6 +202,91 @@ func (x *ListResourcesResponse) GetResources() []*Resource { return nil } +type DeleteResourceRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *DeleteResourceRequest) Reset() { + *x = DeleteResourceRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_api_v2_resource_service_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteResourceRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteResourceRequest) ProtoMessage() {} + +func (x *DeleteResourceRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_v2_resource_service_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteResourceRequest.ProtoReflect.Descriptor instead. +func (*DeleteResourceRequest) Descriptor() ([]byte, []int) { + return file_api_v2_resource_service_proto_rawDescGZIP(), []int{3} +} + +func (x *DeleteResourceRequest) GetId() int32 { + if x != nil { + return x.Id + } + return 0 +} + +type DeleteResourceResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *DeleteResourceResponse) Reset() { + *x = DeleteResourceResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_api_v2_resource_service_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteResourceResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteResourceResponse) ProtoMessage() {} + +func (x *DeleteResourceResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_v2_resource_service_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteResourceResponse.ProtoReflect.Descriptor instead. +func (*DeleteResourceResponse) Descriptor() ([]byte, []int) { + return file_api_v2_resource_service_proto_rawDescGZIP(), []int{4} +} + var File_api_v2_resource_service_proto protoreflect.FileDescriptor var file_api_v2_resource_service_proto_rawDesc = []byte{ @@ -209,50 +294,64 @@ var file_api_v2_resource_service_proto_rawDesc = []byte{ 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, - 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe8, 0x01, 0x0a, - 0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x64, 0x54, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, - 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6c, 0x69, 0x6e, - 0x6b, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x4c, 0x69, 0x6e, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, - 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x1c, 0x0a, - 0x07, 0x6d, 0x65, 0x6d, 0x6f, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, - 0x52, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x49, 0x64, 0x88, 0x01, 0x01, 0x42, 0x0a, 0x0a, 0x08, 0x5f, - 0x6d, 0x65, 0x6d, 0x6f, 0x5f, 0x69, 0x64, 0x22, 0x16, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, - 0x4d, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x65, - 0x6d, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x32, 0x86, - 0x01, 0x0a, 0x0f, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x12, 0x73, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x73, 0x12, 0x22, 0x2e, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x17, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe8, 0x01, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, + 0x69, 0x64, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x73, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x54, 0x73, 0x12, 0x1a, 0x0a, + 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x78, 0x74, + 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4c, 0x69, 0x6e, 0x6b, 0x12, 0x12, + 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, + 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x1c, 0x0a, 0x07, 0x6d, 0x65, 0x6d, 0x6f, 0x5f, 0x69, + 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x49, + 0x64, 0x88, 0x01, 0x01, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x6d, 0x65, 0x6d, 0x6f, 0x5f, 0x69, 0x64, + 0x22, 0x16, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x4d, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x34, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x76, 0x32, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x22, 0x27, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, + 0x22, 0x18, 0x0a, 0x16, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x89, 0x02, 0x0a, 0x0f, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x73, + 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, + 0x22, 0x2e, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x13, 0x12, 0x11, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x32, 0x2f, 0x72, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x42, 0xac, 0x01, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, - 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x42, 0x14, 0x52, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, - 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x75, 0x73, 0x65, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2f, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2f, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x32, - 0x3b, 0x61, 0x70, 0x69, 0x76, 0x32, 0xa2, 0x02, 0x03, 0x4d, 0x41, 0x58, 0xaa, 0x02, 0x0c, 0x4d, - 0x65, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x70, 0x69, 0x2e, 0x56, 0x32, 0xca, 0x02, 0x0c, 0x4d, 0x65, - 0x6d, 0x6f, 0x73, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x56, 0x32, 0xe2, 0x02, 0x18, 0x4d, 0x65, 0x6d, - 0x6f, 0x73, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x56, 0x32, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, - 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0e, 0x4d, 0x65, 0x6d, 0x6f, 0x73, 0x3a, 0x3a, 0x41, - 0x70, 0x69, 0x3a, 0x3a, 0x56, 0x32, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, + 0x12, 0x11, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x32, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x73, 0x12, 0x80, 0x01, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x23, 0x2e, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x6d, 0x65, + 0x6d, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x23, 0xda, 0x41, 0x02, 0x69, 0x64, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x12, 0x16, + 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x32, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x42, 0xac, 0x01, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, + 0x65, 0x6d, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x42, 0x14, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x75, 0x73, 0x65, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2f, 0x6d, 0x65, 0x6d, 0x6f, 0x73, 0x2f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x32, 0x3b, + 0x61, 0x70, 0x69, 0x76, 0x32, 0xa2, 0x02, 0x03, 0x4d, 0x41, 0x58, 0xaa, 0x02, 0x0c, 0x4d, 0x65, + 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x70, 0x69, 0x2e, 0x56, 0x32, 0xca, 0x02, 0x0c, 0x4d, 0x65, 0x6d, + 0x6f, 0x73, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x56, 0x32, 0xe2, 0x02, 0x18, 0x4d, 0x65, 0x6d, 0x6f, + 0x73, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x56, 0x32, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0e, 0x4d, 0x65, 0x6d, 0x6f, 0x73, 0x3a, 0x3a, 0x41, 0x70, + 0x69, 0x3a, 0x3a, 0x56, 0x32, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -267,20 +366,24 @@ func file_api_v2_resource_service_proto_rawDescGZIP() []byte { return file_api_v2_resource_service_proto_rawDescData } -var file_api_v2_resource_service_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_api_v2_resource_service_proto_msgTypes = make([]protoimpl.MessageInfo, 5) var file_api_v2_resource_service_proto_goTypes = []interface{}{ - (*Resource)(nil), // 0: memos.api.v2.Resource - (*ListResourcesRequest)(nil), // 1: memos.api.v2.ListResourcesRequest - (*ListResourcesResponse)(nil), // 2: memos.api.v2.ListResourcesResponse - (*timestamppb.Timestamp)(nil), // 3: google.protobuf.Timestamp + (*Resource)(nil), // 0: memos.api.v2.Resource + (*ListResourcesRequest)(nil), // 1: memos.api.v2.ListResourcesRequest + (*ListResourcesResponse)(nil), // 2: memos.api.v2.ListResourcesResponse + (*DeleteResourceRequest)(nil), // 3: memos.api.v2.DeleteResourceRequest + (*DeleteResourceResponse)(nil), // 4: memos.api.v2.DeleteResourceResponse + (*timestamppb.Timestamp)(nil), // 5: google.protobuf.Timestamp } var file_api_v2_resource_service_proto_depIdxs = []int32{ - 3, // 0: memos.api.v2.Resource.created_ts:type_name -> google.protobuf.Timestamp + 5, // 0: memos.api.v2.Resource.created_ts:type_name -> google.protobuf.Timestamp 0, // 1: memos.api.v2.ListResourcesResponse.resources:type_name -> memos.api.v2.Resource 1, // 2: memos.api.v2.ResourceService.ListResources:input_type -> memos.api.v2.ListResourcesRequest - 2, // 3: memos.api.v2.ResourceService.ListResources:output_type -> memos.api.v2.ListResourcesResponse - 3, // [3:4] is the sub-list for method output_type - 2, // [2:3] is the sub-list for method input_type + 3, // 3: memos.api.v2.ResourceService.DeleteResource:input_type -> memos.api.v2.DeleteResourceRequest + 2, // 4: memos.api.v2.ResourceService.ListResources:output_type -> memos.api.v2.ListResourcesResponse + 4, // 5: memos.api.v2.ResourceService.DeleteResource:output_type -> memos.api.v2.DeleteResourceResponse + 4, // [4:6] is the sub-list for method output_type + 2, // [2:4] is the sub-list for method input_type 2, // [2:2] is the sub-list for extension type_name 2, // [2:2] is the sub-list for extension extendee 0, // [0:2] is the sub-list for field type_name @@ -328,6 +431,30 @@ func file_api_v2_resource_service_proto_init() { return nil } } + file_api_v2_resource_service_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteResourceRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_v2_resource_service_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteResourceResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } file_api_v2_resource_service_proto_msgTypes[0].OneofWrappers = []interface{}{} type x struct{} @@ -336,7 +463,7 @@ func file_api_v2_resource_service_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_api_v2_resource_service_proto_rawDesc, NumEnums: 0, - NumMessages: 3, + NumMessages: 5, NumExtensions: 0, NumServices: 1, }, diff --git a/proto/gen/api/v2/resource_service.pb.gw.go b/proto/gen/api/v2/resource_service.pb.gw.go index f3d340d9..63d3dcaa 100644 --- a/proto/gen/api/v2/resource_service.pb.gw.go +++ b/proto/gen/api/v2/resource_service.pb.gw.go @@ -49,6 +49,58 @@ func local_request_ResourceService_ListResources_0(ctx context.Context, marshale } +func request_ResourceService_DeleteResource_0(ctx context.Context, marshaler runtime.Marshaler, client ResourceServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq DeleteResourceRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.Int32(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + msg, err := client.DeleteResource(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_ResourceService_DeleteResource_0(ctx context.Context, marshaler runtime.Marshaler, server ResourceServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq DeleteResourceRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.Int32(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + msg, err := server.DeleteResource(ctx, &protoReq) + return msg, metadata, err + +} + // RegisterResourceServiceHandlerServer registers the http handlers for service ResourceService to "mux". // UnaryRPC :call ResourceServiceServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -80,6 +132,31 @@ func RegisterResourceServiceHandlerServer(ctx context.Context, mux *runtime.Serv }) + mux.Handle("GET", pattern_ResourceService_DeleteResource_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/memos.api.v2.ResourceService/DeleteResource", runtime.WithHTTPPathPattern("/api/v2/resources/{id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_ResourceService_DeleteResource_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_ResourceService_DeleteResource_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -143,13 +220,39 @@ func RegisterResourceServiceHandlerClient(ctx context.Context, mux *runtime.Serv }) + mux.Handle("GET", pattern_ResourceService_DeleteResource_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/memos.api.v2.ResourceService/DeleteResource", runtime.WithHTTPPathPattern("/api/v2/resources/{id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_ResourceService_DeleteResource_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_ResourceService_DeleteResource_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } var ( pattern_ResourceService_ListResources_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v2", "resources"}, "")) + + pattern_ResourceService_DeleteResource_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"api", "v2", "resources", "id"}, "")) ) var ( forward_ResourceService_ListResources_0 = runtime.ForwardResponseMessage + + forward_ResourceService_DeleteResource_0 = runtime.ForwardResponseMessage ) diff --git a/proto/gen/api/v2/resource_service_grpc.pb.go b/proto/gen/api/v2/resource_service_grpc.pb.go index 2acc5cf3..c4b0f215 100644 --- a/proto/gen/api/v2/resource_service_grpc.pb.go +++ b/proto/gen/api/v2/resource_service_grpc.pb.go @@ -19,7 +19,8 @@ import ( const _ = grpc.SupportPackageIsVersion7 const ( - ResourceService_ListResources_FullMethodName = "/memos.api.v2.ResourceService/ListResources" + ResourceService_ListResources_FullMethodName = "/memos.api.v2.ResourceService/ListResources" + ResourceService_DeleteResource_FullMethodName = "/memos.api.v2.ResourceService/DeleteResource" ) // ResourceServiceClient is the client API for ResourceService service. @@ -27,6 +28,7 @@ const ( // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type ResourceServiceClient interface { ListResources(ctx context.Context, in *ListResourcesRequest, opts ...grpc.CallOption) (*ListResourcesResponse, error) + DeleteResource(ctx context.Context, in *DeleteResourceRequest, opts ...grpc.CallOption) (*DeleteResourceResponse, error) } type resourceServiceClient struct { @@ -46,11 +48,21 @@ func (c *resourceServiceClient) ListResources(ctx context.Context, in *ListResou return out, nil } +func (c *resourceServiceClient) DeleteResource(ctx context.Context, in *DeleteResourceRequest, opts ...grpc.CallOption) (*DeleteResourceResponse, error) { + out := new(DeleteResourceResponse) + err := c.cc.Invoke(ctx, ResourceService_DeleteResource_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // ResourceServiceServer is the server API for ResourceService service. // All implementations must embed UnimplementedResourceServiceServer // for forward compatibility type ResourceServiceServer interface { ListResources(context.Context, *ListResourcesRequest) (*ListResourcesResponse, error) + DeleteResource(context.Context, *DeleteResourceRequest) (*DeleteResourceResponse, error) mustEmbedUnimplementedResourceServiceServer() } @@ -61,6 +73,9 @@ type UnimplementedResourceServiceServer struct { func (UnimplementedResourceServiceServer) ListResources(context.Context, *ListResourcesRequest) (*ListResourcesResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ListResources not implemented") } +func (UnimplementedResourceServiceServer) DeleteResource(context.Context, *DeleteResourceRequest) (*DeleteResourceResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteResource not implemented") +} func (UnimplementedResourceServiceServer) mustEmbedUnimplementedResourceServiceServer() {} // UnsafeResourceServiceServer may be embedded to opt out of forward compatibility for this service. @@ -92,6 +107,24 @@ func _ResourceService_ListResources_Handler(srv interface{}, ctx context.Context return interceptor(ctx, in, info, handler) } +func _ResourceService_DeleteResource_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteResourceRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ResourceServiceServer).DeleteResource(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ResourceService_DeleteResource_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ResourceServiceServer).DeleteResource(ctx, req.(*DeleteResourceRequest)) + } + return interceptor(ctx, in, info, handler) +} + // ResourceService_ServiceDesc is the grpc.ServiceDesc for ResourceService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -103,6 +136,10 @@ var ResourceService_ServiceDesc = grpc.ServiceDesc{ MethodName: "ListResources", Handler: _ResourceService_ListResources_Handler, }, + { + MethodName: "DeleteResource", + Handler: _ResourceService_DeleteResource_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "api/v2/resource_service.proto", diff --git a/web/src/pages/Resources.tsx b/web/src/pages/Resources.tsx index aa2b30ff..13097219 100644 --- a/web/src/pages/Resources.tsx +++ b/web/src/pages/Resources.tsx @@ -1,5 +1,7 @@ +import { Divider, IconButton, Tooltip } from "@mui/joy"; import { useEffect, useState } from "react"; import { Link } from "react-router-dom"; +import { showCommonDialog } from "@/components/Dialog/CommonDialog"; import Empty from "@/components/Empty"; import Icon from "@/components/Icon"; import MobileHeader from "@/components/MobileHeader"; @@ -33,7 +35,8 @@ const Resources = () => { const t = useTranslate(); const loadingState = useLoading(); const [resources, setResources] = useState([]); - const groupedResources = groupResourcesByDate(resources); + const groupedResources = groupResourcesByDate(resources.filter((resoure) => resoure.memoId)); + const unusedResources = resources.filter((resoure) => !resoure.memoId); useEffect(() => { fetchAllResources().then((resources) => { @@ -42,12 +45,27 @@ const Resources = () => { }); }, []); + const handleDeleteUnusedResources = () => { + showCommonDialog({ + title: "Delete all unused resources", + content: "Are you sure to delete all unused resources? This action cannot be undone.", + style: "warning", + dialogName: "delete-unused-resources-dialog", + onConfirm: async () => { + for (const resource of unusedResources) { + await resourceServiceClient.deleteResource({ id: resource.id }); + } + setResources(resources.filter((resoure) => resoure.memoId)); + }, + }); + }; + return (
-

+

{t("common.resources")}

@@ -97,6 +115,38 @@ const Resources = () => {
); })} + + {unusedResources.length > 0 && ( + <> + +
+
+
+
+ Unused resources + ({unusedResources.length}) + + + + + +
+ {unusedResources.map((resource) => { + return ( +
+
+ +
+
+

{resource.filename}

+
+
+ ); + })} +
+
+ + )} )}