mirror of
https://github.com/usememos/memos.git
synced 2025-12-18 06:41:32 +08:00
Remove work-related terminology by renaming "workspace" to "instance" across the entire application. This change better reflects that Memos is a self-hosted tool suitable for personal and non-work use cases. Breaking Changes: - API endpoints: /api/v1/workspace/* → /api/v1/instance/* - gRPC service: WorkspaceService → InstanceService - Proto types: WorkspaceSetting → InstanceSetting - Frontend translation keys: workspace-section → instance-section Backend Changes: - Renamed proto definitions and regenerated code - Updated all store layer methods and database drivers - Renamed service implementations and API handlers - Updated cache from workspaceSettingCache to instanceSettingCache Frontend Changes: - Renamed service client: workspaceServiceClient → instanceServiceClient - Updated all React components and state management - Refactored stores: workspace.ts → instance.ts - Updated all 32 locale translation files All tests pass and both backend and frontend build successfully. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
307 lines
11 KiB
Go
307 lines
11 KiB
Go
package v1
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/pkg/errors"
|
|
"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"
|
|
)
|
|
|
|
// GetInstanceProfile returns the instance profile.
|
|
func (s *APIV1Service) GetInstanceProfile(ctx context.Context, _ *v1pb.GetInstanceProfileRequest) (*v1pb.InstanceProfile, error) {
|
|
instanceProfile := &v1pb.InstanceProfile{
|
|
Version: s.Profile.Version,
|
|
Mode: s.Profile.Mode,
|
|
InstanceUrl: s.Profile.InstanceURL,
|
|
}
|
|
owner, err := s.GetInstanceOwner(ctx)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "failed to get instance owner: %v", err)
|
|
}
|
|
if owner != nil {
|
|
instanceProfile.Owner = owner.Name
|
|
}
|
|
return instanceProfile, nil
|
|
}
|
|
|
|
func (s *APIV1Service) GetInstanceSetting(ctx context.Context, request *v1pb.GetInstanceSettingRequest) (*v1pb.InstanceSetting, error) {
|
|
instanceSettingKeyString, err := ExtractInstanceSettingKeyFromName(request.Name)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.InvalidArgument, "invalid instance setting name: %v", err)
|
|
}
|
|
|
|
instanceSettingKey := storepb.InstanceSettingKey(storepb.InstanceSettingKey_value[instanceSettingKeyString])
|
|
// Get instance setting from store with default value.
|
|
switch instanceSettingKey {
|
|
case storepb.InstanceSettingKey_BASIC:
|
|
_, err = s.Store.GetInstanceBasicSetting(ctx)
|
|
case storepb.InstanceSettingKey_GENERAL:
|
|
_, err = s.Store.GetInstanceGeneralSetting(ctx)
|
|
case storepb.InstanceSettingKey_MEMO_RELATED:
|
|
_, err = s.Store.GetInstanceMemoRelatedSetting(ctx)
|
|
case storepb.InstanceSettingKey_STORAGE:
|
|
_, err = s.Store.GetInstanceStorageSetting(ctx)
|
|
default:
|
|
return nil, status.Errorf(codes.InvalidArgument, "unsupported instance setting key: %v", instanceSettingKey)
|
|
}
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "failed to get instance setting: %v", err)
|
|
}
|
|
|
|
instanceSetting, err := s.Store.GetInstanceSetting(ctx, &store.FindInstanceSetting{
|
|
Name: instanceSettingKey.String(),
|
|
})
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "failed to get instance setting: %v", err)
|
|
}
|
|
if instanceSetting == nil {
|
|
return nil, status.Errorf(codes.NotFound, "instance setting not found")
|
|
}
|
|
|
|
// For storage setting, only host can get it.
|
|
if instanceSetting.Key == storepb.InstanceSettingKey_STORAGE {
|
|
user, err := s.GetCurrentUser(ctx)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err)
|
|
}
|
|
if user == nil || user.Role != store.RoleHost {
|
|
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
|
}
|
|
}
|
|
|
|
return convertInstanceSettingFromStore(instanceSetting), nil
|
|
}
|
|
|
|
func (s *APIV1Service) UpdateInstanceSetting(ctx context.Context, request *v1pb.UpdateInstanceSettingRequest) (*v1pb.InstanceSetting, error) {
|
|
user, err := s.GetCurrentUser(ctx)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err)
|
|
}
|
|
if user == nil {
|
|
return nil, status.Errorf(codes.Unauthenticated, "user not authenticated")
|
|
}
|
|
if user.Role != store.RoleHost {
|
|
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
|
}
|
|
|
|
// TODO: Apply update_mask if specified
|
|
_ = request.UpdateMask
|
|
|
|
updateSetting := convertInstanceSettingToStore(request.Setting)
|
|
instanceSetting, err := s.Store.UpsertInstanceSetting(ctx, updateSetting)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "failed to upsert instance setting: %v", err)
|
|
}
|
|
|
|
return convertInstanceSettingFromStore(instanceSetting), nil
|
|
}
|
|
|
|
func convertInstanceSettingFromStore(setting *storepb.InstanceSetting) *v1pb.InstanceSetting {
|
|
instanceSetting := &v1pb.InstanceSetting{
|
|
Name: fmt.Sprintf("instance/settings/%s", setting.Key.String()),
|
|
}
|
|
switch setting.Value.(type) {
|
|
case *storepb.InstanceSetting_GeneralSetting:
|
|
instanceSetting.Value = &v1pb.InstanceSetting_GeneralSetting_{
|
|
GeneralSetting: convertInstanceGeneralSettingFromStore(setting.GetGeneralSetting()),
|
|
}
|
|
case *storepb.InstanceSetting_StorageSetting:
|
|
instanceSetting.Value = &v1pb.InstanceSetting_StorageSetting_{
|
|
StorageSetting: convertInstanceStorageSettingFromStore(setting.GetStorageSetting()),
|
|
}
|
|
case *storepb.InstanceSetting_MemoRelatedSetting:
|
|
instanceSetting.Value = &v1pb.InstanceSetting_MemoRelatedSetting_{
|
|
MemoRelatedSetting: convertInstanceMemoRelatedSettingFromStore(setting.GetMemoRelatedSetting()),
|
|
}
|
|
}
|
|
return instanceSetting
|
|
}
|
|
|
|
func convertInstanceSettingToStore(setting *v1pb.InstanceSetting) *storepb.InstanceSetting {
|
|
settingKeyString, _ := ExtractInstanceSettingKeyFromName(setting.Name)
|
|
instanceSetting := &storepb.InstanceSetting{
|
|
Key: storepb.InstanceSettingKey(storepb.InstanceSettingKey_value[settingKeyString]),
|
|
Value: &storepb.InstanceSetting_GeneralSetting{
|
|
GeneralSetting: convertInstanceGeneralSettingToStore(setting.GetGeneralSetting()),
|
|
},
|
|
}
|
|
switch instanceSetting.Key {
|
|
case storepb.InstanceSettingKey_GENERAL:
|
|
instanceSetting.Value = &storepb.InstanceSetting_GeneralSetting{
|
|
GeneralSetting: convertInstanceGeneralSettingToStore(setting.GetGeneralSetting()),
|
|
}
|
|
case storepb.InstanceSettingKey_STORAGE:
|
|
instanceSetting.Value = &storepb.InstanceSetting_StorageSetting{
|
|
StorageSetting: convertInstanceStorageSettingToStore(setting.GetStorageSetting()),
|
|
}
|
|
case storepb.InstanceSettingKey_MEMO_RELATED:
|
|
instanceSetting.Value = &storepb.InstanceSetting_MemoRelatedSetting{
|
|
MemoRelatedSetting: convertInstanceMemoRelatedSettingToStore(setting.GetMemoRelatedSetting()),
|
|
}
|
|
default:
|
|
// Keep the default GeneralSetting value
|
|
}
|
|
return instanceSetting
|
|
}
|
|
|
|
func convertInstanceGeneralSettingFromStore(setting *storepb.InstanceGeneralSetting) *v1pb.InstanceSetting_GeneralSetting {
|
|
if setting == nil {
|
|
return nil
|
|
}
|
|
// Backfill theme if empty
|
|
theme := setting.Theme
|
|
if theme == "" {
|
|
theme = "default"
|
|
}
|
|
|
|
generalSetting := &v1pb.InstanceSetting_GeneralSetting{
|
|
Theme: theme,
|
|
DisallowUserRegistration: setting.DisallowUserRegistration,
|
|
DisallowPasswordAuth: setting.DisallowPasswordAuth,
|
|
AdditionalScript: setting.AdditionalScript,
|
|
AdditionalStyle: setting.AdditionalStyle,
|
|
WeekStartDayOffset: setting.WeekStartDayOffset,
|
|
DisallowChangeUsername: setting.DisallowChangeUsername,
|
|
DisallowChangeNickname: setting.DisallowChangeNickname,
|
|
}
|
|
if setting.CustomProfile != nil {
|
|
generalSetting.CustomProfile = &v1pb.InstanceSetting_GeneralSetting_CustomProfile{
|
|
Title: setting.CustomProfile.Title,
|
|
Description: setting.CustomProfile.Description,
|
|
LogoUrl: setting.CustomProfile.LogoUrl,
|
|
Locale: setting.CustomProfile.Locale,
|
|
}
|
|
}
|
|
return generalSetting
|
|
}
|
|
|
|
func convertInstanceGeneralSettingToStore(setting *v1pb.InstanceSetting_GeneralSetting) *storepb.InstanceGeneralSetting {
|
|
if setting == nil {
|
|
return nil
|
|
}
|
|
generalSetting := &storepb.InstanceGeneralSetting{
|
|
Theme: setting.Theme,
|
|
DisallowUserRegistration: setting.DisallowUserRegistration,
|
|
DisallowPasswordAuth: setting.DisallowPasswordAuth,
|
|
AdditionalScript: setting.AdditionalScript,
|
|
AdditionalStyle: setting.AdditionalStyle,
|
|
WeekStartDayOffset: setting.WeekStartDayOffset,
|
|
DisallowChangeUsername: setting.DisallowChangeUsername,
|
|
DisallowChangeNickname: setting.DisallowChangeNickname,
|
|
}
|
|
if setting.CustomProfile != nil {
|
|
generalSetting.CustomProfile = &storepb.InstanceCustomProfile{
|
|
Title: setting.CustomProfile.Title,
|
|
Description: setting.CustomProfile.Description,
|
|
LogoUrl: setting.CustomProfile.LogoUrl,
|
|
Locale: setting.CustomProfile.Locale,
|
|
}
|
|
}
|
|
return generalSetting
|
|
}
|
|
|
|
func convertInstanceStorageSettingFromStore(settingpb *storepb.InstanceStorageSetting) *v1pb.InstanceSetting_StorageSetting {
|
|
if settingpb == nil {
|
|
return nil
|
|
}
|
|
setting := &v1pb.InstanceSetting_StorageSetting{
|
|
StorageType: v1pb.InstanceSetting_StorageSetting_StorageType(settingpb.StorageType),
|
|
FilepathTemplate: settingpb.FilepathTemplate,
|
|
UploadSizeLimitMb: settingpb.UploadSizeLimitMb,
|
|
}
|
|
if settingpb.S3Config != nil {
|
|
setting.S3Config = &v1pb.InstanceSetting_StorageSetting_S3Config{
|
|
AccessKeyId: settingpb.S3Config.AccessKeyId,
|
|
AccessKeySecret: settingpb.S3Config.AccessKeySecret,
|
|
Endpoint: settingpb.S3Config.Endpoint,
|
|
Region: settingpb.S3Config.Region,
|
|
Bucket: settingpb.S3Config.Bucket,
|
|
UsePathStyle: settingpb.S3Config.UsePathStyle,
|
|
}
|
|
}
|
|
return setting
|
|
}
|
|
|
|
func convertInstanceStorageSettingToStore(setting *v1pb.InstanceSetting_StorageSetting) *storepb.InstanceStorageSetting {
|
|
if setting == nil {
|
|
return nil
|
|
}
|
|
settingpb := &storepb.InstanceStorageSetting{
|
|
StorageType: storepb.InstanceStorageSetting_StorageType(setting.StorageType),
|
|
FilepathTemplate: setting.FilepathTemplate,
|
|
UploadSizeLimitMb: setting.UploadSizeLimitMb,
|
|
}
|
|
if setting.S3Config != nil {
|
|
settingpb.S3Config = &storepb.StorageS3Config{
|
|
AccessKeyId: setting.S3Config.AccessKeyId,
|
|
AccessKeySecret: setting.S3Config.AccessKeySecret,
|
|
Endpoint: setting.S3Config.Endpoint,
|
|
Region: setting.S3Config.Region,
|
|
Bucket: setting.S3Config.Bucket,
|
|
UsePathStyle: setting.S3Config.UsePathStyle,
|
|
}
|
|
}
|
|
return settingpb
|
|
}
|
|
|
|
func convertInstanceMemoRelatedSettingFromStore(setting *storepb.InstanceMemoRelatedSetting) *v1pb.InstanceSetting_MemoRelatedSetting {
|
|
if setting == nil {
|
|
return nil
|
|
}
|
|
return &v1pb.InstanceSetting_MemoRelatedSetting{
|
|
DisallowPublicVisibility: setting.DisallowPublicVisibility,
|
|
DisplayWithUpdateTime: setting.DisplayWithUpdateTime,
|
|
ContentLengthLimit: setting.ContentLengthLimit,
|
|
EnableDoubleClickEdit: setting.EnableDoubleClickEdit,
|
|
EnableLinkPreview: setting.EnableLinkPreview,
|
|
Reactions: setting.Reactions,
|
|
DisableMarkdownShortcuts: setting.DisableMarkdownShortcuts,
|
|
EnableBlurNsfwContent: setting.EnableBlurNsfwContent,
|
|
NsfwTags: setting.NsfwTags,
|
|
}
|
|
}
|
|
|
|
func convertInstanceMemoRelatedSettingToStore(setting *v1pb.InstanceSetting_MemoRelatedSetting) *storepb.InstanceMemoRelatedSetting {
|
|
if setting == nil {
|
|
return nil
|
|
}
|
|
return &storepb.InstanceMemoRelatedSetting{
|
|
DisallowPublicVisibility: setting.DisallowPublicVisibility,
|
|
DisplayWithUpdateTime: setting.DisplayWithUpdateTime,
|
|
ContentLengthLimit: setting.ContentLengthLimit,
|
|
EnableDoubleClickEdit: setting.EnableDoubleClickEdit,
|
|
EnableLinkPreview: setting.EnableLinkPreview,
|
|
Reactions: setting.Reactions,
|
|
DisableMarkdownShortcuts: setting.DisableMarkdownShortcuts,
|
|
EnableBlurNsfwContent: setting.EnableBlurNsfwContent,
|
|
NsfwTags: setting.NsfwTags,
|
|
}
|
|
}
|
|
|
|
var ownerCache *v1pb.User
|
|
|
|
func (s *APIV1Service) GetInstanceOwner(ctx context.Context) (*v1pb.User, error) {
|
|
if ownerCache != nil {
|
|
return ownerCache, nil
|
|
}
|
|
|
|
hostUserType := store.RoleHost
|
|
user, err := s.Store.GetUser(ctx, &store.FindUser{
|
|
Role: &hostUserType,
|
|
})
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to find owner")
|
|
}
|
|
if user == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
ownerCache = convertUserFromStore(user)
|
|
return ownerCache, nil
|
|
}
|