Commit e60e47f7 authored by Steven's avatar Steven

chore: update user definition

parent e67820ca
......@@ -10,6 +10,7 @@ import (
)
const (
UserNamePrefix = "users/"
InboxNamePrefix = "inboxes/"
)
......@@ -33,6 +34,15 @@ func GetNameParentTokens(name string, tokenPrefixes ...string) ([]string, error)
return tokens, nil
}
// GetUsername returns the username from a resource name.
func GetUsername(name string) (string, error) {
tokens, err := GetNameParentTokens(name, UserNamePrefix)
if err != nil {
return "", err
}
return tokens[0], nil
}
// GetInboxID returns the inbox ID from a resource name.
func GetInboxID(name string) (int32, error) {
tokens, err := GetNameParentTokens(name, InboxNamePrefix)
......
......@@ -2,6 +2,7 @@ package v2
import (
"context"
"fmt"
"net/http"
"regexp"
"strings"
......@@ -27,8 +28,12 @@ var (
)
func (s *APIV2Service) GetUser(ctx context.Context, request *apiv2pb.GetUserRequest) (*apiv2pb.GetUserResponse, error) {
username, err := GetUsername(request.Name)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "name is required")
}
user, err := s.Store.GetUser(ctx, &store.FindUser{
Username: &request.Username,
Username: &username,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
......@@ -53,8 +58,12 @@ func (s *APIV2Service) CreateUser(ctx context.Context, request *apiv2pb.CreateUs
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
}
if !usernameMatcher.MatchString(strings.ToLower(request.User.Username)) {
return nil, status.Errorf(codes.InvalidArgument, "invalid username: %s", request.User.Username)
username, err := GetUsername(request.User.Name)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "name is required")
}
if !usernameMatcher.MatchString(strings.ToLower(username)) {
return nil, status.Errorf(codes.InvalidArgument, "invalid username: %s", username)
}
passwordHash, err := bcrypt.GenerateFromPassword([]byte(request.User.Password), bcrypt.DefaultCost)
if err != nil {
......@@ -62,7 +71,7 @@ func (s *APIV2Service) CreateUser(ctx context.Context, request *apiv2pb.CreateUs
}
user, err := s.Store.CreateUser(ctx, &store.User{
Username: request.User.Username,
Username: username,
Role: convertUserRoleToStore(request.User.Role),
Email: request.User.Email,
Nickname: request.User.Nickname,
......@@ -79,11 +88,15 @@ func (s *APIV2Service) CreateUser(ctx context.Context, request *apiv2pb.CreateUs
}
func (s *APIV2Service) UpdateUser(ctx context.Context, request *apiv2pb.UpdateUserRequest) (*apiv2pb.UpdateUserResponse, error) {
username, err := GetUsername(request.User.Name)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "name is required")
}
currentUser, err := getCurrentUser(ctx, s.Store)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
}
if currentUser.Username != request.User.Username && currentUser.Role != store.RoleAdmin {
if currentUser.Username != username && currentUser.Role != store.RoleAdmin {
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
}
if request.UpdateMask == nil || len(request.UpdateMask.Paths) == 0 {
......@@ -97,10 +110,10 @@ func (s *APIV2Service) UpdateUser(ctx context.Context, request *apiv2pb.UpdateUs
}
for _, field := range request.UpdateMask.Paths {
if field == "username" {
if !usernameMatcher.MatchString(strings.ToLower(request.User.Username)) {
return nil, status.Errorf(codes.InvalidArgument, "invalid username: %s", request.User.Username)
if !usernameMatcher.MatchString(strings.ToLower(username)) {
return nil, status.Errorf(codes.InvalidArgument, "invalid username: %s", username)
}
update.Username = &request.User.Username
update.Username = &username
} else if field == "nickname" {
update.Nickname = &request.User.Nickname
} else if field == "email" {
......@@ -146,17 +159,21 @@ func (s *APIV2Service) ListUserAccessTokens(ctx context.Context, request *apiv2p
}
userID := user.ID
username, err := GetUsername(request.Name)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "name is required")
}
// List access token for other users need to be verified.
if user.Username != request.Username {
if user.Username != username {
// Normal users can only list their access tokens.
if user.Role == store.RoleUser {
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
}
// The request user must be exist.
requestUser, err := s.Store.GetUser(ctx, &store.FindUser{Username: &request.Username})
requestUser, err := s.Store.GetUser(ctx, &store.FindUser{Username: &username})
if requestUser == nil || err != nil {
return nil, status.Errorf(codes.NotFound, "fail to find user %s", request.Username)
return nil, status.Errorf(codes.NotFound, "fail to find user %s", username)
}
userID = requestUser.ID
}
......@@ -217,21 +234,7 @@ func (s *APIV2Service) CreateUserAccessToken(ctx context.Context, request *apiv2
expiresAt = request.ExpiresAt.AsTime()
}
// Create access token for other users need to be verified.
if user.Username != request.Username {
// Normal users can only create access tokens for others.
if user.Role == store.RoleUser {
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
}
// The request user must be exist.
requestUser, err := s.Store.GetUser(ctx, &store.FindUser{Username: &request.Username})
if requestUser == nil || err != nil {
return nil, status.Errorf(codes.NotFound, "fail to find user %s", request.Username)
}
}
accessToken, err := auth.GenerateAccessToken(request.Username, user.ID, expiresAt, []byte(s.Secret))
accessToken, err := auth.GenerateAccessToken(user.Username, user.ID, expiresAt, []byte(s.Secret))
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to generate access token: %v", err)
}
......@@ -329,11 +332,11 @@ func (s *APIV2Service) UpsertAccessTokenToStore(ctx context.Context, user *store
func convertUserFromStore(user *store.User) *apiv2pb.User {
return &apiv2pb.User{
Id: int32(user.ID),
Name: fmt.Sprintf("%s%s", UserNamePrefix, user.Username),
Id: user.ID,
RowStatus: convertRowStatusFromStore(user.RowStatus),
CreateTime: timestamppb.New(time.Unix(user.CreatedTs, 0)),
UpdateTime: timestamppb.New(time.Unix(user.UpdatedTs, 0)),
Username: user.Username,
Role: convertUserRoleFromStore(user.Role),
Email: user.Email,
Nickname: user.Nickname,
......
......@@ -13,8 +13,8 @@ option go_package = "gen/api/v2";
service UserService {
rpc GetUser(GetUserRequest) returns (GetUserResponse) {
option (google.api.http) = {get: "/api/v2/users/{username}"};
option (google.api.method_signature) = "username";
option (google.api.http) = {get: "/api/v2/{name=users/*}"};
option (google.api.method_signature) = "name";
}
rpc CreateUser(CreateUserRequest) returns (CreateUserResponse) {
option (google.api.http) = {
......@@ -25,35 +25,37 @@ service UserService {
}
rpc UpdateUser(UpdateUserRequest) returns (UpdateUserResponse) {
option (google.api.http) = {
patch: "/api/v2/users/{user.username}"
patch: "/api/v2/{user.name=users/*}"
body: "user"
};
option (google.api.method_signature) = "user,update_mask";
}
// ListUserAccessTokens returns a list of access tokens for a user.
rpc ListUserAccessTokens(ListUserAccessTokensRequest) returns (ListUserAccessTokensResponse) {
option (google.api.http) = {get: "/api/v2/users/{username}/access_tokens"};
option (google.api.method_signature) = "username";
option (google.api.http) = {get: "/api/v2/{name=users/*}/access_tokens"};
option (google.api.method_signature) = "name";
}
// CreateUserAccessToken creates a new access token for a user.
rpc CreateUserAccessToken(CreateUserAccessTokenRequest) returns (CreateUserAccessTokenResponse) {
option (google.api.http) = {
post: "/api/v2/users/{username}/access_tokens"
post: "/api/v2/{name=users/*}/access_tokens"
body: "*"
};
option (google.api.method_signature) = "username";
option (google.api.method_signature) = "name";
}
// DeleteUserAccessToken deletes an access token for a user.
rpc DeleteUserAccessToken(DeleteUserAccessTokenRequest) returns (DeleteUserAccessTokenResponse) {
option (google.api.http) = {delete: "/api/v2/users/{username}/access_tokens/{access_token}"};
option (google.api.method_signature) = "username,access_token";
option (google.api.http) = {delete: "/api/v2/{name=users/*}/access_tokens/{access_token}"};
option (google.api.method_signature) = "name,access_token";
}
}
message User {
int32 id = 1;
// The name of the user.
// Format: users/{username}
string name = 1;
string username = 2;
int32 id = 2;
enum Role {
ROLE_UNSPECIFIED = 0;
......@@ -79,7 +81,9 @@ message User {
}
message GetUserRequest {
string username = 1;
// The name of the user.
// Format: users/{username}
string name = 1;
}
message GetUserResponse {
......@@ -105,7 +109,9 @@ message UpdateUserResponse {
}
message ListUserAccessTokensRequest {
string username = 1;
// The name of the user.
// Format: users/{username}
string name = 1;
}
message ListUserAccessTokensResponse {
......@@ -113,7 +119,9 @@ message ListUserAccessTokensResponse {
}
message CreateUserAccessTokenRequest {
string username = 1;
// The name of the user.
// Format: users/{username}
string name = 1;
string description = 2;
......@@ -125,7 +133,9 @@ message CreateUserAccessTokenResponse {
}
message DeleteUserAccessTokenRequest {
string username = 1;
// The name of the user.
// Format: users/{username}
string name = 1;
// access_token is the access token to delete.
string access_token = 2;
}
......
......@@ -1032,7 +1032,7 @@
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| username | [string](#string) | | |
| name | [string](#string) | | The name of the user. Format: users/{username} |
| description | [string](#string) | | |
| expires_at | [google.protobuf.Timestamp](#google-protobuf-Timestamp) | optional | |
......@@ -1094,7 +1094,7 @@
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| username | [string](#string) | | |
| name | [string](#string) | | The name of the user. Format: users/{username} |
| access_token | [string](#string) | | access_token is the access token to delete. |
......@@ -1120,7 +1120,7 @@
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| username | [string](#string) | | |
| name | [string](#string) | | The name of the user. Format: users/{username} |
......@@ -1150,7 +1150,7 @@
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| username | [string](#string) | | |
| name | [string](#string) | | The name of the user. Format: users/{username} |
......@@ -1211,8 +1211,8 @@
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| name | [string](#string) | | The name of the user. Format: users/{username} |
| id | [int32](#int32) | | |
| username | [string](#string) | | |
| role | [User.Role](#memos-api-v2-User-Role) | | |
| email | [string](#string) | | |
| nickname | [string](#string) | | |
......
This diff is collapsed.
This diff is collapsed.
......@@ -2,6 +2,7 @@ import { useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { useGlobalStore, useUserStore } from "@/store/module";
import { useUserV1Store } from "@/store/v1";
import { UserNamePrefix } from "@/store/v1/resourceName";
import { useTranslate } from "@/utils/i18n";
import { generateDialog } from "./Dialog";
import Icon from "./Icon";
......@@ -54,7 +55,7 @@ const ChangePasswordDialog: React.FC<Props> = ({ destroy }: Props) => {
const user = userStore.getState().user as User;
await userV1Store.updateUser(
{
username: user.username,
name: `${UserNamePrefix}${user.username}`,
password: newPassword,
},
["password"]
......
......@@ -69,7 +69,7 @@ const CreateAccessTokenDialog: React.FC<Props> = (props: Props) => {
try {
await userServiceClient.createUserAccessToken({
username: currentUser.username,
name: currentUser.name,
description: state.description,
expiresAt: state.expiration ? new Date(Date.now() + state.expiration * 1000) : undefined,
});
......
......@@ -5,7 +5,7 @@ import toast from "react-hot-toast";
import { activityServiceClient } from "@/grpcweb";
import useNavigateTo from "@/hooks/useNavigateTo";
import useInboxStore from "@/store/v1/inbox";
import { extractUsernameFromName } from "@/store/v1/user";
import { extractUsernameFromName } from "@/store/v1/resourceName";
import { Activity } from "@/types/proto/api/v2/activity_service";
import { Inbox, Inbox_Status } from "@/types/proto/api/v2/inbox_service";
import { useTranslate } from "@/utils/i18n";
......
......@@ -9,6 +9,7 @@ import useCurrentUser from "@/hooks/useCurrentUser";
import useNavigateTo from "@/hooks/useNavigateTo";
import { useFilterStore, useMemoStore, useUserStore } from "@/store/module";
import { useUserV1Store } from "@/store/v1";
import { extractUsernameFromName } from "@/store/v1/resourceName";
import { useTranslate } from "@/utils/i18n";
import showChangeMemoCreatedTsDialog from "./ChangeMemoCreatedTsDialog";
import { showCommonDialog } from "./Dialog/CommonDialog";
......@@ -42,7 +43,7 @@ const Memo: React.FC<Props> = (props: Props) => {
const [shouldRender, setShouldRender] = useState<boolean>(lazyRendering ? false : true);
const [displayTime, setDisplayTime] = useState<string>(getRelativeTimeString(memo.displayTs));
const memoContainerRef = useRef<HTMLDivElement>(null);
const readonly = memo.creatorUsername !== user?.username;
const readonly = memo.creatorUsername !== extractUsernameFromName(user?.name);
const [creator, setCreator] = useState(userV1Store.getUserByUsername(memo.creatorUsername));
const referenceRelations = memo.relationList.filter((relation) => relation.type === "REFERENCE");
const commentRelations = memo.relationList.filter((relation) => relation.relatedMemoId === memo.id && relation.type === "COMMENT");
......@@ -300,7 +301,7 @@ const Memo: React.FC<Props> = (props: Props) => {
<span className="flex flex-row justify-start items-center">
<UserAvatar className="!w-5 !h-auto mr-1" avatarUrl={creator.avatarUrl} />
<span className="text-sm text-gray-600 max-w-[8em] truncate dark:text-gray-400">
{creator.nickname || creator.username}
{creator.nickname || extractUsernameFromName(creator.name)}
</span>
</span>
</Tooltip>
......
......@@ -7,6 +7,7 @@ import { getTimeStampByDate } from "@/helpers/datetime";
import useCurrentUser from "@/hooks/useCurrentUser";
import { TAG_REG } from "@/labs/marked/parser";
import { useFilterStore, useMemoStore } from "@/store/module";
import { extractUsernameFromName } from "@/store/v1/resourceName";
import { useTranslate } from "@/utils/i18n";
import Empty from "./Empty";
import Memo from "./Memo";
......@@ -21,7 +22,7 @@ const MemoList: React.FC = () => {
const user = useCurrentUser();
const { tag: tagQuery, duration, text: textQuery, visibility } = filter;
const showMemoFilter = Boolean(tagQuery || (duration && duration.from < duration.to) || textQuery || visibility);
const username = params.username || user?.username || "";
const username = params.username || extractUsernameFromName(user.name) || "";
const fetchMoreRef = useRef<HTMLSpanElement>(null);
......
......@@ -4,6 +4,7 @@ import { useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { userServiceClient } from "@/grpcweb";
import useCurrentUser from "@/hooks/useCurrentUser";
import { extractUsernameFromName } from "@/store/v1/resourceName";
import { UserAccessToken } from "@/types/proto/api/v2/user_service";
import { useTranslate } from "@/utils/i18n";
import showCreateAccessTokenDialog from "../CreateAccessTokenDialog";
......@@ -12,7 +13,7 @@ import Icon from "../Icon";
import LearnMore from "../LearnMore";
const listAccessTokens = async (username: string) => {
const { accessTokens } = await userServiceClient.listUserAccessTokens({ username: username });
const { accessTokens } = await userServiceClient.listUserAccessTokens({ name: `${UserAccessToken}${username}` });
return accessTokens;
};
......@@ -22,13 +23,13 @@ const AccessTokenSection = () => {
const [userAccessTokens, setUserAccessTokens] = useState<UserAccessToken[]>([]);
useEffect(() => {
listAccessTokens(currentUser.username).then((accessTokens) => {
listAccessTokens(extractUsernameFromName(currentUser.name)).then((accessTokens) => {
setUserAccessTokens(accessTokens);
});
}, []);
const handleCreateAccessTokenDialogConfirm = async () => {
const accessTokens = await listAccessTokens(currentUser.username);
const accessTokens = await listAccessTokens(extractUsernameFromName(currentUser.name));
setUserAccessTokens(accessTokens);
};
......@@ -44,7 +45,7 @@ const AccessTokenSection = () => {
style: "danger",
dialogName: "delete-access-token-dialog",
onConfirm: async () => {
await userServiceClient.deleteUserAccessToken({ username: currentUser.username, accessToken: accessToken });
await userServiceClient.deleteUserAccessToken({ name: currentUser.name, accessToken: accessToken });
setUserAccessTokens(userAccessTokens.filter((token) => token.accessToken !== accessToken));
},
});
......
import { Button } from "@mui/joy";
import useCurrentUser from "@/hooks/useCurrentUser";
import { extractUsernameFromName } from "@/store/v1/resourceName";
import { useTranslate } from "@/utils/i18n";
import showChangePasswordDialog from "../ChangePasswordDialog";
import showUpdateAccountDialog from "../UpdateAccountDialog";
......@@ -18,7 +19,7 @@ const MyAccountSection = () => {
<UserAvatar className="mr-2 w-14 h-14" avatarUrl={user.avatarUrl} />
<div className="flex flex-col justify-center items-start">
<span className="text-2xl font-medium">{user.nickname}</span>
<span className="-mt-2 text-base text-gray-500 dark:text-gray-400">({user.username})</span>
<span className="-mt-2 text-base text-gray-500 dark:text-gray-400">({extractUsernameFromName(user.name)})</span>
</div>
</div>
<div className="w-full flex flex-row justify-start items-center mt-4 space-x-2">
......
......@@ -6,6 +6,7 @@ import { getDateTimeString } from "@/helpers/datetime";
import useLoading from "@/hooks/useLoading";
import toImage from "@/labs/html2image";
import { useUserV1Store } from "@/store/v1";
import { extractUsernameFromName } from "@/store/v1/resourceName";
import { useTranslate } from "@/utils/i18n";
import { generateDialog } from "./Dialog";
import showEmbedMemoDialog from "./EmbedMemoDialog";
......@@ -120,7 +121,7 @@ const ShareMemoDialog: React.FC<Props> = (props: Props) => {
<UserAvatar className="mr-2" avatarUrl={user.avatarUrl} />
<div className="w-auto grow truncate flex mr-2 flex-col justify-center items-start">
<span className="w-full text truncate font-medium text-gray-600 dark:text-gray-300">
{user.nickname || user.username}
{user.nickname || extractUsernameFromName(user.name)}
</span>
</div>
</div>
......
......@@ -6,6 +6,7 @@ import * as utils from "@/helpers/utils";
import useCurrentUser from "@/hooks/useCurrentUser";
import { useGlobalStore } from "@/store/module";
import { useUserV1Store } from "@/store/v1";
import { extractUsernameFromName } from "@/store/v1/resourceName";
import { useTranslate, Translations } from "@/utils/i18n";
import { useFilterStore, useMemoStore } from "../store/module";
import "@/less/usage-heat-map.less";
......@@ -53,20 +54,20 @@ const UsageHeatMap = () => {
const containerElRef = useRef<HTMLDivElement>(null);
useEffect(() => {
userV1Store.getOrFetchUserByUsername(user.username).then((user) => {
userV1Store.getOrFetchUserByUsername(extractUsernameFromName(user.name)).then((user) => {
if (!user) {
return;
}
setCreatedDays(Math.ceil((Date.now() - getTimeStampByDate(user.createTime)) / 1000 / 3600 / 24));
});
}, [user.username]);
}, [user.name]);
useEffect(() => {
if (memos.length === 0) {
return;
}
getMemoStats(user.username)
getMemoStats(extractUsernameFromName(user.name))
.then(({ data }) => {
setMemoAmount(data.length);
const newStat: DailyUsageStat[] = getInitialUsageStat(usedDaysAmount, beginDayTimestamp);
......@@ -85,7 +86,7 @@ const UsageHeatMap = () => {
.catch((error) => {
console.error(error);
});
}, [memos.length, user.username]);
}, [memos.length, user.name]);
const handleUsageStatItemMouseEnter = useCallback((event: React.MouseEvent, item: DailyUsageStat) => {
const tempDiv = document.createElement("div");
......
import useCurrentUser from "@/hooks/useCurrentUser";
import useNavigateTo from "@/hooks/useNavigateTo";
import { useGlobalStore, useUserStore } from "@/store/module";
import { extractUsernameFromName } from "@/store/v1/resourceName";
import { User_Role } from "@/types/proto/api/v2/user_service";
import { useTranslate } from "@/utils/i18n";
import showAboutSiteDialog from "./AboutSiteDialog";
......@@ -18,7 +19,7 @@ const UserBanner = () => {
const title = user ? user.nickname : systemStatus.customizedProfile.name || "memos";
const handleMyAccountClick = () => {
navigateTo(`/u/${encodeURIComponent(user.username)}`);
navigateTo(`/u/${encodeURIComponent(extractUsernameFromName(user.name))}`);
};
const handleAboutBtnClick = () => {
......
......@@ -5,6 +5,7 @@ import { getMemoStats } from "@/helpers/api";
import { DAILY_TIMESTAMP } from "@/helpers/consts";
import { getDateStampByDate, isFutureDate } from "@/helpers/datetime";
import useCurrentUser from "@/hooks/useCurrentUser";
import { extractUsernameFromName } from "@/store/v1/resourceName";
import { useTranslate } from "@/utils/i18n";
import Icon from "../Icon";
import "@/less/common/date-picker.less";
......@@ -28,7 +29,7 @@ const DatePicker: React.FC<DatePickerProps> = (props: DatePickerProps) => {
}, [datestamp]);
useEffect(() => {
getMemoStats(user.username).then(({ data }) => {
getMemoStats(extractUsernameFromName(user.name)).then(({ data }) => {
const m = new Map();
for (const record of data) {
const date = getDateStampByDate(record * 1000);
......@@ -36,7 +37,7 @@ const DatePicker: React.FC<DatePickerProps> = (props: DatePickerProps) => {
}
setCountByDate(m);
});
}, [user.username]);
}, [user.name]);
const firstDate = new Date(currentDateStamp);
const dayList = [];
......
......@@ -15,6 +15,7 @@ import { DAILY_TIMESTAMP, DEFAULT_MEMO_LIMIT } from "@/helpers/consts";
import { getDateStampByDate, getNormalizedDateString, getTimeStampByDate, getTimeString } from "@/helpers/datetime";
import useCurrentUser from "@/hooks/useCurrentUser";
import { useMemoStore, useUserStore } from "@/store/module";
import { extractUsernameFromName } from "@/store/v1/resourceName";
import { useTranslate } from "@/utils/i18n";
const DailyReview = () => {
......@@ -32,7 +33,7 @@ const DailyReview = () => {
const selectedDateStampWithOffset = selectedDateStamp + localSetting.dailyReviewTimeOffset * 60 * 60 * 1000;
return (
m.rowStatus === "NORMAL" &&
m.creatorUsername === user.username &&
m.creatorUsername === extractUsernameFromName(user.name) &&
displayTimestamp >= selectedDateStampWithOffset &&
displayTimestamp < selectedDateStampWithOffset + DAILY_TIMESTAMP
);
......
......@@ -20,6 +20,7 @@ import useCurrentUser from "@/hooks/useCurrentUser";
import useNavigateTo from "@/hooks/useNavigateTo";
import { useGlobalStore, useMemoStore } from "@/store/module";
import { useUserV1Store } from "@/store/v1";
import { extractUsernameFromName } from "@/store/v1/resourceName";
import { User, User_Role } from "@/types/proto/api/v2/user_service";
import { useTranslate } from "@/utils/i18n";
......@@ -35,7 +36,7 @@ const MemoDetail = () => {
const { systemStatus } = globalStore.state;
const memoId = Number(params.memoId);
const memo = memoStore.state.memos.find((memo) => memo.id === memoId);
const allowEdit = memo?.creatorUsername === currentUser?.username;
const allowEdit = memo?.creatorUsername === extractUsernameFromName(currentUser.name);
const referenceRelations = memo?.relationList.filter((relation) => relation.type === "REFERENCE") || [];
const commentRelations = memo?.relationList.filter((relation) => relation.relatedMemoId === memo.id && relation.type === "COMMENT") || [];
const comments = commentRelations
......
export const UserNamePrefix = "users/";
export const extractUsernameFromName = (name: string) => {
return name.split("/")[1];
};
import { create } from "zustand";
import { userServiceClient } from "@/grpcweb";
import { User } from "@/types/proto/api/v2/user_service";
import { UserNamePrefix, extractUsernameFromName } from "./resourceName";
interface UserV1Store {
userMapByUsername: Record<string, User>;
......@@ -25,7 +26,7 @@ const useUserV1Store = create<UserV1Store>()((set, get) => ({
const promisedUser = userServiceClient
.getUser({
username: username,
name: `${UserNamePrefix}${username}`,
})
.then(({ user }) => user);
requestCache.set(username, promisedUser);
......@@ -50,15 +51,12 @@ const useUserV1Store = create<UserV1Store>()((set, get) => ({
if (!updatedUser) {
throw new Error("User not found");
}
const username = extractUsernameFromName(updatedUser.name);
const userMap = get().userMapByUsername;
userMap[updatedUser.username] = updatedUser;
userMap[username] = updatedUser;
set(userMap);
return updatedUser;
},
}));
export const extractUsernameFromName = (name: string) => {
return name.split("/")[1];
};
export default useUserV1Store;
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