chore: implement storage service

This commit is contained in:
Steven
2024-04-13 02:55:40 +08:00
parent 707e5caf89
commit 8f51529c78
21 changed files with 3466 additions and 778 deletions

View File

@@ -11,6 +11,7 @@ tags:
- name: LinkService
- name: ResourceService
- name: MemoService
- name: StorageService
- name: TagService
- name: WebhookService
- name: WorkspaceService
@@ -515,6 +516,115 @@ paths:
type: string
tags:
- ResourceService
/api/v2/storages:
get:
summary: ListStorages returns a list of storages.
operationId: StorageService_ListStorages
responses:
"200":
description: A successful response.
schema:
$ref: '#/definitions/v2ListStoragesResponse'
default:
description: An unexpected error response.
schema:
$ref: '#/definitions/googlerpcStatus'
tags:
- StorageService
post:
summary: CreateStorage creates a new storage.
operationId: StorageService_CreateStorage
responses:
"200":
description: A successful response.
schema:
$ref: '#/definitions/v2CreateStorageResponse'
default:
description: An unexpected error response.
schema:
$ref: '#/definitions/googlerpcStatus'
parameters:
- name: body
in: body
required: true
schema:
$ref: '#/definitions/v2CreateStorageRequest'
tags:
- StorageService
/api/v2/storages/{id}:
get:
summary: GetStorage returns a storage by id.
operationId: StorageService_GetStorage
responses:
"200":
description: A successful response.
schema:
$ref: '#/definitions/v2GetStorageResponse'
default:
description: An unexpected error response.
schema:
$ref: '#/definitions/googlerpcStatus'
parameters:
- name: id
in: path
required: true
type: integer
format: int32
tags:
- StorageService
delete:
summary: DeleteStorage deletes a storage by id.
operationId: StorageService_DeleteStorage
responses:
"200":
description: A successful response.
schema:
$ref: '#/definitions/v2DeleteStorageResponse'
default:
description: An unexpected error response.
schema:
$ref: '#/definitions/googlerpcStatus'
parameters:
- name: id
in: path
required: true
type: integer
format: int32
tags:
- StorageService
/api/v2/storages/{storage.id}:
patch:
summary: UpdateStorage updates a storage.
operationId: StorageService_UpdateStorage
responses:
"200":
description: A successful response.
schema:
$ref: '#/definitions/v2UpdateStorageResponse'
default:
description: An unexpected error response.
schema:
$ref: '#/definitions/googlerpcStatus'
parameters:
- name: storage.id
in: path
required: true
type: integer
format: int32
- name: storage
in: body
required: true
schema:
type: object
properties:
title:
type: string
type:
$ref: '#/definitions/apiv2StorageType'
config:
$ref: '#/definitions/apiv2StorageConfig'
tags:
- StorageService
/api/v2/tags:
get:
summary: ListTags lists tags.
@@ -966,7 +1076,7 @@ paths:
identifierFilter:
type: string
config:
$ref: '#/definitions/IdentityProviderConfig'
$ref: '#/definitions/apiv2IdentityProviderConfig'
title: The identityProvider to update.
tags:
- IdentityProviderService
@@ -1834,39 +1944,6 @@ paths:
tags:
- ActivityService
definitions:
IdentityProviderConfig:
type: object
properties:
oauth2:
$ref: '#/definitions/IdentityProviderConfigOAuth2'
IdentityProviderConfigFieldMapping:
type: object
properties:
identifier:
type: string
displayName:
type: string
email:
type: string
IdentityProviderConfigOAuth2:
type: object
properties:
clientId:
type: string
clientSecret:
type: string
authUrl:
type: string
tokenUrl:
type: string
userInfoUrl:
type: string
scopes:
type: array
items:
type: string
fieldMapping:
$ref: '#/definitions/IdentityProviderConfigFieldMapping'
MemoServiceSetMemoRelationsBody:
type: object
properties:
@@ -1920,6 +1997,39 @@ definitions:
properties:
version:
type: string
apiv2FieldMapping:
type: object
properties:
identifier:
type: string
displayName:
type: string
email:
type: string
apiv2IdentityProviderConfig:
type: object
properties:
oauth2:
$ref: '#/definitions/apiv2OAuth2Config'
apiv2OAuth2Config:
type: object
properties:
clientId:
type: string
clientSecret:
type: string
authUrl:
type: string
tokenUrl:
type: string
userInfoUrl:
type: string
scopes:
type: array
items:
type: string
fieldMapping:
$ref: '#/definitions/apiv2FieldMapping'
apiv2Reaction:
type: object
properties:
@@ -1959,6 +2069,50 @@ definitions:
- ACTIVE
- ARCHIVED
default: ROW_STATUS_UNSPECIFIED
apiv2S3Config:
type: object
properties:
endPoint:
type: string
path:
type: string
region:
type: string
accessKey:
type: string
secretKey:
type: string
bucket:
type: string
urlPrefix:
type: string
urlSuffix:
type: string
preSign:
type: boolean
apiv2Storage:
type: object
properties:
id:
type: integer
format: int32
title:
type: string
type:
$ref: '#/definitions/apiv2StorageType'
config:
$ref: '#/definitions/apiv2StorageConfig'
apiv2StorageConfig:
type: object
properties:
s3Config:
$ref: '#/definitions/apiv2S3Config'
apiv2StorageType:
type: string
enum:
- TYPE_UNSPECIFIED
- S3
default: TYPE_UNSPECIFIED
apiv2UserSetting:
type: object
properties:
@@ -2163,6 +2317,16 @@ definitions:
properties:
resource:
$ref: '#/definitions/v2Resource'
v2CreateStorageRequest:
type: object
properties:
storage:
$ref: '#/definitions/apiv2Storage'
v2CreateStorageResponse:
type: object
properties:
storage:
$ref: '#/definitions/apiv2Storage'
v2CreateUserAccessTokenResponse:
type: object
properties:
@@ -2195,6 +2359,8 @@ definitions:
type: object
v2DeleteResourceResponse:
type: object
v2DeleteStorageResponse:
type: object
v2DeleteTagResponse:
type: object
v2DeleteUserAccessTokenResponse:
@@ -2240,6 +2406,11 @@ definitions:
properties:
resource:
$ref: '#/definitions/v2Resource'
v2GetStorageResponse:
type: object
properties:
storage:
$ref: '#/definitions/apiv2Storage'
v2GetTagSuggestionsResponse:
type: object
properties:
@@ -2298,7 +2469,7 @@ definitions:
identifierFilter:
type: string
config:
$ref: '#/definitions/IdentityProviderConfig'
$ref: '#/definitions/apiv2IdentityProviderConfig'
v2IdentityProviderType:
type: string
enum:
@@ -2421,6 +2592,14 @@ definitions:
items:
type: object
$ref: '#/definitions/v2Resource'
v2ListStoragesResponse:
type: object
properties:
storages:
type: array
items:
type: object
$ref: '#/definitions/apiv2Storage'
v2ListTagsResponse:
type: object
properties:
@@ -2658,6 +2837,11 @@ definitions:
properties:
resource:
$ref: '#/definitions/v2Resource'
v2UpdateStorageResponse:
type: object
properties:
storage:
$ref: '#/definitions/apiv2Storage'
v2UpdateUserResponse:
type: object
properties:

