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
}
response := &v1pb.ListAllUserStatsResponse{
UserStats: userMemoStats,
Stats: userMemoStats,
}
return response, nil
}
......
......@@ -30,7 +30,6 @@ type APIV1Service struct {
v1pb.UnimplementedShortcutServiceServer
v1pb.UnimplementedInboxServiceServer
v1pb.UnimplementedActivityServiceServer
v1pb.UnimplementedWebhookServiceServer
v1pb.UnimplementedMarkdownServiceServer
v1pb.UnimplementedIdentityProviderServiceServer
......@@ -58,7 +57,6 @@ func NewAPIV1Service(secret string, profile *profile.Profile, store *store.Store
v1pb.RegisterShortcutServiceServer(grpcServer, apiv1Service)
v1pb.RegisterInboxServiceServer(grpcServer, apiv1Service)
v1pb.RegisterActivityServiceServer(grpcServer, apiv1Service)
v1pb.RegisterWebhookServiceServer(grpcServer, apiv1Service)
v1pb.RegisterMarkdownServiceServer(grpcServer, apiv1Service)
v1pb.RegisterIdentityProviderServiceServer(grpcServer, apiv1Service)
reflection.Register(grpcServer)
......@@ -107,9 +105,6 @@ func (s *APIV1Service) RegisterGateway(ctx context.Context, echoServer *echo.Ech
if err := v1pb.RegisterActivityServiceHandler(ctx, gwMux, conn); err != nil {
return err
}
if err := v1pb.RegisterWebhookServiceHandler(ctx, gwMux, conn); err != nil {
return err
}
if err := v1pb.RegisterMarkdownServiceHandler(ctx, gwMux, conn); err != nil {
return err
}
......
This diff is collapsed.
......@@ -12,7 +12,7 @@ const App = observer(() => {
const navigateTo = useNavigateTo();
const [mode, setMode] = useState<"light" | "dark">("light");
const workspaceProfile = workspaceStore.state.profile;
const userSetting = userStore.state.userSetting;
const userGeneralSetting = userStore.state.userGeneralSetting;
const workspaceGeneralSetting = workspaceStore.state.generalSetting;
// Redirect to sign up page if no instance owner.
......@@ -94,22 +94,22 @@ const App = observer(() => {
}, [mode]);
useEffect(() => {
if (!userSetting) {
if (!userGeneralSetting) {
return;
}
workspaceStore.state.setPartial({
locale: userSetting.locale || workspaceStore.state.locale,
appearance: userSetting.appearance || workspaceStore.state.appearance,
locale: userGeneralSetting.locale || workspaceStore.state.locale,
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)
useEffect(() => {
if (userSetting?.theme) {
loadTheme(userSetting.theme);
if (userGeneralSetting?.theme) {
loadTheme(userGeneralSetting.theme);
}
}, [userSetting?.theme]);
}, [userGeneralSetting?.theme]);
return <Outlet />;
});
......
......@@ -79,7 +79,7 @@ function CreateShortcutDialog({ open, onOpenChange, shortcut: initialShortcut, o
toast.success("Update shortcut successfully");
}
// Refresh shortcuts.
await userStore.fetchShortcuts();
await userStore.fetchUserSettings();
requestState.setFinish();
onSuccess?.();
onOpenChange(false);
......
......@@ -4,7 +4,7 @@ import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { webhookServiceClient } from "@/grpcweb";
import { userServiceClient } from "@/grpcweb";
import useCurrentUser from "@/hooks/useCurrentUser";
import useLoading from "@/hooks/useLoading";
import { useTranslate } from "@/utils/i18n";
......@@ -32,19 +32,24 @@ function CreateWebhookDialog({ open, onOpenChange, webhookName, onSuccess }: Pro
const isCreating = webhookName === undefined;
useEffect(() => {
if (webhookName) {
webhookServiceClient
.getWebhook({
name: webhookName,
if (webhookName && currentUser) {
// For editing, we need to get the webhook data
// Since we're using user webhooks now, we need to list all webhooks and find the one we want
userServiceClient
.listUserWebhooks({
parent: currentUser.name,
})
.then((webhook) => {
setState({
displayName: webhook.displayName,
url: webhook.url,
});
.then((response) => {
const webhook = response.webhooks.find((w) => w.name === webhookName);
if (webhook) {
setState({
displayName: webhook.displayName,
url: webhook.url,
});
}
});
}
}, [webhookName]);
}, [webhookName, currentUser]);
const setPartialState = (partialState: Partial<State>) => {
setState({
......@@ -79,7 +84,7 @@ function CreateWebhookDialog({ open, onOpenChange, webhookName, onSuccess }: Pro
try {
requestState.setLoading();
if (isCreating) {
await webhookServiceClient.createWebhook({
await userServiceClient.createUserWebhook({
parent: currentUser.name,
webhook: {
displayName: state.displayName,
......@@ -87,7 +92,7 @@ function CreateWebhookDialog({ open, onOpenChange, webhookName, onSuccess }: Pro
},
});
} else {
await webhookServiceClient.updateWebhook({
await userServiceClient.updateUserWebhook({
webhook: {
name: webhookName,
displayName: state.displayName,
......
......@@ -28,14 +28,14 @@ const ShortcutsSection = observer(() => {
const [editingShortcut, setEditingShortcut] = useState<Shortcut | undefined>();
useAsyncEffect(async () => {
await userStore.fetchShortcuts();
await userStore.fetchUserSettings();
}, []);
const handleDeleteShortcut = async (shortcut: Shortcut) => {
const confirmed = window.confirm("Are you sure you want to delete this shortcut?");
if (confirmed) {
await shortcutServiceClient.deleteShortcut({ name: shortcut.name });
await userStore.fetchShortcuts();
await userStore.fetchUserSettings();
}
};
......
......@@ -17,7 +17,6 @@ import { memoStore, attachmentStore, userStore, workspaceStore } from "@/store";
import { extractMemoIdFromName } from "@/store/common";
import { Attachment } from "@/types/proto/api/v1/attachment_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 { convertVisibilityFromString } from "@/utils/memo";
import DateTimeInput from "../DateTimeInput";
......@@ -77,7 +76,7 @@ const MemoEditor = observer((props: Props) => {
const [hasContent, setHasContent] = useState<boolean>(false);
const [isVisibilitySelectorOpen, setIsVisibilitySelectorOpen] = useState(false);
const editorRef = useRef<EditorRefActions>(null);
const userSetting = userStore.state.userSetting as UserSetting;
const userGeneralSetting = userStore.state.userGeneralSetting;
const contentCacheKey = `${currentUser.name}-${cacheKey || ""}`;
const [contentCache, setContentCache] = useLocalStorage<string>(contentCacheKey, "");
const referenceRelations = memoName
......@@ -99,7 +98,7 @@ const MemoEditor = observer((props: Props) => {
}, [autoFocus]);
useAsyncEffect(async () => {
let visibility = convertVisibilityFromString(userSetting.memoVisibility);
let visibility = convertVisibilityFromString(userGeneralSetting?.memoVisibility || "PRIVATE");
if (workspaceMemoRelatedSetting.disallowPublicVisibility && visibility === Visibility.PUBLIC) {
visibility = Visibility.PROTECTED;
}
......@@ -111,7 +110,7 @@ const MemoEditor = observer((props: Props) => {
...prevState,
memoVisibility: convertVisibilityFromString(visibility),
}));
}, [parentMemoName, userSetting.memoVisibility, workspaceMemoRelatedSetting.disallowPublicVisibility]);
}, [parentMemoName, userGeneralSetting?.memoVisibility, workspaceMemoRelatedSetting.disallowPublicVisibility]);
useAsyncEffect(async () => {
if (!memoName) {
......
......@@ -2,21 +2,21 @@ import { ExternalLinkIcon, TrashIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { Button } from "@/components/ui/button";
import { webhookServiceClient } from "@/grpcweb";
import { userServiceClient } from "@/grpcweb";
import useCurrentUser from "@/hooks/useCurrentUser";
import { Webhook } from "@/types/proto/api/v1/webhook_service";
import { UserWebhook } from "@/types/proto/api/v1/user_service";
import { useTranslate } from "@/utils/i18n";
import CreateWebhookDialog from "../CreateWebhookDialog";
const WebhookSection = () => {
const t = useTranslate();
const currentUser = useCurrentUser();
const [webhooks, setWebhooks] = useState<Webhook[]>([]);
const [webhooks, setWebhooks] = useState<UserWebhook[]>([]);
const [isCreateWebhookDialogOpen, setIsCreateWebhookDialogOpen] = useState(false);
const listWebhooks = async () => {
if (!currentUser) return [];
const { webhooks } = await webhookServiceClient.listWebhooks({
const { webhooks } = await userServiceClient.listUserWebhooks({
parent: currentUser.name,
});
return webhooks;
......@@ -34,10 +34,10 @@ const WebhookSection = () => {
setIsCreateWebhookDialogOpen(false);
};
const handleDeleteWebhook = async (webhook: Webhook) => {
const handleDeleteWebhook = async (webhook: UserWebhook) => {
const confirmed = window.confirm(`Are you sure to delete webhook \`${webhook.displayName}\`? You cannot undo this action.`);
if (confirmed) {
await webhookServiceClient.deleteWebhook({ name: webhook.name });
await userServiceClient.deleteUserWebhook({ name: webhook.name });
setWebhooks(webhooks.filter((item) => item.name !== webhook.name));
}
};
......
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