mirror of https://github.com/usememos/memos
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
147 lines
4.9 KiB
TypeScript
147 lines
4.9 KiB
TypeScript
import { create } from "zustand";
|
|
import { combine } from "zustand/middleware";
|
|
import { authServiceClient, userServiceClient } from "@/grpcweb";
|
|
import storage from "@/helpers/storage";
|
|
import store from "@/store";
|
|
import { setAppearance, setLocale } from "@/store/reducer/global";
|
|
import { User, UserSetting } from "@/types/proto/api/v2/user_service";
|
|
import { UserNamePrefix, extractUsernameFromName } from "./resourceName";
|
|
|
|
interface State {
|
|
userMapByUsername: Record<string, User>;
|
|
// The name of current user. Format: `users/${username}`
|
|
currentUser?: string;
|
|
userSetting?: UserSetting;
|
|
}
|
|
|
|
const getDefaultState = (): State => ({
|
|
userMapByUsername: {},
|
|
currentUser: undefined,
|
|
userSetting: undefined,
|
|
});
|
|
|
|
const getDefaultUserSetting = () => {
|
|
return UserSetting.fromPartial({
|
|
locale: "en",
|
|
appearance: "auto",
|
|
memoVisibility: "PRIVATE",
|
|
});
|
|
};
|
|
|
|
// Request cache is used to prevent multiple requests.
|
|
const requestCache = new Map<string, Promise<any>>();
|
|
|
|
export const useUserStore = create(
|
|
combine(getDefaultState(), (set, get) => ({
|
|
fetchUsers: async () => {
|
|
const { users } = await userServiceClient.listUsers({});
|
|
const userMap = get().userMapByUsername;
|
|
for (const user of users) {
|
|
userMap[user.username] = user;
|
|
}
|
|
set({ userMapByUsername: userMap });
|
|
return users;
|
|
},
|
|
getOrFetchUserByUsername: async (username: string) => {
|
|
const userMap = get().userMapByUsername;
|
|
if (userMap[username]) {
|
|
return userMap[username] as User;
|
|
}
|
|
if (requestCache.has(username)) {
|
|
return await requestCache.get(username);
|
|
}
|
|
|
|
const promisedUser = userServiceClient
|
|
.getUser({
|
|
name: `${UserNamePrefix}${username}`,
|
|
})
|
|
.then(({ user }) => user);
|
|
requestCache.set(username, promisedUser);
|
|
const user = await promisedUser;
|
|
if (!user) {
|
|
throw new Error("User not found");
|
|
}
|
|
requestCache.delete(username);
|
|
userMap[username] = user;
|
|
set({ userMapByUsername: userMap });
|
|
return user;
|
|
},
|
|
getUserByUsername: (username: string) => {
|
|
const userMap = get().userMapByUsername;
|
|
return userMap[username];
|
|
},
|
|
updateUser: async (user: Partial<User>, updateMask: string[]) => {
|
|
const { user: updatedUser } = await userServiceClient.updateUser({
|
|
user: user,
|
|
updateMask: updateMask,
|
|
});
|
|
if (!updatedUser) {
|
|
throw new Error("User not found");
|
|
}
|
|
const userMap = get().userMapByUsername;
|
|
if (user.name !== updatedUser.name) {
|
|
delete userMap[extractUsernameFromName(user.name)];
|
|
}
|
|
userMap[updatedUser.username] = updatedUser;
|
|
set({ userMapByUsername: userMap });
|
|
if (user.name === get().currentUser) {
|
|
set({ currentUser: updatedUser.name });
|
|
}
|
|
return updatedUser;
|
|
},
|
|
deleteUser: async (name: string) => {
|
|
await userServiceClient.deleteUser({
|
|
name,
|
|
});
|
|
const username = extractUsernameFromName(name);
|
|
const userMap = get().userMapByUsername;
|
|
delete userMap[username];
|
|
set({ userMapByUsername: userMap });
|
|
},
|
|
fetchCurrentUser: async () => {
|
|
const { user } = await authServiceClient.getAuthStatus({});
|
|
if (!user) {
|
|
throw new Error("User not found");
|
|
}
|
|
const userMap = get().userMapByUsername;
|
|
userMap[user.username] = user;
|
|
set({ currentUser: user.name, userMapByUsername: userMap });
|
|
const { setting } = await userServiceClient.getUserSetting({});
|
|
set({
|
|
userSetting: UserSetting.fromPartial({
|
|
...getDefaultUserSetting(),
|
|
...setting,
|
|
}),
|
|
});
|
|
const userLocale = get().userSetting?.locale;
|
|
const userAppearance = get().userSetting?.appearance;
|
|
const { locale: storedLocale, appearance: storedAppearance } = storage.get(["locale", "appearance"]);
|
|
// Use storageLocale > userLocale > default locale
|
|
const locale = storedLocale || userLocale || store.getState().global.locale;
|
|
const appearance = (storedAppearance || userAppearance || store.getState().global.appearance) as Appearance;
|
|
|
|
// If storedLocale is undefined, set storageLocale to userLocale.
|
|
if (storedLocale === undefined && storedAppearance === undefined) {
|
|
storage.set({ locale: locale });
|
|
storage.set({ appearance: appearance });
|
|
}
|
|
|
|
store.dispatch(setLocale(locale));
|
|
store.dispatch(setAppearance(appearance));
|
|
|
|
return user;
|
|
},
|
|
updateUserSetting: async (userSetting: Partial<UserSetting>, updateMask: string[]) => {
|
|
const { setting: updatedUserSetting } = await userServiceClient.updateUserSetting({
|
|
setting: userSetting,
|
|
updateMask: updateMask,
|
|
});
|
|
if (!updatedUserSetting) {
|
|
throw new Error("User setting not found");
|
|
}
|
|
set({ userSetting: updatedUserSetting });
|
|
return updatedUserSetting;
|
|
},
|
|
}))
|
|
);
|