Commit d050a6fd authored by johnnyjoy's avatar johnnyjoy

chore: update user stats

parent 34c26a39
......@@ -53,9 +53,12 @@ service UserService {
option (google.api.http) = {delete: "/api/v1/{name=users/*}"};
option (google.api.method_signature) = "name";
}
// ListUserStats returns the stats of a user.
// Use `users/-` to list all users.
rpc ListUserStats(ListUserStatsRequest) returns (ListUserStatsResponse) {
// ListAllUserStats returns all user stats.
rpc ListAllUserStats(ListAllUserStatsRequest) returns (ListAllUserStatsResponse) {
option (google.api.http) = {post: "/api/v1/users/-/stats"};
}
// GetUserStats returns the stats of a user.
rpc GetUserStats(GetUserStatsRequest) returns (UserStats) {
option (google.api.http) = {get: "/api/v1/{name=users/*}/stats"};
option (google.api.method_signature) = "name";
}
......@@ -194,16 +197,26 @@ message UserStats {
}
}
message ListUserStatsRequest {
// The name of the user.
// Format: users/{user}. Use "-" to list all users.
string name = 1;
message ListAllUserStatsRequest {
// Filter is used to filter memos to calculate stats.
// Same as `ListMemosRequest.filter`.
string filter = 1;
}
message ListUserStatsResponse {
message ListAllUserStatsResponse {
repeated UserStats user_stats = 1;
}
message GetUserStatsRequest {
// The name of the user.
// Format: users/{user}.
string name = 1;
// Filter is used to filter memos to calculate stats.
// Same as `ListMemosRequest.filter`.
string filter = 2;
}
message UserSetting {
// The name of the user.
// Format: users/{user}
......
This diff is collapsed.
This diff is collapsed.
......@@ -28,7 +28,8 @@ const (
UserService_CreateUser_FullMethodName = "/memos.api.v1.UserService/CreateUser"
UserService_UpdateUser_FullMethodName = "/memos.api.v1.UserService/UpdateUser"
UserService_DeleteUser_FullMethodName = "/memos.api.v1.UserService/DeleteUser"
UserService_ListUserStats_FullMethodName = "/memos.api.v1.UserService/ListUserStats"
UserService_ListAllUserStats_FullMethodName = "/memos.api.v1.UserService/ListAllUserStats"
UserService_GetUserStats_FullMethodName = "/memos.api.v1.UserService/GetUserStats"
UserService_GetUserSetting_FullMethodName = "/memos.api.v1.UserService/GetUserSetting"
UserService_UpdateUserSetting_FullMethodName = "/memos.api.v1.UserService/UpdateUserSetting"
UserService_ListUserAccessTokens_FullMethodName = "/memos.api.v1.UserService/ListUserAccessTokens"
......@@ -54,7 +55,10 @@ type UserServiceClient interface {
UpdateUser(ctx context.Context, in *UpdateUserRequest, opts ...grpc.CallOption) (*User, error)
// DeleteUser deletes a user.
DeleteUser(ctx context.Context, in *DeleteUserRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
ListUserStats(ctx context.Context, in *ListUserStatsRequest, opts ...grpc.CallOption) (*ListUserStatsResponse, error)
// ListAllUserStats returns all user stats.
ListAllUserStats(ctx context.Context, in *ListAllUserStatsRequest, opts ...grpc.CallOption) (*ListAllUserStatsResponse, error)
// GetUserStats returns the stats of a user.
GetUserStats(ctx context.Context, in *GetUserStatsRequest, opts ...grpc.CallOption) (*UserStats, error)
// GetUserSetting gets the setting of a user.
GetUserSetting(ctx context.Context, in *GetUserSettingRequest, opts ...grpc.CallOption) (*UserSetting, error)
// UpdateUserSetting updates the setting of a user.
......@@ -145,10 +149,20 @@ func (c *userServiceClient) DeleteUser(ctx context.Context, in *DeleteUserReques
return out, nil
}
func (c *userServiceClient) ListUserStats(ctx context.Context, in *ListUserStatsRequest, opts ...grpc.CallOption) (*ListUserStatsResponse, error) {
func (c *userServiceClient) ListAllUserStats(ctx context.Context, in *ListAllUserStatsRequest, opts ...grpc.CallOption) (*ListAllUserStatsResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ListUserStatsResponse)
err := c.cc.Invoke(ctx, UserService_ListUserStats_FullMethodName, in, out, cOpts...)
out := new(ListAllUserStatsResponse)
err := c.cc.Invoke(ctx, UserService_ListAllUserStats_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *userServiceClient) GetUserStats(ctx context.Context, in *GetUserStatsRequest, opts ...grpc.CallOption) (*UserStats, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(UserStats)
err := c.cc.Invoke(ctx, UserService_GetUserStats_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
......@@ -223,7 +237,10 @@ type UserServiceServer interface {
UpdateUser(context.Context, *UpdateUserRequest) (*User, error)
// DeleteUser deletes a user.
DeleteUser(context.Context, *DeleteUserRequest) (*emptypb.Empty, error)
ListUserStats(context.Context, *ListUserStatsRequest) (*ListUserStatsResponse, error)
// ListAllUserStats returns all user stats.
ListAllUserStats(context.Context, *ListAllUserStatsRequest) (*ListAllUserStatsResponse, error)
// GetUserStats returns the stats of a user.
GetUserStats(context.Context, *GetUserStatsRequest) (*UserStats, error)
// GetUserSetting gets the setting of a user.
GetUserSetting(context.Context, *GetUserSettingRequest) (*UserSetting, error)
// UpdateUserSetting updates the setting of a user.
......@@ -265,8 +282,11 @@ func (UnimplementedUserServiceServer) UpdateUser(context.Context, *UpdateUserReq
func (UnimplementedUserServiceServer) DeleteUser(context.Context, *DeleteUserRequest) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteUser not implemented")
}
func (UnimplementedUserServiceServer) ListUserStats(context.Context, *ListUserStatsRequest) (*ListUserStatsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListUserStats not implemented")
func (UnimplementedUserServiceServer) ListAllUserStats(context.Context, *ListAllUserStatsRequest) (*ListAllUserStatsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListAllUserStats not implemented")
}
func (UnimplementedUserServiceServer) GetUserStats(context.Context, *GetUserStatsRequest) (*UserStats, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetUserStats not implemented")
}
func (UnimplementedUserServiceServer) GetUserSetting(context.Context, *GetUserSettingRequest) (*UserSetting, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetUserSetting not implemented")
......@@ -430,20 +450,38 @@ func _UserService_DeleteUser_Handler(srv interface{}, ctx context.Context, dec f
return interceptor(ctx, in, info, handler)
}
func _UserService_ListUserStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListUserStatsRequest)
func _UserService_ListAllUserStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListAllUserStatsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserServiceServer).ListUserStats(ctx, in)
return srv.(UserServiceServer).ListAllUserStats(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: UserService_ListUserStats_FullMethodName,
FullMethod: UserService_ListAllUserStats_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserServiceServer).ListUserStats(ctx, req.(*ListUserStatsRequest))
return srv.(UserServiceServer).ListAllUserStats(ctx, req.(*ListAllUserStatsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _UserService_GetUserStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetUserStatsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserServiceServer).GetUserStats(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: UserService_GetUserStats_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserServiceServer).GetUserStats(ctx, req.(*GetUserStatsRequest))
}
return interceptor(ctx, in, info, handler)
}
......@@ -574,8 +612,12 @@ var UserService_ServiceDesc = grpc.ServiceDesc{
Handler: _UserService_DeleteUser_Handler,
},
{
MethodName: "ListUserStats",
Handler: _UserService_ListUserStats_Handler,
MethodName: "ListAllUserStats",
Handler: _UserService_ListAllUserStats_Handler,
},
{
MethodName: "GetUserStats",
Handler: _UserService_GetUserStats_Handler,
},
{
MethodName: "GetUserSetting",
......
......@@ -494,6 +494,29 @@ paths:
$ref: '#/definitions/v1User'
tags:
- UserService
/api/v1/users/-/stats:
post:
summary: ListAllUserStats returns all user stats.
operationId: UserService_ListAllUserStats
responses:
"200":
description: A successful response.
schema:
$ref: '#/definitions/v1ListAllUserStatsResponse'
default:
description: An unexpected error response.
schema:
$ref: '#/definitions/googlerpcStatus'
parameters:
- name: filter
description: |-
Filter is used to filter memos to calculate stats.
Same as `ListMemosRequest.filter`.
in: query
required: false
type: string
tags:
- UserService
/api/v1/users:search:
get:
summary: SearchUsers searches users by filter.
......@@ -1466,12 +1489,13 @@ paths:
- UserService
/api/v1/{name}/stats:
get:
operationId: UserService_ListUserStats
summary: GetUserStats returns the stats of a user.
operationId: UserService_GetUserStats
responses:
"200":
description: A successful response.
schema:
$ref: '#/definitions/v1ListUserStatsResponse'
$ref: '#/definitions/v1UserStats'
default:
description: An unexpected error response.
schema:
......@@ -1480,11 +1504,18 @@ paths:
- name: name
description: |-
The name of the user.
Format: users/{user}. Use "-" to list all users.
Format: users/{user}.
in: path
required: true
type: string
pattern: users/[^/]+
- name: filter
description: |-
Filter is used to filter memos to calculate stats.
Same as `ListMemosRequest.filter`.
in: query
required: false
type: string
tags:
- UserService
/api/v1/{parent}/tags/{tag}:
......@@ -2575,6 +2606,14 @@ definitions:
type: string
url:
type: string
v1ListAllUserStatsResponse:
type: object
properties:
userStats:
type: array
items:
type: object
$ref: '#/definitions/v1UserStats'
v1ListIdentityProvidersResponse:
type: object
properties:
......@@ -2670,14 +2709,6 @@ definitions:
items:
type: object
$ref: '#/definitions/v1UserAccessToken'
v1ListUserStatsResponse:
type: object
properties:
userStats:
type: array
items:
type: object
$ref: '#/definitions/v1UserStats'
v1ListUsersResponse:
type: object
properties:
......
......@@ -275,41 +275,49 @@ func (s *APIV1Service) DeleteUser(ctx context.Context, request *v1pb.DeleteUserR
return &emptypb.Empty{}, nil
}
func (s *APIV1Service) ListUserStats(ctx context.Context, request *v1pb.ListUserStatsRequest) (*v1pb.ListUserStatsResponse, error) {
currentUser, err := s.GetCurrentUser(ctx)
func (s *APIV1Service) ListAllUserStats(ctx context.Context, request *v1pb.ListAllUserStatsRequest) (*v1pb.ListAllUserStatsResponse, error) {
users, err := s.Store.ListUsers(ctx, &store.FindUser{})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
}
// For unauthenticated users, only public memos are visible.
defaultVisibilities := []store.Visibility{store.Public}
if currentUser != nil {
// For authenticated users, protected memos are also visible.
defaultVisibilities = append(defaultVisibilities, store.Protected)
return nil, status.Errorf(codes.Internal, "failed to list users: %v", err)
}
users := []*store.User{}
if request.Name == "users/-" {
users, err = s.Store.ListUsers(ctx, &store.FindUser{})
userStatsList := []*v1pb.UserStats{}
for _, user := range users {
userStats, err := s.GetUserStats(ctx, &v1pb.GetUserStatsRequest{
Name: fmt.Sprintf("%s%d", UserNamePrefix, user.ID),
Filter: request.Filter,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to list users: %v", err)
return nil, status.Errorf(codes.Internal, "failed to get user stats: %v", err)
}
} else {
userStatsList = append(userStatsList, userStats)
}
return &v1pb.ListAllUserStatsResponse{
UserStats: userStatsList,
}, nil
}
func (s *APIV1Service) GetUserStats(ctx context.Context, request *v1pb.GetUserStatsRequest) (*v1pb.UserStats, error) {
userID, err := ExtractUserIDFromName(request.Name)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid user name: %v", err)
}
if userID == currentUser.ID {
users = append(users, currentUser)
} else {
user, err := s.Store.GetUser(ctx, &store.FindUser{ID: &userID})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
}
if user == nil {
return nil, status.Errorf(codes.NotFound, "user not found")
currentUser, err := s.GetCurrentUser(ctx)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
}
users = append(users, user)
// For unauthenticated users, only public memos are visible.
visibilities := []store.Visibility{store.Public}
if currentUser != nil {
// For authenticated users, protected memos are also visible.
visibilities = append(visibilities, store.Protected)
if currentUser.ID == user.ID {
// For the current user, show all memos including private ones.
visibilities = []store.Visibility{store.Public, store.Protected, store.Private}
}
}
......@@ -317,26 +325,24 @@ func (s *APIV1Service) ListUserStats(ctx context.Context, request *v1pb.ListUser
if err != nil {
return nil, errors.Wrap(err, "failed to get workspace memo related setting")
}
userStatsList := []*v1pb.UserStats{}
for _, user := range users {
userStats := &v1pb.UserStats{
Name: fmt.Sprintf("%s%d", UserNamePrefix, user.ID),
MemoDisplayTimestamps: []*timestamppb.Timestamp{},
MemoTypeStats: &v1pb.UserStats_MemoTypeStats{},
TagCount: map[string]int32{},
}
var visibilities []store.Visibility = defaultVisibilities
// For the current user, show all memos including private ones.
if user.ID == currentUser.ID {
visibilities = []store.Visibility{store.Public, store.Protected, store.Private}
}
memos, err := s.Store.ListMemos(ctx, &store.FindMemo{
memoFind := &store.FindMemo{
// Exclude comments by default.
ExcludeComments: true,
ExcludeContent: true,
CreatorID: &user.ID,
VisibilityList: visibilities,
})
}
if err := s.buildMemoFindWithFilter(ctx, memoFind, request.Filter); err != nil {
return nil, status.Errorf(codes.InvalidArgument, "failed to build find memos with filter: %v", err)
}
// Override the creator ID and visibility list.
memoFind.CreatorID = &user.ID
memoFind.VisibilityList = visibilities
memos, err := s.Store.ListMemos(ctx, memoFind)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to list memos: %v", err)
}
......@@ -360,12 +366,7 @@ func (s *APIV1Service) ListUserStats(ctx context.Context, request *v1pb.ListUser
userStats.MemoTypeStats.CodeCount++
}
}
userStatsList = append(userStatsList, userStats)
}
return &v1pb.ListUserStatsResponse{
UserStats: userStatsList,
}, nil
return userStats, nil
}
func getDefaultUserSetting(workspaceMemoRelatedSetting *storepb.WorkspaceMemoRelatedSetting) *v1pb.UserSetting {
......
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