Commit 57014e39 authored by johnnyjoy's avatar johnnyjoy

feat: get user by username

parent 45c16f9d
......@@ -18,15 +18,16 @@ service UserService {
rpc ListUsers(ListUsersRequest) returns (ListUsersResponse) {
option (google.api.http) = {get: "/api/v1/users"};
}
// SearchUsers searches users by filter.
rpc SearchUsers(SearchUsersRequest) returns (SearchUsersResponse) {
option (google.api.http) = {get: "/api/v1/users:search"};
}
// GetUser gets a user by name.
rpc GetUser(GetUserRequest) returns (User) {
option (google.api.http) = {get: "/api/v1/{name=users/*}"};
option (google.api.method_signature) = "name";
}
// GetUserByUsername gets a user by username.
rpc GetUserByUsername(GetUserByUsernameRequest) returns (User) {
option (google.api.http) = {get: "/api/v1/users:username"};
option (google.api.method_signature) = "username";
}
// GetUserAvatarBinary gets the avatar of a user.
rpc GetUserAvatarBinary(GetUserAvatarBinaryRequest) returns (google.api.HttpBody) {
option (google.api.http) = {get: "/file/{name=users/*}/avatar"};
......@@ -133,21 +134,16 @@ message ListUsersResponse {
repeated User users = 1;
}
message SearchUsersRequest {
// Filter is used to filter users returned in the list.
// Format: "username == 'frank'"
string filter = 1;
}
message SearchUsersResponse {
repeated User users = 1;
}
message GetUserRequest {
// The name of the user.
string name = 1;
}
message GetUserByUsernameRequest {
// The username of the user.
string username = 1;
}
message GetUserAvatarBinaryRequest {
// The name of the user.
string name = 1;
......
This diff is collapsed.
This diff is collapsed.
......@@ -22,8 +22,8 @@ const _ = grpc.SupportPackageIsVersion9
const (
UserService_ListUsers_FullMethodName = "/memos.api.v1.UserService/ListUsers"
UserService_SearchUsers_FullMethodName = "/memos.api.v1.UserService/SearchUsers"
UserService_GetUser_FullMethodName = "/memos.api.v1.UserService/GetUser"
UserService_GetUserByUsername_FullMethodName = "/memos.api.v1.UserService/GetUserByUsername"
UserService_GetUserAvatarBinary_FullMethodName = "/memos.api.v1.UserService/GetUserAvatarBinary"
UserService_CreateUser_FullMethodName = "/memos.api.v1.UserService/CreateUser"
UserService_UpdateUser_FullMethodName = "/memos.api.v1.UserService/UpdateUser"
......@@ -43,10 +43,10 @@ const (
type UserServiceClient interface {
// ListUsers returns a list of users.
ListUsers(ctx context.Context, in *ListUsersRequest, opts ...grpc.CallOption) (*ListUsersResponse, error)
// SearchUsers searches users by filter.
SearchUsers(ctx context.Context, in *SearchUsersRequest, opts ...grpc.CallOption) (*SearchUsersResponse, error)
// GetUser gets a user by name.
GetUser(ctx context.Context, in *GetUserRequest, opts ...grpc.CallOption) (*User, error)
// GetUserByUsername gets a user by username.
GetUserByUsername(ctx context.Context, in *GetUserByUsernameRequest, opts ...grpc.CallOption) (*User, error)
// GetUserAvatarBinary gets the avatar of a user.
GetUserAvatarBinary(ctx context.Context, in *GetUserAvatarBinaryRequest, opts ...grpc.CallOption) (*httpbody.HttpBody, error)
// CreateUser creates a new user.
......@@ -89,20 +89,20 @@ func (c *userServiceClient) ListUsers(ctx context.Context, in *ListUsersRequest,
return out, nil
}
func (c *userServiceClient) SearchUsers(ctx context.Context, in *SearchUsersRequest, opts ...grpc.CallOption) (*SearchUsersResponse, error) {
func (c *userServiceClient) GetUser(ctx context.Context, in *GetUserRequest, opts ...grpc.CallOption) (*User, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(SearchUsersResponse)
err := c.cc.Invoke(ctx, UserService_SearchUsers_FullMethodName, in, out, cOpts...)
out := new(User)
err := c.cc.Invoke(ctx, UserService_GetUser_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *userServiceClient) GetUser(ctx context.Context, in *GetUserRequest, opts ...grpc.CallOption) (*User, error) {
func (c *userServiceClient) GetUserByUsername(ctx context.Context, in *GetUserByUsernameRequest, opts ...grpc.CallOption) (*User, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(User)
err := c.cc.Invoke(ctx, UserService_GetUser_FullMethodName, in, out, cOpts...)
err := c.cc.Invoke(ctx, UserService_GetUserByUsername_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
......@@ -225,10 +225,10 @@ func (c *userServiceClient) DeleteUserAccessToken(ctx context.Context, in *Delet
type UserServiceServer interface {
// ListUsers returns a list of users.
ListUsers(context.Context, *ListUsersRequest) (*ListUsersResponse, error)
// SearchUsers searches users by filter.
SearchUsers(context.Context, *SearchUsersRequest) (*SearchUsersResponse, error)
// GetUser gets a user by name.
GetUser(context.Context, *GetUserRequest) (*User, error)
// GetUserByUsername gets a user by username.
GetUserByUsername(context.Context, *GetUserByUsernameRequest) (*User, error)
// GetUserAvatarBinary gets the avatar of a user.
GetUserAvatarBinary(context.Context, *GetUserAvatarBinaryRequest) (*httpbody.HttpBody, error)
// CreateUser creates a new user.
......@@ -264,12 +264,12 @@ type UnimplementedUserServiceServer struct{}
func (UnimplementedUserServiceServer) ListUsers(context.Context, *ListUsersRequest) (*ListUsersResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListUsers not implemented")
}
func (UnimplementedUserServiceServer) SearchUsers(context.Context, *SearchUsersRequest) (*SearchUsersResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SearchUsers not implemented")
}
func (UnimplementedUserServiceServer) GetUser(context.Context, *GetUserRequest) (*User, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetUser not implemented")
}
func (UnimplementedUserServiceServer) GetUserByUsername(context.Context, *GetUserByUsernameRequest) (*User, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetUserByUsername not implemented")
}
func (UnimplementedUserServiceServer) GetUserAvatarBinary(context.Context, *GetUserAvatarBinaryRequest) (*httpbody.HttpBody, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetUserAvatarBinary not implemented")
}
......@@ -342,38 +342,38 @@ func _UserService_ListUsers_Handler(srv interface{}, ctx context.Context, dec fu
return interceptor(ctx, in, info, handler)
}
func _UserService_SearchUsers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SearchUsersRequest)
func _UserService_GetUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetUserRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserServiceServer).SearchUsers(ctx, in)
return srv.(UserServiceServer).GetUser(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: UserService_SearchUsers_FullMethodName,
FullMethod: UserService_GetUser_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserServiceServer).SearchUsers(ctx, req.(*SearchUsersRequest))
return srv.(UserServiceServer).GetUser(ctx, req.(*GetUserRequest))
}
return interceptor(ctx, in, info, handler)
}
func _UserService_GetUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetUserRequest)
func _UserService_GetUserByUsername_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetUserByUsernameRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserServiceServer).GetUser(ctx, in)
return srv.(UserServiceServer).GetUserByUsername(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: UserService_GetUser_FullMethodName,
FullMethod: UserService_GetUserByUsername_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserServiceServer).GetUser(ctx, req.(*GetUserRequest))
return srv.(UserServiceServer).GetUserByUsername(ctx, req.(*GetUserByUsernameRequest))
}
return interceptor(ctx, in, info, handler)
}
......@@ -587,14 +587,14 @@ var UserService_ServiceDesc = grpc.ServiceDesc{
MethodName: "ListUsers",
Handler: _UserService_ListUsers_Handler,
},
{
MethodName: "SearchUsers",
Handler: _UserService_SearchUsers_Handler,
},
{
MethodName: "GetUser",
Handler: _UserService_GetUser_Handler,
},
{
MethodName: "GetUserByUsername",
Handler: _UserService_GetUserByUsername_Handler,
},
{
MethodName: "GetUserAvatarBinary",
Handler: _UserService_GetUserAvatarBinary_Handler,
......
......@@ -459,24 +459,22 @@ paths:
$ref: '#/definitions/googlerpcStatus'
tags:
- UserService
/api/v1/users:search:
/api/v1/users:username:
get:
summary: SearchUsers searches users by filter.
operationId: UserService_SearchUsers
summary: GetUserByUsername gets a user by username.
operationId: UserService_GetUserByUsername
responses:
"200":
description: A successful response.
schema:
$ref: '#/definitions/v1SearchUsersResponse'
$ref: '#/definitions/v1User'
default:
description: An unexpected error response.
schema:
$ref: '#/definitions/googlerpcStatus'
parameters:
- name: filter
description: |-
Filter is used to filter users returned in the list.
Format: "username == 'frank'"
- name: username
description: The username of the user.
in: query
required: false
type: string
......@@ -2848,14 +2846,6 @@ definitions:
properties:
markdown:
type: string
v1SearchUsersResponse:
type: object
properties:
users:
type: array
items:
type: object
$ref: '#/definitions/v1User'
v1SpoilerNode:
type: object
properties:
......
......@@ -15,7 +15,6 @@ const (
MemoNamePrefix = "memos/"
ResourceNamePrefix = "resources/"
InboxNamePrefix = "inboxes/"
StorageNamePrefix = "storages/"
IdentityProviderNamePrefix = "identityProviders/"
ActivityNamePrefix = "activities/"
)
......@@ -95,19 +94,6 @@ func ExtractInboxIDFromName(name string) (int32, error) {
return id, nil
}
// ExtractStorageIDFromName returns the storage ID from a resource name.
func ExtractStorageIDFromName(name string) (int32, error) {
tokens, err := GetNameParentTokens(name, StorageNamePrefix)
if err != nil {
return 0, err
}
id, err := util.ConvertStringToInt32(tokens[0])
if err != nil {
return 0, errors.Errorf("invalid storage ID %q", tokens[0])
}
return id, nil
}
func ExtractIdentityProviderIDFromName(name string) (int32, error) {
tokens, err := GetNameParentTokens(name, IdentityProviderNamePrefix)
if err != nil {
......
......@@ -11,11 +11,9 @@ import (
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/google/cel-go/cel"
"github.com/labstack/echo/v4"
"github.com/pkg/errors"
"golang.org/x/crypto/bcrypt"
expr "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
"google.golang.org/genproto/googleapis/api/httpbody"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
......@@ -51,46 +49,27 @@ func (s *APIV1Service) ListUsers(ctx context.Context, _ *v1pb.ListUsersRequest)
return response, nil
}
func (s *APIV1Service) SearchUsers(ctx context.Context, request *v1pb.SearchUsersRequest) (*v1pb.SearchUsersResponse, error) {
if request.Filter == "" {
return nil, status.Errorf(codes.InvalidArgument, "filter is empty")
}
filter, err := parseSearchUsersFilter(request.Filter)
func (s *APIV1Service) GetUser(ctx context.Context, request *v1pb.GetUserRequest) (*v1pb.User, error) {
userID, err := ExtractUserIDFromName(request.Name)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "failed to parse filter: %v", err)
}
userFind := &store.FindUser{}
if filter.Username != nil {
userFind.Username = filter.Username
}
if filter.Random {
userFind.Random = true
}
if filter.Limit != nil {
userFind.Limit = filter.Limit
return nil, status.Errorf(codes.InvalidArgument, "invalid user name: %v", err)
}
users, err := s.Store.ListUsers(ctx, userFind)
user, err := s.Store.GetUser(ctx, &store.FindUser{
ID: &userID,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to search users: %v", err)
}
response := &v1pb.SearchUsersResponse{
Users: []*v1pb.User{},
return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
}
for _, user := range users {
response.Users = append(response.Users, convertUserFromStore(user))
if user == nil {
return nil, status.Errorf(codes.NotFound, "user not found")
}
return response, nil
return convertUserFromStore(user), nil
}
func (s *APIV1Service) GetUser(ctx context.Context, request *v1pb.GetUserRequest) (*v1pb.User, error) {
userID, err := ExtractUserIDFromName(request.Name)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid user name: %v", err)
}
func (s *APIV1Service) GetUserByUsername(ctx context.Context, request *v1pb.GetUserByUsernameRequest) (*v1pb.User, error) {
user, err := s.Store.GetUser(ctx, &store.FindUser{
ID: &userID,
Username: &request.Username,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
......@@ -599,63 +578,6 @@ func convertUserRoleToStore(role v1pb.User_Role) store.Role {
}
}
// SearchUsersFilterCELAttributes are the CEL attributes for SearchUsersFilter.
var SearchUsersFilterCELAttributes = []cel.EnvOption{
cel.Variable("username", cel.StringType),
cel.Variable("random", cel.BoolType),
cel.Variable("limit", cel.IntType),
}
type SearchUsersFilter struct {
Username *string
Random bool
Limit *int
}
func parseSearchUsersFilter(expression string) (*SearchUsersFilter, error) {
e, err := cel.NewEnv(SearchUsersFilterCELAttributes...)
if err != nil {
return nil, err
}
ast, issues := e.Compile(expression)
if issues != nil {
return nil, errors.Errorf("found issue %v", issues)
}
filter := &SearchUsersFilter{}
expr, err := cel.AstToParsedExpr(ast)
if err != nil {
return nil, err
}
callExpr := expr.GetExpr().GetCallExpr()
findSearchUsersField(callExpr, filter)
return filter, nil
}
func findSearchUsersField(callExpr *expr.Expr_Call, filter *SearchUsersFilter) {
if len(callExpr.Args) == 2 {
idExpr := callExpr.Args[0].GetIdentExpr()
if idExpr != nil {
if idExpr.Name == "username" {
username := callExpr.Args[1].GetConstExpr().GetStringValue()
filter.Username = &username
} else if idExpr.Name == "random" {
random := callExpr.Args[1].GetConstExpr().GetBoolValue()
filter.Random = random
} else if idExpr.Name == "limit" {
limit := int(callExpr.Args[1].GetConstExpr().GetInt64Value())
filter.Limit = &limit
}
return
}
}
for _, arg := range callExpr.Args {
callExpr := arg.GetCallExpr()
if callExpr != nil {
findSearchUsersField(callExpr, filter)
}
}
}
func extractImageInfo(dataURI string) (string, string, error) {
dataURIRegex := regexp.MustCompile(`^data:(?P<type>.+);base64,(?P<base64>.+)`)
matches := dataURIRegex.FindStringSubmatch(dataURI)
......
......@@ -32,12 +32,8 @@ const UserProfile = () => {
}
userStore
.searchUsers(`username == "${username}"`)
.then((users) => {
if (users.length !== 1) {
throw new Error("User not found");
}
const user = users[0];
.fetchUserByUsername(username)
.then((user) => {
setUser(user);
loadingState.setFinish();
})
......
......@@ -63,19 +63,15 @@ export const useUserStore = create(
set({ userMapByName: userMap });
return user;
},
listUsers: async () => {
const { users } = await userServiceClient.listUsers({});
fetchUserByUsername: async (username: string) => {
const user = await userServiceClient.getUserByUsername({ username });
const userMap = get().userMapByName;
for (const user of users) {
userMap[user.name] = user;
}
set({ userMapByName: userMap });
return users;
return user;
},
searchUsers: async (filter: string) => {
const { users } = await userServiceClient.searchUsers({
filter,
});
listUsers: async () => {
const { users } = await userServiceClient.listUsers({});
const userMap = get().userMapByName;
for (const user of users) {
userMap[user.name] = user;
......
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