Commit e60e47f7 authored by Steven's avatar Steven

chore: update user definition

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