Commit bb5809ca authored by Steven's avatar Steven

refactor: attachment service

parent 174b1a03
syntax = "proto3";
package memos.api.v1;
import "google/api/annotations.proto";
import "google/api/client.proto";
import "google/api/field_behavior.proto";
import "google/api/httpbody.proto";
import "google/api/resource.proto";
import "google/protobuf/empty.proto";
import "google/protobuf/field_mask.proto";
import "google/protobuf/timestamp.proto";
option go_package = "gen/api/v1";
service AttachmentService {
// CreateAttachment creates a new attachment.
rpc CreateAttachment(CreateAttachmentRequest) returns (Attachment) {
option (google.api.http) = {
post: "/api/v1/attachments"
body: "attachment"
};
option (google.api.method_signature) = "attachment";
}
// ListAttachments lists all attachments.
rpc ListAttachments(ListAttachmentsRequest) returns (ListAttachmentsResponse) {
option (google.api.http) = {get: "/api/v1/attachments"};
}
// GetAttachment returns a attachment by name.
rpc GetAttachment(GetAttachmentRequest) returns (Attachment) {
option (google.api.http) = {get: "/api/v1/{name=attachments/*}"};
option (google.api.method_signature) = "name";
}
// GetAttachmentBinary returns a attachment binary by name.
rpc GetAttachmentBinary(GetAttachmentBinaryRequest) returns (google.api.HttpBody) {
option (google.api.http) = {get: "/file/{name=attachments/*}/{filename}"};
option (google.api.method_signature) = "name,filename";
}
// UpdateAttachment updates a attachment.
rpc UpdateAttachment(UpdateAttachmentRequest) returns (Attachment) {
option (google.api.http) = {
patch: "/api/v1/{attachment.name=attachments/*}"
body: "attachment"
};
option (google.api.method_signature) = "attachment,update_mask";
}
// DeleteAttachment deletes a attachment by name.
rpc DeleteAttachment(DeleteAttachmentRequest) returns (google.protobuf.Empty) {
option (google.api.http) = {delete: "/api/v1/{name=attachments/*}"};
option (google.api.method_signature) = "name";
}
}
message Attachment {
option (google.api.resource) = {
type: "memos.api.v1/Attachment"
pattern: "attachments/{attachment}"
singular: "attachment"
plural: "attachments"
};
reserved 2;
// The name of the attachment.
// Format: attachments/{attachment}
string name = 1 [(google.api.field_behavior) = IDENTIFIER];
// Output only. The creation timestamp.
google.protobuf.Timestamp create_time = 3 [(google.api.field_behavior) = OUTPUT_ONLY];
// The filename of the attachment.
string filename = 4 [(google.api.field_behavior) = REQUIRED];
// Input only. The content of the attachment.
bytes content = 5 [(google.api.field_behavior) = INPUT_ONLY];
// Optional. The external link of the attachment.
string external_link = 6 [(google.api.field_behavior) = OPTIONAL];
// The MIME type of the attachment.
string type = 7 [(google.api.field_behavior) = REQUIRED];
// Output only. The size of the attachment in bytes.
int64 size = 8 [(google.api.field_behavior) = OUTPUT_ONLY];
// Optional. The related memo. Refer to `Memo.name`.
// Format: memos/{memo}
optional string memo = 9 [(google.api.field_behavior) = OPTIONAL];
}
message CreateAttachmentRequest {
// Required. The attachment to create.
Attachment attachment = 1 [(google.api.field_behavior) = REQUIRED];
// Optional. The attachment ID to use for this attachment.
// If empty, a unique ID will be generated.
string attachment_id = 2 [(google.api.field_behavior) = OPTIONAL];
}
message ListAttachmentsRequest {
// Optional. The maximum number of attachments to return.
// The service may return fewer than this value.
// If unspecified, at most 50 attachments will be returned.
// The maximum value is 1000; values above 1000 will be coerced to 1000.
int32 page_size = 1 [(google.api.field_behavior) = OPTIONAL];
// Optional. A page token, received from a previous `ListAttachments` call.
// Provide this to retrieve the subsequent page.
string page_token = 2 [(google.api.field_behavior) = OPTIONAL];
// Optional. Filter to apply to the list results.
// Example: "type=image/png" or "filename:*.jpg"
// Supported operators: =, !=, <, <=, >, >=, :
// Supported fields: filename, type, size, create_time, memo
string filter = 3 [(google.api.field_behavior) = OPTIONAL];
// Optional. The order to sort results by.
// Example: "create_time desc" or "filename asc"
string order_by = 4 [(google.api.field_behavior) = OPTIONAL];
}
message ListAttachmentsResponse {
// The list of attachments.
repeated Attachment attachments = 1;
// A token that can be sent as `page_token` to retrieve the next page.
// If this field is omitted, there are no subsequent pages.
string next_page_token = 2;
// The total count of attachments (may be approximate).
int32 total_size = 3;
}
message GetAttachmentRequest {
// Required. The attachment name of the attachment to retrieve.
// Format: attachments/{attachment}
string name = 1 [
(google.api.field_behavior) = REQUIRED,
(google.api.resource_reference) = {type: "memos.api.v1/Attachment"}
];
}
message GetAttachmentBinaryRequest {
// Required. The attachment name of the attachment.
// Format: attachments/{attachment}
string name = 1 [
(google.api.field_behavior) = REQUIRED,
(google.api.resource_reference) = {type: "memos.api.v1/Attachment"}
];
// The filename of the attachment. Mainly used for downloading.
string filename = 2 [(google.api.field_behavior) = REQUIRED];
// Optional. A flag indicating if the thumbnail version of the attachment should be returned.
bool thumbnail = 3 [(google.api.field_behavior) = OPTIONAL];
}
message UpdateAttachmentRequest {
// Required. The attachment which replaces the attachment on the server.
Attachment attachment = 1 [(google.api.field_behavior) = REQUIRED];
// Required. The list of fields to update.
google.protobuf.FieldMask update_mask = 2 [(google.api.field_behavior) = REQUIRED];
}
message DeleteAttachmentRequest {
// Required. The attachment name of the attachment to delete.
// Format: attachments/{attachment}
string name = 1 [
(google.api.field_behavior) = REQUIRED,
(google.api.resource_reference) = {type: "memos.api.v1/Attachment"}
];
}
......@@ -2,10 +2,10 @@ syntax = "proto3";
package memos.api.v1;
import "api/v1/attachment_service.proto";
import "api/v1/common.proto";
import "api/v1/markdown_service.proto";
import "api/v1/reaction_service.proto";
import "api/v1/resource_service.proto";
import "google/api/annotations.proto";
import "google/api/client.proto";
import "google/api/field_behavior.proto";
......@@ -59,17 +59,17 @@ service MemoService {
rpc DeleteMemoTag(DeleteMemoTagRequest) returns (google.protobuf.Empty) {
option (google.api.http) = {delete: "/api/v1/{parent=memos/*}/tags/{tag}"};
}
// SetMemoResources sets resources for a memo.
rpc SetMemoResources(SetMemoResourcesRequest) returns (google.protobuf.Empty) {
// SetMemoAttachments sets attachments for a memo.
rpc SetMemoAttachments(SetMemoAttachmentsRequest) returns (google.protobuf.Empty) {
option (google.api.http) = {
patch: "/api/v1/{name=memos/*}/resources"
patch: "/api/v1/{name=memos/*}/attachments"
body: "*"
};
option (google.api.method_signature) = "name";
}
// ListMemoResources lists resources for a memo.
rpc ListMemoResources(ListMemoResourcesRequest) returns (ListMemoResourcesResponse) {
option (google.api.http) = {get: "/api/v1/{name=memos/*}/resources"};
// ListMemoAttachments lists attachments for a memo.
rpc ListMemoAttachments(ListMemoAttachmentsRequest) returns (ListMemoAttachmentsResponse) {
option (google.api.http) = {get: "/api/v1/{name=memos/*}/attachments"};
option (google.api.method_signature) = "name";
}
// SetMemoRelations sets relations for a memo.
......@@ -157,7 +157,7 @@ message Memo {
bool pinned = 12;
repeated Resource resources = 14;
repeated Attachment attachments = 14;
repeated MemoRelation relations = 15;
......@@ -269,20 +269,20 @@ message DeleteMemoTagRequest {
bool delete_related_memos = 3;
}
message SetMemoResourcesRequest {
message SetMemoAttachmentsRequest {
// The name of the memo.
string name = 1;
repeated Resource resources = 2;
repeated Attachment attachments = 2;
}
message ListMemoResourcesRequest {
message ListMemoAttachmentsRequest {
// The name of the memo.
string name = 1;
}
message ListMemoResourcesResponse {
repeated Resource resources = 1;
message ListMemoAttachmentsResponse {
repeated Attachment attachments = 1;
}
message MemoRelation {
......
syntax = "proto3";
package memos.api.v1;
import "google/api/annotations.proto";
import "google/api/client.proto";
import "google/api/field_behavior.proto";
import "google/api/httpbody.proto";
import "google/protobuf/empty.proto";
import "google/protobuf/field_mask.proto";
import "google/protobuf/timestamp.proto";
option go_package = "gen/api/v1";
service ResourceService {
// CreateResource creates a new resource.
rpc CreateResource(CreateResourceRequest) returns (Resource) {
option (google.api.http) = {
post: "/api/v1/resources"
body: "resource"
};
}
// ListResources lists all resources.
rpc ListResources(ListResourcesRequest) returns (ListResourcesResponse) {
option (google.api.http) = {get: "/api/v1/resources"};
}
// GetResource returns a resource by name.
rpc GetResource(GetResourceRequest) returns (Resource) {
option (google.api.http) = {get: "/api/v1/{name=resources/*}"};
option (google.api.method_signature) = "name";
}
// GetResourceBinary returns a resource binary by name.
rpc GetResourceBinary(GetResourceBinaryRequest) returns (google.api.HttpBody) {
option (google.api.http) = {get: "/file/{name=resources/*}/{filename}"};
option (google.api.method_signature) = "name,filename";
}
// UpdateResource updates a resource.
rpc UpdateResource(UpdateResourceRequest) returns (Resource) {
option (google.api.http) = {
patch: "/api/v1/{resource.name=resources/*}"
body: "resource"
};
option (google.api.method_signature) = "resource,update_mask";
}
// DeleteResource deletes a resource by name.
rpc DeleteResource(DeleteResourceRequest) returns (google.protobuf.Empty) {
option (google.api.http) = {delete: "/api/v1/{name=resources/*}"};
option (google.api.method_signature) = "name";
}
}
message Resource {
reserved 2;
// The name of the resource.
// Format: resources/{resource}, resource is the user defined if or uuid.
string name = 1 [
(google.api.field_behavior) = OUTPUT_ONLY,
(google.api.field_behavior) = IDENTIFIER
];
google.protobuf.Timestamp create_time = 3 [(google.api.field_behavior) = OUTPUT_ONLY];
string filename = 4;
bytes content = 5 [(google.api.field_behavior) = INPUT_ONLY];
string external_link = 6;
string type = 7;
int64 size = 8;
// The related memo. Refer to `Memo.name`.
optional string memo = 9;
}
message CreateResourceRequest {
Resource resource = 1;
}
message ListResourcesRequest {}
message ListResourcesResponse {
repeated Resource resources = 1;
}
message GetResourceRequest {
// The name of the resource.
string name = 1;
}
message GetResourceBinaryRequest {
// The name of the resource.
string name = 1;
// The filename of the resource. Mainly used for downloading.
string filename = 2;
// A flag indicating if the thumbnail version of the resource should be returned
bool thumbnail = 3;
}
message UpdateResourceRequest {
Resource resource = 1;
google.protobuf.FieldMask update_mask = 2;
}
message DeleteResourceRequest {
// The name of the resource.
string name = 1;
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -13,7 +13,7 @@ import (
"github.com/usememos/memos/store"
)
func (s *APIV1Service) SetMemoResources(ctx context.Context, request *v1pb.SetMemoResourcesRequest) (*emptypb.Empty, error) {
func (s *APIV1Service) SetMemoAttachments(ctx context.Context, request *v1pb.SetMemoAttachmentsRequest) (*emptypb.Empty, error) {
memoUID, err := ExtractMemoUIDFromName(request.Name)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid memo name: %v", err)
......@@ -32,10 +32,10 @@ func (s *APIV1Service) SetMemoResources(ctx context.Context, request *v1pb.SetMe
// Delete resources that are not in the request.
for _, resource := range resources {
found := false
for _, requestResource := range request.Resources {
requestResourceUID, err := ExtractResourceUIDFromName(requestResource.Name)
for _, requestResource := range request.Attachments {
requestResourceUID, err := ExtractAttachmentUIDFromName(requestResource.Name)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid resource name: %v", err)
return nil, status.Errorf(codes.InvalidArgument, "invalid attachment name: %v", err)
}
if resource.UID == requestResourceUID {
found = true
......@@ -52,12 +52,12 @@ func (s *APIV1Service) SetMemoResources(ctx context.Context, request *v1pb.SetMe
}
}
slices.Reverse(request.Resources)
slices.Reverse(request.Attachments)
// Update resources' memo_id in the request.
for index, resource := range request.Resources {
resourceUID, err := ExtractResourceUIDFromName(resource.Name)
for index, resource := range request.Attachments {
resourceUID, err := ExtractAttachmentUIDFromName(resource.Name)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid resource name: %v", err)
return nil, status.Errorf(codes.InvalidArgument, "invalid attachment name: %v", err)
}
tempResource, err := s.Store.GetResource(ctx, &store.FindResource{UID: &resourceUID})
if err != nil {
......@@ -76,7 +76,7 @@ func (s *APIV1Service) SetMemoResources(ctx context.Context, request *v1pb.SetMe
return &emptypb.Empty{}, nil
}
func (s *APIV1Service) ListMemoResources(ctx context.Context, request *v1pb.ListMemoResourcesRequest) (*v1pb.ListMemoResourcesResponse, error) {
func (s *APIV1Service) ListMemoAttachments(ctx context.Context, request *v1pb.ListMemoAttachmentsRequest) (*v1pb.ListMemoAttachmentsResponse, error) {
memoUID, err := ExtractMemoUIDFromName(request.Name)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid memo name: %v", err)
......@@ -92,11 +92,11 @@ func (s *APIV1Service) ListMemoResources(ctx context.Context, request *v1pb.List
return nil, status.Errorf(codes.Internal, "failed to list resources: %v", err)
}
response := &v1pb.ListMemoResourcesResponse{
Resources: []*v1pb.Resource{},
response := &v1pb.ListMemoAttachmentsResponse{
Attachments: []*v1pb.Attachment{},
}
for _, resource := range resources {
response.Resources = append(response.Resources, s.convertResourceFromStore(ctx, resource))
response.Attachments = append(response.Attachments, s.convertAttachmentFromStore(ctx, resource))
}
return response, nil
}
......@@ -63,13 +63,13 @@ func (s *APIV1Service) CreateMemo(ctx context.Context, request *v1pb.CreateMemoR
if err != nil {
return nil, err
}
if len(request.Memo.Resources) > 0 {
_, err := s.SetMemoResources(ctx, &v1pb.SetMemoResourcesRequest{
Name: fmt.Sprintf("%s%s", MemoNamePrefix, memo.UID),
Resources: request.Memo.Resources,
if len(request.Memo.Attachments) > 0 {
_, err := s.SetMemoAttachments(ctx, &v1pb.SetMemoAttachmentsRequest{
Name: fmt.Sprintf("%s%s", MemoNamePrefix, memo.UID),
Attachments: request.Memo.Attachments,
})
if err != nil {
return nil, errors.Wrap(err, "failed to set memo resources")
return nil, errors.Wrap(err, "failed to set memo attachments")
}
}
if len(request.Memo.Relations) > 0 {
......@@ -318,13 +318,13 @@ func (s *APIV1Service) UpdateMemo(ctx context.Context, request *v1pb.UpdateMemoR
payload := memo.Payload
payload.Location = convertLocationToStore(request.Memo.Location)
update.Payload = payload
} else if path == "resources" {
_, err := s.SetMemoResources(ctx, &v1pb.SetMemoResourcesRequest{
Name: request.Memo.Name,
Resources: request.Memo.Resources,
} else if path == "attachments" {
_, err := s.SetMemoAttachments(ctx, &v1pb.SetMemoAttachmentsRequest{
Name: request.Memo.Name,
Attachments: request.Memo.Attachments,
})
if err != nil {
return nil, errors.Wrap(err, "failed to set memo resources")
return nil, errors.Wrap(err, "failed to set memo attachments")
}
} else if path == "relations" {
_, err := s.SetMemoRelations(ctx, &v1pb.SetMemoRelationsRequest{
......
......@@ -61,11 +61,11 @@ func (s *APIV1Service) convertMemoFromStore(ctx context.Context, memo *store.Mem
}
memoMessage.Relations = listMemoRelationsResponse.Relations
listMemoResourcesResponse, err := s.ListMemoResources(ctx, &v1pb.ListMemoResourcesRequest{Name: name})
listMemoAttachmentsResponse, err := s.ListMemoAttachments(ctx, &v1pb.ListMemoAttachmentsRequest{Name: name})
if err != nil {
return nil, errors.Wrap(err, "failed to list memo resources")
return nil, errors.Wrap(err, "failed to list memo attachments")
}
memoMessage.Resources = listMemoResourcesResponse.Resources
memoMessage.Attachments = listMemoAttachmentsResponse.Attachments
listMemoReactionsResponse, err := s.ListMemoReactions(ctx, &v1pb.ListMemoReactionsRequest{Name: name})
if err != nil {
......
......@@ -13,7 +13,7 @@ const (
WorkspaceSettingNamePrefix = "workspace/settings/"
UserNamePrefix = "users/"
MemoNamePrefix = "memos/"
ResourceNamePrefix = "resources/"
AttachmentNamePrefix = "attachments/"
InboxNamePrefix = "inboxes/"
IdentityProviderNamePrefix = "identityProviders/"
ActivityNamePrefix = "activities/"
......@@ -83,9 +83,9 @@ func ExtractMemoUIDFromName(name string) (string, error) {
return id, nil
}
// ExtractResourceUIDFromName returns the resource UID from a resource name.
func ExtractResourceUIDFromName(name string) (string, error) {
tokens, err := GetNameParentTokens(name, ResourceNamePrefix)
// ExtractAttachmentUIDFromName returns the attachment UID from a resource name.
func ExtractAttachmentUIDFromName(name string) (string, error) {
tokens, err := GetNameParentTokens(name, AttachmentNamePrefix)
if err != nil {
return "", err
}
......
......@@ -26,7 +26,7 @@ type APIV1Service struct {
v1pb.UnimplementedAuthServiceServer
v1pb.UnimplementedUserServiceServer
v1pb.UnimplementedMemoServiceServer
v1pb.UnimplementedResourceServiceServer
v1pb.UnimplementedAttachmentServiceServer
v1pb.UnimplementedShortcutServiceServer
v1pb.UnimplementedInboxServiceServer
v1pb.UnimplementedActivityServiceServer
......@@ -54,7 +54,7 @@ func NewAPIV1Service(secret string, profile *profile.Profile, store *store.Store
v1pb.RegisterAuthServiceServer(grpcServer, apiv1Service)
v1pb.RegisterUserServiceServer(grpcServer, apiv1Service)
v1pb.RegisterMemoServiceServer(grpcServer, apiv1Service)
v1pb.RegisterResourceServiceServer(grpcServer, apiv1Service)
v1pb.RegisterAttachmentServiceServer(grpcServer, apiv1Service)
v1pb.RegisterShortcutServiceServer(grpcServer, apiv1Service)
v1pb.RegisterInboxServiceServer(grpcServer, apiv1Service)
v1pb.RegisterActivityServiceServer(grpcServer, apiv1Service)
......@@ -95,7 +95,7 @@ func (s *APIV1Service) RegisterGateway(ctx context.Context, echoServer *echo.Ech
if err := v1pb.RegisterMemoServiceHandler(ctx, gwMux, conn); err != nil {
return err
}
if err := v1pb.RegisterResourceServiceHandler(ctx, gwMux, conn); err != nil {
if err := v1pb.RegisterAttachmentServiceHandler(ctx, gwMux, conn); err != nil {
return err
}
if err := v1pb.RegisterShortcutServiceHandler(ctx, gwMux, conn); err != nil {
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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