chore: update list users

pull/2658/head^2
Steven 2 years ago
parent c267074851
commit df3303dcd3

@ -27,6 +27,29 @@ var (
usernameMatcher = regexp.MustCompile("^[a-z0-9]([a-z0-9-]{1,30}[a-z0-9])$") usernameMatcher = regexp.MustCompile("^[a-z0-9]([a-z0-9-]{1,30}[a-z0-9])$")
) )
func (s *APIV2Service) ListUsers(ctx context.Context, _ *apiv2pb.ListUsersRequest) (*apiv2pb.ListUsersResponse, error) {
currentUser, err := getCurrentUser(ctx, s.Store)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
}
if currentUser.Role != store.RoleHost && currentUser.Role != store.RoleAdmin {
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
}
users, err := s.Store.ListUsers(ctx, &store.FindUser{})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to list users: %v", err)
}
response := &apiv2pb.ListUsersResponse{
Users: []*apiv2pb.User{},
}
for _, user := range users {
response.Users = append(response.Users, convertUserFromStore(user))
}
return response, nil
}
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 := ExtractUsernameFromName(request.Name) username, err := ExtractUsernameFromName(request.Name)
if err != nil { if err != nil {

@ -12,6 +12,10 @@ import "google/protobuf/timestamp.proto";
option go_package = "gen/api/v2"; option go_package = "gen/api/v2";
service UserService { service UserService {
// ListUsers returns a list of users.
rpc ListUsers(ListUsersRequest) returns (ListUsersResponse) {
option (google.api.http) = {get: "/api/v2/users"};
}
// GetUser gets a user by name. // GetUser gets a user by name.
rpc GetUser(GetUserRequest) returns (GetUserResponse) { rpc GetUser(GetUserRequest) returns (GetUserResponse) {
option (google.api.http) = {get: "/api/v2/{name=users/*}"}; option (google.api.http) = {get: "/api/v2/{name=users/*}"};
@ -99,6 +103,12 @@ message User {
google.protobuf.Timestamp update_time = 10; google.protobuf.Timestamp update_time = 10;
} }
message ListUsersRequest {}
message ListUsersResponse {
repeated User users = 1;
}
message GetUserRequest { message GetUserRequest {
// The name of the user. // The name of the user.
// Format: users/{username} // Format: users/{username}

@ -31,6 +31,8 @@
- [GetUserSettingResponse](#memos-api-v2-GetUserSettingResponse) - [GetUserSettingResponse](#memos-api-v2-GetUserSettingResponse)
- [ListUserAccessTokensRequest](#memos-api-v2-ListUserAccessTokensRequest) - [ListUserAccessTokensRequest](#memos-api-v2-ListUserAccessTokensRequest)
- [ListUserAccessTokensResponse](#memos-api-v2-ListUserAccessTokensResponse) - [ListUserAccessTokensResponse](#memos-api-v2-ListUserAccessTokensResponse)
- [ListUsersRequest](#memos-api-v2-ListUsersRequest)
- [ListUsersResponse](#memos-api-v2-ListUsersResponse)
- [UpdateUserRequest](#memos-api-v2-UpdateUserRequest) - [UpdateUserRequest](#memos-api-v2-UpdateUserRequest)
- [UpdateUserResponse](#memos-api-v2-UpdateUserResponse) - [UpdateUserResponse](#memos-api-v2-UpdateUserResponse)
- [UpdateUserSettingRequest](#memos-api-v2-UpdateUserSettingRequest) - [UpdateUserSettingRequest](#memos-api-v2-UpdateUserSettingRequest)
@ -541,6 +543,31 @@
<a name="memos-api-v2-ListUsersRequest"></a>
### ListUsersRequest
<a name="memos-api-v2-ListUsersResponse"></a>
### ListUsersResponse
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| users | [User](#memos-api-v2-User) | repeated | |
<a name="memos-api-v2-UpdateUserRequest"></a> <a name="memos-api-v2-UpdateUserRequest"></a>
### UpdateUserRequest ### UpdateUserRequest
@ -691,6 +718,7 @@
| Method Name | Request Type | Response Type | Description | | Method Name | Request Type | Response Type | Description |
| ----------- | ------------ | ------------- | ------------| | ----------- | ------------ | ------------- | ------------|
| ListUsers | [ListUsersRequest](#memos-api-v2-ListUsersRequest) | [ListUsersResponse](#memos-api-v2-ListUsersResponse) | ListUsers returns a list of users. |
| GetUser | [GetUserRequest](#memos-api-v2-GetUserRequest) | [GetUserResponse](#memos-api-v2-GetUserResponse) | GetUser gets a user by name. | | GetUser | [GetUserRequest](#memos-api-v2-GetUserRequest) | [GetUserResponse](#memos-api-v2-GetUserResponse) | GetUser gets a user by name. |
| CreateUser | [CreateUserRequest](#memos-api-v2-CreateUserRequest) | [CreateUserResponse](#memos-api-v2-CreateUserResponse) | CreateUser creates a new user. | | CreateUser | [CreateUserRequest](#memos-api-v2-CreateUserRequest) | [CreateUserResponse](#memos-api-v2-CreateUserResponse) | CreateUser creates a new user. |
| UpdateUser | [UpdateUserRequest](#memos-api-v2-UpdateUserRequest) | [UpdateUserResponse](#memos-api-v2-UpdateUserResponse) | UpdateUser updates a user. | | UpdateUser | [UpdateUserRequest](#memos-api-v2-UpdateUserRequest) | [UpdateUserResponse](#memos-api-v2-UpdateUserResponse) | UpdateUser updates a user. |

File diff suppressed because it is too large Load Diff

@ -31,6 +31,24 @@ var _ = runtime.String
var _ = utilities.NewDoubleArray var _ = utilities.NewDoubleArray
var _ = metadata.Join var _ = metadata.Join
func request_UserService_ListUsers_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListUsersRequest
var metadata runtime.ServerMetadata
msg, err := client.ListUsers(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_UserService_ListUsers_0(ctx context.Context, marshaler runtime.Marshaler, server UserServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListUsersRequest
var metadata runtime.ServerMetadata
msg, err := server.ListUsers(ctx, &protoReq)
return msg, metadata, err
}
func request_UserService_GetUser_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { func request_UserService_GetUser_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetUserRequest var protoReq GetUserRequest
var metadata runtime.ServerMetadata var metadata runtime.ServerMetadata
@ -619,6 +637,31 @@ func local_request_UserService_DeleteUserAccessToken_0(ctx context.Context, mars
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterUserServiceHandlerFromEndpoint instead. // Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterUserServiceHandlerFromEndpoint instead.
func RegisterUserServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server UserServiceServer) error { func RegisterUserServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server UserServiceServer) error {
mux.Handle("GET", pattern_UserService_ListUsers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/memos.api.v2.UserService/ListUsers", runtime.WithHTTPPathPattern("/api/v2/users"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_UserService_ListUsers_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_UserService_ListUsers_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_UserService_GetUser_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { mux.Handle("GET", pattern_UserService_GetUser_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context()) ctx, cancel := context.WithCancel(req.Context())
defer cancel() defer cancel()
@ -885,6 +928,28 @@ func RegisterUserServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn
// "UserServiceClient" to call the correct interceptors. // "UserServiceClient" to call the correct interceptors.
func RegisterUserServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client UserServiceClient) error { func RegisterUserServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client UserServiceClient) error {
mux.Handle("GET", pattern_UserService_ListUsers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/memos.api.v2.UserService/ListUsers", runtime.WithHTTPPathPattern("/api/v2/users"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_UserService_ListUsers_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_UserService_ListUsers_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_UserService_GetUser_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { mux.Handle("GET", pattern_UserService_GetUser_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context()) ctx, cancel := context.WithCancel(req.Context())
defer cancel() defer cancel()
@ -1087,6 +1152,8 @@ func RegisterUserServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux
} }
var ( var (
pattern_UserService_ListUsers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v2", "users"}, ""))
pattern_UserService_GetUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v2", "users", "name"}, "")) pattern_UserService_GetUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v2", "users", "name"}, ""))
pattern_UserService_CreateUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "users"}, "")) pattern_UserService_CreateUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "users"}, ""))
@ -1107,6 +1174,8 @@ var (
) )
var ( var (
forward_UserService_ListUsers_0 = runtime.ForwardResponseMessage
forward_UserService_GetUser_0 = runtime.ForwardResponseMessage forward_UserService_GetUser_0 = runtime.ForwardResponseMessage
forward_UserService_CreateUser_0 = runtime.ForwardResponseMessage forward_UserService_CreateUser_0 = runtime.ForwardResponseMessage

@ -19,6 +19,7 @@ import (
const _ = grpc.SupportPackageIsVersion7 const _ = grpc.SupportPackageIsVersion7
const ( const (
UserService_ListUsers_FullMethodName = "/memos.api.v2.UserService/ListUsers"
UserService_GetUser_FullMethodName = "/memos.api.v2.UserService/GetUser" UserService_GetUser_FullMethodName = "/memos.api.v2.UserService/GetUser"
UserService_CreateUser_FullMethodName = "/memos.api.v2.UserService/CreateUser" UserService_CreateUser_FullMethodName = "/memos.api.v2.UserService/CreateUser"
UserService_UpdateUser_FullMethodName = "/memos.api.v2.UserService/UpdateUser" UserService_UpdateUser_FullMethodName = "/memos.api.v2.UserService/UpdateUser"
@ -34,6 +35,8 @@ const (
// //
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type UserServiceClient interface { type UserServiceClient interface {
// ListUsers returns a list of users.
ListUsers(ctx context.Context, in *ListUsersRequest, opts ...grpc.CallOption) (*ListUsersResponse, error)
// GetUser gets a user by name. // GetUser gets a user by name.
GetUser(ctx context.Context, in *GetUserRequest, opts ...grpc.CallOption) (*GetUserResponse, error) GetUser(ctx context.Context, in *GetUserRequest, opts ...grpc.CallOption) (*GetUserResponse, error)
// CreateUser creates a new user. // CreateUser creates a new user.
@ -60,6 +63,15 @@ func NewUserServiceClient(cc grpc.ClientConnInterface) UserServiceClient {
return &userServiceClient{cc} return &userServiceClient{cc}
} }
func (c *userServiceClient) ListUsers(ctx context.Context, in *ListUsersRequest, opts ...grpc.CallOption) (*ListUsersResponse, error) {
out := new(ListUsersResponse)
err := c.cc.Invoke(ctx, UserService_ListUsers_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *userServiceClient) GetUser(ctx context.Context, in *GetUserRequest, opts ...grpc.CallOption) (*GetUserResponse, error) { func (c *userServiceClient) GetUser(ctx context.Context, in *GetUserRequest, opts ...grpc.CallOption) (*GetUserResponse, error) {
out := new(GetUserResponse) out := new(GetUserResponse)
err := c.cc.Invoke(ctx, UserService_GetUser_FullMethodName, in, out, opts...) err := c.cc.Invoke(ctx, UserService_GetUser_FullMethodName, in, out, opts...)
@ -145,6 +157,8 @@ func (c *userServiceClient) DeleteUserAccessToken(ctx context.Context, in *Delet
// All implementations must embed UnimplementedUserServiceServer // All implementations must embed UnimplementedUserServiceServer
// for forward compatibility // for forward compatibility
type UserServiceServer interface { type UserServiceServer interface {
// ListUsers returns a list of users.
ListUsers(context.Context, *ListUsersRequest) (*ListUsersResponse, error)
// GetUser gets a user by name. // GetUser gets a user by name.
GetUser(context.Context, *GetUserRequest) (*GetUserResponse, error) GetUser(context.Context, *GetUserRequest) (*GetUserResponse, error)
// CreateUser creates a new user. // CreateUser creates a new user.
@ -168,6 +182,9 @@ type UserServiceServer interface {
type UnimplementedUserServiceServer struct { type UnimplementedUserServiceServer struct {
} }
func (UnimplementedUserServiceServer) ListUsers(context.Context, *ListUsersRequest) (*ListUsersResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListUsers not implemented")
}
func (UnimplementedUserServiceServer) GetUser(context.Context, *GetUserRequest) (*GetUserResponse, error) { func (UnimplementedUserServiceServer) GetUser(context.Context, *GetUserRequest) (*GetUserResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetUser not implemented") return nil, status.Errorf(codes.Unimplemented, "method GetUser not implemented")
} }
@ -208,6 +225,24 @@ func RegisterUserServiceServer(s grpc.ServiceRegistrar, srv UserServiceServer) {
s.RegisterService(&UserService_ServiceDesc, srv) s.RegisterService(&UserService_ServiceDesc, srv)
} }
func _UserService_ListUsers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListUsersRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserServiceServer).ListUsers(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: UserService_ListUsers_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserServiceServer).ListUsers(ctx, req.(*ListUsersRequest))
}
return interceptor(ctx, in, info, handler)
}
func _UserService_GetUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { func _UserService_GetUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetUserRequest) in := new(GetUserRequest)
if err := dec(in); err != nil { if err := dec(in); err != nil {
@ -377,6 +412,10 @@ var UserService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "memos.api.v2.UserService", ServiceName: "memos.api.v2.UserService",
HandlerType: (*UserServiceServer)(nil), HandlerType: (*UserServiceServer)(nil),
Methods: []grpc.MethodDesc{ Methods: []grpc.MethodDesc{
{
MethodName: "ListUsers",
Handler: _UserService_ListUsers_Handler,
},
{ {
MethodName: "GetUser", MethodName: "GetUser",
Handler: _UserService_GetUser_Handler, Handler: _UserService_GetUser_Handler,

@ -39,7 +39,7 @@ func NewFrontendService(profile *profile.Profile, store *store.Store) *FrontendS
} }
func (s *FrontendService) Serve(e *echo.Echo) { func (s *FrontendService) Serve(e *echo.Echo) {
// Use echo static middleware to serve the built dist folder // Use echo static middleware to serve the built dist folder.
// refer: https://github.com/labstack/echo/blob/master/middleware/static.go // refer: https://github.com/labstack/echo/blob/master/middleware/static.go
e.Use(middleware.StaticWithConfig(middleware.StaticConfig{ e.Use(middleware.StaticWithConfig(middleware.StaticConfig{
Skipper: defaultAPIRequestSkipper, Skipper: defaultAPIRequestSkipper,
@ -80,6 +80,10 @@ func (s *FrontendService) registerRoutes(e *echo.Echo) {
return echo.NewHTTPError(http.StatusInternalServerError, "Instance URL system setting is not set") return echo.NewHTTPError(http.StatusInternalServerError, "Instance URL system setting is not set")
} }
instanceURL := instanceURLSetting.Value instanceURL := instanceURLSetting.Value
if instanceURL == "" {
return echo.NewHTTPError(http.StatusInternalServerError, "Instance URL system setting is not set")
}
robotsTxt := fmt.Sprintf(`User-agent: * robotsTxt := fmt.Sprintf(`User-agent: *
Allow: / Allow: /
Host: %s Host: %s
@ -98,8 +102,11 @@ Sitemap: %s/sitemap.xml`, instanceURL, instanceURL)
if instanceURLSetting == nil { if instanceURLSetting == nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Instance URL system setting is not set") return echo.NewHTTPError(http.StatusInternalServerError, "Instance URL system setting is not set")
} }
instanceURL := instanceURLSetting.Value instanceURL := instanceURLSetting.Value
if instanceURL == "" {
return echo.NewHTTPError(http.StatusInternalServerError, "Instance URL system setting is not set")
}
urlsets := []string{} urlsets := []string{}
// Append memo list. // Append memo list.
memoList, err := s.Store.ListMemos(ctx, &store.FindMemo{ memoList, err := s.Store.ListMemos(ctx, &store.FindMemo{

@ -1,6 +1,7 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import { useUserV1Store, UserNamePrefix } from "@/store/v1"; import { useUserV1Store } from "@/store/v1";
import { User } from "@/types/proto/api/v2/user_service";
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";
@ -49,7 +50,7 @@ const ChangeMemberPasswordDialog: React.FC<Props> = (props: Props) => {
try { try {
await userV1Store.updateUser( await userV1Store.updateUser(
{ {
name: `${UserNamePrefix}${user.username}`, name: user.name,
password: newPassword, password: newPassword,
}, },
["password"] ["password"]
@ -66,7 +67,7 @@ const ChangeMemberPasswordDialog: React.FC<Props> = (props: Props) => {
<> <>
<div className="dialog-header-container !w-64"> <div className="dialog-header-container !w-64">
<p className="title-text"> <p className="title-text">
{t("setting.account-section.change-password")} ({user.username}) {t("setting.account-section.change-password")} ({user.nickname})
</p> </p>
<button className="btn close-btn" onClick={handleCloseBtnClick}> <button className="btn close-btn" onClick={handleCloseBtnClick}>
<Icon.X /> <Icon.X />

@ -2,11 +2,10 @@ import { Button, Dropdown, Input, Menu, MenuButton } from "@mui/joy";
import React, { useEffect, useState } from "react"; import React, { 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 * as api from "@/helpers/api";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import { UserNamePrefix, useUserV1Store } from "@/store/v1"; import { UserNamePrefix, extractUsernameFromName, useUserV1Store } from "@/store/v1";
import { RowStatus } from "@/types/proto/api/v2/common"; import { RowStatus } from "@/types/proto/api/v2/common";
import { 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";
import showChangeMemberPasswordDialog from "../ChangeMemberPasswordDialog"; import showChangeMemberPasswordDialog from "../ChangeMemberPasswordDialog";
import { showCommonDialog } from "../Dialog/CommonDialog"; import { showCommonDialog } from "../Dialog/CommonDialog";
@ -32,8 +31,8 @@ const MemberSection = () => {
}, []); }, []);
const fetchUserList = async () => { const fetchUserList = async () => {
const { data } = await api.getUserList(); const users = await userV1Store.fetchUsers();
setUserList(data.sort((a, b) => a.id - b.id)); setUserList(users);
}; };
const handleUsernameInputChange = (event: React.ChangeEvent<HTMLInputElement>) => { const handleUsernameInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
@ -81,13 +80,13 @@ const MemberSection = () => {
const handleArchiveUserClick = (user: User) => { const handleArchiveUserClick = (user: User) => {
showCommonDialog({ showCommonDialog({
title: t("setting.member-section.archive-member"), title: t("setting.member-section.archive-member"),
content: t("setting.member-section.archive-warning", { username: user.username }), content: t("setting.member-section.archive-warning", { username: user.nickname }),
style: "danger", style: "danger",
dialogName: "archive-user-dialog", dialogName: "archive-user-dialog",
onConfirm: async () => { onConfirm: async () => {
await userServiceClient.updateUser({ await userServiceClient.updateUser({
user: { user: {
name: `${UserNamePrefix}${user.username}`, name: user.name,
rowStatus: RowStatus.ARCHIVED, rowStatus: RowStatus.ARCHIVED,
}, },
updateMask: ["row_status"], updateMask: ["row_status"],
@ -100,7 +99,7 @@ const MemberSection = () => {
const handleRestoreUserClick = async (user: User) => { const handleRestoreUserClick = async (user: User) => {
await userServiceClient.updateUser({ await userServiceClient.updateUser({
user: { user: {
name: `${UserNamePrefix}${user.username}`, name: user.name,
rowStatus: RowStatus.ACTIVE, rowStatus: RowStatus.ACTIVE,
}, },
updateMask: ["row_status"], updateMask: ["row_status"],
@ -111,11 +110,11 @@ const MemberSection = () => {
const handleDeleteUserClick = (user: User) => { const handleDeleteUserClick = (user: User) => {
showCommonDialog({ showCommonDialog({
title: t("setting.member-section.delete-member"), title: t("setting.member-section.delete-member"),
content: t("setting.member-section.delete-warning", { username: user.username }), content: t("setting.member-section.delete-warning", { username: user.nickname }),
style: "danger", style: "danger",
dialogName: "delete-user-dialog", dialogName: "delete-user-dialog",
onConfirm: async () => { onConfirm: async () => {
await userV1Store.deleteUser(`${UserNamePrefix}${user.username}`); await userV1Store.deleteUser(user.name);
fetchUserList(); fetchUserList();
}, },
}); });
@ -165,8 +164,8 @@ const MemberSection = () => {
<tr key={user.id}> <tr key={user.id}>
<td className="whitespace-nowrap py-2 pl-4 pr-3 text-sm text-gray-900 dark:text-gray-300">{user.id}</td> <td className="whitespace-nowrap py-2 pl-4 pr-3 text-sm text-gray-900 dark:text-gray-300">{user.id}</td>
<td className="whitespace-nowrap px-3 py-2 text-sm text-gray-500 dark:text-gray-300"> <td className="whitespace-nowrap px-3 py-2 text-sm text-gray-500 dark:text-gray-300">
{user.username} {extractUsernameFromName(user.name)}
<span className="ml-1 italic">{user.rowStatus === "ARCHIVED" && "(Archived)"}</span> <span className="ml-1 italic">{user.rowStatus === RowStatus.ARCHIVED && "(Archived)"}</span>
</td> </td>
<td className="whitespace-nowrap px-3 py-2 text-sm text-gray-500 dark:text-gray-300">{user.nickname}</td> <td className="whitespace-nowrap px-3 py-2 text-sm text-gray-500 dark:text-gray-300">{user.nickname}</td>
<td className="whitespace-nowrap px-3 py-2 text-sm text-gray-500 dark:text-gray-300">{user.email}</td> <td className="whitespace-nowrap px-3 py-2 text-sm text-gray-500 dark:text-gray-300">{user.email}</td>
@ -185,7 +184,7 @@ const MemberSection = () => {
> >
{t("setting.account-section.change-password")} {t("setting.account-section.change-password")}
</button> </button>
{user.rowStatus === "NORMAL" ? ( {user.rowStatus === RowStatus.ACTIVE ? (
<button <button
className="w-full text-left text-sm leading-6 py-1 px-3 cursor-pointer rounded hover:bg-gray-100 dark:hover:bg-zinc-600" className="w-full text-left text-sm leading-6 py-1 px-3 cursor-pointer rounded hover:bg-gray-100 dark:hover:bg-zinc-600"
onClick={() => handleArchiveUserClick(user)} onClick={() => handleArchiveUserClick(user)}

@ -44,10 +44,6 @@ export function signout() {
return axios.post("/api/v1/auth/signout"); return axios.post("/api/v1/auth/signout");
} }
export function getUserList() {
return axios.get<User[]>("/api/v1/user");
}
export function getMemoStats(username: string) { export function getMemoStats(username: string) {
return axios.get<number[]>(`/api/v1/memo/stats?creatorUsername=${username}`); return axios.get<number[]>(`/api/v1/memo/stats?creatorUsername=${username}`);
} }

@ -1,21 +1,21 @@
import { create } from "zustand"; import { create } from "zustand";
import { combine } from "zustand/middleware";
import { authServiceClient, userServiceClient } from "@/grpcweb"; import { authServiceClient, userServiceClient } from "@/grpcweb";
import { User, UserSetting } from "@/types/proto/api/v2/user_service"; import { User, UserSetting } from "@/types/proto/api/v2/user_service";
import { UserNamePrefix, extractUsernameFromName } from "./resourceName"; import { UserNamePrefix, extractUsernameFromName } from "./resourceName";
interface UserV1Store { interface State {
userMapByUsername: Record<string, User>; userMapByUsername: Record<string, User>;
currentUser?: User; currentUser?: User;
userSetting?: UserSetting; userSetting?: UserSetting;
getOrFetchUserByUsername: (username: string) => Promise<User>;
getUserByUsername: (username: string) => User;
updateUser: (user: Partial<User>, updateMask: string[]) => Promise<User>;
deleteUser: (name: string) => Promise<void>;
fetchCurrentUser: () => Promise<User>;
setCurrentUser: (user: User) => void;
updateUserSetting: (userSetting: Partial<UserSetting>, updateMark: string[]) => Promise<UserSetting>;
} }
const getDefaultState = (): State => ({
userMapByUsername: {},
currentUser: undefined,
userSetting: undefined,
});
const getDefaultUserSetting = () => { const getDefaultUserSetting = () => {
return UserSetting.fromPartial({ return UserSetting.fromPartial({
locale: "en", locale: "en",
@ -27,82 +27,93 @@ const getDefaultUserSetting = () => {
// Request cache is used to prevent multiple requests. // Request cache is used to prevent multiple requests.
const requestCache = new Map<string, Promise<any>>(); const requestCache = new Map<string, Promise<any>>();
export const useUserV1Store = create<UserV1Store>()((set, get) => ({ export const useUserV1Store = create(
userMapByUsername: {}, combine(getDefaultState(), (set, get) => ({
getOrFetchUserByUsername: async (username: string) => { fetchUsers: async () => {
const userMap = get().userMapByUsername; const { users } = await userServiceClient.listUsers({});
if (userMap[username]) { const userMap = get().userMapByUsername;
return userMap[username] as User; for (const user of users) {
} const username = extractUsernameFromName(user.name);
if (requestCache.has(username)) { userMap[username] = user;
return await requestCache.get(username); }
} 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 const promisedUser = userServiceClient
.getUser({ .getUser({
name: `${UserNamePrefix}${username}`, name: `${UserNamePrefix}${username}`,
}) })
.then(({ user }) => user); .then(({ user }) => user);
requestCache.set(username, promisedUser); requestCache.set(username, promisedUser);
const user = await promisedUser; const user = await promisedUser;
if (!user) { if (!user) {
throw new Error("User not found"); throw new Error("User not found");
} }
requestCache.delete(username); requestCache.delete(username);
userMap[username] = user; userMap[username] = user;
set(userMap); set({ userMapByUsername: userMap });
return user; return user;
}, },
getUserByUsername: (username: string) => { getUserByUsername: (username: string) => {
const userMap = get().userMapByUsername; const userMap = get().userMapByUsername;
return userMap[username]; return userMap[username];
}, },
updateUser: async (user: Partial<User>, updateMask: string[]) => { updateUser: async (user: Partial<User>, updateMask: string[]) => {
const { user: updatedUser } = await userServiceClient.updateUser({ const { user: updatedUser } = await userServiceClient.updateUser({
user: user, user: user,
updateMask: updateMask, updateMask: updateMask,
}); });
if (!updatedUser) { if (!updatedUser) {
throw new Error("User not found"); throw new Error("User not found");
} }
const username = extractUsernameFromName(updatedUser.name); const username = extractUsernameFromName(updatedUser.name);
const userMap = get().userMapByUsername; const userMap = get().userMapByUsername;
userMap[username] = updatedUser; userMap[username] = updatedUser;
set(userMap); set({ userMapByUsername: userMap });
return updatedUser; return updatedUser;
}, },
deleteUser: async (name: string) => { deleteUser: async (name: string) => {
await userServiceClient.deleteUser({ await userServiceClient.deleteUser({
name, name,
}); });
}, },
fetchCurrentUser: async () => { fetchCurrentUser: async () => {
const { user } = await authServiceClient.getAuthStatus({}); const { user } = await authServiceClient.getAuthStatus({});
if (!user) { if (!user) {
throw new Error("User not found"); throw new Error("User not found");
} }
set({ currentUser: user }); set({ currentUser: user });
const { setting } = await userServiceClient.getUserSetting({}); const { setting } = await userServiceClient.getUserSetting({});
set({ set({
userSetting: UserSetting.fromPartial({ userSetting: UserSetting.fromPartial({
...getDefaultUserSetting(), ...getDefaultUserSetting(),
...setting, ...setting,
}), }),
}); });
return user; return user;
}, },
setCurrentUser: (user: User) => { setCurrentUser: (user: User) => {
set({ currentUser: user }); set({ currentUser: user });
}, },
updateUserSetting: async (userSetting: Partial<UserSetting>, updateMask: string[]) => { updateUserSetting: async (userSetting: Partial<UserSetting>, updateMask: string[]) => {
const { setting: updatedUserSetting } = await userServiceClient.updateUserSetting({ const { setting: updatedUserSetting } = await userServiceClient.updateUserSetting({
setting: userSetting, setting: userSetting,
updateMask: updateMask, updateMask: updateMask,
}); });
if (!updatedUserSetting) { if (!updatedUserSetting) {
throw new Error("User setting not found"); throw new Error("User setting not found");
} }
set({ userSetting: updatedUserSetting }); set({ userSetting: updatedUserSetting });
return updatedUserSetting; return updatedUserSetting;
}, },
})); }))
);

Loading…
Cancel
Save