Commit 34c26a39 authored by johnnyjoy's avatar johnnyjoy

feat: implement user stats endpoint

parent 5ff8ab9a
...@@ -53,6 +53,12 @@ service UserService { ...@@ -53,6 +53,12 @@ service UserService {
option (google.api.http) = {delete: "/api/v1/{name=users/*}"}; option (google.api.http) = {delete: "/api/v1/{name=users/*}"};
option (google.api.method_signature) = "name"; option (google.api.method_signature) = "name";
} }
// ListUserStats returns the stats of a user.
// Use `users/-` to list all users.
rpc ListUserStats(ListUserStatsRequest) returns (ListUserStatsResponse) {
option (google.api.http) = {get: "/api/v1/{name=users/*}/stats"};
option (google.api.method_signature) = "name";
}
// GetUserSetting gets the setting of a user. // GetUserSetting gets the setting of a user.
rpc GetUserSetting(GetUserSettingRequest) returns (UserSetting) { rpc GetUserSetting(GetUserSettingRequest) returns (UserSetting) {
option (google.api.http) = {get: "/api/v1/{name=users/*}/setting"}; option (google.api.http) = {get: "/api/v1/{name=users/*}/setting"};
...@@ -165,6 +171,39 @@ message DeleteUserRequest { ...@@ -165,6 +171,39 @@ message DeleteUserRequest {
string name = 1; string name = 1;
} }
message UserStats {
// The name of the user.
// Format: users/{user}
string name = 1;
// The timestamps when the memos were displayed.
// We should return raw data to the client, and let the client format the data with the user's timezone.
repeated google.protobuf.Timestamp memo_display_timestamps = 2;
// The stats of memo types.
MemoTypeStats memo_type_stats = 3;
// The count of tags.
// Format: "tag1": 1, "tag2": 2
map<string, int32> tag_count = 4;
message MemoTypeStats {
int32 link_count = 1;
int32 task_count = 2;
int32 code_count = 3;
}
}
message ListUserStatsRequest {
// The name of the user.
// Format: users/{user}. Use "-" to list all users.
string name = 1;
}
message ListUserStatsResponse {
repeated UserStats user_stats = 1;
}
message UserSetting { message UserSetting {
// The name of the user. // The name of the user.
// Format: users/{user} // Format: users/{user}
......
This diff is collapsed.
...@@ -309,6 +309,42 @@ func local_request_UserService_DeleteUser_0(ctx context.Context, marshaler runti ...@@ -309,6 +309,42 @@ func local_request_UserService_DeleteUser_0(ctx context.Context, marshaler runti
return msg, metadata, err return msg, metadata, err
} }
func request_UserService_ListUserStats_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var (
protoReq ListUserStatsRequest
metadata runtime.ServerMetadata
err error
)
val, ok := pathParams["name"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "name")
}
protoReq.Name, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err)
}
msg, err := client.ListUserStats(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_UserService_ListUserStats_0(ctx context.Context, marshaler runtime.Marshaler, server UserServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var (
protoReq ListUserStatsRequest
metadata runtime.ServerMetadata
err error
)
val, ok := pathParams["name"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "name")
}
protoReq.Name, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err)
}
msg, err := server.ListUserStats(ctx, &protoReq)
return msg, metadata, err
}
func request_UserService_GetUserSetting_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { func request_UserService_GetUserSetting_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var ( var (
protoReq GetUserSettingRequest protoReq GetUserSettingRequest
...@@ -699,6 +735,26 @@ func RegisterUserServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux ...@@ -699,6 +735,26 @@ func RegisterUserServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
} }
forward_UserService_DeleteUser_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) forward_UserService_DeleteUser_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
}) })
mux.Handle(http.MethodGet, pattern_UserService_ListUserStats_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)
annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/memos.api.v1.UserService/ListUserStats", runtime.WithHTTPPathPattern("/api/v1/{name=users/*}/stats"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_UserService_ListUserStats_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_ListUserStats_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle(http.MethodGet, pattern_UserService_GetUserSetting_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { mux.Handle(http.MethodGet, pattern_UserService_GetUserSetting_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()
...@@ -958,6 +1014,23 @@ func RegisterUserServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux ...@@ -958,6 +1014,23 @@ func RegisterUserServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux
} }
forward_UserService_DeleteUser_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) forward_UserService_DeleteUser_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
}) })
mux.Handle(http.MethodGet, pattern_UserService_ListUserStats_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)
annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/memos.api.v1.UserService/ListUserStats", runtime.WithHTTPPathPattern("/api/v1/{name=users/*}/stats"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_UserService_ListUserStats_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_ListUserStats_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle(http.MethodGet, pattern_UserService_GetUserSetting_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { mux.Handle(http.MethodGet, pattern_UserService_GetUserSetting_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()
...@@ -1054,6 +1127,7 @@ var ( ...@@ -1054,6 +1127,7 @@ var (
pattern_UserService_CreateUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "users"}, "")) pattern_UserService_CreateUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "users"}, ""))
pattern_UserService_UpdateUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "users", "user.name"}, "")) pattern_UserService_UpdateUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "users", "user.name"}, ""))
pattern_UserService_DeleteUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "users", "name"}, "")) pattern_UserService_DeleteUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "users", "name"}, ""))
pattern_UserService_ListUserStats_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "users", "name", "stats"}, ""))
pattern_UserService_GetUserSetting_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "users", "name", "setting"}, "")) pattern_UserService_GetUserSetting_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "users", "name", "setting"}, ""))
pattern_UserService_UpdateUserSetting_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 2, 3, 4, 3, 5, 4}, []string{"api", "v1", "users", "setting", "setting.name"}, "")) pattern_UserService_UpdateUserSetting_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 2, 3, 4, 3, 5, 4}, []string{"api", "v1", "users", "setting", "setting.name"}, ""))
pattern_UserService_ListUserAccessTokens_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "users", "name", "access_tokens"}, "")) pattern_UserService_ListUserAccessTokens_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "users", "name", "access_tokens"}, ""))
...@@ -1069,6 +1143,7 @@ var ( ...@@ -1069,6 +1143,7 @@ var (
forward_UserService_CreateUser_0 = runtime.ForwardResponseMessage forward_UserService_CreateUser_0 = runtime.ForwardResponseMessage
forward_UserService_UpdateUser_0 = runtime.ForwardResponseMessage forward_UserService_UpdateUser_0 = runtime.ForwardResponseMessage
forward_UserService_DeleteUser_0 = runtime.ForwardResponseMessage forward_UserService_DeleteUser_0 = runtime.ForwardResponseMessage
forward_UserService_ListUserStats_0 = runtime.ForwardResponseMessage
forward_UserService_GetUserSetting_0 = runtime.ForwardResponseMessage forward_UserService_GetUserSetting_0 = runtime.ForwardResponseMessage
forward_UserService_UpdateUserSetting_0 = runtime.ForwardResponseMessage forward_UserService_UpdateUserSetting_0 = runtime.ForwardResponseMessage
forward_UserService_ListUserAccessTokens_0 = runtime.ForwardResponseMessage forward_UserService_ListUserAccessTokens_0 = runtime.ForwardResponseMessage
......
...@@ -28,6 +28,7 @@ const ( ...@@ -28,6 +28,7 @@ const (
UserService_CreateUser_FullMethodName = "/memos.api.v1.UserService/CreateUser" UserService_CreateUser_FullMethodName = "/memos.api.v1.UserService/CreateUser"
UserService_UpdateUser_FullMethodName = "/memos.api.v1.UserService/UpdateUser" UserService_UpdateUser_FullMethodName = "/memos.api.v1.UserService/UpdateUser"
UserService_DeleteUser_FullMethodName = "/memos.api.v1.UserService/DeleteUser" UserService_DeleteUser_FullMethodName = "/memos.api.v1.UserService/DeleteUser"
UserService_ListUserStats_FullMethodName = "/memos.api.v1.UserService/ListUserStats"
UserService_GetUserSetting_FullMethodName = "/memos.api.v1.UserService/GetUserSetting" UserService_GetUserSetting_FullMethodName = "/memos.api.v1.UserService/GetUserSetting"
UserService_UpdateUserSetting_FullMethodName = "/memos.api.v1.UserService/UpdateUserSetting" UserService_UpdateUserSetting_FullMethodName = "/memos.api.v1.UserService/UpdateUserSetting"
UserService_ListUserAccessTokens_FullMethodName = "/memos.api.v1.UserService/ListUserAccessTokens" UserService_ListUserAccessTokens_FullMethodName = "/memos.api.v1.UserService/ListUserAccessTokens"
...@@ -53,6 +54,7 @@ type UserServiceClient interface { ...@@ -53,6 +54,7 @@ type UserServiceClient interface {
UpdateUser(ctx context.Context, in *UpdateUserRequest, opts ...grpc.CallOption) (*User, error) UpdateUser(ctx context.Context, in *UpdateUserRequest, opts ...grpc.CallOption) (*User, error)
// DeleteUser deletes a user. // DeleteUser deletes a user.
DeleteUser(ctx context.Context, in *DeleteUserRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) DeleteUser(ctx context.Context, in *DeleteUserRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
ListUserStats(ctx context.Context, in *ListUserStatsRequest, opts ...grpc.CallOption) (*ListUserStatsResponse, error)
// GetUserSetting gets the setting of a user. // GetUserSetting gets the setting of a user.
GetUserSetting(ctx context.Context, in *GetUserSettingRequest, opts ...grpc.CallOption) (*UserSetting, error) GetUserSetting(ctx context.Context, in *GetUserSettingRequest, opts ...grpc.CallOption) (*UserSetting, error)
// UpdateUserSetting updates the setting of a user. // UpdateUserSetting updates the setting of a user.
...@@ -143,6 +145,16 @@ func (c *userServiceClient) DeleteUser(ctx context.Context, in *DeleteUserReques ...@@ -143,6 +145,16 @@ func (c *userServiceClient) DeleteUser(ctx context.Context, in *DeleteUserReques
return out, nil return out, nil
} }
func (c *userServiceClient) ListUserStats(ctx context.Context, in *ListUserStatsRequest, opts ...grpc.CallOption) (*ListUserStatsResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ListUserStatsResponse)
err := c.cc.Invoke(ctx, UserService_ListUserStats_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *userServiceClient) GetUserSetting(ctx context.Context, in *GetUserSettingRequest, opts ...grpc.CallOption) (*UserSetting, error) { func (c *userServiceClient) GetUserSetting(ctx context.Context, in *GetUserSettingRequest, opts ...grpc.CallOption) (*UserSetting, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(UserSetting) out := new(UserSetting)
...@@ -211,6 +223,7 @@ type UserServiceServer interface { ...@@ -211,6 +223,7 @@ type UserServiceServer interface {
UpdateUser(context.Context, *UpdateUserRequest) (*User, error) UpdateUser(context.Context, *UpdateUserRequest) (*User, error)
// DeleteUser deletes a user. // DeleteUser deletes a user.
DeleteUser(context.Context, *DeleteUserRequest) (*emptypb.Empty, error) DeleteUser(context.Context, *DeleteUserRequest) (*emptypb.Empty, error)
ListUserStats(context.Context, *ListUserStatsRequest) (*ListUserStatsResponse, error)
// GetUserSetting gets the setting of a user. // GetUserSetting gets the setting of a user.
GetUserSetting(context.Context, *GetUserSettingRequest) (*UserSetting, error) GetUserSetting(context.Context, *GetUserSettingRequest) (*UserSetting, error)
// UpdateUserSetting updates the setting of a user. // UpdateUserSetting updates the setting of a user.
...@@ -252,6 +265,9 @@ func (UnimplementedUserServiceServer) UpdateUser(context.Context, *UpdateUserReq ...@@ -252,6 +265,9 @@ func (UnimplementedUserServiceServer) UpdateUser(context.Context, *UpdateUserReq
func (UnimplementedUserServiceServer) DeleteUser(context.Context, *DeleteUserRequest) (*emptypb.Empty, error) { func (UnimplementedUserServiceServer) DeleteUser(context.Context, *DeleteUserRequest) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteUser not implemented") 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) GetUserSetting(context.Context, *GetUserSettingRequest) (*UserSetting, error) { func (UnimplementedUserServiceServer) GetUserSetting(context.Context, *GetUserSettingRequest) (*UserSetting, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetUserSetting not implemented") return nil, status.Errorf(codes.Unimplemented, "method GetUserSetting not implemented")
} }
...@@ -414,6 +430,24 @@ func _UserService_DeleteUser_Handler(srv interface{}, ctx context.Context, dec f ...@@ -414,6 +430,24 @@ func _UserService_DeleteUser_Handler(srv interface{}, ctx context.Context, dec f
return interceptor(ctx, in, info, handler) 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)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserServiceServer).ListUserStats(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: UserService_ListUserStats_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserServiceServer).ListUserStats(ctx, req.(*ListUserStatsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _UserService_GetUserSetting_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { func _UserService_GetUserSetting_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetUserSettingRequest) in := new(GetUserSettingRequest)
if err := dec(in); err != nil { if err := dec(in); err != nil {
...@@ -539,6 +573,10 @@ var UserService_ServiceDesc = grpc.ServiceDesc{ ...@@ -539,6 +573,10 @@ var UserService_ServiceDesc = grpc.ServiceDesc{
MethodName: "DeleteUser", MethodName: "DeleteUser",
Handler: _UserService_DeleteUser_Handler, Handler: _UserService_DeleteUser_Handler,
}, },
{
MethodName: "ListUserStats",
Handler: _UserService_ListUserStats_Handler,
},
{ {
MethodName: "GetUserSetting", MethodName: "GetUserSetting",
Handler: _UserService_GetUserSetting_Handler, Handler: _UserService_GetUserSetting_Handler,
......
...@@ -1464,6 +1464,29 @@ paths: ...@@ -1464,6 +1464,29 @@ paths:
pattern: users/[^/]+ pattern: users/[^/]+
tags: tags:
- UserService - UserService
/api/v1/{name}/stats:
get:
operationId: UserService_ListUserStats
responses:
"200":
description: A successful response.
schema:
$ref: '#/definitions/v1ListUserStatsResponse'
default:
description: An unexpected error response.
schema:
$ref: '#/definitions/googlerpcStatus'
parameters:
- name: name
description: |-
The name of the user.
Format: users/{user}. Use "-" to list all users.
in: path
required: true
type: string
pattern: users/[^/]+
tags:
- UserService
/api/v1/{parent}/tags/{tag}: /api/v1/{parent}/tags/{tag}:
delete: delete:
summary: DeleteMemoTag deletes a tag for a memo. summary: DeleteMemoTag deletes a tag for a memo.
...@@ -1808,6 +1831,18 @@ definitions: ...@@ -1808,6 +1831,18 @@ definitions:
expiresAt: expiresAt:
type: string type: string
format: date-time format: date-time
UserStatsMemoTypeStats:
type: object
properties:
linkCount:
type: integer
format: int32
taskCount:
type: integer
format: int32
codeCount:
type: integer
format: int32
WorkspaceStorageSettingS3Config: WorkspaceStorageSettingS3Config:
type: object type: object
properties: properties:
...@@ -2635,6 +2670,14 @@ definitions: ...@@ -2635,6 +2670,14 @@ definitions:
items: items:
type: object type: object
$ref: '#/definitions/v1UserAccessToken' $ref: '#/definitions/v1UserAccessToken'
v1ListUserStatsResponse:
type: object
properties:
userStats:
type: array
items:
type: object
$ref: '#/definitions/v1UserStats'
v1ListUsersResponse: v1ListUsersResponse:
type: object type: object
properties: properties:
...@@ -3074,6 +3117,33 @@ definitions: ...@@ -3074,6 +3117,33 @@ definitions:
expiresAt: expiresAt:
type: string type: string
format: date-time format: date-time
v1UserStats:
type: object
properties:
name:
type: string
title: |-
The name of the user.
Format: users/{user}
memoDisplayTimestamps:
type: array
items:
type: string
format: date-time
description: |-
The timestamps when the memos were displayed.
We should return raw data to the client, and let the client format the data with the user's timezone.
memoTypeStats:
$ref: '#/definitions/UserStatsMemoTypeStats'
description: The stats of memo types.
tagCount:
type: object
additionalProperties:
type: integer
format: int32
title: |-
The count of tags.
Format: "tag1": 1, "tag2": 2
v1Visibility: v1Visibility:
type: string type: string
enum: enum:
......
...@@ -13,6 +13,7 @@ var authenticationAllowlistMethods = map[string]bool{ ...@@ -13,6 +13,7 @@ var authenticationAllowlistMethods = map[string]bool{
"/memos.api.v1.AuthService/SignUp": true, "/memos.api.v1.AuthService/SignUp": true,
"/memos.api.v1.UserService/GetUser": true, "/memos.api.v1.UserService/GetUser": true,
"/memos.api.v1.UserService/GetUserAvatarBinary": true, "/memos.api.v1.UserService/GetUserAvatarBinary": true,
"/memos.api.v1.UserService/ListUserStats": true,
"/memos.api.v1.UserService/SearchUsers": true, "/memos.api.v1.UserService/SearchUsers": true,
"/memos.api.v1.MemoService/GetMemo": true, "/memos.api.v1.MemoService/GetMemo": true,
"/memos.api.v1.MemoService/GetMemoByUid": true, "/memos.api.v1.MemoService/GetMemoByUid": true,
......
...@@ -275,6 +275,99 @@ func (s *APIV1Service) DeleteUser(ctx context.Context, request *v1pb.DeleteUserR ...@@ -275,6 +275,99 @@ func (s *APIV1Service) DeleteUser(ctx context.Context, request *v1pb.DeleteUserR
return &emptypb.Empty{}, nil return &emptypb.Empty{}, nil
} }
func (s *APIV1Service) ListUserStats(ctx context.Context, request *v1pb.ListUserStatsRequest) (*v1pb.ListUserStatsResponse, error) {
currentUser, err := s.GetCurrentUser(ctx)
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)
}
users := []*store.User{}
if request.Name == "users/-" {
users, err = s.Store.ListUsers(ctx, &store.FindUser{})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to list users: %v", err)
}
} else {
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")
}
users = append(users, user)
}
}
workspaceMemoRelatedSetting, err := s.Store.GetWorkspaceMemoRelatedSetting(ctx)
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{
// Exclude comments by default.
ExcludeComments: true,
ExcludeContent: true,
CreatorID: &user.ID,
VisibilityList: visibilities,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to list memos: %v", err)
}
for _, memo := range memos {
displayTs := memo.CreatedTs
if workspaceMemoRelatedSetting.DisplayWithUpdateTime {
displayTs = memo.UpdatedTs
}
userStats.MemoDisplayTimestamps = append(userStats.MemoDisplayTimestamps, timestamppb.New(time.Unix(displayTs, 0)))
// Handle duplicated tags.
for _, tag := range memo.Payload.Tags {
userStats.TagCount[tag]++
}
if memo.Payload.Property.GetHasLink() {
userStats.MemoTypeStats.LinkCount++
}
if memo.Payload.Property.GetHasTaskList() {
userStats.MemoTypeStats.TaskCount++
}
if memo.Payload.Property.GetHasCode() {
userStats.MemoTypeStats.CodeCount++
}
}
userStatsList = append(userStatsList, userStats)
}
return &v1pb.ListUserStatsResponse{
UserStats: userStatsList,
}, nil
}
func getDefaultUserSetting(workspaceMemoRelatedSetting *storepb.WorkspaceMemoRelatedSetting) *v1pb.UserSetting { func getDefaultUserSetting(workspaceMemoRelatedSetting *storepb.WorkspaceMemoRelatedSetting) *v1pb.UserSetting {
defaultVisibility := "PRIVATE" defaultVisibility := "PRIVATE"
if workspaceMemoRelatedSetting.DefaultVisibility != "" { if workspaceMemoRelatedSetting.DefaultVisibility != "" {
......
...@@ -54,10 +54,8 @@ func TestMemoStore(t *testing.T) { ...@@ -54,10 +54,8 @@ func TestMemoStore(t *testing.T) {
require.Equal(t, 0, len(memoList)) require.Equal(t, 0, len(memoList))
memoList, err = ts.ListMemos(ctx, &store.FindMemo{ memoList, err = ts.ListMemos(ctx, &store.FindMemo{
CreatorID: &user.ID, CreatorID: &user.ID,
VisibilityList: []store.Visibility{ VisibilityList: []store.Visibility{store.Public},
store.Public,
},
}) })
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 0, len(memoList)) require.Equal(t, 0, len(memoList))
......
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