mirror of
https://github.com/usememos/memos.git
synced 2025-06-05 22:09:59 +02:00
chore: implement storage service
This commit is contained in:
@@ -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:
|
||||
|
156
server/route/api/v2/storage_service.go
Normal file
156
server/route/api/v2/storage_service.go
Normal 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
|
||||
}
|
@@ -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.
|
||||
|
@@ -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: ¤tUser.ID,
|
||||
})
|
||||
|
Reference in New Issue
Block a user