From ba099b72ed43a3616173f9abca05eefca6e0aa4e Mon Sep 17 00:00:00 2001 From: Steven Date: Thu, 22 Jan 2026 20:59:40 +0800 Subject: [PATCH] feat: update InstanceProfile to include initialization status - Removed the owner field from InstanceProfile as it is no longer needed. - Added an initialized field to InstanceProfile to indicate if the instance has completed first-time setup. - Updated GetInstanceProfile method to set initialized based on the existence of an admin user. - Modified tests to reflect changes in InstanceProfile and ensure correct behavior regarding instance initialization. - Adjusted frontend logic to redirect users based on the initialized status instead of the owner field. --- proto/api/v1/instance_service.proto | 9 +-- proto/gen/api/v1/instance_service.pb.go | 31 +++++----- proto/gen/openapi.yaml | 11 ++-- server/router/api/v1/instance_service.go | 19 +++--- .../api/v1/test/instance_owner_cache_test.go | 58 +++++++++++++++++++ .../api/v1/test/instance_service_test.go | 19 +++--- server/router/api/v1/user_service.go | 6 ++ web/src/App.tsx | 6 +- web/src/pages/Home.tsx | 2 +- web/src/pages/SignUp.tsx | 14 ++++- .../types/proto/api/v1/activity_service_pb.ts | 2 +- .../proto/api/v1/attachment_service_pb.ts | 2 +- web/src/types/proto/api/v1/auth_service_pb.ts | 2 +- web/src/types/proto/api/v1/common_pb.ts | 2 +- web/src/types/proto/api/v1/idp_service_pb.ts | 2 +- .../types/proto/api/v1/instance_service_pb.ts | 21 +++---- web/src/types/proto/api/v1/memo_service_pb.ts | 2 +- .../types/proto/api/v1/shortcut_service_pb.ts | 2 +- web/src/types/proto/api/v1/user_service_pb.ts | 2 +- .../types/proto/google/api/annotations_pb.ts | 2 +- web/src/types/proto/google/api/client_pb.ts | 2 +- .../proto/google/api/field_behavior_pb.ts | 2 +- web/src/types/proto/google/api/http_pb.ts | 2 +- .../types/proto/google/api/launch_stage_pb.ts | 2 +- web/src/types/proto/google/api/resource_pb.ts | 2 +- 25 files changed, 151 insertions(+), 73 deletions(-) create mode 100644 server/router/api/v1/test/instance_owner_cache_test.go diff --git a/proto/api/v1/instance_service.proto b/proto/api/v1/instance_service.proto index ad0ff146c..baeea0bd9 100644 --- a/proto/api/v1/instance_service.proto +++ b/proto/api/v1/instance_service.proto @@ -34,10 +34,6 @@ service InstanceService { // Instance profile message containing basic instance information. message InstanceProfile { - // The name of instance owner. - // Format: users/{user} - string owner = 1; - // Version is the current version of instance. string version = 2; @@ -46,6 +42,11 @@ message InstanceProfile { // Instance URL is the URL of the instance. string instance_url = 6; + + // Indicates if the instance has completed first-time setup. + // When false, the instance requires initialization (creating the first admin account). + // This follows the pattern used by other self-hosted platforms for setup workflows. + bool initialized = 7; } // Request for instance profile. diff --git a/proto/gen/api/v1/instance_service.pb.go b/proto/gen/api/v1/instance_service.pb.go index bcb03d5b8..edef32e91 100644 --- a/proto/gen/api/v1/instance_service.pb.go +++ b/proto/gen/api/v1/instance_service.pb.go @@ -138,15 +138,16 @@ func (InstanceSetting_StorageSetting_StorageType) EnumDescriptor() ([]byte, []in // Instance profile message containing basic instance information. type InstanceProfile struct { state protoimpl.MessageState `protogen:"open.v1"` - // The name of instance owner. - // Format: users/{user} - Owner string `protobuf:"bytes,1,opt,name=owner,proto3" json:"owner,omitempty"` // Version is the current version of instance. Version string `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"` // Demo indicates if the instance is in demo mode. Demo bool `protobuf:"varint,3,opt,name=demo,proto3" json:"demo,omitempty"` // Instance URL is the URL of the instance. - InstanceUrl string `protobuf:"bytes,6,opt,name=instance_url,json=instanceUrl,proto3" json:"instance_url,omitempty"` + InstanceUrl string `protobuf:"bytes,6,opt,name=instance_url,json=instanceUrl,proto3" json:"instance_url,omitempty"` + // Indicates if the instance has completed first-time setup. + // When false, the instance requires initialization (creating the first admin account). + // This follows the pattern used by other self-hosted platforms for setup workflows. + Initialized bool `protobuf:"varint,7,opt,name=initialized,proto3" json:"initialized,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -181,13 +182,6 @@ func (*InstanceProfile) Descriptor() ([]byte, []int) { return file_api_v1_instance_service_proto_rawDescGZIP(), []int{0} } -func (x *InstanceProfile) GetOwner() string { - if x != nil { - return x.Owner - } - return "" -} - func (x *InstanceProfile) GetVersion() string { if x != nil { return x.Version @@ -209,6 +203,13 @@ func (x *InstanceProfile) GetInstanceUrl() string { return "" } +func (x *InstanceProfile) GetInitialized() bool { + if x != nil { + return x.Initialized + } + return false +} + // Request for instance profile. type GetInstanceProfileRequest struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -875,12 +876,12 @@ var File_api_v1_instance_service_proto protoreflect.FileDescriptor const file_api_v1_instance_service_proto_rawDesc = "" + "\n" + - "\x1dapi/v1/instance_service.proto\x12\fmemos.api.v1\x1a\x1cgoogle/api/annotations.proto\x1a\x17google/api/client.proto\x1a\x1fgoogle/api/field_behavior.proto\x1a\x19google/api/resource.proto\x1a google/protobuf/field_mask.proto\"x\n" + - "\x0fInstanceProfile\x12\x14\n" + - "\x05owner\x18\x01 \x01(\tR\x05owner\x12\x18\n" + + "\x1dapi/v1/instance_service.proto\x12\fmemos.api.v1\x1a\x1cgoogle/api/annotations.proto\x1a\x17google/api/client.proto\x1a\x1fgoogle/api/field_behavior.proto\x1a\x19google/api/resource.proto\x1a google/protobuf/field_mask.proto\"\x84\x01\n" + + "\x0fInstanceProfile\x12\x18\n" + "\aversion\x18\x02 \x01(\tR\aversion\x12\x12\n" + "\x04demo\x18\x03 \x01(\bR\x04demo\x12!\n" + - "\finstance_url\x18\x06 \x01(\tR\vinstanceUrl\"\x1b\n" + + "\finstance_url\x18\x06 \x01(\tR\vinstanceUrl\x12 \n" + + "\vinitialized\x18\a \x01(\bR\vinitialized\"\x1b\n" + "\x19GetInstanceProfileRequest\"\x99\x0f\n" + "\x0fInstanceSetting\x12\x17\n" + "\x04name\x18\x01 \x01(\tB\x03\xe0A\bR\x04name\x12W\n" + diff --git a/proto/gen/openapi.yaml b/proto/gen/openapi.yaml index bdf5ec0be..3cbc36ebf 100644 --- a/proto/gen/openapi.yaml +++ b/proto/gen/openapi.yaml @@ -2132,11 +2132,6 @@ components: InstanceProfile: type: object properties: - owner: - type: string - description: |- - The name of instance owner. - Format: users/{user} version: type: string description: Version is the current version of instance. @@ -2146,6 +2141,12 @@ components: instanceUrl: type: string description: Instance URL is the URL of the instance. + initialized: + type: boolean + description: |- + Indicates if the instance has completed first-time setup. + When false, the instance requires initialization (creating the first admin account). + This follows the pattern used by other self-hosted platforms for setup workflows. description: Instance profile message containing basic instance information. InstanceSetting: type: object diff --git a/server/router/api/v1/instance_service.go b/server/router/api/v1/instance_service.go index 247e993ca..9d0f7fac0 100644 --- a/server/router/api/v1/instance_service.go +++ b/server/router/api/v1/instance_service.go @@ -15,17 +15,16 @@ import ( // GetInstanceProfile returns the instance profile. func (s *APIV1Service) GetInstanceProfile(ctx context.Context, _ *v1pb.GetInstanceProfileRequest) (*v1pb.InstanceProfile, error) { - instanceProfile := &v1pb.InstanceProfile{ - Version: s.Profile.Version, - Demo: s.Profile.Demo, - 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 + + instanceProfile := &v1pb.InstanceProfile{ + Version: s.Profile.Version, + Demo: s.Profile.Demo, + InstanceUrl: s.Profile.InstanceURL, + Initialized: owner != nil, } return instanceProfile, nil } @@ -291,3 +290,9 @@ func (s *APIV1Service) GetInstanceOwner(ctx context.Context) (*v1pb.User, error) ownerCache = convertUserFromStore(user) return ownerCache, nil } + +// ClearInstanceOwnerCache clears the cached instance owner. +// This should be called when an admin user is created or when the owner changes. +func (s *APIV1Service) ClearInstanceOwnerCache() { + ownerCache = nil +} diff --git a/server/router/api/v1/test/instance_owner_cache_test.go b/server/router/api/v1/test/instance_owner_cache_test.go new file mode 100644 index 000000000..032bd3435 --- /dev/null +++ b/server/router/api/v1/test/instance_owner_cache_test.go @@ -0,0 +1,58 @@ +package test + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + v1pb "github.com/usememos/memos/proto/gen/api/v1" +) + +func TestInstanceOwnerCache(t *testing.T) { + ctx := context.Background() + + t.Run("Instance becomes initialized after first admin user is created", func(t *testing.T) { + // Create test service + ts := NewTestService(t) + defer ts.Cleanup() + + // Verify instance is not initialized initially + profile1, err := ts.Service.GetInstanceProfile(ctx, &v1pb.GetInstanceProfileRequest{}) + require.NoError(t, err) + require.False(t, profile1.Initialized, "Instance should not be initialized before first admin user") + + // Create the first admin user + user, err := ts.CreateHostUser(ctx, "admin") + require.NoError(t, err) + require.NotNil(t, user) + + // Verify instance is now initialized + profile2, err := ts.Service.GetInstanceProfile(ctx, &v1pb.GetInstanceProfileRequest{}) + require.NoError(t, err) + require.True(t, profile2.Initialized, "Instance should be initialized after first admin user is created") + }) + + t.Run("ClearInstanceOwnerCache works correctly", func(t *testing.T) { + // Create test service + ts := NewTestService(t) + defer ts.Cleanup() + + // Create admin user + _, err := ts.CreateHostUser(ctx, "admin") + require.NoError(t, err) + + // Verify initialized + profile1, err := ts.Service.GetInstanceProfile(ctx, &v1pb.GetInstanceProfileRequest{}) + require.NoError(t, err) + require.True(t, profile1.Initialized) + + // Clear cache + ts.Service.ClearInstanceOwnerCache() + + // Should still be initialized (cache is refilled from DB) + profile2, err := ts.Service.GetInstanceProfile(ctx, &v1pb.GetInstanceProfileRequest{}) + require.NoError(t, err) + require.True(t, profile2.Initialized) + }) +} diff --git a/server/router/api/v1/test/instance_service_test.go b/server/router/api/v1/test/instance_service_test.go index 7d319a55d..383c25319 100644 --- a/server/router/api/v1/test/instance_service_test.go +++ b/server/router/api/v1/test/instance_service_test.go @@ -2,7 +2,6 @@ package test import ( "context" - "fmt" "testing" "github.com/stretchr/testify/require" @@ -31,11 +30,11 @@ func TestGetInstanceProfile(t *testing.T) { require.True(t, resp.Demo) require.Equal(t, "http://localhost:8080", resp.InstanceUrl) - // Owner should be empty since no users are created - require.Empty(t, resp.Owner) + // Instance should not be initialized since no admin users are created + require.False(t, resp.Initialized) }) - t.Run("GetInstanceProfile with owner", func(t *testing.T) { + t.Run("GetInstanceProfile with initialized instance", func(t *testing.T) { // Create test service for this specific test ts := NewTestService(t) defer ts.Cleanup() @@ -53,14 +52,13 @@ func TestGetInstanceProfile(t *testing.T) { require.NoError(t, err) require.NotNil(t, resp) - // Verify the response contains expected data including owner + // Verify the response contains expected data with initialized flag require.Equal(t, "test-1.0.0", resp.Version) require.True(t, resp.Demo) require.Equal(t, "http://localhost:8080", resp.InstanceUrl) - // User name should be "users/{id}" format where id is the user's ID - expectedOwnerName := fmt.Sprintf("users/%d", hostUser.ID) - require.Equal(t, expectedOwnerName, resp.Owner) + // Instance should be initialized since an admin user exists + require.True(t, resp.Initialized) }) } @@ -73,9 +71,8 @@ func TestGetInstanceProfile_Concurrency(t *testing.T) { defer ts.Cleanup() // Create a host user - hostUser, err := ts.CreateHostUser(ctx, "admin") + _, err := ts.CreateHostUser(ctx, "admin") require.NoError(t, err) - expectedOwnerName := fmt.Sprintf("users/%d", hostUser.ID) // Make concurrent requests numGoroutines := 10 @@ -104,7 +101,7 @@ func TestGetInstanceProfile_Concurrency(t *testing.T) { require.Equal(t, "test-1.0.0", resp.Version) require.True(t, resp.Demo) require.Equal(t, "http://localhost:8080", resp.InstanceUrl) - require.Equal(t, expectedOwnerName, resp.Owner) + require.True(t, resp.Initialized) } } }) diff --git a/server/router/api/v1/user_service.go b/server/router/api/v1/user_service.go index 64e0e42e9..da0b85cb2 100644 --- a/server/router/api/v1/user_service.go +++ b/server/router/api/v1/user_service.go @@ -177,6 +177,12 @@ func (s *APIV1Service) CreateUser(ctx context.Context, request *v1pb.CreateUserR return nil, status.Errorf(codes.Internal, "failed to create user: %v", err) } + // If this is the first admin user being created, clear the owner cache + // so that GetInstanceProfile will return initialized=true + if roleToAssign == store.RoleAdmin { + s.ClearInstanceOwnerCache() + } + return convertUserFromStore(user), nil } diff --git a/web/src/App.tsx b/web/src/App.tsx index d8e0f949b..c94b77986 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -20,12 +20,12 @@ const App = () => { cleanupExpiredOAuthState(); }, []); - // Redirect to sign up page if no instance owner + // Redirect to sign up page if instance not initialized (no admin account exists yet) useEffect(() => { - if (!instanceProfile.owner) { + if (!instanceProfile.initialized) { navigateTo("/auth/signup"); } - }, [instanceProfile.owner, navigateTo]); + }, [instanceProfile.initialized, navigateTo]); useEffect(() => { if (instanceGeneralSetting.additionalStyle) { diff --git a/web/src/pages/Home.tsx b/web/src/pages/Home.tsx index 93cc389ba..3b694c488 100644 --- a/web/src/pages/Home.tsx +++ b/web/src/pages/Home.tsx @@ -1,9 +1,9 @@ import { MemoRenderContext } from "@/components/MasonryView"; import MemoView from "@/components/MemoView"; import PagedMemoList from "@/components/PagedMemoList"; +import { useInstance } from "@/contexts/InstanceContext"; import { useMemoFilters, useMemoSorting } from "@/hooks"; import useCurrentUser from "@/hooks/useCurrentUser"; -import { useInstance } from "@/contexts/InstanceContext"; import { State } from "@/types/proto/api/v1/common_pb"; import { Memo } from "@/types/proto/api/v1/memo_service_pb"; diff --git a/web/src/pages/SignUp.tsx b/web/src/pages/SignUp.tsx index f7a6429ee..c291cab23 100644 --- a/web/src/pages/SignUp.tsx +++ b/web/src/pages/SignUp.tsx @@ -9,18 +9,22 @@ import AuthFooter from "@/components/AuthFooter"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { authServiceClient, userServiceClient } from "@/connect"; +import { useAuth } from "@/contexts/AuthContext"; import { useInstance } from "@/contexts/InstanceContext"; import useLoading from "@/hooks/useLoading"; +import useNavigateTo from "@/hooks/useNavigateTo"; import { handleError } from "@/lib/error"; import { User_Role, UserSchema } from "@/types/proto/api/v1/user_service_pb"; import { useTranslate } from "@/utils/i18n"; const SignUp = () => { const t = useTranslate(); + const navigateTo = useNavigateTo(); const actionBtnLoadingState = useLoading(false); const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); - const { generalSetting: instanceGeneralSetting, profile } = useInstance(); + const { initialize: initAuth } = useAuth(); + const { generalSetting: instanceGeneralSetting, profile, initialize: initInstance } = useInstance(); const handleUsernameInputChanged = (e: React.ChangeEvent) => { const text = e.target.value as string; @@ -64,7 +68,11 @@ const SignUp = () => { if (response.accessToken) { setAccessToken(response.accessToken, response.accessTokenExpiresAt ? timestampDate(response.accessTokenExpiresAt) : undefined); } - window.location.href = "/"; + // Refresh auth context to load the current user + await initAuth(); + // Refetch instance profile to update the initialized status + await initInstance(); + navigateTo("/"); } catch (error: unknown) { handleError(error, toast.error, { fallbackMessage: "Sign up failed", @@ -127,7 +135,7 @@ const SignUp = () => { ) : (

Sign up is not allowed.

)} - {!profile.owner ? ( + {!profile.initialized ? (

{t("auth.host-tip")}

) : (

diff --git a/web/src/types/proto/api/v1/activity_service_pb.ts b/web/src/types/proto/api/v1/activity_service_pb.ts index 95e7a5007..75e0ea9cb 100644 --- a/web/src/types/proto/api/v1/activity_service_pb.ts +++ b/web/src/types/proto/api/v1/activity_service_pb.ts @@ -1,4 +1,4 @@ -// @generated by protoc-gen-es v2.10.2 with parameter "target=ts" +// @generated by protoc-gen-es v2.11.0 with parameter "target=ts" // @generated from file api/v1/activity_service.proto (package memos.api.v1, syntax proto3) /* eslint-disable */ diff --git a/web/src/types/proto/api/v1/attachment_service_pb.ts b/web/src/types/proto/api/v1/attachment_service_pb.ts index defda15c9..5eb4e87db 100644 --- a/web/src/types/proto/api/v1/attachment_service_pb.ts +++ b/web/src/types/proto/api/v1/attachment_service_pb.ts @@ -1,4 +1,4 @@ -// @generated by protoc-gen-es v2.10.2 with parameter "target=ts" +// @generated by protoc-gen-es v2.11.0 with parameter "target=ts" // @generated from file api/v1/attachment_service.proto (package memos.api.v1, syntax proto3) /* eslint-disable */ diff --git a/web/src/types/proto/api/v1/auth_service_pb.ts b/web/src/types/proto/api/v1/auth_service_pb.ts index 225e68b55..b54f4cb68 100644 --- a/web/src/types/proto/api/v1/auth_service_pb.ts +++ b/web/src/types/proto/api/v1/auth_service_pb.ts @@ -1,4 +1,4 @@ -// @generated by protoc-gen-es v2.10.2 with parameter "target=ts" +// @generated by protoc-gen-es v2.11.0 with parameter "target=ts" // @generated from file api/v1/auth_service.proto (package memos.api.v1, syntax proto3) /* eslint-disable */ diff --git a/web/src/types/proto/api/v1/common_pb.ts b/web/src/types/proto/api/v1/common_pb.ts index 7a792af10..4d554fc7f 100644 --- a/web/src/types/proto/api/v1/common_pb.ts +++ b/web/src/types/proto/api/v1/common_pb.ts @@ -1,4 +1,4 @@ -// @generated by protoc-gen-es v2.10.2 with parameter "target=ts" +// @generated by protoc-gen-es v2.11.0 with parameter "target=ts" // @generated from file api/v1/common.proto (package memos.api.v1, syntax proto3) /* eslint-disable */ diff --git a/web/src/types/proto/api/v1/idp_service_pb.ts b/web/src/types/proto/api/v1/idp_service_pb.ts index cddae42c0..de4615c42 100644 --- a/web/src/types/proto/api/v1/idp_service_pb.ts +++ b/web/src/types/proto/api/v1/idp_service_pb.ts @@ -1,4 +1,4 @@ -// @generated by protoc-gen-es v2.10.2 with parameter "target=ts" +// @generated by protoc-gen-es v2.11.0 with parameter "target=ts" // @generated from file api/v1/idp_service.proto (package memos.api.v1, syntax proto3) /* eslint-disable */ diff --git a/web/src/types/proto/api/v1/instance_service_pb.ts b/web/src/types/proto/api/v1/instance_service_pb.ts index 892976953..5d4163bf1 100644 --- a/web/src/types/proto/api/v1/instance_service_pb.ts +++ b/web/src/types/proto/api/v1/instance_service_pb.ts @@ -1,4 +1,4 @@ -// @generated by protoc-gen-es v2.10.2 with parameter "target=ts" +// @generated by protoc-gen-es v2.11.0 with parameter "target=ts" // @generated from file api/v1/instance_service.proto (package memos.api.v1, syntax proto3) /* eslint-disable */ @@ -16,7 +16,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file api/v1/instance_service.proto. */ export const file_api_v1_instance_service: GenFile = /*@__PURE__*/ - fileDesc("Ch1hcGkvdjEvaW5zdGFuY2Vfc2VydmljZS5wcm90bxIMbWVtb3MuYXBpLnYxIlUKD0luc3RhbmNlUHJvZmlsZRINCgVvd25lchgBIAEoCRIPCgd2ZXJzaW9uGAIgASgJEgwKBGRlbW8YAyABKAgSFAoMaW5zdGFuY2VfdXJsGAYgASgJIhsKGUdldEluc3RhbmNlUHJvZmlsZVJlcXVlc3QiswsKD0luc3RhbmNlU2V0dGluZxIRCgRuYW1lGAEgASgJQgPgQQgSRwoPZ2VuZXJhbF9zZXR0aW5nGAIgASgLMiwubWVtb3MuYXBpLnYxLkluc3RhbmNlU2V0dGluZy5HZW5lcmFsU2V0dGluZ0gAEkcKD3N0b3JhZ2Vfc2V0dGluZxgDIAEoCzIsLm1lbW9zLmFwaS52MS5JbnN0YW5jZVNldHRpbmcuU3RvcmFnZVNldHRpbmdIABJQChRtZW1vX3JlbGF0ZWRfc2V0dGluZxgEIAEoCzIwLm1lbW9zLmFwaS52MS5JbnN0YW5jZVNldHRpbmcuTWVtb1JlbGF0ZWRTZXR0aW5nSAAahwMKDkdlbmVyYWxTZXR0aW5nEiIKGmRpc2FsbG93X3VzZXJfcmVnaXN0cmF0aW9uGAIgASgIEh4KFmRpc2FsbG93X3Bhc3N3b3JkX2F1dGgYAyABKAgSGQoRYWRkaXRpb25hbF9zY3JpcHQYBCABKAkSGAoQYWRkaXRpb25hbF9zdHlsZRgFIAEoCRJSCg5jdXN0b21fcHJvZmlsZRgGIAEoCzI6Lm1lbW9zLmFwaS52MS5JbnN0YW5jZVNldHRpbmcuR2VuZXJhbFNldHRpbmcuQ3VzdG9tUHJvZmlsZRIdChV3ZWVrX3N0YXJ0X2RheV9vZmZzZXQYByABKAUSIAoYZGlzYWxsb3dfY2hhbmdlX3VzZXJuYW1lGAggASgIEiAKGGRpc2FsbG93X2NoYW5nZV9uaWNrbmFtZRgJIAEoCBpFCg1DdXN0b21Qcm9maWxlEg0KBXRpdGxlGAEgASgJEhMKC2Rlc2NyaXB0aW9uGAIgASgJEhAKCGxvZ29fdXJsGAMgASgJGroDCg5TdG9yYWdlU2V0dGluZxJOCgxzdG9yYWdlX3R5cGUYASABKA4yOC5tZW1vcy5hcGkudjEuSW5zdGFuY2VTZXR0aW5nLlN0b3JhZ2VTZXR0aW5nLlN0b3JhZ2VUeXBlEhkKEWZpbGVwYXRoX3RlbXBsYXRlGAIgASgJEhwKFHVwbG9hZF9zaXplX2xpbWl0X21iGAMgASgDEkgKCXMzX2NvbmZpZxgEIAEoCzI1Lm1lbW9zLmFwaS52MS5JbnN0YW5jZVNldHRpbmcuU3RvcmFnZVNldHRpbmcuUzNDb25maWcahgEKCFMzQ29uZmlnEhUKDWFjY2Vzc19rZXlfaWQYASABKAkSGQoRYWNjZXNzX2tleV9zZWNyZXQYAiABKAkSEAoIZW5kcG9pbnQYAyABKAkSDgoGcmVnaW9uGAQgASgJEg4KBmJ1Y2tldBgFIAEoCRIWCg51c2VfcGF0aF9zdHlsZRgGIAEoCCJMCgtTdG9yYWdlVHlwZRIcChhTVE9SQUdFX1RZUEVfVU5TUEVDSUZJRUQQABIMCghEQVRBQkFTRRABEgkKBUxPQ0FMEAISBgoCUzMQAxqtAQoSTWVtb1JlbGF0ZWRTZXR0aW5nEiIKGmRpc2FsbG93X3B1YmxpY192aXNpYmlsaXR5GAEgASgIEiAKGGRpc3BsYXlfd2l0aF91cGRhdGVfdGltZRgCIAEoCBIcChRjb250ZW50X2xlbmd0aF9saW1pdBgDIAEoBRIgChhlbmFibGVfZG91YmxlX2NsaWNrX2VkaXQYBCABKAgSEQoJcmVhY3Rpb25zGAcgAygJIkYKA0tleRITCg9LRVlfVU5TUEVDSUZJRUQQABILCgdHRU5FUkFMEAESCwoHU1RPUkFHRRACEhAKDE1FTU9fUkVMQVRFRBADOmHqQV4KHG1lbW9zLmFwaS52MS9JbnN0YW5jZVNldHRpbmcSG2luc3RhbmNlL3NldHRpbmdzL3tzZXR0aW5nfSoQaW5zdGFuY2VTZXR0aW5nczIPaW5zdGFuY2VTZXR0aW5nQgcKBXZhbHVlIk8KGUdldEluc3RhbmNlU2V0dGluZ1JlcXVlc3QSMgoEbmFtZRgBIAEoCUIk4EEC+kEeChxtZW1vcy5hcGkudjEvSW5zdGFuY2VTZXR0aW5nIokBChxVcGRhdGVJbnN0YW5jZVNldHRpbmdSZXF1ZXN0EjMKB3NldHRpbmcYASABKAsyHS5tZW1vcy5hcGkudjEuSW5zdGFuY2VTZXR0aW5nQgPgQQISNAoLdXBkYXRlX21hc2sYAiABKAsyGi5nb29nbGUucHJvdG9idWYuRmllbGRNYXNrQgPgQQEy2wMKD0luc3RhbmNlU2VydmljZRJ+ChJHZXRJbnN0YW5jZVByb2ZpbGUSJy5tZW1vcy5hcGkudjEuR2V0SW5zdGFuY2VQcm9maWxlUmVxdWVzdBodLm1lbW9zLmFwaS52MS5JbnN0YW5jZVByb2ZpbGUiIILT5JMCGhIYL2FwaS92MS9pbnN0YW5jZS9wcm9maWxlEo8BChJHZXRJbnN0YW5jZVNldHRpbmcSJy5tZW1vcy5hcGkudjEuR2V0SW5zdGFuY2VTZXR0aW5nUmVxdWVzdBodLm1lbW9zLmFwaS52MS5JbnN0YW5jZVNldHRpbmciMdpBBG5hbWWC0+STAiQSIi9hcGkvdjEve25hbWU9aW5zdGFuY2Uvc2V0dGluZ3MvKn0StQEKFVVwZGF0ZUluc3RhbmNlU2V0dGluZxIqLm1lbW9zLmFwaS52MS5VcGRhdGVJbnN0YW5jZVNldHRpbmdSZXF1ZXN0Gh0ubWVtb3MuYXBpLnYxLkluc3RhbmNlU2V0dGluZyJR2kETc2V0dGluZyx1cGRhdGVfbWFza4LT5JMCNToHc2V0dGluZzIqL2FwaS92MS97c2V0dGluZy5uYW1lPWluc3RhbmNlL3NldHRpbmdzLyp9QqwBChBjb20ubWVtb3MuYXBpLnYxQhRJbnN0YW5jZVNlcnZpY2VQcm90b1ABWjBnaXRodWIuY29tL3VzZW1lbW9zL21lbW9zL3Byb3RvL2dlbi9hcGkvdjE7YXBpdjGiAgNNQViqAgxNZW1vcy5BcGkuVjHKAgxNZW1vc1xBcGlcVjHiAhhNZW1vc1xBcGlcVjFcR1BCTWV0YWRhdGHqAg5NZW1vczo6QXBpOjpWMWIGcHJvdG8z", [file_google_api_annotations, file_google_api_client, file_google_api_field_behavior, file_google_api_resource, file_google_protobuf_field_mask]); + fileDesc("Ch1hcGkvdjEvaW5zdGFuY2Vfc2VydmljZS5wcm90bxIMbWVtb3MuYXBpLnYxIlsKD0luc3RhbmNlUHJvZmlsZRIPCgd2ZXJzaW9uGAIgASgJEgwKBGRlbW8YAyABKAgSFAoMaW5zdGFuY2VfdXJsGAYgASgJEhMKC2luaXRpYWxpemVkGAcgASgIIhsKGUdldEluc3RhbmNlUHJvZmlsZVJlcXVlc3QiswsKD0luc3RhbmNlU2V0dGluZxIRCgRuYW1lGAEgASgJQgPgQQgSRwoPZ2VuZXJhbF9zZXR0aW5nGAIgASgLMiwubWVtb3MuYXBpLnYxLkluc3RhbmNlU2V0dGluZy5HZW5lcmFsU2V0dGluZ0gAEkcKD3N0b3JhZ2Vfc2V0dGluZxgDIAEoCzIsLm1lbW9zLmFwaS52MS5JbnN0YW5jZVNldHRpbmcuU3RvcmFnZVNldHRpbmdIABJQChRtZW1vX3JlbGF0ZWRfc2V0dGluZxgEIAEoCzIwLm1lbW9zLmFwaS52MS5JbnN0YW5jZVNldHRpbmcuTWVtb1JlbGF0ZWRTZXR0aW5nSAAahwMKDkdlbmVyYWxTZXR0aW5nEiIKGmRpc2FsbG93X3VzZXJfcmVnaXN0cmF0aW9uGAIgASgIEh4KFmRpc2FsbG93X3Bhc3N3b3JkX2F1dGgYAyABKAgSGQoRYWRkaXRpb25hbF9zY3JpcHQYBCABKAkSGAoQYWRkaXRpb25hbF9zdHlsZRgFIAEoCRJSCg5jdXN0b21fcHJvZmlsZRgGIAEoCzI6Lm1lbW9zLmFwaS52MS5JbnN0YW5jZVNldHRpbmcuR2VuZXJhbFNldHRpbmcuQ3VzdG9tUHJvZmlsZRIdChV3ZWVrX3N0YXJ0X2RheV9vZmZzZXQYByABKAUSIAoYZGlzYWxsb3dfY2hhbmdlX3VzZXJuYW1lGAggASgIEiAKGGRpc2FsbG93X2NoYW5nZV9uaWNrbmFtZRgJIAEoCBpFCg1DdXN0b21Qcm9maWxlEg0KBXRpdGxlGAEgASgJEhMKC2Rlc2NyaXB0aW9uGAIgASgJEhAKCGxvZ29fdXJsGAMgASgJGroDCg5TdG9yYWdlU2V0dGluZxJOCgxzdG9yYWdlX3R5cGUYASABKA4yOC5tZW1vcy5hcGkudjEuSW5zdGFuY2VTZXR0aW5nLlN0b3JhZ2VTZXR0aW5nLlN0b3JhZ2VUeXBlEhkKEWZpbGVwYXRoX3RlbXBsYXRlGAIgASgJEhwKFHVwbG9hZF9zaXplX2xpbWl0X21iGAMgASgDEkgKCXMzX2NvbmZpZxgEIAEoCzI1Lm1lbW9zLmFwaS52MS5JbnN0YW5jZVNldHRpbmcuU3RvcmFnZVNldHRpbmcuUzNDb25maWcahgEKCFMzQ29uZmlnEhUKDWFjY2Vzc19rZXlfaWQYASABKAkSGQoRYWNjZXNzX2tleV9zZWNyZXQYAiABKAkSEAoIZW5kcG9pbnQYAyABKAkSDgoGcmVnaW9uGAQgASgJEg4KBmJ1Y2tldBgFIAEoCRIWCg51c2VfcGF0aF9zdHlsZRgGIAEoCCJMCgtTdG9yYWdlVHlwZRIcChhTVE9SQUdFX1RZUEVfVU5TUEVDSUZJRUQQABIMCghEQVRBQkFTRRABEgkKBUxPQ0FMEAISBgoCUzMQAxqtAQoSTWVtb1JlbGF0ZWRTZXR0aW5nEiIKGmRpc2FsbG93X3B1YmxpY192aXNpYmlsaXR5GAEgASgIEiAKGGRpc3BsYXlfd2l0aF91cGRhdGVfdGltZRgCIAEoCBIcChRjb250ZW50X2xlbmd0aF9saW1pdBgDIAEoBRIgChhlbmFibGVfZG91YmxlX2NsaWNrX2VkaXQYBCABKAgSEQoJcmVhY3Rpb25zGAcgAygJIkYKA0tleRITCg9LRVlfVU5TUEVDSUZJRUQQABILCgdHRU5FUkFMEAESCwoHU1RPUkFHRRACEhAKDE1FTU9fUkVMQVRFRBADOmHqQV4KHG1lbW9zLmFwaS52MS9JbnN0YW5jZVNldHRpbmcSG2luc3RhbmNlL3NldHRpbmdzL3tzZXR0aW5nfSoQaW5zdGFuY2VTZXR0aW5nczIPaW5zdGFuY2VTZXR0aW5nQgcKBXZhbHVlIk8KGUdldEluc3RhbmNlU2V0dGluZ1JlcXVlc3QSMgoEbmFtZRgBIAEoCUIk4EEC+kEeChxtZW1vcy5hcGkudjEvSW5zdGFuY2VTZXR0aW5nIokBChxVcGRhdGVJbnN0YW5jZVNldHRpbmdSZXF1ZXN0EjMKB3NldHRpbmcYASABKAsyHS5tZW1vcy5hcGkudjEuSW5zdGFuY2VTZXR0aW5nQgPgQQISNAoLdXBkYXRlX21hc2sYAiABKAsyGi5nb29nbGUucHJvdG9idWYuRmllbGRNYXNrQgPgQQEy2wMKD0luc3RhbmNlU2VydmljZRJ+ChJHZXRJbnN0YW5jZVByb2ZpbGUSJy5tZW1vcy5hcGkudjEuR2V0SW5zdGFuY2VQcm9maWxlUmVxdWVzdBodLm1lbW9zLmFwaS52MS5JbnN0YW5jZVByb2ZpbGUiIILT5JMCGhIYL2FwaS92MS9pbnN0YW5jZS9wcm9maWxlEo8BChJHZXRJbnN0YW5jZVNldHRpbmcSJy5tZW1vcy5hcGkudjEuR2V0SW5zdGFuY2VTZXR0aW5nUmVxdWVzdBodLm1lbW9zLmFwaS52MS5JbnN0YW5jZVNldHRpbmciMdpBBG5hbWWC0+STAiQSIi9hcGkvdjEve25hbWU9aW5zdGFuY2Uvc2V0dGluZ3MvKn0StQEKFVVwZGF0ZUluc3RhbmNlU2V0dGluZxIqLm1lbW9zLmFwaS52MS5VcGRhdGVJbnN0YW5jZVNldHRpbmdSZXF1ZXN0Gh0ubWVtb3MuYXBpLnYxLkluc3RhbmNlU2V0dGluZyJR2kETc2V0dGluZyx1cGRhdGVfbWFza4LT5JMCNToHc2V0dGluZzIqL2FwaS92MS97c2V0dGluZy5uYW1lPWluc3RhbmNlL3NldHRpbmdzLyp9QqwBChBjb20ubWVtb3MuYXBpLnYxQhRJbnN0YW5jZVNlcnZpY2VQcm90b1ABWjBnaXRodWIuY29tL3VzZW1lbW9zL21lbW9zL3Byb3RvL2dlbi9hcGkvdjE7YXBpdjGiAgNNQViqAgxNZW1vcy5BcGkuVjHKAgxNZW1vc1xBcGlcVjHiAhhNZW1vc1xBcGlcVjFcR1BCTWV0YWRhdGHqAg5NZW1vczo6QXBpOjpWMWIGcHJvdG8z", [file_google_api_annotations, file_google_api_client, file_google_api_field_behavior, file_google_api_resource, file_google_protobuf_field_mask]); /** * Instance profile message containing basic instance information. @@ -24,14 +24,6 @@ export const file_api_v1_instance_service: GenFile = /*@__PURE__*/ * @generated from message memos.api.v1.InstanceProfile */ export type InstanceProfile = Message<"memos.api.v1.InstanceProfile"> & { - /** - * The name of instance owner. - * Format: users/{user} - * - * @generated from field: string owner = 1; - */ - owner: string; - /** * Version is the current version of instance. * @@ -52,6 +44,15 @@ export type InstanceProfile = Message<"memos.api.v1.InstanceProfile"> & { * @generated from field: string instance_url = 6; */ instanceUrl: string; + + /** + * Indicates if the instance has completed first-time setup. + * When false, the instance requires initialization (creating the first admin account). + * This follows the pattern used by other self-hosted platforms for setup workflows. + * + * @generated from field: bool initialized = 7; + */ + initialized: boolean; }; /** diff --git a/web/src/types/proto/api/v1/memo_service_pb.ts b/web/src/types/proto/api/v1/memo_service_pb.ts index 9143997f4..f9b9860ca 100644 --- a/web/src/types/proto/api/v1/memo_service_pb.ts +++ b/web/src/types/proto/api/v1/memo_service_pb.ts @@ -1,4 +1,4 @@ -// @generated by protoc-gen-es v2.10.2 with parameter "target=ts" +// @generated by protoc-gen-es v2.11.0 with parameter "target=ts" // @generated from file api/v1/memo_service.proto (package memos.api.v1, syntax proto3) /* eslint-disable */ diff --git a/web/src/types/proto/api/v1/shortcut_service_pb.ts b/web/src/types/proto/api/v1/shortcut_service_pb.ts index 0dc8e1a49..ad4145246 100644 --- a/web/src/types/proto/api/v1/shortcut_service_pb.ts +++ b/web/src/types/proto/api/v1/shortcut_service_pb.ts @@ -1,4 +1,4 @@ -// @generated by protoc-gen-es v2.10.2 with parameter "target=ts" +// @generated by protoc-gen-es v2.11.0 with parameter "target=ts" // @generated from file api/v1/shortcut_service.proto (package memos.api.v1, syntax proto3) /* eslint-disable */ diff --git a/web/src/types/proto/api/v1/user_service_pb.ts b/web/src/types/proto/api/v1/user_service_pb.ts index df060fd6d..46c748123 100644 --- a/web/src/types/proto/api/v1/user_service_pb.ts +++ b/web/src/types/proto/api/v1/user_service_pb.ts @@ -1,4 +1,4 @@ -// @generated by protoc-gen-es v2.10.2 with parameter "target=ts" +// @generated by protoc-gen-es v2.11.0 with parameter "target=ts" // @generated from file api/v1/user_service.proto (package memos.api.v1, syntax proto3) /* eslint-disable */ diff --git a/web/src/types/proto/google/api/annotations_pb.ts b/web/src/types/proto/google/api/annotations_pb.ts index 6044582f7..b6315b5e7 100644 --- a/web/src/types/proto/google/api/annotations_pb.ts +++ b/web/src/types/proto/google/api/annotations_pb.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// @generated by protoc-gen-es v2.10.2 with parameter "target=ts" +// @generated by protoc-gen-es v2.11.0 with parameter "target=ts" // @generated from file google/api/annotations.proto (package google.api, syntax proto3) /* eslint-disable */ diff --git a/web/src/types/proto/google/api/client_pb.ts b/web/src/types/proto/google/api/client_pb.ts index 08464eb45..00f8f5d68 100644 --- a/web/src/types/proto/google/api/client_pb.ts +++ b/web/src/types/proto/google/api/client_pb.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// @generated by protoc-gen-es v2.10.2 with parameter "target=ts" +// @generated by protoc-gen-es v2.11.0 with parameter "target=ts" // @generated from file google/api/client.proto (package google.api, syntax proto3) /* eslint-disable */ diff --git a/web/src/types/proto/google/api/field_behavior_pb.ts b/web/src/types/proto/google/api/field_behavior_pb.ts index 3b7b204a2..7103aafd5 100644 --- a/web/src/types/proto/google/api/field_behavior_pb.ts +++ b/web/src/types/proto/google/api/field_behavior_pb.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// @generated by protoc-gen-es v2.10.2 with parameter "target=ts" +// @generated by protoc-gen-es v2.11.0 with parameter "target=ts" // @generated from file google/api/field_behavior.proto (package google.api, syntax proto3) /* eslint-disable */ diff --git a/web/src/types/proto/google/api/http_pb.ts b/web/src/types/proto/google/api/http_pb.ts index c8605c9ab..677a92a9a 100644 --- a/web/src/types/proto/google/api/http_pb.ts +++ b/web/src/types/proto/google/api/http_pb.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// @generated by protoc-gen-es v2.10.2 with parameter "target=ts" +// @generated by protoc-gen-es v2.11.0 with parameter "target=ts" // @generated from file google/api/http.proto (package google.api, syntax proto3) /* eslint-disable */ diff --git a/web/src/types/proto/google/api/launch_stage_pb.ts b/web/src/types/proto/google/api/launch_stage_pb.ts index 1515b7693..e1ce0acd8 100644 --- a/web/src/types/proto/google/api/launch_stage_pb.ts +++ b/web/src/types/proto/google/api/launch_stage_pb.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// @generated by protoc-gen-es v2.10.2 with parameter "target=ts" +// @generated by protoc-gen-es v2.11.0 with parameter "target=ts" // @generated from file google/api/launch_stage.proto (package google.api, syntax proto3) /* eslint-disable */ diff --git a/web/src/types/proto/google/api/resource_pb.ts b/web/src/types/proto/google/api/resource_pb.ts index 0ae2e23ab..4142fffd1 100644 --- a/web/src/types/proto/google/api/resource_pb.ts +++ b/web/src/types/proto/google/api/resource_pb.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// @generated by protoc-gen-es v2.10.2 with parameter "target=ts" +// @generated by protoc-gen-es v2.11.0 with parameter "target=ts" // @generated from file google/api/resource.proto (package google.api, syntax proto3) /* eslint-disable */