Commit 43382346 authored by Steven's avatar Steven

chore: retire storage related functions

parent 32096309
...@@ -11,7 +11,6 @@ tags: ...@@ -11,7 +11,6 @@ tags:
- name: LinkService - name: LinkService
- name: ResourceService - name: ResourceService
- name: MemoService - name: MemoService
- name: StorageService
- name: TagService - name: TagService
- name: WebhookService - name: WebhookService
- name: WorkspaceService - name: WorkspaceService
...@@ -453,115 +452,6 @@ paths: ...@@ -453,115 +452,6 @@ paths:
type: string type: string
tags: tags:
- ResourceService - ResourceService
/api/v1/storages:
get:
summary: ListStorages returns a list of storages.
operationId: StorageService_ListStorages
responses:
"200":
description: A successful response.
schema:
$ref: '#/definitions/v1ListStoragesResponse'
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/v1CreateStorageResponse'
default:
description: An unexpected error response.
schema:
$ref: '#/definitions/googlerpcStatus'
parameters:
- name: body
in: body
required: true
schema:
$ref: '#/definitions/v1CreateStorageRequest'
tags:
- StorageService
/api/v1/storages/{id}:
get:
summary: GetStorage returns a storage by id.
operationId: StorageService_GetStorage
responses:
"200":
description: A successful response.
schema:
$ref: '#/definitions/v1GetStorageResponse'
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/v1DeleteStorageResponse'
default:
description: An unexpected error response.
schema:
$ref: '#/definitions/googlerpcStatus'
parameters:
- name: id
in: path
required: true
type: integer
format: int32
tags:
- StorageService
/api/v1/storages/{storage.id}:
patch:
summary: UpdateStorage updates a storage.
operationId: StorageService_UpdateStorage
responses:
"200":
description: A successful response.
schema:
$ref: '#/definitions/v1UpdateStorageResponse'
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/apiv1StorageType'
config:
$ref: '#/definitions/apiv1StorageConfig'
tags:
- StorageService
/api/v1/tags: /api/v1/tags:
get: get:
summary: ListTags lists tags. summary: ListTags lists tags.
...@@ -1935,50 +1825,6 @@ definitions: ...@@ -1935,50 +1825,6 @@ definitions:
type: string type: string
fieldMapping: fieldMapping:
$ref: '#/definitions/apiv1FieldMapping' $ref: '#/definitions/apiv1FieldMapping'
apiv1S3Config:
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
apiv1Storage:
type: object
properties:
id:
type: integer
format: int32
title:
type: string
type:
$ref: '#/definitions/apiv1StorageType'
config:
$ref: '#/definitions/apiv1StorageConfig'
apiv1StorageConfig:
type: object
properties:
s3Config:
$ref: '#/definitions/apiv1S3Config'
apiv1StorageType:
type: string
enum:
- TYPE_UNSPECIFIED
- S3
default: TYPE_UNSPECIFIED
apiv1UserSetting: apiv1UserSetting:
type: object type: object
properties: properties:
...@@ -2156,16 +2002,6 @@ definitions: ...@@ -2156,16 +2002,6 @@ definitions:
type: string type: string
visibility: visibility:
$ref: '#/definitions/v1Visibility' $ref: '#/definitions/v1Visibility'
v1CreateStorageRequest:
type: object
properties:
storage:
$ref: '#/definitions/apiv1Storage'
v1CreateStorageResponse:
type: object
properties:
storage:
$ref: '#/definitions/apiv1Storage'
v1CreateWebhookRequest: v1CreateWebhookRequest:
type: object type: object
properties: properties:
...@@ -2173,8 +2009,6 @@ definitions: ...@@ -2173,8 +2009,6 @@ definitions:
type: string type: string
url: url:
type: string type: string
v1DeleteStorageResponse:
type: object
v1ExportMemosRequest: v1ExportMemosRequest:
type: object type: object
properties: properties:
...@@ -2192,11 +2026,6 @@ definitions: ...@@ -2192,11 +2026,6 @@ definitions:
properties: properties:
linkMetadata: linkMetadata:
$ref: '#/definitions/v1LinkMetadata' $ref: '#/definitions/v1LinkMetadata'
v1GetStorageResponse:
type: object
properties:
storage:
$ref: '#/definitions/apiv1Storage'
v1GetTagSuggestionsResponse: v1GetTagSuggestionsResponse:
type: object type: object
properties: properties:
...@@ -2331,14 +2160,6 @@ definitions: ...@@ -2331,14 +2160,6 @@ definitions:
items: items:
type: object type: object
$ref: '#/definitions/v1Resource' $ref: '#/definitions/v1Resource'
v1ListStoragesResponse:
type: object
properties:
storages:
type: array
items:
type: object
$ref: '#/definitions/apiv1Storage'
v1ListTagsResponse: v1ListTagsResponse:
type: object type: object
properties: properties:
...@@ -2575,11 +2396,6 @@ definitions: ...@@ -2575,11 +2396,6 @@ definitions:
title: |- title: |-
The creator of tags. The creator of tags.
Format: users/{id} Format: users/{id}
v1UpdateStorageResponse:
type: object
properties:
storage:
$ref: '#/definitions/apiv1Storage'
v1UpsertTagRequest: v1UpsertTagRequest:
type: object type: object
properties: properties:
......
syntax = "proto3";
package memos.api.v1;
import "google/api/annotations.proto";
import "google/api/client.proto";
import "google/protobuf/field_mask.proto";
option go_package = "gen/api/v1";
service StorageService {
// CreateStorage creates a new storage.
rpc CreateStorage(CreateStorageRequest) returns (CreateStorageResponse) {
option (google.api.http) = {
post: "/api/v1/storages"
body: "*"
};
}
// GetStorage returns a storage by id.
rpc GetStorage(GetStorageRequest) returns (GetStorageResponse) {
option (google.api.http) = {get: "/api/v1/storages/{id}"};
option (google.api.method_signature) = "id";
}
// ListStorages returns a list of storages.
rpc ListStorages(ListStoragesRequest) returns (ListStoragesResponse) {
option (google.api.http) = {get: "/api/v1/storages"};
}
// UpdateStorage updates a storage.
rpc UpdateStorage(UpdateStorageRequest) returns (UpdateStorageResponse) {
option (google.api.http) = {
patch: "/api/v1/storages/{storage.id}"
body: "storage"
};
option (google.api.method_signature) = "storage,update_mask";
}
// DeleteStorage deletes a storage by id.
rpc DeleteStorage(DeleteStorageRequest) returns (DeleteStorageResponse) {
option (google.api.http) = {delete: "/api/v1/storages/{id}"};
option (google.api.method_signature) = "id";
}
}
message Storage {
int32 id = 1;
string title = 2;
enum Type {
TYPE_UNSPECIFIED = 0;
S3 = 1;
}
Type type = 3;
StorageConfig config = 4;
}
message StorageConfig {
oneof config {
S3Config s3_config = 1;
}
}
message S3Config {
string end_point = 1;
string path = 2;
string region = 3;
string access_key = 4;
string secret_key = 5;
string bucket = 6;
string url_prefix = 7;
string url_suffix = 8;
bool pre_sign = 9;
}
message CreateStorageRequest {
Storage storage = 1;
}
message CreateStorageResponse {
Storage storage = 1;
}
message GetStorageRequest {
int32 id = 1;
}
message GetStorageResponse {
Storage storage = 1;
}
message ListStoragesRequest {}
message ListStoragesResponse {
repeated Storage storages = 1;
}
message UpdateStorageRequest {
Storage storage = 1;
google.protobuf.FieldMask update_mask = 2;
}
message UpdateStorageResponse {
Storage storage = 1;
}
message DeleteStorageRequest {
int32 id = 1;
}
message DeleteStorageResponse {}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
syntax = "proto3";
package memos.store;
option go_package = "gen/store";
message Storage {
int32 id = 1;
string name = 2;
enum Type {
TYPE_UNSPECIFIED = 0;
S3 = 1;
}
Type type = 3;
StorageConfig config = 4;
}
message StorageConfig {
oneof storage_config {
S3Config s3_config = 1;
}
}
message S3Config {
string end_point = 1;
string path = 2;
string region = 3;
string access_key = 4;
string secret_key = 5;
string bucket = 6;
string url_prefix = 7;
string url_suffix = 8;
bool pre_sign = 9;
}
package v1
import (
"context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
v1pb "github.com/usememos/memos/proto/gen/api/v1"
storepb "github.com/usememos/memos/proto/gen/store"
"github.com/usememos/memos/store"
)
func (s *APIV1Service) CreateStorage(ctx context.Context, request *v1pb.CreateStorageRequest) (*v1pb.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.CreateStorage(ctx, convertStorageToStore(request.Storage))
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to create storage, error: %+v", err)
}
return &v1pb.CreateStorageResponse{
Storage: ConvertStorageFromStore(storage),
}, nil
}
func (s *APIV1Service) ListStorages(ctx context.Context, _ *v1pb.ListStoragesRequest) (*v1pb.ListStoragesResponse, error) {
storages, err := s.Store.ListStorages(ctx, &store.FindStorage{})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to list storages, error: %+v", err)
}
response := &v1pb.ListStoragesResponse{
Storages: []*v1pb.Storage{},
}
for _, storage := range storages {
response.Storages = append(response.Storages, ConvertStorageFromStore(storage))
}
return response, nil
}
func (s *APIV1Service) GetStorage(ctx context.Context, request *v1pb.GetStorageRequest) (*v1pb.GetStorageResponse, error) {
storage, err := s.Store.GetStorage(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 &v1pb.GetStorageResponse{
Storage: ConvertStorageFromStore(storage),
}, nil
}
func (s *APIV1Service) UpdateStorage(ctx context.Context, request *v1pb.UpdateStorageRequest) (*v1pb.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.UpdateStorage(ctx, update)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to update storage, error: %+v", err)
}
return &v1pb.UpdateStorageResponse{
Storage: ConvertStorageFromStore(storage),
}, nil
}
func (s *APIV1Service) DeleteStorage(ctx context.Context, request *v1pb.DeleteStorageRequest) (*v1pb.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 &v1pb.DeleteStorageResponse{}, nil
}
func ConvertStorageFromStore(storage *storepb.Storage) *v1pb.Storage {
temp := &v1pb.Storage{
Id: storage.Id,
Title: storage.Name,
Type: v1pb.Storage_Type(v1pb.Storage_Type_value[storage.Type.String()]),
}
if storage.Type == storepb.Storage_S3 {
s3Config := storage.Config.GetS3Config()
temp.Config = &v1pb.StorageConfig{
Config: &v1pb.StorageConfig_S3Config{
S3Config: &v1pb.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 *v1pb.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 v1pb.Storage_Type, config *v1pb.StorageConfig) *storepb.StorageConfig {
if storageType == v1pb.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
}
...@@ -28,7 +28,6 @@ type APIV1Service struct { ...@@ -28,7 +28,6 @@ type APIV1Service struct {
v1pb.UnimplementedActivityServiceServer v1pb.UnimplementedActivityServiceServer
v1pb.UnimplementedWebhookServiceServer v1pb.UnimplementedWebhookServiceServer
v1pb.UnimplementedLinkServiceServer v1pb.UnimplementedLinkServiceServer
v1pb.UnimplementedStorageServiceServer
v1pb.UnimplementedIdentityProviderServiceServer v1pb.UnimplementedIdentityProviderServiceServer
Secret string Secret string
...@@ -57,7 +56,6 @@ func NewAPIV1Service(secret string, profile *profile.Profile, store *store.Store ...@@ -57,7 +56,6 @@ func NewAPIV1Service(secret string, profile *profile.Profile, store *store.Store
v1pb.RegisterActivityServiceServer(grpcServer, apiv1Service) v1pb.RegisterActivityServiceServer(grpcServer, apiv1Service)
v1pb.RegisterWebhookServiceServer(grpcServer, apiv1Service) v1pb.RegisterWebhookServiceServer(grpcServer, apiv1Service)
v1pb.RegisterLinkServiceServer(grpcServer, apiv1Service) v1pb.RegisterLinkServiceServer(grpcServer, apiv1Service)
v1pb.RegisterStorageServiceServer(grpcServer, apiv1Service)
v1pb.RegisterIdentityProviderServiceServer(grpcServer, apiv1Service) v1pb.RegisterIdentityProviderServiceServer(grpcServer, apiv1Service)
reflection.Register(grpcServer) reflection.Register(grpcServer)
return apiv1Service return apiv1Service
...@@ -110,9 +108,6 @@ func (s *APIV1Service) RegisterGateway(ctx context.Context, echoServer *echo.Ech ...@@ -110,9 +108,6 @@ func (s *APIV1Service) RegisterGateway(ctx context.Context, echoServer *echo.Ech
if err := v1pb.RegisterLinkServiceHandler(context.Background(), gwMux, conn); err != nil { if err := v1pb.RegisterLinkServiceHandler(context.Background(), gwMux, conn); err != nil {
return err return err
} }
if err := v1pb.RegisterStorageServiceHandler(context.Background(), gwMux, conn); err != nil {
return err
}
if err := v1pb.RegisterIdentityProviderServiceHandler(context.Background(), gwMux, conn); err != nil { if err := v1pb.RegisterIdentityProviderServiceHandler(context.Background(), gwMux, conn); err != nil {
return err return err
} }
......
package mysql
import (
"context"
"strings"
"github.com/usememos/memos/store"
)
func (d *DB) CreateStorage(ctx context.Context, create *store.Storage) (*store.Storage, error) {
fields := []string{"`name`", "`type`", "`config`"}
placeholder := []string{"?", "?", "?"}
args := []any{create.Name, create.Type, create.Config}
stmt := "INSERT INTO `storage` (" + strings.Join(fields, ", ") + ") VALUES (" + strings.Join(placeholder, ", ") + ")"
result, err := d.db.ExecContext(ctx, stmt, args...)
if err != nil {
return nil, err
}
id, err := result.LastInsertId()
if err != nil {
return nil, err
}
create.ID = int32(id)
return create, nil
}
func (d *DB) ListStorages(ctx context.Context, find *store.FindStorage) ([]*store.Storage, error) {
where, args := []string{"1 = 1"}, []any{}
if find.ID != nil {
where, args = append(where, "`id` = ?"), append(args, *find.ID)
}
rows, err := d.db.QueryContext(ctx, "SELECT `id`, `name`, `type`, `config` FROM `storage` WHERE "+strings.Join(where, " AND ")+" ORDER BY `id` DESC",
args...,
)
if err != nil {
return nil, err
}
defer rows.Close()
list := []*store.Storage{}
for rows.Next() {
storage := &store.Storage{}
if err := rows.Scan(
&storage.ID,
&storage.Name,
&storage.Type,
&storage.Config,
); err != nil {
return nil, err
}
list = append(list, storage)
}
if err := rows.Err(); err != nil {
return nil, err
}
return list, nil
}
func (d *DB) UpdateStorage(ctx context.Context, update *store.UpdateStorage) (*store.Storage, error) {
set, args := []string{}, []any{}
if update.Name != nil {
set = append(set, "`name` = ?")
args = append(args, *update.Name)
}
if update.Config != nil {
set = append(set, "`config` = ?")
args = append(args, *update.Config)
}
args = append(args, update.ID)
stmt := "UPDATE `storage` SET " + strings.Join(set, ", ") + " WHERE `id` = ?"
_, err := d.db.ExecContext(ctx, stmt, args...)
if err != nil {
return nil, err
}
storage := &store.Storage{}
stmt = "SELECT `id`, `name`, `type`, `config` FROM `storage` WHERE `id` = ?"
if err := d.db.QueryRowContext(ctx, stmt, update.ID).Scan(
&storage.ID,
&storage.Name,
&storage.Type,
&storage.Config,
); err != nil {
return nil, err
}
return storage, nil
}
func (d *DB) DeleteStorage(ctx context.Context, delete *store.DeleteStorage) error {
stmt := "DELETE FROM `storage` WHERE `id` = ?"
result, err := d.db.ExecContext(ctx, stmt, delete.ID)
if err != nil {
return err
}
if _, err := result.RowsAffected(); err != nil {
return err
}
return nil
}
package postgres
import (
"context"
"strings"
"github.com/usememos/memos/store"
)
func (d *DB) CreateStorage(ctx context.Context, create *store.Storage) (*store.Storage, error) {
fields := []string{"name", "type", "config"}
args := []any{create.Name, create.Type, create.Config}
stmt := "INSERT INTO storage (" + strings.Join(fields, ", ") + ") VALUES (" + placeholders(len(args)) + ") RETURNING id"
if err := d.db.QueryRowContext(ctx, stmt, args...).Scan(
&create.ID,
); err != nil {
return nil, err
}
storage := create
return storage, nil
}
func (d *DB) ListStorages(ctx context.Context, find *store.FindStorage) ([]*store.Storage, error) {
where, args := []string{"1 = 1"}, []any{}
if find.ID != nil {
where, args = append(where, "id = "+placeholder(len(args)+1)), append(args, *find.ID)
}
rows, err := d.db.QueryContext(ctx, `
SELECT
id,
name,
type,
config
FROM storage
WHERE `+strings.Join(where, " AND ")+`
ORDER BY id DESC`,
args...,
)
if err != nil {
return nil, err
}
defer rows.Close()
list := []*store.Storage{}
for rows.Next() {
storage := &store.Storage{}
if err := rows.Scan(
&storage.ID,
&storage.Name,
&storage.Type,
&storage.Config,
); err != nil {
return nil, err
}
list = append(list, storage)
}
if err := rows.Err(); err != nil {
return nil, err
}
return list, nil
}
func (d *DB) UpdateStorage(ctx context.Context, update *store.UpdateStorage) (*store.Storage, error) {
set, args := []string{}, []any{}
if update.Name != nil {
set, args = append(set, "name = "+placeholder(len(args)+1)), append(args, *update.Name)
}
if update.Config != nil {
set, args = append(set, "config = "+placeholder(len(args)+1)), append(args, *update.Config)
}
stmt := `UPDATE storage SET ` + strings.Join(set, ", ") + ` WHERE id = ` + placeholder(len(args)+1) + ` RETURNING id, name, type, config`
args = append(args, update.ID)
storage := &store.Storage{}
if err := d.db.QueryRowContext(ctx, stmt, args...).Scan(
&storage.ID,
&storage.Name,
&storage.Type,
&storage.Config,
); err != nil {
return nil, err
}
return storage, nil
}
func (d *DB) DeleteStorage(ctx context.Context, delete *store.DeleteStorage) error {
stmt := `DELETE FROM storage WHERE id = $1`
result, err := d.db.ExecContext(ctx, stmt, delete.ID)
if err != nil {
return err
}
if _, err := result.RowsAffected(); err != nil {
return err
}
return nil
}
package sqlite
import (
"context"
"strings"
"github.com/usememos/memos/store"
)
func (d *DB) CreateStorage(ctx context.Context, create *store.Storage) (*store.Storage, error) {
fields := []string{"`name`", "`type`", "`config`"}
placeholder := []string{"?", "?", "?"}
args := []any{create.Name, create.Type, create.Config}
stmt := "INSERT INTO `storage` (" + strings.Join(fields, ", ") + ") VALUES (" + strings.Join(placeholder, ", ") + ") RETURNING `id`"
if err := d.db.QueryRowContext(ctx, stmt, args...).Scan(
&create.ID,
); err != nil {
return nil, err
}
storage := create
return storage, nil
}
func (d *DB) ListStorages(ctx context.Context, find *store.FindStorage) ([]*store.Storage, error) {
where, args := []string{"1 = 1"}, []any{}
if find.ID != nil {
where, args = append(where, "id = ?"), append(args, *find.ID)
}
rows, err := d.db.QueryContext(ctx, `
SELECT
id,
name,
type,
config
FROM storage
WHERE `+strings.Join(where, " AND ")+`
ORDER BY id DESC`,
args...,
)
if err != nil {
return nil, err
}
defer rows.Close()
list := []*store.Storage{}
for rows.Next() {
storage := &store.Storage{}
if err := rows.Scan(
&storage.ID,
&storage.Name,
&storage.Type,
&storage.Config,
); err != nil {
return nil, err
}
list = append(list, storage)
}
if err := rows.Err(); err != nil {
return nil, err
}
return list, nil
}
func (d *DB) UpdateStorage(ctx context.Context, update *store.UpdateStorage) (*store.Storage, error) {
set, args := []string{}, []any{}
if update.Name != nil {
set = append(set, "name = ?")
args = append(args, *update.Name)
}
if update.Config != nil {
set = append(set, "config = ?")
args = append(args, *update.Config)
}
args = append(args, update.ID)
stmt := `
UPDATE storage
SET ` + strings.Join(set, ", ") + `
WHERE id = ?
RETURNING
id,
name,
type,
config
`
storage := &store.Storage{}
if err := d.db.QueryRowContext(ctx, stmt, args...).Scan(
&storage.ID,
&storage.Name,
&storage.Type,
&storage.Config,
); err != nil {
return nil, err
}
return storage, nil
}
func (d *DB) DeleteStorage(ctx context.Context, delete *store.DeleteStorage) error {
stmt := `
DELETE FROM storage
WHERE id = ?
`
result, err := d.db.ExecContext(ctx, stmt, delete.ID)
if err != nil {
return err
}
if _, err := result.RowsAffected(); err != nil {
return err
}
return nil
}
...@@ -73,12 +73,6 @@ type Driver interface { ...@@ -73,12 +73,6 @@ type Driver interface {
ListTags(ctx context.Context, find *FindTag) ([]*Tag, error) ListTags(ctx context.Context, find *FindTag) ([]*Tag, error)
DeleteTag(ctx context.Context, delete *DeleteTag) error DeleteTag(ctx context.Context, delete *DeleteTag) error
// Storage model related methods.
CreateStorage(ctx context.Context, create *Storage) (*Storage, error)
ListStorages(ctx context.Context, find *FindStorage) ([]*Storage, error)
UpdateStorage(ctx context.Context, update *UpdateStorage) (*Storage, error)
DeleteStorage(ctx context.Context, delete *DeleteStorage) error
// Inbox model related methods. // Inbox model related methods.
CreateInbox(ctx context.Context, create *Inbox) (*Inbox, error) CreateInbox(ctx context.Context, create *Inbox) (*Inbox, error)
ListInboxes(ctx context.Context, find *FindInbox) ([]*Inbox, error) ListInboxes(ctx context.Context, find *FindInbox) ([]*Inbox, error)
......
package store
import (
"context"
"github.com/pkg/errors"
"google.golang.org/protobuf/encoding/protojson"
storepb "github.com/usememos/memos/proto/gen/store"
)
type Storage struct {
ID int32
Name string
Type string
Config string
}
type FindStorage struct {
ID *int32
}
type UpdateStorage struct {
ID int32
Name *string
Config *string
}
type DeleteStorage struct {
ID int32
}
func (s *Store) CreateStorage(ctx context.Context, create *storepb.Storage) (*storepb.Storage, error) {
storageRaw := &Storage{
Name: create.Name,
Type: create.Type.String(),
}
if create.Type == storepb.Storage_S3 {
configBytes, err := protojson.Marshal(create.Config.GetS3Config())
if err != nil {
return nil, errors.Wrap(err, "failed to marshal s3 config")
}
storageRaw.Config = string(configBytes)
}
storageRaw, err := s.driver.CreateStorage(ctx, storageRaw)
if err != nil {
return nil, err
}
storage, err := convertStorageFromRaw(storageRaw)
if err != nil {
return nil, err
}
return storage, nil
}
func (s *Store) ListStorages(ctx context.Context, find *FindStorage) ([]*storepb.Storage, error) {
list, err := s.driver.ListStorages(ctx, find)
if err != nil {
return nil, err
}
storages := []*storepb.Storage{}
for _, storageRaw := range list {
storage, err := convertStorageFromRaw(storageRaw)
if err != nil {
return nil, err
}
storages = append(storages, storage)
}
return storages, nil
}
func (s *Store) GetStorage(ctx context.Context, find *FindStorage) (*storepb.Storage, error) {
list, err := s.ListStorages(ctx, find)
if err != nil {
return nil, err
}
if len(list) == 0 {
return nil, nil
}
return list[0], nil
}
type UpdateStorageV1 struct {
ID int32
Type storepb.Storage_Type
Name *string
Config *storepb.StorageConfig
}
func (s *Store) UpdateStorage(ctx context.Context, update *UpdateStorageV1) (*storepb.Storage, error) {
updateRaw := &UpdateStorage{
ID: update.ID,
}
if update.Name != nil {
updateRaw.Name = update.Name
}
if update.Config != nil {
configRaw, err := convertStorageConfigToRaw(update.Type, update.Config)
if err != nil {
return nil, err
}
updateRaw.Config = &configRaw
}
storageRaw, err := s.driver.UpdateStorage(ctx, updateRaw)
if err != nil {
return nil, err
}
storage, err := convertStorageFromRaw(storageRaw)
if err != nil {
return nil, err
}
return storage, nil
}
func (s *Store) DeleteStorage(ctx context.Context, delete *DeleteStorage) error {
return s.driver.DeleteStorage(ctx, delete)
}
func convertStorageFromRaw(storageRaw *Storage) (*storepb.Storage, error) {
storage := &storepb.Storage{
Id: storageRaw.ID,
Name: storageRaw.Name,
Type: storepb.Storage_Type(storepb.Storage_Type_value[storageRaw.Type]),
}
storageConfig, err := convertStorageConfigFromRaw(storage.Type, storageRaw.Config)
if err != nil {
return nil, err
}
storage.Config = storageConfig
return storage, nil
}
func convertStorageConfigFromRaw(storageType storepb.Storage_Type, configRaw string) (*storepb.StorageConfig, error) {
storageConfig := &storepb.StorageConfig{}
if storageType == storepb.Storage_S3 {
s3Config := &storepb.S3Config{}
err := protojsonUnmarshaler.Unmarshal([]byte(configRaw), s3Config)
if err != nil {
return nil, err
}
storageConfig.StorageConfig = &storepb.StorageConfig_S3Config{S3Config: s3Config}
}
return storageConfig, nil
}
func convertStorageConfigToRaw(storageType storepb.Storage_Type, config *storepb.StorageConfig) (string, error) {
raw := ""
if storageType == storepb.Storage_S3 {
bytes, err := protojson.Marshal(config.GetS3Config())
if err != nil {
return "", err
}
raw = string(bytes)
}
return raw, nil
}
package teststore
import (
"context"
"testing"
"github.com/stretchr/testify/require"
storepb "github.com/usememos/memos/proto/gen/store"
"github.com/usememos/memos/store"
)
func TestStorageStore(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
storage, err := ts.CreateStorage(ctx, &storepb.Storage{
Name: "test_storage",
Type: storepb.Storage_S3,
Config: &storepb.StorageConfig{
StorageConfig: &storepb.StorageConfig_S3Config{
S3Config: &storepb.S3Config{
EndPoint: "http://localhost:9000",
},
},
},
})
require.NoError(t, err)
newStorageName := "new_storage_name"
updatedStorage, err := ts.UpdateStorage(ctx, &store.UpdateStorageV1{
ID: storage.Id,
Type: storage.Type,
Name: &newStorageName,
})
require.NoError(t, err)
require.Equal(t, newStorageName, updatedStorage.Name)
storageList, err := ts.ListStorages(ctx, &store.FindStorage{})
require.NoError(t, err)
require.Equal(t, 1, len(storageList))
require.Equal(t, updatedStorage, storageList[0])
err = ts.DeleteStorage(ctx, &store.DeleteStorage{
ID: storage.Id,
})
require.NoError(t, err)
storageList, err = ts.ListStorages(ctx, &store.FindStorage{})
require.NoError(t, err)
require.Equal(t, 0, len(storageList))
ts.Close()
}
...@@ -2,7 +2,7 @@ import { useColorScheme } from "@mui/joy"; ...@@ -2,7 +2,7 @@ import { useColorScheme } from "@mui/joy";
import { useEffect } from "react"; import { useEffect } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Outlet } from "react-router-dom"; import { Outlet } from "react-router-dom";
import storage from "./helpers/storage"; import useLocalStorage from "react-use/lib/useLocalStorage";
import { getSystemColorScheme } from "./helpers/utils"; import { getSystemColorScheme } from "./helpers/utils";
import useNavigateTo from "./hooks/useNavigateTo"; import useNavigateTo from "./hooks/useNavigateTo";
import { useCommonContext } from "./layouts/CommonContextProvider"; import { useCommonContext } from "./layouts/CommonContextProvider";
...@@ -16,8 +16,11 @@ const App = () => { ...@@ -16,8 +16,11 @@ const App = () => {
const workspaceSettingStore = useWorkspaceSettingStore(); const workspaceSettingStore = useWorkspaceSettingStore();
const userStore = useUserStore(); const userStore = useUserStore();
const commonContext = useCommonContext(); const commonContext = useCommonContext();
const [, setLocale] = useLocalStorage("locale", "en");
const [, setAppearance] = useLocalStorage("appearance", "system");
const workspaceProfile = commonContext.profile; const workspaceProfile = commonContext.profile;
const userSetting = userStore.userSetting; const userSetting = userStore.userSetting;
const workspaceGeneralSetting = const workspaceGeneralSetting =
workspaceSettingStore.getWorkspaceSettingByKey(WorkspaceSettingKey.WORKSPACE_SETTING_GENERAL).generalSetting || workspaceSettingStore.getWorkspaceSettingByKey(WorkspaceSettingKey.WORKSPACE_SETTING_GENERAL).generalSetting ||
WorkspaceGeneralSetting.fromPartial({}); WorkspaceGeneralSetting.fromPartial({});
...@@ -86,9 +89,7 @@ const App = () => { ...@@ -86,9 +89,7 @@ const App = () => {
} else { } else {
document.documentElement.setAttribute("dir", "ltr"); document.documentElement.setAttribute("dir", "ltr");
} }
storage.set({ setLocale(currentLocale);
locale: currentLocale,
});
}, [commonContext.locale]); }, [commonContext.locale]);
useEffect(() => { useEffect(() => {
...@@ -97,9 +98,7 @@ const App = () => { ...@@ -97,9 +98,7 @@ const App = () => {
currentAppearance = getSystemColorScheme(); currentAppearance = getSystemColorScheme();
} }
setMode(currentAppearance); setMode(currentAppearance);
storage.set({ setAppearance(currentAppearance);
appearance: currentAppearance,
});
}, [commonContext.appearance]); }, [commonContext.appearance]);
useEffect(() => { useEffect(() => {
......
/**
* Define storage data type
*/
interface StorageData {
// locale
locale: Locale;
// appearance
appearance: Appearance;
}
type StorageKey = keyof StorageData;
const storage = {
get: (keys: StorageKey[]): Partial<StorageData> => {
const data: Partial<StorageData> = {};
for (const key of keys) {
try {
const stringifyValue = localStorage.getItem(key);
if (stringifyValue !== null) {
const val = JSON.parse(stringifyValue);
data[key] = val;
}
} catch (error: any) {
console.error("Get storage failed in ", key, error);
}
}
return data;
},
set: (data: Partial<StorageData>) => {
for (const key in data) {
try {
const stringifyValue = JSON.stringify(data[key as StorageKey]);
localStorage.setItem(key, stringifyValue);
} catch (error: any) {
console.error("Save storage failed in ", key, error);
}
}
},
remove: (keys: StorageKey[]) => {
for (const key of keys) {
try {
localStorage.removeItem(key);
} catch (error: any) {
console.error("Remove storage failed in ", key, error);
}
}
},
};
export default storage;
import { createContext, useContext, useEffect, useState } from "react"; import { createContext, useContext, useEffect, useState } from "react";
import useLocalStorage from "react-use/lib/useLocalStorage";
import { workspaceServiceClient } from "@/grpcweb"; import { workspaceServiceClient } from "@/grpcweb";
import storage from "@/helpers/storage";
import { useUserStore, useWorkspaceSettingStore } from "@/store/v1"; import { useUserStore, useWorkspaceSettingStore } from "@/store/v1";
import { WorkspaceProfile } from "@/types/proto/api/v1/workspace_service"; import { WorkspaceProfile } from "@/types/proto/api/v1/workspace_service";
import { WorkspaceGeneralSetting, WorkspaceSettingKey } from "@/types/proto/store/workspace_setting"; import { WorkspaceGeneralSetting, WorkspaceSettingKey } from "@/types/proto/store/workspace_setting";
...@@ -30,6 +30,8 @@ const CommonContextProvider = ({ children }: { children: React.ReactNode }) => { ...@@ -30,6 +30,8 @@ const CommonContextProvider = ({ children }: { children: React.ReactNode }) => {
appearance: "system", appearance: "system",
profile: WorkspaceProfile.fromPartial({}), profile: WorkspaceProfile.fromPartial({}),
}); });
const [locale] = useLocalStorage("locale", "en");
const [appearance] = useLocalStorage("appearance", "system");
useEffect(() => { useEffect(() => {
const initialWorkspace = async () => { const initialWorkspace = async () => {
...@@ -39,8 +41,6 @@ const CommonContextProvider = ({ children }: { children: React.ReactNode }) => { ...@@ -39,8 +41,6 @@ const CommonContextProvider = ({ children }: { children: React.ReactNode }) => {
const workspaceGeneralSetting = const workspaceGeneralSetting =
workspaceSettingStore.getWorkspaceSettingByKey(WorkspaceSettingKey.WORKSPACE_SETTING_GENERAL).generalSetting || workspaceSettingStore.getWorkspaceSettingByKey(WorkspaceSettingKey.WORKSPACE_SETTING_GENERAL).generalSetting ||
WorkspaceGeneralSetting.fromPartial({}); WorkspaceGeneralSetting.fromPartial({});
const { locale } = storage.get(["locale"]);
const { appearance } = storage.get(["appearance"]);
setCommonContext({ setCommonContext({
locale: locale || workspaceGeneralSetting.customProfile?.locale || "en", locale: locale || workspaceGeneralSetting.customProfile?.locale || "en",
appearance: appearance || workspaceGeneralSetting.customProfile?.appearance || "system", appearance: appearance || workspaceGeneralSetting.customProfile?.appearance || "system",
......
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