View File

@@ -0,0 +1,156 @@
package v2
import (
"context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
apiv2pb "github.com/usememos/memos/proto/gen/api/v2"
storepb "github.com/usememos/memos/proto/gen/store"
"github.com/usememos/memos/store"
)
func (s *APIV2Service) CreateStorage(ctx context.Context, request *apiv2pb.CreateStorageRequest) (*apiv2pb.CreateStorageResponse, error) {
currentUser, err := getCurrentUser(ctx, s.Store)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
}
if currentUser.Role != store.RoleHost {
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
}
storage, err := s.Store.CreateStorageV1(ctx, convertStorageToStore(request.Storage))
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to create storage, error: %+v", err)
}
return &apiv2pb.CreateStorageResponse{
Storage: convertStorageFromStore(storage),
}, nil
}
func (s *APIV2Service) ListStorages(ctx context.Context, _ *apiv2pb.ListStoragesRequest) (*apiv2pb.ListStoragesResponse, error) {
storages, err := s.Store.ListStoragesV1(ctx, &store.FindStorage{})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to list storages, error: %+v", err)
}
response := &apiv2pb.ListStoragesResponse{
Storages: []*apiv2pb.Storage{},
}
for _, storage := range storages {
response.Storages = append(response.Storages, convertStorageFromStore(storage))
}
return response, nil
}
func (s *APIV2Service) GetStorage(ctx context.Context, request *apiv2pb.GetStorageRequest) (*apiv2pb.GetStorageResponse, error) {
storage, err := s.Store.GetStorageV1(ctx, &store.FindStorage{
ID: &request.Id,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get storage, error: %+v", err)
}
if storage == nil {
return nil, status.Errorf(codes.NotFound, "storage not found")
}
return &apiv2pb.GetStorageResponse{
Storage: convertStorageFromStore(storage),
}, nil
}
func (s *APIV2Service) UpdateStorage(ctx context.Context, request *apiv2pb.UpdateStorageRequest) (*apiv2pb.UpdateStorageResponse, error) {
if request.UpdateMask == nil || len(request.UpdateMask.Paths) == 0 {
return nil, status.Errorf(codes.InvalidArgument, "update_mask is required")
}
update := &store.UpdateStorageV1{
ID: request.Storage.Id,
Type: storepb.Storage_Type(storepb.Storage_Type_value[request.Storage.Type.String()]),
}
for _, field := range request.UpdateMask.Paths {
switch field {
case "name":
update.Name = &request.Storage.Title
case "config":
update.Config = convertStorageConfigToStore(request.Storage.Type, request.Storage.Config)
}
}
storage, err := s.Store.UpdateStorageV1(ctx, update)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to update storage, error: %+v", err)
}
return &apiv2pb.UpdateStorageResponse{
Storage: convertStorageFromStore(storage),
}, nil
}
func (s *APIV2Service) DeleteStorage(ctx context.Context, request *apiv2pb.DeleteStorageRequest) (*apiv2pb.DeleteStorageResponse, error) {
err := s.Store.DeleteStorage(ctx, &store.DeleteStorage{
ID: request.Id,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to delete storage, error: %+v", err)
}
return &apiv2pb.DeleteStorageResponse{}, nil
}
func convertStorageFromStore(storage *storepb.Storage) *apiv2pb.Storage {
temp := &apiv2pb.Storage{
Id: storage.Id,
Title: storage.Name,
Type: apiv2pb.Storage_Type(apiv2pb.Storage_Type_value[storage.Type.String()]),
}
if storage.Type == storepb.Storage_S3 {
s3Config := storage.Config.GetS3Config()
temp.Config = &apiv2pb.StorageConfig{
StorageConfig: &apiv2pb.StorageConfig_S3Config{
S3Config: &apiv2pb.S3Config{
EndPoint: s3Config.EndPoint,
Path: s3Config.Path,
Region: s3Config.Region,
AccessKey: s3Config.AccessKey,
SecretKey: s3Config.SecretKey,
Bucket: s3Config.Bucket,
UrlPrefix: s3Config.UrlPrefix,
UrlSuffix: s3Config.UrlSuffix,
PreSign: s3Config.PreSign,
},
},
}
}
return temp
}
func convertStorageToStore(storage *apiv2pb.Storage) *storepb.Storage {
temp := &storepb.Storage{
Id: storage.Id,
Name: storage.Title,
Type: storepb.Storage_Type(storepb.Storage_Type_value[storage.Type.String()]),
Config: convertStorageConfigToStore(storage.Type, storage.Config),
}
return temp
}
func convertStorageConfigToStore(storageType apiv2pb.Storage_Type, config *apiv2pb.StorageConfig) *storepb.StorageConfig {
if storageType == apiv2pb.Storage_S3 {
s3Config := config.GetS3Config()
return &storepb.StorageConfig{
StorageConfig: &storepb.StorageConfig_S3Config{
S3Config: &storepb.S3Config{
EndPoint: s3Config.EndPoint,
Path: s3Config.Path,
Region: s3Config.Region,
AccessKey: s3Config.AccessKey,
SecretKey: s3Config.SecretKey,
Bucket: s3Config.Bucket,
UrlPrefix: s3Config.UrlPrefix,
UrlSuffix: s3Config.UrlSuffix,
PreSign: s3Config.PreSign,
},
},
}
}
return nil
}

View File

@@ -31,6 +31,7 @@ type APIV2Service struct {
apiv2pb.UnimplementedActivityServiceServer
apiv2pb.UnimplementedWebhookServiceServer
apiv2pb.UnimplementedLinkServiceServer
apiv2pb.UnimplementedStorageServiceServer
Secret string
Profile *profile.Profile
@@ -68,6 +69,7 @@ func NewAPIV2Service(secret string, profile *profile.Profile, store *store.Store
apiv2pb.RegisterActivityServiceServer(grpcServer, apiv2Service)
apiv2pb.RegisterWebhookServiceServer(grpcServer, apiv2Service)
apiv2pb.RegisterLinkServiceServer(grpcServer, apiv2Service)
apiv2pb.RegisterStorageServiceServer(grpcServer, apiv2Service)
reflection.Register(grpcServer)
return apiv2Service
@@ -124,6 +126,9 @@ func (s *APIV2Service) RegisterGateway(ctx context.Context, e *echo.Echo) error
if err := apiv2pb.RegisterLinkServiceHandler(context.Background(), gwMux, conn); err != nil {
return err
}
if err := apiv2pb.RegisterStorageServiceHandler(context.Background(), gwMux, conn); err != nil {
return err
}
e.Any("/api/v2/*", echo.WrapHandler(gwMux))
// GRPC web proxy.

View File

@@ -55,7 +55,7 @@ func (s *APIV2Service) GetWebhook(ctx context.Context, request *apiv2pb.GetWebho
return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
}
webhook, err := s.Store.GetWebhooks(ctx, &store.FindWebhook{
webhook, err := s.Store.GetWebhook(ctx, &store.FindWebhook{
ID: &request.Id,
CreatorID: &currentUser.ID,
})