Commit c5d497a1 authored by johnnyjoy's avatar johnnyjoy

chore: update user settings

parent 3f56ce47
This diff is collapsed.
syntax = "proto3";
package memos.api.v1;
import "google/api/annotations.proto";
import "google/api/client.proto";
import "google/api/field_behavior.proto";
import "google/api/resource.proto";
import "google/protobuf/empty.proto";
import "google/protobuf/field_mask.proto";
option go_package = "gen/api/v1";
service WebhookService {
// ListWebhooks returns a list of webhooks for a user.
rpc ListWebhooks(ListWebhooksRequest) returns (ListWebhooksResponse) {
option (google.api.http) = {get: "/api/v1/{parent=users/*}/webhooks"};
option (google.api.method_signature) = "parent";
}
// GetWebhook gets a webhook by name.
rpc GetWebhook(GetWebhookRequest) returns (Webhook) {
option (google.api.http) = {get: "/api/v1/{name=users/*/webhooks/*}"};
option (google.api.method_signature) = "name";
}
// CreateWebhook creates a new webhook for a user.
rpc CreateWebhook(CreateWebhookRequest) returns (Webhook) {
option (google.api.http) = {
post: "/api/v1/{parent=users/*}/webhooks"
body: "webhook"
};
option (google.api.method_signature) = "parent,webhook";
}
// UpdateWebhook updates a webhook for a user.
rpc UpdateWebhook(UpdateWebhookRequest) returns (Webhook) {
option (google.api.http) = {
patch: "/api/v1/{webhook.name=users/*/webhooks/*}"
body: "webhook"
};
option (google.api.method_signature) = "webhook,update_mask";
}
// DeleteWebhook deletes a webhook for a user.
rpc DeleteWebhook(DeleteWebhookRequest) returns (google.protobuf.Empty) {
option (google.api.http) = {delete: "/api/v1/{name=users/*/webhooks/*}"};
option (google.api.method_signature) = "name";
}
}
message Webhook {
option (google.api.resource) = {
type: "memos.api.v1/Webhook"
pattern: "users/{user}/webhooks/{webhook}"
singular: "webhook"
plural: "webhooks"
};
// The resource name of the webhook.
// Format: users/{user}/webhooks/{webhook}
string name = 1 [(google.api.field_behavior) = IDENTIFIER];
// The display name of the webhook.
string display_name = 2 [(google.api.field_behavior) = REQUIRED];
// The target URL for the webhook.
string url = 3 [(google.api.field_behavior) = REQUIRED];
}
message ListWebhooksRequest {
// Required. The parent resource where webhooks are listed.
// Format: users/{user}
string parent = 1 [
(google.api.field_behavior) = REQUIRED,
(google.api.resource_reference) = {child_type: "memos.api.v1/Webhook"}
];
}
message ListWebhooksResponse {
// The list of webhooks.
repeated Webhook webhooks = 1;
}
message GetWebhookRequest {
// Required. The resource name of the webhook to retrieve.
// Format: users/{user}/webhooks/{webhook}
string name = 1 [
(google.api.field_behavior) = REQUIRED,
(google.api.resource_reference) = {type: "memos.api.v1/Webhook"}
];
}
message CreateWebhookRequest {
// Required. The parent resource where this webhook will be created.
// Format: users/{user}
string parent = 1 [
(google.api.field_behavior) = REQUIRED,
(google.api.resource_reference) = {child_type: "memos.api.v1/Webhook"}
];
// Required. The webhook to create.
Webhook webhook = 2 [(google.api.field_behavior) = REQUIRED];
// Optional. If set, validate the request, but do not actually create the webhook.
bool validate_only = 3 [(google.api.field_behavior) = OPTIONAL];
}
message UpdateWebhookRequest {
// Required. The webhook resource which replaces the resource on the server.
Webhook webhook = 1 [(google.api.field_behavior) = REQUIRED];
// Optional. The list of fields to update.
google.protobuf.FieldMask update_mask = 2 [(google.api.field_behavior) = OPTIONAL];
}
message DeleteWebhookRequest {
// Required. The resource name of the webhook to delete.
// Format: users/{user}/webhooks/{webhook}
string name = 1 [
(google.api.field_behavior) = REQUIRED,
(google.api.resource_reference) = {type: "memos.api.v1/Webhook"}
];
}
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -65,7 +65,7 @@ func (s *APIV1Service) ListAllUserStats(ctx context.Context, _ *v1pb.ListAllUser ...@@ -65,7 +65,7 @@ func (s *APIV1Service) ListAllUserStats(ctx context.Context, _ *v1pb.ListAllUser
} }
response := &v1pb.ListAllUserStatsResponse{ response := &v1pb.ListAllUserStatsResponse{
UserStats: userMemoStats, Stats: userMemoStats,
} }
return response, nil return response, nil
} }
......
...@@ -30,7 +30,6 @@ type APIV1Service struct { ...@@ -30,7 +30,6 @@ type APIV1Service struct {
v1pb.UnimplementedShortcutServiceServer v1pb.UnimplementedShortcutServiceServer
v1pb.UnimplementedInboxServiceServer v1pb.UnimplementedInboxServiceServer
v1pb.UnimplementedActivityServiceServer v1pb.UnimplementedActivityServiceServer
v1pb.UnimplementedWebhookServiceServer
v1pb.UnimplementedMarkdownServiceServer v1pb.UnimplementedMarkdownServiceServer
v1pb.UnimplementedIdentityProviderServiceServer v1pb.UnimplementedIdentityProviderServiceServer
...@@ -58,7 +57,6 @@ func NewAPIV1Service(secret string, profile *profile.Profile, store *store.Store ...@@ -58,7 +57,6 @@ func NewAPIV1Service(secret string, profile *profile.Profile, store *store.Store
v1pb.RegisterShortcutServiceServer(grpcServer, apiv1Service) v1pb.RegisterShortcutServiceServer(grpcServer, apiv1Service)
v1pb.RegisterInboxServiceServer(grpcServer, apiv1Service) v1pb.RegisterInboxServiceServer(grpcServer, apiv1Service)
v1pb.RegisterActivityServiceServer(grpcServer, apiv1Service) v1pb.RegisterActivityServiceServer(grpcServer, apiv1Service)
v1pb.RegisterWebhookServiceServer(grpcServer, apiv1Service)
v1pb.RegisterMarkdownServiceServer(grpcServer, apiv1Service) v1pb.RegisterMarkdownServiceServer(grpcServer, apiv1Service)
v1pb.RegisterIdentityProviderServiceServer(grpcServer, apiv1Service) v1pb.RegisterIdentityProviderServiceServer(grpcServer, apiv1Service)
reflection.Register(grpcServer) reflection.Register(grpcServer)
...@@ -107,9 +105,6 @@ func (s *APIV1Service) RegisterGateway(ctx context.Context, echoServer *echo.Ech ...@@ -107,9 +105,6 @@ func (s *APIV1Service) RegisterGateway(ctx context.Context, echoServer *echo.Ech
if err := v1pb.RegisterActivityServiceHandler(ctx, gwMux, conn); err != nil { if err := v1pb.RegisterActivityServiceHandler(ctx, gwMux, conn); err != nil {
return err return err
} }
if err := v1pb.RegisterWebhookServiceHandler(ctx, gwMux, conn); err != nil {
return err
}
if err := v1pb.RegisterMarkdownServiceHandler(ctx, gwMux, conn); err != nil { if err := v1pb.RegisterMarkdownServiceHandler(ctx, gwMux, conn); err != nil {
return err return err
} }
......
This diff is collapsed.
...@@ -12,7 +12,7 @@ const App = observer(() => { ...@@ -12,7 +12,7 @@ const App = observer(() => {
const navigateTo = useNavigateTo(); const navigateTo = useNavigateTo();
const [mode, setMode] = useState<"light" | "dark">("light"); const [mode, setMode] = useState<"light" | "dark">("light");
const workspaceProfile = workspaceStore.state.profile; const workspaceProfile = workspaceStore.state.profile;
const userSetting = userStore.state.userSetting; const userGeneralSetting = userStore.state.userGeneralSetting;
const workspaceGeneralSetting = workspaceStore.state.generalSetting; const workspaceGeneralSetting = workspaceStore.state.generalSetting;
// Redirect to sign up page if no instance owner. // Redirect to sign up page if no instance owner.
...@@ -94,22 +94,22 @@ const App = observer(() => { ...@@ -94,22 +94,22 @@ const App = observer(() => {
}, [mode]); }, [mode]);
useEffect(() => { useEffect(() => {
if (!userSetting) { if (!userGeneralSetting) {
return; return;
} }
workspaceStore.state.setPartial({ workspaceStore.state.setPartial({
locale: userSetting.locale || workspaceStore.state.locale, locale: userGeneralSetting.locale || workspaceStore.state.locale,
appearance: userSetting.appearance || workspaceStore.state.appearance, appearance: userGeneralSetting.appearance || workspaceStore.state.appearance,
}); });
}, [userSetting?.locale, userSetting?.appearance]); }, [userGeneralSetting?.locale, userGeneralSetting?.appearance]);
// Load theme when user setting changes (user theme is already backfilled with workspace theme) // Load theme when user setting changes (user theme is already backfilled with workspace theme)
useEffect(() => { useEffect(() => {
if (userSetting?.theme) { if (userGeneralSetting?.theme) {
loadTheme(userSetting.theme); loadTheme(userGeneralSetting.theme);
} }
}, [userSetting?.theme]); }, [userGeneralSetting?.theme]);
return <Outlet />; return <Outlet />;
}); });
......
...@@ -79,7 +79,7 @@ function CreateShortcutDialog({ open, onOpenChange, shortcut: initialShortcut, o ...@@ -79,7 +79,7 @@ function CreateShortcutDialog({ open, onOpenChange, shortcut: initialShortcut, o
toast.success("Update shortcut successfully"); toast.success("Update shortcut successfully");
} }
// Refresh shortcuts. // Refresh shortcuts.
await userStore.fetchShortcuts(); await userStore.fetchUserSettings();
requestState.setFinish(); requestState.setFinish();
onSuccess?.(); onSuccess?.();
onOpenChange(false); onOpenChange(false);
......
...@@ -4,7 +4,7 @@ import { Button } from "@/components/ui/button"; ...@@ -4,7 +4,7 @@ import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { webhookServiceClient } from "@/grpcweb"; import { userServiceClient } from "@/grpcweb";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import useLoading from "@/hooks/useLoading"; import useLoading from "@/hooks/useLoading";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
...@@ -32,19 +32,24 @@ function CreateWebhookDialog({ open, onOpenChange, webhookName, onSuccess }: Pro ...@@ -32,19 +32,24 @@ function CreateWebhookDialog({ open, onOpenChange, webhookName, onSuccess }: Pro
const isCreating = webhookName === undefined; const isCreating = webhookName === undefined;
useEffect(() => { useEffect(() => {
if (webhookName) { if (webhookName && currentUser) {
webhookServiceClient // For editing, we need to get the webhook data
.getWebhook({ // Since we're using user webhooks now, we need to list all webhooks and find the one we want
name: webhookName, userServiceClient
.listUserWebhooks({
parent: currentUser.name,
}) })
.then((webhook) => { .then((response) => {
const webhook = response.webhooks.find((w) => w.name === webhookName);
if (webhook) {
setState({ setState({
displayName: webhook.displayName, displayName: webhook.displayName,
url: webhook.url, url: webhook.url,
}); });
}
}); });
} }
}, [webhookName]); }, [webhookName, currentUser]);
const setPartialState = (partialState: Partial<State>) => { const setPartialState = (partialState: Partial<State>) => {
setState({ setState({
...@@ -79,7 +84,7 @@ function CreateWebhookDialog({ open, onOpenChange, webhookName, onSuccess }: Pro ...@@ -79,7 +84,7 @@ function CreateWebhookDialog({ open, onOpenChange, webhookName, onSuccess }: Pro
try { try {
requestState.setLoading(); requestState.setLoading();
if (isCreating) { if (isCreating) {
await webhookServiceClient.createWebhook({ await userServiceClient.createUserWebhook({
parent: currentUser.name, parent: currentUser.name,
webhook: { webhook: {
displayName: state.displayName, displayName: state.displayName,
...@@ -87,7 +92,7 @@ function CreateWebhookDialog({ open, onOpenChange, webhookName, onSuccess }: Pro ...@@ -87,7 +92,7 @@ function CreateWebhookDialog({ open, onOpenChange, webhookName, onSuccess }: Pro
}, },
}); });
} else { } else {
await webhookServiceClient.updateWebhook({ await userServiceClient.updateUserWebhook({
webhook: { webhook: {
name: webhookName, name: webhookName,
displayName: state.displayName, displayName: state.displayName,
......
...@@ -28,14 +28,14 @@ const ShortcutsSection = observer(() => { ...@@ -28,14 +28,14 @@ const ShortcutsSection = observer(() => {
const [editingShortcut, setEditingShortcut] = useState<Shortcut | undefined>(); const [editingShortcut, setEditingShortcut] = useState<Shortcut | undefined>();
useAsyncEffect(async () => { useAsyncEffect(async () => {
await userStore.fetchShortcuts(); await userStore.fetchUserSettings();
}, []); }, []);
const handleDeleteShortcut = async (shortcut: Shortcut) => { const handleDeleteShortcut = async (shortcut: Shortcut) => {
const confirmed = window.confirm("Are you sure you want to delete this shortcut?"); const confirmed = window.confirm("Are you sure you want to delete this shortcut?");
if (confirmed) { if (confirmed) {
await shortcutServiceClient.deleteShortcut({ name: shortcut.name }); await shortcutServiceClient.deleteShortcut({ name: shortcut.name });
await userStore.fetchShortcuts(); await userStore.fetchUserSettings();
} }
}; };
......
...@@ -17,7 +17,6 @@ import { memoStore, attachmentStore, userStore, workspaceStore } from "@/store"; ...@@ -17,7 +17,6 @@ import { memoStore, attachmentStore, userStore, workspaceStore } from "@/store";
import { extractMemoIdFromName } from "@/store/common"; import { extractMemoIdFromName } from "@/store/common";
import { Attachment } from "@/types/proto/api/v1/attachment_service"; import { Attachment } from "@/types/proto/api/v1/attachment_service";
import { Location, Memo, MemoRelation, MemoRelation_Type, Visibility } from "@/types/proto/api/v1/memo_service"; import { Location, Memo, MemoRelation, MemoRelation_Type, Visibility } from "@/types/proto/api/v1/memo_service";
import { UserSetting } from "@/types/proto/api/v1/user_service";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import { convertVisibilityFromString } from "@/utils/memo"; import { convertVisibilityFromString } from "@/utils/memo";
import DateTimeInput from "../DateTimeInput"; import DateTimeInput from "../DateTimeInput";
...@@ -77,7 +76,7 @@ const MemoEditor = observer((props: Props) => { ...@@ -77,7 +76,7 @@ const MemoEditor = observer((props: Props) => {
const [hasContent, setHasContent] = useState<boolean>(false); const [hasContent, setHasContent] = useState<boolean>(false);
const [isVisibilitySelectorOpen, setIsVisibilitySelectorOpen] = useState(false); const [isVisibilitySelectorOpen, setIsVisibilitySelectorOpen] = useState(false);
const editorRef = useRef<EditorRefActions>(null); const editorRef = useRef<EditorRefActions>(null);
const userSetting = userStore.state.userSetting as UserSetting; const userGeneralSetting = userStore.state.userGeneralSetting;
const contentCacheKey = `${currentUser.name}-${cacheKey || ""}`; const contentCacheKey = `${currentUser.name}-${cacheKey || ""}`;
const [contentCache, setContentCache] = useLocalStorage<string>(contentCacheKey, ""); const [contentCache, setContentCache] = useLocalStorage<string>(contentCacheKey, "");
const referenceRelations = memoName const referenceRelations = memoName
...@@ -99,7 +98,7 @@ const MemoEditor = observer((props: Props) => { ...@@ -99,7 +98,7 @@ const MemoEditor = observer((props: Props) => {
}, [autoFocus]); }, [autoFocus]);
useAsyncEffect(async () => { useAsyncEffect(async () => {
let visibility = convertVisibilityFromString(userSetting.memoVisibility); let visibility = convertVisibilityFromString(userGeneralSetting?.memoVisibility || "PRIVATE");
if (workspaceMemoRelatedSetting.disallowPublicVisibility && visibility === Visibility.PUBLIC) { if (workspaceMemoRelatedSetting.disallowPublicVisibility && visibility === Visibility.PUBLIC) {
visibility = Visibility.PROTECTED; visibility = Visibility.PROTECTED;
} }
...@@ -111,7 +110,7 @@ const MemoEditor = observer((props: Props) => { ...@@ -111,7 +110,7 @@ const MemoEditor = observer((props: Props) => {
...prevState, ...prevState,
memoVisibility: convertVisibilityFromString(visibility), memoVisibility: convertVisibilityFromString(visibility),
})); }));
}, [parentMemoName, userSetting.memoVisibility, workspaceMemoRelatedSetting.disallowPublicVisibility]); }, [parentMemoName, userGeneralSetting?.memoVisibility, workspaceMemoRelatedSetting.disallowPublicVisibility]);
useAsyncEffect(async () => { useAsyncEffect(async () => {
if (!memoName) { if (!memoName) {
......
...@@ -3,7 +3,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@ ...@@ -3,7 +3,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { userStore } from "@/store"; import { userStore } from "@/store";
import { Visibility } from "@/types/proto/api/v1/memo_service"; import { Visibility } from "@/types/proto/api/v1/memo_service";
import { UserSetting } from "@/types/proto/api/v1/user_service"; import { UserSetting_GeneralSetting } from "@/types/proto/api/v1/user_service";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import { convertVisibilityFromString, convertVisibilityToString } from "@/utils/memo"; import { convertVisibilityFromString, convertVisibilityToString } from "@/utils/memo";
import AppearanceSelect from "../AppearanceSelect"; import AppearanceSelect from "../AppearanceSelect";
...@@ -14,22 +14,30 @@ import WebhookSection from "./WebhookSection"; ...@@ -14,22 +14,30 @@ import WebhookSection from "./WebhookSection";
const PreferencesSection = observer(() => { const PreferencesSection = observer(() => {
const t = useTranslate(); const t = useTranslate();
const setting = userStore.state.userSetting as UserSetting; const generalSetting = userStore.state.userGeneralSetting;
const handleLocaleSelectChange = async (locale: Locale) => { const handleLocaleSelectChange = async (locale: Locale) => {
await userStore.updateUserSetting({ locale }, ["locale"]); await userStore.updateUserGeneralSetting({ locale }, ["locale"]);
}; };
const handleAppearanceSelectChange = async (appearance: Appearance) => { const handleAppearanceSelectChange = async (appearance: Appearance) => {
await userStore.updateUserSetting({ appearance }, ["appearance"]); await userStore.updateUserGeneralSetting({ appearance }, ["appearance"]);
}; };
const handleDefaultMemoVisibilityChanged = async (value: string) => { const handleDefaultMemoVisibilityChanged = async (value: string) => {
await userStore.updateUserSetting({ memoVisibility: value }, ["memo_visibility"]); await userStore.updateUserGeneralSetting({ memoVisibility: value }, ["memoVisibility"]);
}; };
const handleThemeChange = async (theme: string) => { const handleThemeChange = async (theme: string) => {
await userStore.updateUserSetting({ theme }, ["theme"]); await userStore.updateUserGeneralSetting({ theme }, ["theme"]);
};
// Provide default values if setting is not loaded yet
const setting: UserSetting_GeneralSetting = generalSetting || {
locale: "en",
appearance: "system",
memoVisibility: "PRIVATE",
theme: "",
}; };
return ( return (
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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