From a735f32b1f72bcf965ba5317ecce3721f9b4f9a1 Mon Sep 17 00:00:00 2001 From: Christian Carlsson Date: Mon, 2 Dec 2024 18:27:51 +0000 Subject: [PATCH] refact: stream unread notifications (#114) --- protobufs/api/v1/notifications.proto | 53 ++ protobufs/api/v1/users.proto | 31 - .../v1/apiv1connect/notifications.connect.go | 144 +++++ .../pb/api/v1/apiv1connect/users.connect.go | 56 +- server/pkg/pb/api/v1/notifications.pb.go | 579 ++++++++++++++++++ server/pkg/pb/api/v1/users.pb.go | 534 +++------------- server/rpc/interceptors/auth.go | 119 ++-- server/rpc/interceptors/interface.go | 9 - server/rpc/interceptors/module.go | 8 +- server/rpc/interceptors/validator.go | 66 +- server/rpc/middlewares/middlewares.go | 6 + server/rpc/module.go | 24 +- server/rpc/v1/notification.go | 148 +++++ server/rpc/v1/user.go | 84 --- web/src/clients/clients.ts | 2 + web/src/main.ts | 4 +- web/src/proto/api/v1/notifications_pb.ts | 203 ++++++ web/src/proto/api/v1/users_pb.ts | 140 +---- web/src/stores/notifications.ts | 39 +- web/src/ui/components/NavigationMobile.vue | 1 + .../ui/notifications/ListNotifications.vue | 9 +- 21 files changed, 1381 insertions(+), 878 deletions(-) create mode 100644 protobufs/api/v1/notifications.proto create mode 100644 server/pkg/pb/api/v1/apiv1connect/notifications.connect.go create mode 100644 server/pkg/pb/api/v1/notifications.pb.go delete mode 100644 server/rpc/interceptors/interface.go create mode 100644 server/rpc/v1/notification.go create mode 100644 web/src/proto/api/v1/notifications_pb.ts diff --git a/protobufs/api/v1/notifications.proto b/protobufs/api/v1/notifications.proto new file mode 100644 index 00000000..5abee28b --- /dev/null +++ b/protobufs/api/v1/notifications.proto @@ -0,0 +1,53 @@ +syntax = "proto3"; + +package api.v1; + +import "api/v1/options.proto"; +import "api/v1/shared.proto"; +import "api/v1/workouts.proto"; + +import "google/protobuf/empty.proto"; + +import "buf/validate/validate.proto"; + +service NotificationService { + rpc ListNotifications (ListNotificationsRequest) returns (ListNotificationsResponse) { + option (auth) = true; + } + rpc UnreadNotifications (UnreadNotificationsRequest) returns (stream UnreadNotificationsResponse) { + option (auth) = true; + } +} + +message ListNotificationsRequest { + bool unread_only = 1; + bool mark_as_read = 2; + PaginationRequest pagination = 3 [(buf.validate.field).required = true]; +} +message ListNotificationsResponse { + repeated Notification notifications = 1; + PaginationResponse pagination = 2; +} + +message Notification { + message UserFollowed { + User actor = 1; + } + message WorkoutComment { + User actor = 1; + Workout workout = 2; + } + + string id = 1; + // DEBT: This should be a timestamp but the client is not able to parse it. + int64 notified_at_unix = 2; + oneof type { + UserFollowed user_followed = 3; + WorkoutComment workout_comment = 4; + } +} + +message UnreadNotificationsRequest {} +message UnreadNotificationsResponse { + int64 count = 1; +} diff --git a/protobufs/api/v1/users.proto b/protobufs/api/v1/users.proto index 15626890..e9265de2 100644 --- a/protobufs/api/v1/users.proto +++ b/protobufs/api/v1/users.proto @@ -29,9 +29,6 @@ service UserService { rpc Search(SearchRequest) returns (SearchResponse) { option (auth) = true; } - rpc ListNotifications(ListNotificationsRequest) returns (ListNotificationsResponse) { - option (auth) = true; - } } message GetUserRequest { @@ -73,31 +70,3 @@ message SearchResponse { repeated User users = 1; PaginationResponse pagination = 2; } - -message ListNotificationsRequest { - bool unread_only = 1; - bool mark_as_read = 2; - PaginationRequest pagination = 3 [(buf.validate.field).required = true]; -} -message ListNotificationsResponse { - repeated Notification notifications = 1; - PaginationResponse pagination = 2; -} - -message Notification { - message UserFollowed { - User actor = 1; - } - message WorkoutComment { - User actor = 1; - Workout workout = 2; - } - - string id = 1; - // DEBT: This should be a timestamp but the client is not able to parse it. - int64 notified_at_unix = 2; - oneof type { - UserFollowed user_followed = 3; - WorkoutComment workout_comment = 4; - } -} diff --git a/server/pkg/pb/api/v1/apiv1connect/notifications.connect.go b/server/pkg/pb/api/v1/apiv1connect/notifications.connect.go new file mode 100644 index 00000000..f7ccf6c5 --- /dev/null +++ b/server/pkg/pb/api/v1/apiv1connect/notifications.connect.go @@ -0,0 +1,144 @@ +// Code generated by protoc-gen-connect-go. DO NOT EDIT. +// +// Source: api/v1/notifications.proto + +package apiv1connect + +import ( + context "context" + errors "errors" + http "net/http" + strings "strings" + + connect "connectrpc.com/connect" + v1 "github.com/crlssn/getstronger/server/pkg/pb/api/v1" +) + +// This is a compile-time assertion to ensure that this generated file and the connect package are +// compatible. If you get a compiler error that this constant is not defined, this code was +// generated with a version of connect newer than the one compiled into your binary. You can fix the +// problem by either regenerating this code with an older version of connect or updating the connect +// version compiled into your binary. +const _ = connect.IsAtLeastVersion1_13_0 + +const ( + // NotificationServiceName is the fully-qualified name of the NotificationService service. + NotificationServiceName = "api.v1.NotificationService" +) + +// These constants are the fully-qualified names of the RPCs defined in this package. They're +// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route. +// +// Note that these are different from the fully-qualified method names used by +// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to +// reflection-formatted method names, remove the leading slash and convert the remaining slash to a +// period. +const ( + // NotificationServiceListNotificationsProcedure is the fully-qualified name of the + // NotificationService's ListNotifications RPC. + NotificationServiceListNotificationsProcedure = "/api.v1.NotificationService/ListNotifications" + // NotificationServiceUnreadNotificationsProcedure is the fully-qualified name of the + // NotificationService's UnreadNotifications RPC. + NotificationServiceUnreadNotificationsProcedure = "/api.v1.NotificationService/UnreadNotifications" +) + +// These variables are the protoreflect.Descriptor objects for the RPCs defined in this package. +var ( + notificationServiceServiceDescriptor = v1.File_api_v1_notifications_proto.Services().ByName("NotificationService") + notificationServiceListNotificationsMethodDescriptor = notificationServiceServiceDescriptor.Methods().ByName("ListNotifications") + notificationServiceUnreadNotificationsMethodDescriptor = notificationServiceServiceDescriptor.Methods().ByName("UnreadNotifications") +) + +// NotificationServiceClient is a client for the api.v1.NotificationService service. +type NotificationServiceClient interface { + ListNotifications(context.Context, *connect.Request[v1.ListNotificationsRequest]) (*connect.Response[v1.ListNotificationsResponse], error) + UnreadNotifications(context.Context, *connect.Request[v1.UnreadNotificationsRequest]) (*connect.ServerStreamForClient[v1.UnreadNotificationsResponse], error) +} + +// NewNotificationServiceClient constructs a client for the api.v1.NotificationService service. By +// default, it uses the Connect protocol with the binary Protobuf Codec, asks for gzipped responses, +// and sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the +// connect.WithGRPC() or connect.WithGRPCWeb() options. +// +// The URL supplied here should be the base URL for the Connect or gRPC server (for example, +// http://api.acme.com or https://acme.com/grpc). +func NewNotificationServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) NotificationServiceClient { + baseURL = strings.TrimRight(baseURL, "/") + return ¬ificationServiceClient{ + listNotifications: connect.NewClient[v1.ListNotificationsRequest, v1.ListNotificationsResponse]( + httpClient, + baseURL+NotificationServiceListNotificationsProcedure, + connect.WithSchema(notificationServiceListNotificationsMethodDescriptor), + connect.WithClientOptions(opts...), + ), + unreadNotifications: connect.NewClient[v1.UnreadNotificationsRequest, v1.UnreadNotificationsResponse]( + httpClient, + baseURL+NotificationServiceUnreadNotificationsProcedure, + connect.WithSchema(notificationServiceUnreadNotificationsMethodDescriptor), + connect.WithClientOptions(opts...), + ), + } +} + +// notificationServiceClient implements NotificationServiceClient. +type notificationServiceClient struct { + listNotifications *connect.Client[v1.ListNotificationsRequest, v1.ListNotificationsResponse] + unreadNotifications *connect.Client[v1.UnreadNotificationsRequest, v1.UnreadNotificationsResponse] +} + +// ListNotifications calls api.v1.NotificationService.ListNotifications. +func (c *notificationServiceClient) ListNotifications(ctx context.Context, req *connect.Request[v1.ListNotificationsRequest]) (*connect.Response[v1.ListNotificationsResponse], error) { + return c.listNotifications.CallUnary(ctx, req) +} + +// UnreadNotifications calls api.v1.NotificationService.UnreadNotifications. +func (c *notificationServiceClient) UnreadNotifications(ctx context.Context, req *connect.Request[v1.UnreadNotificationsRequest]) (*connect.ServerStreamForClient[v1.UnreadNotificationsResponse], error) { + return c.unreadNotifications.CallServerStream(ctx, req) +} + +// NotificationServiceHandler is an implementation of the api.v1.NotificationService service. +type NotificationServiceHandler interface { + ListNotifications(context.Context, *connect.Request[v1.ListNotificationsRequest]) (*connect.Response[v1.ListNotificationsResponse], error) + UnreadNotifications(context.Context, *connect.Request[v1.UnreadNotificationsRequest], *connect.ServerStream[v1.UnreadNotificationsResponse]) error +} + +// NewNotificationServiceHandler builds an HTTP handler from the service implementation. It returns +// the path on which to mount the handler and the handler itself. +// +// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf +// and JSON codecs. They also support gzip compression. +func NewNotificationServiceHandler(svc NotificationServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) { + notificationServiceListNotificationsHandler := connect.NewUnaryHandler( + NotificationServiceListNotificationsProcedure, + svc.ListNotifications, + connect.WithSchema(notificationServiceListNotificationsMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + notificationServiceUnreadNotificationsHandler := connect.NewServerStreamHandler( + NotificationServiceUnreadNotificationsProcedure, + svc.UnreadNotifications, + connect.WithSchema(notificationServiceUnreadNotificationsMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + return "/api.v1.NotificationService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case NotificationServiceListNotificationsProcedure: + notificationServiceListNotificationsHandler.ServeHTTP(w, r) + case NotificationServiceUnreadNotificationsProcedure: + notificationServiceUnreadNotificationsHandler.ServeHTTP(w, r) + default: + http.NotFound(w, r) + } + }) +} + +// UnimplementedNotificationServiceHandler returns CodeUnimplemented from all methods. +type UnimplementedNotificationServiceHandler struct{} + +func (UnimplementedNotificationServiceHandler) ListNotifications(context.Context, *connect.Request[v1.ListNotificationsRequest]) (*connect.Response[v1.ListNotificationsResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("api.v1.NotificationService.ListNotifications is not implemented")) +} + +func (UnimplementedNotificationServiceHandler) UnreadNotifications(context.Context, *connect.Request[v1.UnreadNotificationsRequest], *connect.ServerStream[v1.UnreadNotificationsResponse]) error { + return connect.NewError(connect.CodeUnimplemented, errors.New("api.v1.NotificationService.UnreadNotifications is not implemented")) +} diff --git a/server/pkg/pb/api/v1/apiv1connect/users.connect.go b/server/pkg/pb/api/v1/apiv1connect/users.connect.go index 114071de..4ed5a642 100644 --- a/server/pkg/pb/api/v1/apiv1connect/users.connect.go +++ b/server/pkg/pb/api/v1/apiv1connect/users.connect.go @@ -48,21 +48,17 @@ const ( UserServiceListFolloweesProcedure = "/api.v1.UserService/ListFollowees" // UserServiceSearchProcedure is the fully-qualified name of the UserService's Search RPC. UserServiceSearchProcedure = "/api.v1.UserService/Search" - // UserServiceListNotificationsProcedure is the fully-qualified name of the UserService's - // ListNotifications RPC. - UserServiceListNotificationsProcedure = "/api.v1.UserService/ListNotifications" ) // These variables are the protoreflect.Descriptor objects for the RPCs defined in this package. var ( - userServiceServiceDescriptor = v1.File_api_v1_users_proto.Services().ByName("UserService") - userServiceGetMethodDescriptor = userServiceServiceDescriptor.Methods().ByName("Get") - userServiceFollowMethodDescriptor = userServiceServiceDescriptor.Methods().ByName("Follow") - userServiceUnfollowMethodDescriptor = userServiceServiceDescriptor.Methods().ByName("Unfollow") - userServiceListFollowersMethodDescriptor = userServiceServiceDescriptor.Methods().ByName("ListFollowers") - userServiceListFolloweesMethodDescriptor = userServiceServiceDescriptor.Methods().ByName("ListFollowees") - userServiceSearchMethodDescriptor = userServiceServiceDescriptor.Methods().ByName("Search") - userServiceListNotificationsMethodDescriptor = userServiceServiceDescriptor.Methods().ByName("ListNotifications") + userServiceServiceDescriptor = v1.File_api_v1_users_proto.Services().ByName("UserService") + userServiceGetMethodDescriptor = userServiceServiceDescriptor.Methods().ByName("Get") + userServiceFollowMethodDescriptor = userServiceServiceDescriptor.Methods().ByName("Follow") + userServiceUnfollowMethodDescriptor = userServiceServiceDescriptor.Methods().ByName("Unfollow") + userServiceListFollowersMethodDescriptor = userServiceServiceDescriptor.Methods().ByName("ListFollowers") + userServiceListFolloweesMethodDescriptor = userServiceServiceDescriptor.Methods().ByName("ListFollowees") + userServiceSearchMethodDescriptor = userServiceServiceDescriptor.Methods().ByName("Search") ) // UserServiceClient is a client for the api.v1.UserService service. @@ -73,7 +69,6 @@ type UserServiceClient interface { ListFollowers(context.Context, *connect.Request[v1.ListFollowersRequest]) (*connect.Response[v1.ListFollowersResponse], error) ListFollowees(context.Context, *connect.Request[v1.ListFolloweesRequest]) (*connect.Response[v1.ListFolloweesResponse], error) Search(context.Context, *connect.Request[v1.SearchRequest]) (*connect.Response[v1.SearchResponse], error) - ListNotifications(context.Context, *connect.Request[v1.ListNotificationsRequest]) (*connect.Response[v1.ListNotificationsResponse], error) } // NewUserServiceClient constructs a client for the api.v1.UserService service. By default, it uses @@ -122,24 +117,17 @@ func NewUserServiceClient(httpClient connect.HTTPClient, baseURL string, opts .. connect.WithSchema(userServiceSearchMethodDescriptor), connect.WithClientOptions(opts...), ), - listNotifications: connect.NewClient[v1.ListNotificationsRequest, v1.ListNotificationsResponse]( - httpClient, - baseURL+UserServiceListNotificationsProcedure, - connect.WithSchema(userServiceListNotificationsMethodDescriptor), - connect.WithClientOptions(opts...), - ), } } // userServiceClient implements UserServiceClient. type userServiceClient struct { - get *connect.Client[v1.GetUserRequest, v1.GetUserResponse] - follow *connect.Client[v1.FollowRequest, v1.FollowResponse] - unfollow *connect.Client[v1.UnfollowRequest, v1.UnfollowResponse] - listFollowers *connect.Client[v1.ListFollowersRequest, v1.ListFollowersResponse] - listFollowees *connect.Client[v1.ListFolloweesRequest, v1.ListFolloweesResponse] - search *connect.Client[v1.SearchRequest, v1.SearchResponse] - listNotifications *connect.Client[v1.ListNotificationsRequest, v1.ListNotificationsResponse] + get *connect.Client[v1.GetUserRequest, v1.GetUserResponse] + follow *connect.Client[v1.FollowRequest, v1.FollowResponse] + unfollow *connect.Client[v1.UnfollowRequest, v1.UnfollowResponse] + listFollowers *connect.Client[v1.ListFollowersRequest, v1.ListFollowersResponse] + listFollowees *connect.Client[v1.ListFolloweesRequest, v1.ListFolloweesResponse] + search *connect.Client[v1.SearchRequest, v1.SearchResponse] } // Get calls api.v1.UserService.Get. @@ -172,11 +160,6 @@ func (c *userServiceClient) Search(ctx context.Context, req *connect.Request[v1. return c.search.CallUnary(ctx, req) } -// ListNotifications calls api.v1.UserService.ListNotifications. -func (c *userServiceClient) ListNotifications(ctx context.Context, req *connect.Request[v1.ListNotificationsRequest]) (*connect.Response[v1.ListNotificationsResponse], error) { - return c.listNotifications.CallUnary(ctx, req) -} - // UserServiceHandler is an implementation of the api.v1.UserService service. type UserServiceHandler interface { Get(context.Context, *connect.Request[v1.GetUserRequest]) (*connect.Response[v1.GetUserResponse], error) @@ -185,7 +168,6 @@ type UserServiceHandler interface { ListFollowers(context.Context, *connect.Request[v1.ListFollowersRequest]) (*connect.Response[v1.ListFollowersResponse], error) ListFollowees(context.Context, *connect.Request[v1.ListFolloweesRequest]) (*connect.Response[v1.ListFolloweesResponse], error) Search(context.Context, *connect.Request[v1.SearchRequest]) (*connect.Response[v1.SearchResponse], error) - ListNotifications(context.Context, *connect.Request[v1.ListNotificationsRequest]) (*connect.Response[v1.ListNotificationsResponse], error) } // NewUserServiceHandler builds an HTTP handler from the service implementation. It returns the path @@ -230,12 +212,6 @@ func NewUserServiceHandler(svc UserServiceHandler, opts ...connect.HandlerOption connect.WithSchema(userServiceSearchMethodDescriptor), connect.WithHandlerOptions(opts...), ) - userServiceListNotificationsHandler := connect.NewUnaryHandler( - UserServiceListNotificationsProcedure, - svc.ListNotifications, - connect.WithSchema(userServiceListNotificationsMethodDescriptor), - connect.WithHandlerOptions(opts...), - ) return "/api.v1.UserService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case UserServiceGetProcedure: @@ -250,8 +226,6 @@ func NewUserServiceHandler(svc UserServiceHandler, opts ...connect.HandlerOption userServiceListFolloweesHandler.ServeHTTP(w, r) case UserServiceSearchProcedure: userServiceSearchHandler.ServeHTTP(w, r) - case UserServiceListNotificationsProcedure: - userServiceListNotificationsHandler.ServeHTTP(w, r) default: http.NotFound(w, r) } @@ -284,7 +258,3 @@ func (UnimplementedUserServiceHandler) ListFollowees(context.Context, *connect.R func (UnimplementedUserServiceHandler) Search(context.Context, *connect.Request[v1.SearchRequest]) (*connect.Response[v1.SearchResponse], error) { return nil, connect.NewError(connect.CodeUnimplemented, errors.New("api.v1.UserService.Search is not implemented")) } - -func (UnimplementedUserServiceHandler) ListNotifications(context.Context, *connect.Request[v1.ListNotificationsRequest]) (*connect.Response[v1.ListNotificationsResponse], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("api.v1.UserService.ListNotifications is not implemented")) -} diff --git a/server/pkg/pb/api/v1/notifications.pb.go b/server/pkg/pb/api/v1/notifications.pb.go new file mode 100644 index 00000000..ba75d5a9 --- /dev/null +++ b/server/pkg/pb/api/v1/notifications.pb.go @@ -0,0 +1,579 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.35.2 +// protoc (unknown) +// source: api/v1/notifications.proto + +package apiv1 + +import ( + reflect "reflect" + sync "sync" + + _ "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + _ "google.golang.org/protobuf/types/known/emptypb" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type ListNotificationsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UnreadOnly bool `protobuf:"varint,1,opt,name=unread_only,json=unreadOnly,proto3" json:"unread_only,omitempty"` + MarkAsRead bool `protobuf:"varint,2,opt,name=mark_as_read,json=markAsRead,proto3" json:"mark_as_read,omitempty"` + Pagination *PaginationRequest `protobuf:"bytes,3,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (x *ListNotificationsRequest) Reset() { + *x = ListNotificationsRequest{} + mi := &file_api_v1_notifications_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListNotificationsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListNotificationsRequest) ProtoMessage() {} + +func (x *ListNotificationsRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_v1_notifications_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListNotificationsRequest.ProtoReflect.Descriptor instead. +func (*ListNotificationsRequest) Descriptor() ([]byte, []int) { + return file_api_v1_notifications_proto_rawDescGZIP(), []int{0} +} + +func (x *ListNotificationsRequest) GetUnreadOnly() bool { + if x != nil { + return x.UnreadOnly + } + return false +} + +func (x *ListNotificationsRequest) GetMarkAsRead() bool { + if x != nil { + return x.MarkAsRead + } + return false +} + +func (x *ListNotificationsRequest) GetPagination() *PaginationRequest { + if x != nil { + return x.Pagination + } + return nil +} + +type ListNotificationsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Notifications []*Notification `protobuf:"bytes,1,rep,name=notifications,proto3" json:"notifications,omitempty"` + Pagination *PaginationResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (x *ListNotificationsResponse) Reset() { + *x = ListNotificationsResponse{} + mi := &file_api_v1_notifications_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListNotificationsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListNotificationsResponse) ProtoMessage() {} + +func (x *ListNotificationsResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_v1_notifications_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListNotificationsResponse.ProtoReflect.Descriptor instead. +func (*ListNotificationsResponse) Descriptor() ([]byte, []int) { + return file_api_v1_notifications_proto_rawDescGZIP(), []int{1} +} + +func (x *ListNotificationsResponse) GetNotifications() []*Notification { + if x != nil { + return x.Notifications + } + return nil +} + +func (x *ListNotificationsResponse) GetPagination() *PaginationResponse { + if x != nil { + return x.Pagination + } + return nil +} + +type Notification struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // DEBT: This should be a timestamp but the client is not able to parse it. + NotifiedAtUnix int64 `protobuf:"varint,2,opt,name=notified_at_unix,json=notifiedAtUnix,proto3" json:"notified_at_unix,omitempty"` + // Types that are assignable to Type: + // + // *Notification_UserFollowed_ + // *Notification_WorkoutComment_ + Type isNotification_Type `protobuf_oneof:"type"` +} + +func (x *Notification) Reset() { + *x = Notification{} + mi := &file_api_v1_notifications_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Notification) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Notification) ProtoMessage() {} + +func (x *Notification) ProtoReflect() protoreflect.Message { + mi := &file_api_v1_notifications_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Notification.ProtoReflect.Descriptor instead. +func (*Notification) Descriptor() ([]byte, []int) { + return file_api_v1_notifications_proto_rawDescGZIP(), []int{2} +} + +func (x *Notification) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Notification) GetNotifiedAtUnix() int64 { + if x != nil { + return x.NotifiedAtUnix + } + return 0 +} + +func (m *Notification) GetType() isNotification_Type { + if m != nil { + return m.Type + } + return nil +} + +func (x *Notification) GetUserFollowed() *Notification_UserFollowed { + if x, ok := x.GetType().(*Notification_UserFollowed_); ok { + return x.UserFollowed + } + return nil +} + +func (x *Notification) GetWorkoutComment() *Notification_WorkoutComment { + if x, ok := x.GetType().(*Notification_WorkoutComment_); ok { + return x.WorkoutComment + } + return nil +} + +type isNotification_Type interface { + isNotification_Type() +} + +type Notification_UserFollowed_ struct { + UserFollowed *Notification_UserFollowed `protobuf:"bytes,3,opt,name=user_followed,json=userFollowed,proto3,oneof"` +} + +type Notification_WorkoutComment_ struct { + WorkoutComment *Notification_WorkoutComment `protobuf:"bytes,4,opt,name=workout_comment,json=workoutComment,proto3,oneof"` +} + +func (*Notification_UserFollowed_) isNotification_Type() {} + +func (*Notification_WorkoutComment_) isNotification_Type() {} + +type UnreadNotificationsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *UnreadNotificationsRequest) Reset() { + *x = UnreadNotificationsRequest{} + mi := &file_api_v1_notifications_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UnreadNotificationsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UnreadNotificationsRequest) ProtoMessage() {} + +func (x *UnreadNotificationsRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_v1_notifications_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UnreadNotificationsRequest.ProtoReflect.Descriptor instead. +func (*UnreadNotificationsRequest) Descriptor() ([]byte, []int) { + return file_api_v1_notifications_proto_rawDescGZIP(), []int{3} +} + +type UnreadNotificationsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Count int64 `protobuf:"varint,1,opt,name=count,proto3" json:"count,omitempty"` +} + +func (x *UnreadNotificationsResponse) Reset() { + *x = UnreadNotificationsResponse{} + mi := &file_api_v1_notifications_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UnreadNotificationsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UnreadNotificationsResponse) ProtoMessage() {} + +func (x *UnreadNotificationsResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_v1_notifications_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UnreadNotificationsResponse.ProtoReflect.Descriptor instead. +func (*UnreadNotificationsResponse) Descriptor() ([]byte, []int) { + return file_api_v1_notifications_proto_rawDescGZIP(), []int{4} +} + +func (x *UnreadNotificationsResponse) GetCount() int64 { + if x != nil { + return x.Count + } + return 0 +} + +type Notification_UserFollowed struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Actor *User `protobuf:"bytes,1,opt,name=actor,proto3" json:"actor,omitempty"` +} + +func (x *Notification_UserFollowed) Reset() { + *x = Notification_UserFollowed{} + mi := &file_api_v1_notifications_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Notification_UserFollowed) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Notification_UserFollowed) ProtoMessage() {} + +func (x *Notification_UserFollowed) ProtoReflect() protoreflect.Message { + mi := &file_api_v1_notifications_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Notification_UserFollowed.ProtoReflect.Descriptor instead. +func (*Notification_UserFollowed) Descriptor() ([]byte, []int) { + return file_api_v1_notifications_proto_rawDescGZIP(), []int{2, 0} +} + +func (x *Notification_UserFollowed) GetActor() *User { + if x != nil { + return x.Actor + } + return nil +} + +type Notification_WorkoutComment struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Actor *User `protobuf:"bytes,1,opt,name=actor,proto3" json:"actor,omitempty"` + Workout *Workout `protobuf:"bytes,2,opt,name=workout,proto3" json:"workout,omitempty"` +} + +func (x *Notification_WorkoutComment) Reset() { + *x = Notification_WorkoutComment{} + mi := &file_api_v1_notifications_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Notification_WorkoutComment) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Notification_WorkoutComment) ProtoMessage() {} + +func (x *Notification_WorkoutComment) ProtoReflect() protoreflect.Message { + mi := &file_api_v1_notifications_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Notification_WorkoutComment.ProtoReflect.Descriptor instead. +func (*Notification_WorkoutComment) Descriptor() ([]byte, []int) { + return file_api_v1_notifications_proto_rawDescGZIP(), []int{2, 1} +} + +func (x *Notification_WorkoutComment) GetActor() *User { + if x != nil { + return x.Actor + } + return nil +} + +func (x *Notification_WorkoutComment) GetWorkout() *Workout { + if x != nil { + return x.Workout + } + return nil +} + +var File_api_v1_notifications_proto protoreflect.FileDescriptor + +var file_api_v1_notifications_proto_rawDesc = []byte{ + 0x0a, 0x1a, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x61, 0x70, + 0x69, 0x2e, 0x76, 0x31, 0x1a, 0x14, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6f, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x13, 0x61, 0x70, 0x69, 0x2f, + 0x76, 0x31, 0x2f, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, + 0x15, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x6f, 0x72, 0x6b, 0x6f, 0x75, 0x74, 0x73, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x62, 0x75, 0x66, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, + 0x65, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x22, 0xa0, 0x01, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, + 0x0b, 0x75, 0x6e, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0a, 0x75, 0x6e, 0x72, 0x65, 0x61, 0x64, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x20, + 0x0a, 0x0c, 0x6d, 0x61, 0x72, 0x6b, 0x5f, 0x61, 0x73, 0x5f, 0x72, 0x65, 0x61, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x6d, 0x61, 0x72, 0x6b, 0x41, 0x73, 0x52, 0x65, 0x61, 0x64, + 0x12, 0x41, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, + 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x42, + 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x22, 0x93, 0x01, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x3a, 0x0a, 0x0d, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, + 0x31, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0d, + 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x3a, 0x0a, + 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x67, 0x69, 0x6e, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x0a, 0x70, + 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xff, 0x02, 0x0a, 0x0c, 0x4e, 0x6f, + 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x6e, 0x6f, + 0x74, 0x69, 0x66, 0x69, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x75, 0x6e, 0x69, 0x78, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x65, 0x64, 0x41, 0x74, + 0x55, 0x6e, 0x69, 0x78, 0x12, 0x48, 0x0a, 0x0d, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x66, 0x6f, 0x6c, + 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x48, 0x00, + 0x52, 0x0c, 0x75, 0x73, 0x65, 0x72, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x12, 0x4e, + 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x6f, 0x75, 0x74, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, + 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, + 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x57, 0x6f, + 0x72, 0x6b, 0x6f, 0x75, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0e, + 0x77, 0x6f, 0x72, 0x6b, 0x6f, 0x75, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x1a, 0x32, + 0x0a, 0x0c, 0x55, 0x73, 0x65, 0x72, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x12, 0x22, + 0x0a, 0x05, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x05, 0x61, 0x63, 0x74, + 0x6f, 0x72, 0x1a, 0x5f, 0x0a, 0x0e, 0x57, 0x6f, 0x72, 0x6b, 0x6f, 0x75, 0x74, 0x43, 0x6f, 0x6d, + 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x05, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, + 0x72, 0x52, 0x05, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x29, 0x0a, 0x07, 0x77, 0x6f, 0x72, 0x6b, + 0x6f, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x6f, 0x75, 0x74, 0x52, 0x07, 0x77, 0x6f, 0x72, 0x6b, + 0x6f, 0x75, 0x74, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x1c, 0x0a, 0x1a, 0x55, + 0x6e, 0x72, 0x65, 0x61, 0x64, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x33, 0x0a, 0x1b, 0x55, 0x6e, 0x72, + 0x65, 0x61, 0x64, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x32, 0xdd, + 0x01, 0x0a, 0x13, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5e, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x6f, + 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x20, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x04, 0x88, 0xb5, 0x18, 0x01, 0x12, 0x66, 0x0a, 0x13, 0x55, 0x6e, 0x72, 0x65, 0x61, 0x64, + 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x22, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x72, 0x65, 0x61, 0x64, 0x4e, 0x6f, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x23, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x72, 0x65, 0x61, + 0x64, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x04, 0x88, 0xb5, 0x18, 0x01, 0x30, 0x01, 0x42, 0x93, + 0x01, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x42, 0x12, 0x4e, + 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x50, 0x01, 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x63, 0x72, 0x6c, 0x73, 0x73, 0x6e, 0x2f, 0x67, 0x65, 0x74, 0x73, 0x74, 0x72, 0x6f, 0x6e, 0x67, + 0x65, 0x72, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x62, + 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x3b, 0x61, 0x70, 0x69, 0x76, 0x31, 0xa2, 0x02, 0x03, + 0x41, 0x58, 0x58, 0xaa, 0x02, 0x06, 0x41, 0x70, 0x69, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x06, 0x41, + 0x70, 0x69, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x12, 0x41, 0x70, 0x69, 0x5c, 0x56, 0x31, 0x5c, 0x47, + 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x07, 0x41, 0x70, 0x69, + 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_api_v1_notifications_proto_rawDescOnce sync.Once + file_api_v1_notifications_proto_rawDescData = file_api_v1_notifications_proto_rawDesc +) + +func file_api_v1_notifications_proto_rawDescGZIP() []byte { + file_api_v1_notifications_proto_rawDescOnce.Do(func() { + file_api_v1_notifications_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_v1_notifications_proto_rawDescData) + }) + return file_api_v1_notifications_proto_rawDescData +} + +var file_api_v1_notifications_proto_msgTypes = make([]protoimpl.MessageInfo, 7) +var file_api_v1_notifications_proto_goTypes = []any{ + (*ListNotificationsRequest)(nil), // 0: api.v1.ListNotificationsRequest + (*ListNotificationsResponse)(nil), // 1: api.v1.ListNotificationsResponse + (*Notification)(nil), // 2: api.v1.Notification + (*UnreadNotificationsRequest)(nil), // 3: api.v1.UnreadNotificationsRequest + (*UnreadNotificationsResponse)(nil), // 4: api.v1.UnreadNotificationsResponse + (*Notification_UserFollowed)(nil), // 5: api.v1.Notification.UserFollowed + (*Notification_WorkoutComment)(nil), // 6: api.v1.Notification.WorkoutComment + (*PaginationRequest)(nil), // 7: api.v1.PaginationRequest + (*PaginationResponse)(nil), // 8: api.v1.PaginationResponse + (*User)(nil), // 9: api.v1.User + (*Workout)(nil), // 10: api.v1.Workout +} +var file_api_v1_notifications_proto_depIdxs = []int32{ + 7, // 0: api.v1.ListNotificationsRequest.pagination:type_name -> api.v1.PaginationRequest + 2, // 1: api.v1.ListNotificationsResponse.notifications:type_name -> api.v1.Notification + 8, // 2: api.v1.ListNotificationsResponse.pagination:type_name -> api.v1.PaginationResponse + 5, // 3: api.v1.Notification.user_followed:type_name -> api.v1.Notification.UserFollowed + 6, // 4: api.v1.Notification.workout_comment:type_name -> api.v1.Notification.WorkoutComment + 9, // 5: api.v1.Notification.UserFollowed.actor:type_name -> api.v1.User + 9, // 6: api.v1.Notification.WorkoutComment.actor:type_name -> api.v1.User + 10, // 7: api.v1.Notification.WorkoutComment.workout:type_name -> api.v1.Workout + 0, // 8: api.v1.NotificationService.ListNotifications:input_type -> api.v1.ListNotificationsRequest + 3, // 9: api.v1.NotificationService.UnreadNotifications:input_type -> api.v1.UnreadNotificationsRequest + 1, // 10: api.v1.NotificationService.ListNotifications:output_type -> api.v1.ListNotificationsResponse + 4, // 11: api.v1.NotificationService.UnreadNotifications:output_type -> api.v1.UnreadNotificationsResponse + 10, // [10:12] is the sub-list for method output_type + 8, // [8:10] is the sub-list for method input_type + 8, // [8:8] is the sub-list for extension type_name + 8, // [8:8] is the sub-list for extension extendee + 0, // [0:8] is the sub-list for field type_name +} + +func init() { file_api_v1_notifications_proto_init() } +func file_api_v1_notifications_proto_init() { + if File_api_v1_notifications_proto != nil { + return + } + file_api_v1_options_proto_init() + file_api_v1_shared_proto_init() + file_api_v1_workouts_proto_init() + file_api_v1_notifications_proto_msgTypes[2].OneofWrappers = []any{ + (*Notification_UserFollowed_)(nil), + (*Notification_WorkoutComment_)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_api_v1_notifications_proto_rawDesc, + NumEnums: 0, + NumMessages: 7, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_api_v1_notifications_proto_goTypes, + DependencyIndexes: file_api_v1_notifications_proto_depIdxs, + MessageInfos: file_api_v1_notifications_proto_msgTypes, + }.Build() + File_api_v1_notifications_proto = out.File + file_api_v1_notifications_proto_rawDesc = nil + file_api_v1_notifications_proto_goTypes = nil + file_api_v1_notifications_proto_depIdxs = nil +} diff --git a/server/pkg/pb/api/v1/users.pb.go b/server/pkg/pb/api/v1/users.pb.go index 857844f8..f4b00c14 100644 --- a/server/pkg/pb/api/v1/users.pb.go +++ b/server/pkg/pb/api/v1/users.pb.go @@ -561,314 +561,6 @@ func (x *SearchResponse) GetPagination() *PaginationResponse { return nil } -type ListNotificationsRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - UnreadOnly bool `protobuf:"varint,1,opt,name=unread_only,json=unreadOnly,proto3" json:"unread_only,omitempty"` - MarkAsRead bool `protobuf:"varint,2,opt,name=mark_as_read,json=markAsRead,proto3" json:"mark_as_read,omitempty"` - Pagination *PaginationRequest `protobuf:"bytes,3,opt,name=pagination,proto3" json:"pagination,omitempty"` -} - -func (x *ListNotificationsRequest) Reset() { - *x = ListNotificationsRequest{} - mi := &file_api_v1_users_proto_msgTypes[12] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ListNotificationsRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ListNotificationsRequest) ProtoMessage() {} - -func (x *ListNotificationsRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_users_proto_msgTypes[12] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ListNotificationsRequest.ProtoReflect.Descriptor instead. -func (*ListNotificationsRequest) Descriptor() ([]byte, []int) { - return file_api_v1_users_proto_rawDescGZIP(), []int{12} -} - -func (x *ListNotificationsRequest) GetUnreadOnly() bool { - if x != nil { - return x.UnreadOnly - } - return false -} - -func (x *ListNotificationsRequest) GetMarkAsRead() bool { - if x != nil { - return x.MarkAsRead - } - return false -} - -func (x *ListNotificationsRequest) GetPagination() *PaginationRequest { - if x != nil { - return x.Pagination - } - return nil -} - -type ListNotificationsResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Notifications []*Notification `protobuf:"bytes,1,rep,name=notifications,proto3" json:"notifications,omitempty"` - Pagination *PaginationResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` -} - -func (x *ListNotificationsResponse) Reset() { - *x = ListNotificationsResponse{} - mi := &file_api_v1_users_proto_msgTypes[13] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ListNotificationsResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ListNotificationsResponse) ProtoMessage() {} - -func (x *ListNotificationsResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_users_proto_msgTypes[13] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ListNotificationsResponse.ProtoReflect.Descriptor instead. -func (*ListNotificationsResponse) Descriptor() ([]byte, []int) { - return file_api_v1_users_proto_rawDescGZIP(), []int{13} -} - -func (x *ListNotificationsResponse) GetNotifications() []*Notification { - if x != nil { - return x.Notifications - } - return nil -} - -func (x *ListNotificationsResponse) GetPagination() *PaginationResponse { - if x != nil { - return x.Pagination - } - return nil -} - -type Notification struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - // DEBT: This should be a timestamp but the client is not able to parse it. - NotifiedAtUnix int64 `protobuf:"varint,2,opt,name=notified_at_unix,json=notifiedAtUnix,proto3" json:"notified_at_unix,omitempty"` - // Types that are assignable to Type: - // - // *Notification_UserFollowed_ - // *Notification_WorkoutComment_ - Type isNotification_Type `protobuf_oneof:"type"` -} - -func (x *Notification) Reset() { - *x = Notification{} - mi := &file_api_v1_users_proto_msgTypes[14] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *Notification) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Notification) ProtoMessage() {} - -func (x *Notification) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_users_proto_msgTypes[14] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Notification.ProtoReflect.Descriptor instead. -func (*Notification) Descriptor() ([]byte, []int) { - return file_api_v1_users_proto_rawDescGZIP(), []int{14} -} - -func (x *Notification) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -func (x *Notification) GetNotifiedAtUnix() int64 { - if x != nil { - return x.NotifiedAtUnix - } - return 0 -} - -func (m *Notification) GetType() isNotification_Type { - if m != nil { - return m.Type - } - return nil -} - -func (x *Notification) GetUserFollowed() *Notification_UserFollowed { - if x, ok := x.GetType().(*Notification_UserFollowed_); ok { - return x.UserFollowed - } - return nil -} - -func (x *Notification) GetWorkoutComment() *Notification_WorkoutComment { - if x, ok := x.GetType().(*Notification_WorkoutComment_); ok { - return x.WorkoutComment - } - return nil -} - -type isNotification_Type interface { - isNotification_Type() -} - -type Notification_UserFollowed_ struct { - UserFollowed *Notification_UserFollowed `protobuf:"bytes,3,opt,name=user_followed,json=userFollowed,proto3,oneof"` -} - -type Notification_WorkoutComment_ struct { - WorkoutComment *Notification_WorkoutComment `protobuf:"bytes,4,opt,name=workout_comment,json=workoutComment,proto3,oneof"` -} - -func (*Notification_UserFollowed_) isNotification_Type() {} - -func (*Notification_WorkoutComment_) isNotification_Type() {} - -type Notification_UserFollowed struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Actor *User `protobuf:"bytes,1,opt,name=actor,proto3" json:"actor,omitempty"` -} - -func (x *Notification_UserFollowed) Reset() { - *x = Notification_UserFollowed{} - mi := &file_api_v1_users_proto_msgTypes[15] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *Notification_UserFollowed) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Notification_UserFollowed) ProtoMessage() {} - -func (x *Notification_UserFollowed) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_users_proto_msgTypes[15] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Notification_UserFollowed.ProtoReflect.Descriptor instead. -func (*Notification_UserFollowed) Descriptor() ([]byte, []int) { - return file_api_v1_users_proto_rawDescGZIP(), []int{14, 0} -} - -func (x *Notification_UserFollowed) GetActor() *User { - if x != nil { - return x.Actor - } - return nil -} - -type Notification_WorkoutComment struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Actor *User `protobuf:"bytes,1,opt,name=actor,proto3" json:"actor,omitempty"` - Workout *Workout `protobuf:"bytes,2,opt,name=workout,proto3" json:"workout,omitempty"` -} - -func (x *Notification_WorkoutComment) Reset() { - *x = Notification_WorkoutComment{} - mi := &file_api_v1_users_proto_msgTypes[16] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *Notification_WorkoutComment) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Notification_WorkoutComment) ProtoMessage() {} - -func (x *Notification_WorkoutComment) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_users_proto_msgTypes[16] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Notification_WorkoutComment.ProtoReflect.Descriptor instead. -func (*Notification_WorkoutComment) Descriptor() ([]byte, []int) { - return file_api_v1_users_proto_rawDescGZIP(), []int{14, 1} -} - -func (x *Notification_WorkoutComment) GetActor() *User { - if x != nil { - return x.Actor - } - return nil -} - -func (x *Notification_WorkoutComment) GetWorkout() *Workout { - if x != nil { - return x.Workout - } - return nil -} - var File_api_v1_users_proto protoreflect.FileDescriptor var file_api_v1_users_proto_rawDesc = []byte{ @@ -928,94 +620,44 @@ var file_api_v1_users_proto_rawDesc = []byte{ 0x72, 0x73, 0x12, 0x3a, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xa0, - 0x01, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x75, - 0x6e, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x0a, 0x75, 0x6e, 0x72, 0x65, 0x61, 0x64, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x20, 0x0a, 0x0c, - 0x6d, 0x61, 0x72, 0x6b, 0x5f, 0x61, 0x73, 0x5f, 0x72, 0x65, 0x61, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x0a, 0x6d, 0x61, 0x72, 0x6b, 0x41, 0x73, 0x52, 0x65, 0x61, 0x64, 0x12, 0x41, - 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x67, 0x69, - 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x42, 0x06, 0xba, - 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x22, 0x93, 0x01, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x3a, 0x0a, 0x0d, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, - 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0d, 0x6e, 0x6f, - 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x3a, 0x0a, 0x0a, 0x70, - 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x0a, 0x70, 0x61, 0x67, - 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xff, 0x02, 0x0a, 0x0c, 0x4e, 0x6f, 0x74, 0x69, - 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x6e, 0x6f, 0x74, 0x69, - 0x66, 0x69, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x75, 0x6e, 0x69, 0x78, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x65, 0x64, 0x41, 0x74, 0x55, 0x6e, - 0x69, 0x78, 0x12, 0x48, 0x0a, 0x0d, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, - 0x77, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x61, 0x70, 0x69, 0x2e, - 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, - 0x55, 0x73, 0x65, 0x72, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x48, 0x00, 0x52, 0x0c, - 0x75, 0x73, 0x65, 0x72, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x12, 0x4e, 0x0a, 0x0f, - 0x77, 0x6f, 0x72, 0x6b, 0x6f, 0x75, 0x74, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, - 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x57, 0x6f, 0x72, 0x6b, - 0x6f, 0x75, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x77, 0x6f, - 0x72, 0x6b, 0x6f, 0x75, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x1a, 0x32, 0x0a, 0x0c, - 0x55, 0x73, 0x65, 0x72, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x05, - 0x61, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x05, 0x61, 0x63, 0x74, 0x6f, 0x72, - 0x1a, 0x5f, 0x0a, 0x0e, 0x57, 0x6f, 0x72, 0x6b, 0x6f, 0x75, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x65, - 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x05, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, - 0x05, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x29, 0x0a, 0x07, 0x77, 0x6f, 0x72, 0x6b, 0x6f, 0x75, - 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, - 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x6f, 0x75, 0x74, 0x52, 0x07, 0x77, 0x6f, 0x72, 0x6b, 0x6f, 0x75, - 0x74, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x32, 0x96, 0x04, 0x0a, 0x0b, 0x55, 0x73, - 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3c, 0x0a, 0x03, 0x47, 0x65, 0x74, - 0x12, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, - 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, - 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x04, 0x88, 0xb5, 0x18, 0x01, 0x12, 0x3d, 0x0a, 0x06, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, - 0x77, 0x12, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, - 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, - 0x31, 0x2e, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x04, 0x88, 0xb5, 0x18, 0x01, 0x12, 0x43, 0x0a, 0x08, 0x55, 0x6e, 0x66, 0x6f, 0x6c, 0x6c, - 0x6f, 0x77, 0x12, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x66, 0x6f, - 0x6c, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x04, 0x88, 0xb5, 0x18, 0x01, 0x12, 0x52, 0x0a, 0x0d, 0x4c, - 0x69, 0x73, 0x74, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x72, 0x73, 0x12, 0x1c, 0x2e, 0x61, - 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, - 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x61, 0x70, 0x69, - 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x72, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x04, 0x88, 0xb5, 0x18, 0x01, 0x12, - 0x52, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x65, 0x73, - 0x12, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x6f, - 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, - 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x6f, 0x6c, 0x6c, - 0x6f, 0x77, 0x65, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x04, 0x88, - 0xb5, 0x18, 0x01, 0x12, 0x3d, 0x0a, 0x06, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x15, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, - 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x04, 0x88, 0xb5, - 0x18, 0x01, 0x12, 0x5e, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x20, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x61, 0x70, 0x69, 0x2e, - 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x04, 0x88, 0xb5, - 0x18, 0x01, 0x42, 0x8b, 0x01, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, - 0x31, 0x42, 0x0a, 0x55, 0x73, 0x65, 0x72, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, - 0x38, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x72, 0x6c, 0x73, - 0x73, 0x6e, 0x2f, 0x67, 0x65, 0x74, 0x73, 0x74, 0x72, 0x6f, 0x6e, 0x67, 0x65, 0x72, 0x2f, 0x73, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x62, 0x2f, 0x61, 0x70, 0x69, - 0x2f, 0x76, 0x31, 0x3b, 0x61, 0x70, 0x69, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x41, 0x58, 0x58, 0xaa, - 0x02, 0x06, 0x41, 0x70, 0x69, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x06, 0x41, 0x70, 0x69, 0x5c, 0x56, - 0x31, 0xe2, 0x02, 0x12, 0x41, 0x70, 0x69, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x07, 0x41, 0x70, 0x69, 0x3a, 0x3a, 0x56, 0x31, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x65, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x32, 0xb6, + 0x03, 0x0a, 0x0b, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3c, + 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, + 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x04, 0x88, 0xb5, 0x18, 0x01, 0x12, 0x3d, 0x0a, 0x06, + 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x12, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, + 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x04, 0x88, 0xb5, 0x18, 0x01, 0x12, 0x43, 0x0a, 0x08, 0x55, + 0x6e, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x12, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, + 0x2e, 0x55, 0x6e, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x66, 0x6f, 0x6c, 0x6c, + 0x6f, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x04, 0x88, 0xb5, 0x18, 0x01, + 0x12, 0x52, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x72, + 0x73, 0x12, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, + 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x6f, 0x6c, + 0x6c, 0x6f, 0x77, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x04, + 0x88, 0xb5, 0x18, 0x01, 0x12, 0x52, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x6f, 0x6c, 0x6c, + 0x6f, 0x77, 0x65, 0x65, 0x73, 0x12, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x04, 0x88, 0xb5, 0x18, 0x01, 0x12, 0x3d, 0x0a, 0x06, 0x53, 0x65, 0x61, 0x72, + 0x63, 0x68, 0x12, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, + 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x76, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x04, 0x88, 0xb5, 0x18, 0x01, 0x42, 0x8b, 0x01, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x42, 0x0a, 0x55, 0x73, 0x65, 0x72, 0x73, 0x50, 0x72, 0x6f, + 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x63, 0x72, 0x6c, 0x73, 0x73, 0x6e, 0x2f, 0x67, 0x65, 0x74, 0x73, 0x74, 0x72, 0x6f, 0x6e, + 0x67, 0x65, 0x72, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, + 0x62, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x3b, 0x61, 0x70, 0x69, 0x76, 0x31, 0xa2, 0x02, + 0x03, 0x41, 0x58, 0x58, 0xaa, 0x02, 0x06, 0x41, 0x70, 0x69, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x06, + 0x41, 0x70, 0x69, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x12, 0x41, 0x70, 0x69, 0x5c, 0x56, 0x31, 0x5c, + 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x07, 0x41, 0x70, + 0x69, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1030,64 +672,48 @@ func file_api_v1_users_proto_rawDescGZIP() []byte { return file_api_v1_users_proto_rawDescData } -var file_api_v1_users_proto_msgTypes = make([]protoimpl.MessageInfo, 17) +var file_api_v1_users_proto_msgTypes = make([]protoimpl.MessageInfo, 12) var file_api_v1_users_proto_goTypes = []any{ - (*GetUserRequest)(nil), // 0: api.v1.GetUserRequest - (*GetUserResponse)(nil), // 1: api.v1.GetUserResponse - (*FollowRequest)(nil), // 2: api.v1.FollowRequest - (*FollowResponse)(nil), // 3: api.v1.FollowResponse - (*UnfollowRequest)(nil), // 4: api.v1.UnfollowRequest - (*UnfollowResponse)(nil), // 5: api.v1.UnfollowResponse - (*ListFollowersRequest)(nil), // 6: api.v1.ListFollowersRequest - (*ListFollowersResponse)(nil), // 7: api.v1.ListFollowersResponse - (*ListFolloweesRequest)(nil), // 8: api.v1.ListFolloweesRequest - (*ListFolloweesResponse)(nil), // 9: api.v1.ListFolloweesResponse - (*SearchRequest)(nil), // 10: api.v1.SearchRequest - (*SearchResponse)(nil), // 11: api.v1.SearchResponse - (*ListNotificationsRequest)(nil), // 12: api.v1.ListNotificationsRequest - (*ListNotificationsResponse)(nil), // 13: api.v1.ListNotificationsResponse - (*Notification)(nil), // 14: api.v1.Notification - (*Notification_UserFollowed)(nil), // 15: api.v1.Notification.UserFollowed - (*Notification_WorkoutComment)(nil), // 16: api.v1.Notification.WorkoutComment - (*User)(nil), // 17: api.v1.User - (*PaginationRequest)(nil), // 18: api.v1.PaginationRequest - (*PaginationResponse)(nil), // 19: api.v1.PaginationResponse - (*Workout)(nil), // 20: api.v1.Workout + (*GetUserRequest)(nil), // 0: api.v1.GetUserRequest + (*GetUserResponse)(nil), // 1: api.v1.GetUserResponse + (*FollowRequest)(nil), // 2: api.v1.FollowRequest + (*FollowResponse)(nil), // 3: api.v1.FollowResponse + (*UnfollowRequest)(nil), // 4: api.v1.UnfollowRequest + (*UnfollowResponse)(nil), // 5: api.v1.UnfollowResponse + (*ListFollowersRequest)(nil), // 6: api.v1.ListFollowersRequest + (*ListFollowersResponse)(nil), // 7: api.v1.ListFollowersResponse + (*ListFolloweesRequest)(nil), // 8: api.v1.ListFolloweesRequest + (*ListFolloweesResponse)(nil), // 9: api.v1.ListFolloweesResponse + (*SearchRequest)(nil), // 10: api.v1.SearchRequest + (*SearchResponse)(nil), // 11: api.v1.SearchResponse + (*User)(nil), // 12: api.v1.User + (*PaginationRequest)(nil), // 13: api.v1.PaginationRequest + (*PaginationResponse)(nil), // 14: api.v1.PaginationResponse } var file_api_v1_users_proto_depIdxs = []int32{ - 17, // 0: api.v1.GetUserResponse.user:type_name -> api.v1.User - 17, // 1: api.v1.ListFollowersResponse.followers:type_name -> api.v1.User - 17, // 2: api.v1.ListFolloweesResponse.followees:type_name -> api.v1.User - 18, // 3: api.v1.SearchRequest.pagination:type_name -> api.v1.PaginationRequest - 17, // 4: api.v1.SearchResponse.users:type_name -> api.v1.User - 19, // 5: api.v1.SearchResponse.pagination:type_name -> api.v1.PaginationResponse - 18, // 6: api.v1.ListNotificationsRequest.pagination:type_name -> api.v1.PaginationRequest - 14, // 7: api.v1.ListNotificationsResponse.notifications:type_name -> api.v1.Notification - 19, // 8: api.v1.ListNotificationsResponse.pagination:type_name -> api.v1.PaginationResponse - 15, // 9: api.v1.Notification.user_followed:type_name -> api.v1.Notification.UserFollowed - 16, // 10: api.v1.Notification.workout_comment:type_name -> api.v1.Notification.WorkoutComment - 17, // 11: api.v1.Notification.UserFollowed.actor:type_name -> api.v1.User - 17, // 12: api.v1.Notification.WorkoutComment.actor:type_name -> api.v1.User - 20, // 13: api.v1.Notification.WorkoutComment.workout:type_name -> api.v1.Workout - 0, // 14: api.v1.UserService.Get:input_type -> api.v1.GetUserRequest - 2, // 15: api.v1.UserService.Follow:input_type -> api.v1.FollowRequest - 4, // 16: api.v1.UserService.Unfollow:input_type -> api.v1.UnfollowRequest - 6, // 17: api.v1.UserService.ListFollowers:input_type -> api.v1.ListFollowersRequest - 8, // 18: api.v1.UserService.ListFollowees:input_type -> api.v1.ListFolloweesRequest - 10, // 19: api.v1.UserService.Search:input_type -> api.v1.SearchRequest - 12, // 20: api.v1.UserService.ListNotifications:input_type -> api.v1.ListNotificationsRequest - 1, // 21: api.v1.UserService.Get:output_type -> api.v1.GetUserResponse - 3, // 22: api.v1.UserService.Follow:output_type -> api.v1.FollowResponse - 5, // 23: api.v1.UserService.Unfollow:output_type -> api.v1.UnfollowResponse - 7, // 24: api.v1.UserService.ListFollowers:output_type -> api.v1.ListFollowersResponse - 9, // 25: api.v1.UserService.ListFollowees:output_type -> api.v1.ListFolloweesResponse - 11, // 26: api.v1.UserService.Search:output_type -> api.v1.SearchResponse - 13, // 27: api.v1.UserService.ListNotifications:output_type -> api.v1.ListNotificationsResponse - 21, // [21:28] is the sub-list for method output_type - 14, // [14:21] is the sub-list for method input_type - 14, // [14:14] is the sub-list for extension type_name - 14, // [14:14] is the sub-list for extension extendee - 0, // [0:14] is the sub-list for field type_name + 12, // 0: api.v1.GetUserResponse.user:type_name -> api.v1.User + 12, // 1: api.v1.ListFollowersResponse.followers:type_name -> api.v1.User + 12, // 2: api.v1.ListFolloweesResponse.followees:type_name -> api.v1.User + 13, // 3: api.v1.SearchRequest.pagination:type_name -> api.v1.PaginationRequest + 12, // 4: api.v1.SearchResponse.users:type_name -> api.v1.User + 14, // 5: api.v1.SearchResponse.pagination:type_name -> api.v1.PaginationResponse + 0, // 6: api.v1.UserService.Get:input_type -> api.v1.GetUserRequest + 2, // 7: api.v1.UserService.Follow:input_type -> api.v1.FollowRequest + 4, // 8: api.v1.UserService.Unfollow:input_type -> api.v1.UnfollowRequest + 6, // 9: api.v1.UserService.ListFollowers:input_type -> api.v1.ListFollowersRequest + 8, // 10: api.v1.UserService.ListFollowees:input_type -> api.v1.ListFolloweesRequest + 10, // 11: api.v1.UserService.Search:input_type -> api.v1.SearchRequest + 1, // 12: api.v1.UserService.Get:output_type -> api.v1.GetUserResponse + 3, // 13: api.v1.UserService.Follow:output_type -> api.v1.FollowResponse + 5, // 14: api.v1.UserService.Unfollow:output_type -> api.v1.UnfollowResponse + 7, // 15: api.v1.UserService.ListFollowers:output_type -> api.v1.ListFollowersResponse + 9, // 16: api.v1.UserService.ListFollowees:output_type -> api.v1.ListFolloweesResponse + 11, // 17: api.v1.UserService.Search:output_type -> api.v1.SearchResponse + 12, // [12:18] is the sub-list for method output_type + 6, // [6:12] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name } func init() { file_api_v1_users_proto_init() } @@ -1098,17 +724,13 @@ func file_api_v1_users_proto_init() { file_api_v1_options_proto_init() file_api_v1_shared_proto_init() file_api_v1_workouts_proto_init() - file_api_v1_users_proto_msgTypes[14].OneofWrappers = []any{ - (*Notification_UserFollowed_)(nil), - (*Notification_WorkoutComment_)(nil), - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_api_v1_users_proto_rawDesc, NumEnums: 0, - NumMessages: 17, + NumMessages: 12, NumExtensions: 0, NumServices: 1, }, diff --git a/server/rpc/interceptors/auth.go b/server/rpc/interceptors/auth.go index 3f0cb3a0..25fd31c9 100644 --- a/server/rpc/interceptors/auth.go +++ b/server/rpc/interceptors/auth.go @@ -19,15 +19,9 @@ import ( "github.com/crlssn/getstronger/server/pkg/xzap" ) -type auth struct { - log *zap.Logger - jwt *jwt.Manager - methods map[string]bool -} - -var _ Interceptor = (*auth)(nil) +var _ connect.Interceptor = (*auth)(nil) -func newAuth(log *zap.Logger, m *jwt.Manager) Interceptor { +func newAuth(log *zap.Logger, m *jwt.Manager) connect.Interceptor { a := &auth{ log: log, jwt: m, @@ -37,6 +31,81 @@ func newAuth(log *zap.Logger, m *jwt.Manager) Interceptor { return a } +type auth struct { + log *zap.Logger + jwt *jwt.Manager + methods map[string]bool +} + +func (a *auth) WrapUnary(next connect.UnaryFunc) connect.UnaryFunc { + return func( + ctx context.Context, + req connect.AnyRequest, + ) (connect.AnyResponse, error) { + log := a.log.With(xzap.FieldRPC(req.Spec().Procedure)) + log.Info("request received") + ctx = xcontext.WithLogger(ctx, log) + + requiresAuth := a.methods[req.Spec().Procedure] + if !requiresAuth { + log.Info("request does not require authentication") + return next(ctx, req) + } + + claims, err := a.claimsFromHeader(req.Header()) + if err != nil { + log.Warn("request unauthenticated", zap.Error(err)) + return nil, connect.NewError(connect.CodeUnauthenticated, nil) + } + + log = log.With(xzap.FieldUserID(claims.UserID)) + log.Info("request authenticated") + + ctx = xcontext.WithLogger(ctx, log) + ctx = xcontext.WithUserID(ctx, claims.UserID) + return next(ctx, req) + } +} + +func (a *auth) WrapStreamingClient(next connect.StreamingClientFunc) connect.StreamingClientFunc { + return func( + ctx context.Context, + spec connect.Spec, + ) connect.StreamingClientConn { + return next(ctx, spec) + } +} + +func (a *auth) WrapStreamingHandler(next connect.StreamingHandlerFunc) connect.StreamingHandlerFunc { + return func( + ctx context.Context, + conn connect.StreamingHandlerConn, + ) error { + log := a.log.With(xzap.FieldRPC(conn.Spec().Procedure)) + log.Info("request received") + ctx = xcontext.WithLogger(ctx, log) + + requiresAuth := a.methods[conn.Spec().Procedure] + if !requiresAuth { + log.Info("request does not require authentication") + return next(ctx, conn) + } + + claims, err := a.claimsFromHeader(conn.RequestHeader()) + if err != nil { + log.Warn("request unauthenticated", zap.Error(err)) + return connect.NewError(connect.CodeUnauthenticated, nil) + } + + log = log.With(xzap.FieldUserID(claims.UserID)) + log.Info("request authenticated") + + ctx = xcontext.WithLogger(ctx, log) + ctx = xcontext.WithUserID(ctx, claims.UserID) + return next(ctx, conn) + } +} + func (a *auth) initMethods() { fileDescriptors := []protoreflect.FileDescriptor{ apiv1.File_api_v1_auth_proto, @@ -45,6 +114,7 @@ func (a *auth) initMethods() { apiv1.File_api_v1_exercise_proto, apiv1.File_api_v1_routines_proto, apiv1.File_api_v1_workouts_proto, + apiv1.File_api_v1_notifications_proto, } for _, fileDescriptor := range fileDescriptors { @@ -78,39 +148,6 @@ func (a *auth) initMethods() { } } -// Unary is the unary interceptor method for authentication. -func (a *auth) Unary() connect.UnaryInterceptorFunc { - return func(next connect.UnaryFunc) connect.UnaryFunc { - return func( - ctx context.Context, - req connect.AnyRequest, - ) (connect.AnyResponse, error) { - log := a.log.With(xzap.FieldRPC(req.Spec().Procedure)) - log.Info("request received") - ctx = xcontext.WithLogger(ctx, log) - - requiresAuth := a.methods[req.Spec().Procedure] - if !requiresAuth { - log.Info("request does not require authentication") - return next(ctx, req) - } - - claims, err := a.claimsFromHeader(req.Header()) - if err != nil { - log.Warn("request unauthenticated", zap.Error(err)) - return nil, connect.NewError(connect.CodeUnauthenticated, nil) - } - - log = log.With(xzap.FieldUserID(claims.UserID)) - log.Info("request authenticated") - - ctx = xcontext.WithLogger(ctx, log) - ctx = xcontext.WithUserID(ctx, claims.UserID) - return next(ctx, req) - } - } -} - var ( errMissingAuthorizationToken = errors.New("authorization token is missing") errInvalidAuthorizationToken = errors.New("invalid authorization header format") diff --git a/server/rpc/interceptors/interface.go b/server/rpc/interceptors/interface.go deleted file mode 100644 index 0ae513b2..00000000 --- a/server/rpc/interceptors/interface.go +++ /dev/null @@ -1,9 +0,0 @@ -package interceptors - -import ( - "connectrpc.com/connect" -) - -type Interceptor interface { - Unary() connect.UnaryInterceptorFunc -} diff --git a/server/rpc/interceptors/module.go b/server/rpc/interceptors/module.go index 4c807dd9..2e8342fd 100644 --- a/server/rpc/interceptors/module.go +++ b/server/rpc/interceptors/module.go @@ -27,10 +27,10 @@ func Module() fx.Option { ) } -func provideHandlerOptions(i []Interceptor) []connect.HandlerOption { - opts := make([]connect.HandlerOption, 0, len(i)) - for _, j := range i { - opts = append(opts, connect.WithInterceptors(j.Unary())) +func provideHandlerOptions(interceptors []connect.Interceptor) []connect.HandlerOption { + opts := make([]connect.HandlerOption, 0, len(interceptors)) + for _, interceptor := range interceptors { + opts = append(opts, connect.WithInterceptors(interceptor)) } return opts } diff --git a/server/rpc/interceptors/validator.go b/server/rpc/interceptors/validator.go index a796d1b3..cbe8928f 100644 --- a/server/rpc/interceptors/validator.go +++ b/server/rpc/interceptors/validator.go @@ -10,40 +10,56 @@ import ( "google.golang.org/protobuf/proto" ) -type validator struct { - log *zap.Logger - validator *protovalidate.Validator -} - -var _ Interceptor = (*validator)(nil) +var _ connect.Interceptor = (*validator)(nil) -func newValidator(log *zap.Logger, v *protovalidate.Validator) Interceptor { +func newValidator(log *zap.Logger, v *protovalidate.Validator) connect.Interceptor { return &validator{ log: log, validator: v, } } +type validator struct { + log *zap.Logger + validator *protovalidate.Validator +} + var errRequestMessageNotProtoMessage = errors.New("request message is not a proto.Message") -func (v *validator) Unary() connect.UnaryInterceptorFunc { - return func(next connect.UnaryFunc) connect.UnaryFunc { - return func( - ctx context.Context, - req connect.AnyRequest, - ) (connect.AnyResponse, error) { - msg, ok := req.Any().(proto.Message) - if !ok { - v.log.Warn("request message is not a proto.Message") - return nil, connect.NewError(connect.CodeInvalidArgument, errRequestMessageNotProtoMessage) - } - - if err := v.validator.Validate(msg); err != nil { - v.log.Warn("invalid request", zap.Error(err)) - return nil, connect.NewError(connect.CodeInvalidArgument, err) - } - - return next(ctx, req) +func (v *validator) WrapUnary(next connect.UnaryFunc) connect.UnaryFunc { + return func( + ctx context.Context, + req connect.AnyRequest, + ) (connect.AnyResponse, error) { + msg, ok := req.Any().(proto.Message) + if !ok { + v.log.Warn("request message is not a proto.Message") + return nil, connect.NewError(connect.CodeInvalidArgument, errRequestMessageNotProtoMessage) } + + if err := v.validator.Validate(msg); err != nil { + v.log.Warn("invalid request", zap.Error(err)) + return nil, connect.NewError(connect.CodeInvalidArgument, err) + } + + return next(ctx, req) + } +} + +func (v *validator) WrapStreamingClient(next connect.StreamingClientFunc) connect.StreamingClientFunc { + return func( + ctx context.Context, + spec connect.Spec, + ) connect.StreamingClientConn { + return next(ctx, spec) + } +} + +func (v *validator) WrapStreamingHandler(next connect.StreamingHandlerFunc) connect.StreamingHandlerFunc { + return func( + ctx context.Context, + conn connect.StreamingHandlerConn, + ) error { + return next(ctx, conn) } } diff --git a/server/rpc/middlewares/middlewares.go b/server/rpc/middlewares/middlewares.go index 692fb14a..c7e68999 100644 --- a/server/rpc/middlewares/middlewares.go +++ b/server/rpc/middlewares/middlewares.go @@ -71,6 +71,12 @@ func (m *Middleware) cookies(h http.Handler) http.Handler { func (m *Middleware) trace(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if _, ok := w.(http.Flusher); ok { + // Bypass tracing for streaming requests. + h.ServeHTTP(w, r) + return + } + // Use a custom response writer to capture the status code. rw := &trace.ResponseWriter{ResponseWriter: w} t := m.tracer.Trace(r.RequestURI) diff --git a/server/rpc/module.go b/server/rpc/module.go index 90c5b373..7167d740 100644 --- a/server/rpc/module.go +++ b/server/rpc/module.go @@ -30,6 +30,7 @@ func Module() fx.Option { v1.NewRoutineHandler, v1.NewWorkoutHandler, v1.NewExerciseHandler, + v1.NewNotificationHandler, middlewares.New, ), fx.Invoke( @@ -41,12 +42,13 @@ func Module() fx.Option { type Handlers struct { fx.In - Auth apiv1connect.AuthServiceHandler - Feed apiv1connect.FeedServiceHandler - User apiv1connect.UserServiceHandler - Routine apiv1connect.RoutineServiceHandler - Workout apiv1connect.WorkoutServiceHandler - Exercise apiv1connect.ExerciseServiceHandler + Auth apiv1connect.AuthServiceHandler + Feed apiv1connect.FeedServiceHandler + User apiv1connect.UserServiceHandler + Routine apiv1connect.RoutineServiceHandler + Workout apiv1connect.WorkoutServiceHandler + Exercise apiv1connect.ExerciseServiceHandler + Notification apiv1connect.NotificationServiceHandler } func registerHandlers(p Handlers, o []connect.HandlerOption, m *middlewares.Middleware) *http.ServeMux { @@ -69,6 +71,9 @@ func registerHandlers(p Handlers, o []connect.HandlerOption, m *middlewares.Midd func(opts ...connect.HandlerOption) (string, http.Handler) { return apiv1connect.NewExerciseServiceHandler(p.Exercise, opts...) }, + func(opts ...connect.HandlerOption) (string, http.Handler) { + return apiv1connect.NewNotificationServiceHandler(p.Notification, opts...) + }, } mux := http.NewServeMux() @@ -81,9 +86,8 @@ func registerHandlers(p Handlers, o []connect.HandlerOption, m *middlewares.Midd } const ( - readTimeout = 10 * time.Second - writeTimeout = 10 * time.Second - idleTimeout = 120 * time.Second + readTimeout = 10 * time.Second + idleTimeout = 120 * time.Second ) func startServer(lc fx.Lifecycle, c *config.Config, mux *http.ServeMux) { @@ -94,7 +98,7 @@ func startServer(lc fx.Lifecycle, c *config.Config, mux *http.ServeMux) { Addr: fmt.Sprintf(":%s", c.Server.Port), Handler: h2c.NewHandler(mux, &http2.Server{}), ReadTimeout: readTimeout, - WriteTimeout: writeTimeout, + WriteTimeout: 0, IdleTimeout: idleTimeout, TLSConfig: &tls.Config{ MinVersion: tls.VersionTLS12, diff --git a/server/rpc/v1/notification.go b/server/rpc/v1/notification.go new file mode 100644 index 00000000..72af471c --- /dev/null +++ b/server/rpc/v1/notification.go @@ -0,0 +1,148 @@ +package v1 + +import ( + "context" + "encoding/json" + "time" + + "connectrpc.com/connect" + "go.uber.org/zap" + + "github.com/crlssn/getstronger/server/pkg/orm" + v1 "github.com/crlssn/getstronger/server/pkg/pb/api/v1" + "github.com/crlssn/getstronger/server/pkg/pb/api/v1/apiv1connect" + "github.com/crlssn/getstronger/server/pkg/repo" + "github.com/crlssn/getstronger/server/pkg/xcontext" +) + +var _ apiv1connect.NotificationServiceHandler = (*notificationHandler)(nil) + +type notificationHandler struct { + repo *repo.Repo +} + +func NewNotificationHandler(r *repo.Repo) apiv1connect.NotificationServiceHandler { + return ¬ificationHandler{r} +} + +func (h *notificationHandler) ListNotifications(ctx context.Context, req *connect.Request[v1.ListNotificationsRequest]) (*connect.Response[v1.ListNotificationsResponse], error) { //nolint:cyclop // TODO: Simplify this method. + log := xcontext.MustExtractLogger(ctx) + userID := xcontext.MustExtractUserID(ctx) + + total, err := h.repo.CountNotifications(ctx, + repo.CountNotificationsWithUserID(userID), + repo.CountNotificationsWithUnreadOnly(req.Msg.GetUnreadOnly()), + ) + if err != nil { + log.Error("failed to count notifications", zap.Error(err)) + return nil, connect.NewError(connect.CodeInternal, nil) + } + + limit := int(req.Msg.GetPagination().GetPageLimit()) + notifications, err := h.repo.ListNotifications(ctx, + repo.ListNotificationsWithLimit(limit+1), + repo.ListNotificationsWithUserID(userID), + repo.ListNotificationsWithOnlyUnread(req.Msg.GetUnreadOnly()), + repo.ListNotificationsOrderByCreatedAtDESC(), + ) + if err != nil { + log.Error("failed to list notifications", zap.Error(err)) + return nil, connect.NewError(connect.CodeInternal, nil) + } + + paginated, err := repo.PaginateSlice(notifications, limit, func(n *orm.Notification) time.Time { + return n.CreatedAt + }) + if err != nil { + log.Error("failed to paginate notifications", zap.Error(err)) + return nil, connect.NewError(connect.CodeInternal, nil) + } + + var userIDs []string + var workoutIDs []string + nPayloads := make(map[string]repo.NotificationPayload) + + for _, n := range paginated.Items { + var payload repo.NotificationPayload + if err = json.Unmarshal(n.Payload, &payload); err != nil { + log.Error("failed to unmarshal notification payload", zap.Error(err)) + return nil, connect.NewError(connect.CodeInternal, nil) + } + + nPayloads[n.ID] = payload + if payload.ActorID != "" { + userIDs = append(userIDs, payload.ActorID) + } + if payload.WorkoutID != "" { + workoutIDs = append(workoutIDs, payload.WorkoutID) + } + } + + users, err := h.repo.ListUsers(ctx, repo.ListUsersWithIDs(userIDs)) + if err != nil { + log.Error("failed to list users", zap.Error(err)) + return nil, connect.NewError(connect.CodeInternal, nil) + } + + workouts, err := h.repo.ListWorkouts(ctx, repo.ListWorkoutsWithIDs(workoutIDs)) + if err != nil { + log.Error("failed to list workouts", zap.Error(err)) + return nil, connect.NewError(connect.CodeInternal, nil) + } + + if req.Msg.GetMarkAsRead() { + if err = h.repo.MarkNotificationsAsRead(ctx, repo.MarkNotificationsAsReadByUserID(userID)); err != nil { + log.Error("failed to mark notifications as read", zap.Error(err)) + return nil, connect.NewError(connect.CodeInternal, nil) + } + } + + return &connect.Response[v1.ListNotificationsResponse]{ + Msg: &v1.ListNotificationsResponse{ + Notifications: parseNotificationSliceToPB(paginated.Items, nPayloads, users, workouts), + Pagination: &v1.PaginationResponse{ + TotalResults: total, + NextPageToken: paginated.NextPageToken, + }, + }, + }, nil +} + +func (h *notificationHandler) UnreadNotifications(ctx context.Context, _ *connect.Request[v1.UnreadNotificationsRequest], res *connect.ServerStream[v1.UnreadNotificationsResponse]) error { + log := xcontext.MustExtractLogger(ctx) + userID := xcontext.MustExtractUserID(ctx) + + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + + var lastCount int64 + + for { + select { + case <-ctx.Done(): + log.Info("client disconnected") + return nil + case <-ticker.C: + count, err := h.repo.CountNotifications(ctx, + repo.CountNotificationsWithUserID(userID), + repo.CountNotificationsWithUnreadOnly(true), + ) + if err != nil { + log.Error("failed to count notifications", zap.Error(err)) + return connect.NewError(connect.CodeInternal, nil) + } + + if count == lastCount { + continue + } + lastCount = count + + if err = res.Send(&v1.UnreadNotificationsResponse{ + Count: count, + }); err != nil { + log.Error("failed to send unread notifications", zap.Error(err)) + return connect.NewError(connect.CodeInternal, nil) + } + } + } +} diff --git a/server/rpc/v1/user.go b/server/rpc/v1/user.go index 6bdf2d05..7ba9c75e 100644 --- a/server/rpc/v1/user.go +++ b/server/rpc/v1/user.go @@ -2,7 +2,6 @@ package v1 import ( "context" - "encoding/json" "time" "connectrpc.com/connect" @@ -176,86 +175,3 @@ func (h *userHandler) ListFollowees(ctx context.Context, req *connect.Request[v1 }, }, nil } - -func (h *userHandler) ListNotifications(ctx context.Context, req *connect.Request[v1.ListNotificationsRequest]) (*connect.Response[v1.ListNotificationsResponse], error) { //nolint:cyclop // TODO: Simplify this method. - log := xcontext.MustExtractLogger(ctx) - userID := xcontext.MustExtractUserID(ctx) - - total, err := h.repo.CountNotifications(ctx, - repo.CountNotificationsWithUserID(userID), - repo.CountNotificationsWithUnreadOnly(req.Msg.GetUnreadOnly()), - ) - if err != nil { - log.Error("failed to count notifications", zap.Error(err)) - return nil, connect.NewError(connect.CodeInternal, nil) - } - - limit := int(req.Msg.GetPagination().GetPageLimit()) - notifications, err := h.repo.ListNotifications(ctx, - repo.ListNotificationsWithLimit(limit+1), - repo.ListNotificationsWithUserID(userID), - repo.ListNotificationsWithOnlyUnread(req.Msg.GetUnreadOnly()), - repo.ListNotificationsOrderByCreatedAtDESC(), - ) - if err != nil { - log.Error("failed to list notifications", zap.Error(err)) - return nil, connect.NewError(connect.CodeInternal, nil) - } - - paginated, err := repo.PaginateSlice(notifications, limit, func(n *orm.Notification) time.Time { - return n.CreatedAt - }) - if err != nil { - log.Error("failed to paginate notifications", zap.Error(err)) - return nil, connect.NewError(connect.CodeInternal, nil) - } - - var userIDs []string - var workoutIDs []string - nPayloads := make(map[string]repo.NotificationPayload) - - for _, n := range paginated.Items { - var payload repo.NotificationPayload - if err = json.Unmarshal(n.Payload, &payload); err != nil { - log.Error("failed to unmarshal notification payload", zap.Error(err)) - return nil, connect.NewError(connect.CodeInternal, nil) - } - - nPayloads[n.ID] = payload - if payload.ActorID != "" { - userIDs = append(userIDs, payload.ActorID) - } - if payload.WorkoutID != "" { - workoutIDs = append(workoutIDs, payload.WorkoutID) - } - } - - users, err := h.repo.ListUsers(ctx, repo.ListUsersWithIDs(userIDs)) - if err != nil { - log.Error("failed to list users", zap.Error(err)) - return nil, connect.NewError(connect.CodeInternal, nil) - } - - workouts, err := h.repo.ListWorkouts(ctx, repo.ListWorkoutsWithIDs(workoutIDs)) - if err != nil { - log.Error("failed to list workouts", zap.Error(err)) - return nil, connect.NewError(connect.CodeInternal, nil) - } - - if req.Msg.GetMarkAsRead() { - if err = h.repo.MarkNotificationsAsRead(ctx, repo.MarkNotificationsAsReadByUserID(userID)); err != nil { - log.Error("failed to mark notifications as read", zap.Error(err)) - return nil, connect.NewError(connect.CodeInternal, nil) - } - } - - return &connect.Response[v1.ListNotificationsResponse]{ - Msg: &v1.ListNotificationsResponse{ - Notifications: parseNotificationSliceToPB(paginated.Items, nPayloads, users, workouts), - Pagination: &v1.PaginationResponse{ - TotalResults: total, - NextPageToken: paginated.NextPageToken, - }, - }, - }, nil -} diff --git a/web/src/clients/clients.ts b/web/src/clients/clients.ts index cd8ffd63..231ae86e 100644 --- a/web/src/clients/clients.ts +++ b/web/src/clients/clients.ts @@ -7,6 +7,7 @@ import { RoutineService } from '@/proto/api/v1/routines_pb.ts' import { WorkoutService } from '@/proto/api/v1/workouts_pb.ts' import { ExerciseService } from '@/proto/api/v1/exercise_pb.ts' import { createConnectTransport } from '@connectrpc/connect-web' +import { NotificationService } from '@/proto/api/v1/notifications_pb.ts' const transport = createConnectTransport({ baseUrl: import.meta.env.VITE_API_URL, @@ -23,3 +24,4 @@ export const UserClient = createClient(UserService, transport) export const RoutineClient = createClient(RoutineService, transport) export const WorkoutClient = createClient(WorkoutService, transport) export const ExerciseClient = createClient(ExerciseService, transport) +export const NotificationClient = createClient(NotificationService, transport) diff --git a/web/src/main.ts b/web/src/main.ts index 2ef607e4..459c3a89 100644 --- a/web/src/main.ts +++ b/web/src/main.ts @@ -23,9 +23,7 @@ const init = async () => { if (authStore.accessToken) { await RefreshAccessTokenOrLogout() authStore.setAccessTokenRefreshInterval(ScheduleTokenRefresh()) - - await notificationStore.fetchUnreadNotifications() - notificationStore.setRefreshInterval() + notificationStore.streamUnreadNotifications() } app.mount('#app') diff --git a/web/src/proto/api/v1/notifications_pb.ts b/web/src/proto/api/v1/notifications_pb.ts new file mode 100644 index 00000000..bbd372d5 --- /dev/null +++ b/web/src/proto/api/v1/notifications_pb.ts @@ -0,0 +1,203 @@ +// @generated by protoc-gen-es v2.2.2 with parameter "target=ts" +// @generated from file api/v1/notifications.proto (package api.v1, syntax proto3) +/* eslint-disable */ + +import type { GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv1"; +import { fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv1"; +import { file_api_v1_options } from "./options_pb"; +import type { PaginationRequest, PaginationResponse, User } from "./shared_pb"; +import { file_api_v1_shared } from "./shared_pb"; +import type { Workout } from "./workouts_pb"; +import { file_api_v1_workouts } from "./workouts_pb"; +import { file_google_protobuf_empty } from "@bufbuild/protobuf/wkt"; +import { file_buf_validate_validate } from "../../buf/validate/validate_pb"; +import type { Message } from "@bufbuild/protobuf"; + +/** + * Describes the file api/v1/notifications.proto. + */ +export const file_api_v1_notifications: GenFile = /*@__PURE__*/ + fileDesc("ChphcGkvdjEvbm90aWZpY2F0aW9ucy5wcm90bxIGYXBpLnYxInwKGExpc3ROb3RpZmljYXRpb25zUmVxdWVzdBITCgt1bnJlYWRfb25seRgBIAEoCBIUCgxtYXJrX2FzX3JlYWQYAiABKAgSNQoKcGFnaW5hdGlvbhgDIAEoCzIZLmFwaS52MS5QYWdpbmF0aW9uUmVxdWVzdEIGukgDyAEBIngKGUxpc3ROb3RpZmljYXRpb25zUmVzcG9uc2USKwoNbm90aWZpY2F0aW9ucxgBIAMoCzIULmFwaS52MS5Ob3RpZmljYXRpb24SLgoKcGFnaW5hdGlvbhgCIAEoCzIaLmFwaS52MS5QYWdpbmF0aW9uUmVzcG9uc2UitgIKDE5vdGlmaWNhdGlvbhIKCgJpZBgBIAEoCRIYChBub3RpZmllZF9hdF91bml4GAIgASgDEjoKDXVzZXJfZm9sbG93ZWQYAyABKAsyIS5hcGkudjEuTm90aWZpY2F0aW9uLlVzZXJGb2xsb3dlZEgAEj4KD3dvcmtvdXRfY29tbWVudBgEIAEoCzIjLmFwaS52MS5Ob3RpZmljYXRpb24uV29ya291dENvbW1lbnRIABorCgxVc2VyRm9sbG93ZWQSGwoFYWN0b3IYASABKAsyDC5hcGkudjEuVXNlchpPCg5Xb3Jrb3V0Q29tbWVudBIbCgVhY3RvchgBIAEoCzIMLmFwaS52MS5Vc2VyEiAKB3dvcmtvdXQYAiABKAsyDy5hcGkudjEuV29ya291dEIGCgR0eXBlIhwKGlVucmVhZE5vdGlmaWNhdGlvbnNSZXF1ZXN0IiwKG1VucmVhZE5vdGlmaWNhdGlvbnNSZXNwb25zZRINCgVjb3VudBgBIAEoAzLdAQoTTm90aWZpY2F0aW9uU2VydmljZRJeChFMaXN0Tm90aWZpY2F0aW9ucxIgLmFwaS52MS5MaXN0Tm90aWZpY2F0aW9uc1JlcXVlc3QaIS5hcGkudjEuTGlzdE5vdGlmaWNhdGlvbnNSZXNwb25zZSIEiLUYARJmChNVbnJlYWROb3RpZmljYXRpb25zEiIuYXBpLnYxLlVucmVhZE5vdGlmaWNhdGlvbnNSZXF1ZXN0GiMuYXBpLnYxLlVucmVhZE5vdGlmaWNhdGlvbnNSZXNwb25zZSIEiLUYATABQpMBCgpjb20uYXBpLnYxQhJOb3RpZmljYXRpb25zUHJvdG9QAVo4Z2l0aHViLmNvbS9jcmxzc24vZ2V0c3Ryb25nZXIvc2VydmVyL3BrZy9wYi9hcGkvdjE7YXBpdjGiAgNBWFiqAgZBcGkuVjHKAgZBcGlcVjHiAhJBcGlcVjFcR1BCTWV0YWRhdGHqAgdBcGk6OlYxYgZwcm90bzM", [file_api_v1_options, file_api_v1_shared, file_api_v1_workouts, file_google_protobuf_empty, file_buf_validate_validate]); + +/** + * @generated from message api.v1.ListNotificationsRequest + */ +export type ListNotificationsRequest = Message<"api.v1.ListNotificationsRequest"> & { + /** + * @generated from field: bool unread_only = 1; + */ + unreadOnly: boolean; + + /** + * @generated from field: bool mark_as_read = 2; + */ + markAsRead: boolean; + + /** + * @generated from field: api.v1.PaginationRequest pagination = 3; + */ + pagination?: PaginationRequest; +}; + +/** + * Describes the message api.v1.ListNotificationsRequest. + * Use `create(ListNotificationsRequestSchema)` to create a new message. + */ +export const ListNotificationsRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_api_v1_notifications, 0); + +/** + * @generated from message api.v1.ListNotificationsResponse + */ +export type ListNotificationsResponse = Message<"api.v1.ListNotificationsResponse"> & { + /** + * @generated from field: repeated api.v1.Notification notifications = 1; + */ + notifications: Notification[]; + + /** + * @generated from field: api.v1.PaginationResponse pagination = 2; + */ + pagination?: PaginationResponse; +}; + +/** + * Describes the message api.v1.ListNotificationsResponse. + * Use `create(ListNotificationsResponseSchema)` to create a new message. + */ +export const ListNotificationsResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_api_v1_notifications, 1); + +/** + * @generated from message api.v1.Notification + */ +export type Notification = Message<"api.v1.Notification"> & { + /** + * @generated from field: string id = 1; + */ + id: string; + + /** + * DEBT: This should be a timestamp but the client is not able to parse it. + * + * @generated from field: int64 notified_at_unix = 2; + */ + notifiedAtUnix: bigint; + + /** + * @generated from oneof api.v1.Notification.type + */ + type: { + /** + * @generated from field: api.v1.Notification.UserFollowed user_followed = 3; + */ + value: Notification_UserFollowed; + case: "userFollowed"; + } | { + /** + * @generated from field: api.v1.Notification.WorkoutComment workout_comment = 4; + */ + value: Notification_WorkoutComment; + case: "workoutComment"; + } | { case: undefined; value?: undefined }; +}; + +/** + * Describes the message api.v1.Notification. + * Use `create(NotificationSchema)` to create a new message. + */ +export const NotificationSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_api_v1_notifications, 2); + +/** + * @generated from message api.v1.Notification.UserFollowed + */ +export type Notification_UserFollowed = Message<"api.v1.Notification.UserFollowed"> & { + /** + * @generated from field: api.v1.User actor = 1; + */ + actor?: User; +}; + +/** + * Describes the message api.v1.Notification.UserFollowed. + * Use `create(Notification_UserFollowedSchema)` to create a new message. + */ +export const Notification_UserFollowedSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_api_v1_notifications, 2, 0); + +/** + * @generated from message api.v1.Notification.WorkoutComment + */ +export type Notification_WorkoutComment = Message<"api.v1.Notification.WorkoutComment"> & { + /** + * @generated from field: api.v1.User actor = 1; + */ + actor?: User; + + /** + * @generated from field: api.v1.Workout workout = 2; + */ + workout?: Workout; +}; + +/** + * Describes the message api.v1.Notification.WorkoutComment. + * Use `create(Notification_WorkoutCommentSchema)` to create a new message. + */ +export const Notification_WorkoutCommentSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_api_v1_notifications, 2, 1); + +/** + * @generated from message api.v1.UnreadNotificationsRequest + */ +export type UnreadNotificationsRequest = Message<"api.v1.UnreadNotificationsRequest"> & { +}; + +/** + * Describes the message api.v1.UnreadNotificationsRequest. + * Use `create(UnreadNotificationsRequestSchema)` to create a new message. + */ +export const UnreadNotificationsRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_api_v1_notifications, 3); + +/** + * @generated from message api.v1.UnreadNotificationsResponse + */ +export type UnreadNotificationsResponse = Message<"api.v1.UnreadNotificationsResponse"> & { + /** + * @generated from field: int64 count = 1; + */ + count: bigint; +}; + +/** + * Describes the message api.v1.UnreadNotificationsResponse. + * Use `create(UnreadNotificationsResponseSchema)` to create a new message. + */ +export const UnreadNotificationsResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_api_v1_notifications, 4); + +/** + * @generated from service api.v1.NotificationService + */ +export const NotificationService: GenService<{ + /** + * @generated from rpc api.v1.NotificationService.ListNotifications + */ + listNotifications: { + methodKind: "unary"; + input: typeof ListNotificationsRequestSchema; + output: typeof ListNotificationsResponseSchema; + }, + /** + * @generated from rpc api.v1.NotificationService.UnreadNotifications + */ + unreadNotifications: { + methodKind: "server_streaming"; + input: typeof UnreadNotificationsRequestSchema; + output: typeof UnreadNotificationsResponseSchema; + }, +}> = /*@__PURE__*/ + serviceDesc(file_api_v1_notifications, 0); + diff --git a/web/src/proto/api/v1/users_pb.ts b/web/src/proto/api/v1/users_pb.ts index 1e8a5549..efb7ed9f 100644 --- a/web/src/proto/api/v1/users_pb.ts +++ b/web/src/proto/api/v1/users_pb.ts @@ -7,7 +7,6 @@ import { fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv1 import { file_api_v1_options } from "./options_pb"; import type { PaginationRequest, PaginationResponse, User } from "./shared_pb"; import { file_api_v1_shared } from "./shared_pb"; -import type { Workout } from "./workouts_pb"; import { file_api_v1_workouts } from "./workouts_pb"; import { file_google_protobuf_timestamp } from "@bufbuild/protobuf/wkt"; import { file_buf_validate_validate } from "../../buf/validate/validate_pb"; @@ -17,7 +16,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file api/v1/users.proto. */ export const file_api_v1_users: GenFile = /*@__PURE__*/ - fileDesc("ChJhcGkvdjEvdXNlcnMucHJvdG8SBmFwaS52MSImCg5HZXRVc2VyUmVxdWVzdBIUCgJpZBgBIAEoCUIIukgFcgOwAQEiLQoPR2V0VXNlclJlc3BvbnNlEhoKBHVzZXIYASABKAsyDC5hcGkudjEuVXNlciIsCg1Gb2xsb3dSZXF1ZXN0EhsKCWZvbGxvd19pZBgBIAEoCUIIukgFcgOwAQEiEAoORm9sbG93UmVzcG9uc2UiMAoPVW5mb2xsb3dSZXF1ZXN0Eh0KC3VuZm9sbG93X2lkGAEgASgJQgi6SAVyA7ABASISChBVbmZvbGxvd1Jlc3BvbnNlIjUKFExpc3RGb2xsb3dlcnNSZXF1ZXN0Eh0KC2ZvbGxvd2VyX2lkGAEgASgJQgi6SAVyA7ABASI4ChVMaXN0Rm9sbG93ZXJzUmVzcG9uc2USHwoJZm9sbG93ZXJzGAEgAygLMgwuYXBpLnYxLlVzZXIiNQoUTGlzdEZvbGxvd2Vlc1JlcXVlc3QSHQoLZm9sbG93ZWVfaWQYASABKAlCCLpIBXIDsAEBIjgKFUxpc3RGb2xsb3dlZXNSZXNwb25zZRIfCglmb2xsb3dlZXMYASADKAsyDC5hcGkudjEuVXNlciJeCg1TZWFyY2hSZXF1ZXN0EhYKBXF1ZXJ5GAEgASgJQge6SARyAhADEjUKCnBhZ2luYXRpb24YAiABKAsyGS5hcGkudjEuUGFnaW5hdGlvblJlcXVlc3RCBrpIA8gBASJdCg5TZWFyY2hSZXNwb25zZRIbCgV1c2VycxgBIAMoCzIMLmFwaS52MS5Vc2VyEi4KCnBhZ2luYXRpb24YAiABKAsyGi5hcGkudjEuUGFnaW5hdGlvblJlc3BvbnNlInwKGExpc3ROb3RpZmljYXRpb25zUmVxdWVzdBITCgt1bnJlYWRfb25seRgBIAEoCBIUCgxtYXJrX2FzX3JlYWQYAiABKAgSNQoKcGFnaW5hdGlvbhgDIAEoCzIZLmFwaS52MS5QYWdpbmF0aW9uUmVxdWVzdEIGukgDyAEBIngKGUxpc3ROb3RpZmljYXRpb25zUmVzcG9uc2USKwoNbm90aWZpY2F0aW9ucxgBIAMoCzIULmFwaS52MS5Ob3RpZmljYXRpb24SLgoKcGFnaW5hdGlvbhgCIAEoCzIaLmFwaS52MS5QYWdpbmF0aW9uUmVzcG9uc2UitgIKDE5vdGlmaWNhdGlvbhIKCgJpZBgBIAEoCRIYChBub3RpZmllZF9hdF91bml4GAIgASgDEjoKDXVzZXJfZm9sbG93ZWQYAyABKAsyIS5hcGkudjEuTm90aWZpY2F0aW9uLlVzZXJGb2xsb3dlZEgAEj4KD3dvcmtvdXRfY29tbWVudBgEIAEoCzIjLmFwaS52MS5Ob3RpZmljYXRpb24uV29ya291dENvbW1lbnRIABorCgxVc2VyRm9sbG93ZWQSGwoFYWN0b3IYASABKAsyDC5hcGkudjEuVXNlchpPCg5Xb3Jrb3V0Q29tbWVudBIbCgVhY3RvchgBIAEoCzIMLmFwaS52MS5Vc2VyEiAKB3dvcmtvdXQYAiABKAsyDy5hcGkudjEuV29ya291dEIGCgR0eXBlMpYECgtVc2VyU2VydmljZRI8CgNHZXQSFi5hcGkudjEuR2V0VXNlclJlcXVlc3QaFy5hcGkudjEuR2V0VXNlclJlc3BvbnNlIgSItRgBEj0KBkZvbGxvdxIVLmFwaS52MS5Gb2xsb3dSZXF1ZXN0GhYuYXBpLnYxLkZvbGxvd1Jlc3BvbnNlIgSItRgBEkMKCFVuZm9sbG93EhcuYXBpLnYxLlVuZm9sbG93UmVxdWVzdBoYLmFwaS52MS5VbmZvbGxvd1Jlc3BvbnNlIgSItRgBElIKDUxpc3RGb2xsb3dlcnMSHC5hcGkudjEuTGlzdEZvbGxvd2Vyc1JlcXVlc3QaHS5hcGkudjEuTGlzdEZvbGxvd2Vyc1Jlc3BvbnNlIgSItRgBElIKDUxpc3RGb2xsb3dlZXMSHC5hcGkudjEuTGlzdEZvbGxvd2Vlc1JlcXVlc3QaHS5hcGkudjEuTGlzdEZvbGxvd2Vlc1Jlc3BvbnNlIgSItRgBEj0KBlNlYXJjaBIVLmFwaS52MS5TZWFyY2hSZXF1ZXN0GhYuYXBpLnYxLlNlYXJjaFJlc3BvbnNlIgSItRgBEl4KEUxpc3ROb3RpZmljYXRpb25zEiAuYXBpLnYxLkxpc3ROb3RpZmljYXRpb25zUmVxdWVzdBohLmFwaS52MS5MaXN0Tm90aWZpY2F0aW9uc1Jlc3BvbnNlIgSItRgBQosBCgpjb20uYXBpLnYxQgpVc2Vyc1Byb3RvUAFaOGdpdGh1Yi5jb20vY3Jsc3NuL2dldHN0cm9uZ2VyL3NlcnZlci9wa2cvcGIvYXBpL3YxO2FwaXYxogIDQVhYqgIGQXBpLlYxygIGQXBpXFYx4gISQXBpXFYxXEdQQk1ldGFkYXRh6gIHQXBpOjpWMWIGcHJvdG8z", [file_api_v1_options, file_api_v1_shared, file_api_v1_workouts, file_google_protobuf_timestamp, file_buf_validate_validate]); + fileDesc("ChJhcGkvdjEvdXNlcnMucHJvdG8SBmFwaS52MSImCg5HZXRVc2VyUmVxdWVzdBIUCgJpZBgBIAEoCUIIukgFcgOwAQEiLQoPR2V0VXNlclJlc3BvbnNlEhoKBHVzZXIYASABKAsyDC5hcGkudjEuVXNlciIsCg1Gb2xsb3dSZXF1ZXN0EhsKCWZvbGxvd19pZBgBIAEoCUIIukgFcgOwAQEiEAoORm9sbG93UmVzcG9uc2UiMAoPVW5mb2xsb3dSZXF1ZXN0Eh0KC3VuZm9sbG93X2lkGAEgASgJQgi6SAVyA7ABASISChBVbmZvbGxvd1Jlc3BvbnNlIjUKFExpc3RGb2xsb3dlcnNSZXF1ZXN0Eh0KC2ZvbGxvd2VyX2lkGAEgASgJQgi6SAVyA7ABASI4ChVMaXN0Rm9sbG93ZXJzUmVzcG9uc2USHwoJZm9sbG93ZXJzGAEgAygLMgwuYXBpLnYxLlVzZXIiNQoUTGlzdEZvbGxvd2Vlc1JlcXVlc3QSHQoLZm9sbG93ZWVfaWQYASABKAlCCLpIBXIDsAEBIjgKFUxpc3RGb2xsb3dlZXNSZXNwb25zZRIfCglmb2xsb3dlZXMYASADKAsyDC5hcGkudjEuVXNlciJeCg1TZWFyY2hSZXF1ZXN0EhYKBXF1ZXJ5GAEgASgJQge6SARyAhADEjUKCnBhZ2luYXRpb24YAiABKAsyGS5hcGkudjEuUGFnaW5hdGlvblJlcXVlc3RCBrpIA8gBASJdCg5TZWFyY2hSZXNwb25zZRIbCgV1c2VycxgBIAMoCzIMLmFwaS52MS5Vc2VyEi4KCnBhZ2luYXRpb24YAiABKAsyGi5hcGkudjEuUGFnaW5hdGlvblJlc3BvbnNlMrYDCgtVc2VyU2VydmljZRI8CgNHZXQSFi5hcGkudjEuR2V0VXNlclJlcXVlc3QaFy5hcGkudjEuR2V0VXNlclJlc3BvbnNlIgSItRgBEj0KBkZvbGxvdxIVLmFwaS52MS5Gb2xsb3dSZXF1ZXN0GhYuYXBpLnYxLkZvbGxvd1Jlc3BvbnNlIgSItRgBEkMKCFVuZm9sbG93EhcuYXBpLnYxLlVuZm9sbG93UmVxdWVzdBoYLmFwaS52MS5VbmZvbGxvd1Jlc3BvbnNlIgSItRgBElIKDUxpc3RGb2xsb3dlcnMSHC5hcGkudjEuTGlzdEZvbGxvd2Vyc1JlcXVlc3QaHS5hcGkudjEuTGlzdEZvbGxvd2Vyc1Jlc3BvbnNlIgSItRgBElIKDUxpc3RGb2xsb3dlZXMSHC5hcGkudjEuTGlzdEZvbGxvd2Vlc1JlcXVlc3QaHS5hcGkudjEuTGlzdEZvbGxvd2Vlc1Jlc3BvbnNlIgSItRgBEj0KBlNlYXJjaBIVLmFwaS52MS5TZWFyY2hSZXF1ZXN0GhYuYXBpLnYxLlNlYXJjaFJlc3BvbnNlIgSItRgBQosBCgpjb20uYXBpLnYxQgpVc2Vyc1Byb3RvUAFaOGdpdGh1Yi5jb20vY3Jsc3NuL2dldHN0cm9uZ2VyL3NlcnZlci9wa2cvcGIvYXBpL3YxO2FwaXYxogIDQVhYqgIGQXBpLlYxygIGQXBpXFYx4gISQXBpXFYxXEdQQk1ldGFkYXRh6gIHQXBpOjpWMWIGcHJvdG8z", [file_api_v1_options, file_api_v1_shared, file_api_v1_workouts, file_google_protobuf_timestamp, file_buf_validate_validate]); /** * @generated from message api.v1.GetUserRequest @@ -225,135 +224,6 @@ export type SearchResponse = Message<"api.v1.SearchResponse"> & { export const SearchResponseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_api_v1_users, 11); -/** - * @generated from message api.v1.ListNotificationsRequest - */ -export type ListNotificationsRequest = Message<"api.v1.ListNotificationsRequest"> & { - /** - * @generated from field: bool unread_only = 1; - */ - unreadOnly: boolean; - - /** - * @generated from field: bool mark_as_read = 2; - */ - markAsRead: boolean; - - /** - * @generated from field: api.v1.PaginationRequest pagination = 3; - */ - pagination?: PaginationRequest; -}; - -/** - * Describes the message api.v1.ListNotificationsRequest. - * Use `create(ListNotificationsRequestSchema)` to create a new message. - */ -export const ListNotificationsRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_api_v1_users, 12); - -/** - * @generated from message api.v1.ListNotificationsResponse - */ -export type ListNotificationsResponse = Message<"api.v1.ListNotificationsResponse"> & { - /** - * @generated from field: repeated api.v1.Notification notifications = 1; - */ - notifications: Notification[]; - - /** - * @generated from field: api.v1.PaginationResponse pagination = 2; - */ - pagination?: PaginationResponse; -}; - -/** - * Describes the message api.v1.ListNotificationsResponse. - * Use `create(ListNotificationsResponseSchema)` to create a new message. - */ -export const ListNotificationsResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_api_v1_users, 13); - -/** - * @generated from message api.v1.Notification - */ -export type Notification = Message<"api.v1.Notification"> & { - /** - * @generated from field: string id = 1; - */ - id: string; - - /** - * DEBT: This should be a timestamp but the client is not able to parse it. - * - * @generated from field: int64 notified_at_unix = 2; - */ - notifiedAtUnix: bigint; - - /** - * @generated from oneof api.v1.Notification.type - */ - type: { - /** - * @generated from field: api.v1.Notification.UserFollowed user_followed = 3; - */ - value: Notification_UserFollowed; - case: "userFollowed"; - } | { - /** - * @generated from field: api.v1.Notification.WorkoutComment workout_comment = 4; - */ - value: Notification_WorkoutComment; - case: "workoutComment"; - } | { case: undefined; value?: undefined }; -}; - -/** - * Describes the message api.v1.Notification. - * Use `create(NotificationSchema)` to create a new message. - */ -export const NotificationSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_api_v1_users, 14); - -/** - * @generated from message api.v1.Notification.UserFollowed - */ -export type Notification_UserFollowed = Message<"api.v1.Notification.UserFollowed"> & { - /** - * @generated from field: api.v1.User actor = 1; - */ - actor?: User; -}; - -/** - * Describes the message api.v1.Notification.UserFollowed. - * Use `create(Notification_UserFollowedSchema)` to create a new message. - */ -export const Notification_UserFollowedSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_api_v1_users, 14, 0); - -/** - * @generated from message api.v1.Notification.WorkoutComment - */ -export type Notification_WorkoutComment = Message<"api.v1.Notification.WorkoutComment"> & { - /** - * @generated from field: api.v1.User actor = 1; - */ - actor?: User; - - /** - * @generated from field: api.v1.Workout workout = 2; - */ - workout?: Workout; -}; - -/** - * Describes the message api.v1.Notification.WorkoutComment. - * Use `create(Notification_WorkoutCommentSchema)` to create a new message. - */ -export const Notification_WorkoutCommentSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_api_v1_users, 14, 1); - /** * @generated from service api.v1.UserService */ @@ -406,14 +276,6 @@ export const UserService: GenService<{ input: typeof SearchRequestSchema; output: typeof SearchResponseSchema; }, - /** - * @generated from rpc api.v1.UserService.ListNotifications - */ - listNotifications: { - methodKind: "unary"; - input: typeof ListNotificationsRequestSchema; - output: typeof ListNotificationsResponseSchema; - }, }> = /*@__PURE__*/ serviceDesc(file_api_v1_users, 0); diff --git a/web/src/stores/notifications.ts b/web/src/stores/notifications.ts index 4ee7b6be..6dff47bc 100644 --- a/web/src/stores/notifications.ts +++ b/web/src/stores/notifications.ts @@ -1,34 +1,19 @@ -import type { PaginationRequest } from '@/proto/api/v1/shared_pb.ts' - import { ref } from 'vue' import { defineStore } from 'pinia' import { create } from '@bufbuild/protobuf' -import { UserClient } from '@/clients/clients.ts' -import { ListNotificationsRequestSchema } from '@/proto/api/v1/users_pb.ts' - -export const useNotificationStore = defineStore( - 'notifications', - () => { - const unreadCount = ref(0) - const refreshInterval = ref(0) +import { NotificationClient } from '@/clients/clients.ts' +import { UnreadNotificationsRequestSchema } from '@/proto/api/v1/notifications_pb.ts' - const fetchUnreadNotifications = async () => { - const req = create(ListNotificationsRequestSchema, { - markAsRead: false, - pagination: { - pageLimit: 1, - } as PaginationRequest, - unreadOnly: true, - }) - const res = await UserClient.listNotifications(req) - unreadCount.value = Number(res.pagination?.totalResults) - } +export const useNotificationStore = defineStore('notifications', () => { + const unreadCount = ref(0) - const setRefreshInterval = () => { - // TODO: Implement Server-Sent Events for real-time updates. - refreshInterval.value = window.setInterval(fetchUnreadNotifications, 60000) + const streamUnreadNotifications = async () => { + const req = create(UnreadNotificationsRequestSchema, {}) + const stream = NotificationClient.unreadNotifications(req) + for await (const message of stream) { + unreadCount.value = Number(message.count) } - - return { fetchUnreadNotifications, setRefreshInterval, unreadCount } } -) + + return { streamUnreadNotifications, unreadCount } +}) diff --git a/web/src/ui/components/NavigationMobile.vue b/web/src/ui/components/NavigationMobile.vue index af6b83cb..98c3f615 100644 --- a/web/src/ui/components/NavigationMobile.vue +++ b/web/src/ui/components/NavigationMobile.vue @@ -64,6 +64,7 @@ nav { @apply fixed w-full bottom-0 z-50 h-16 px-8 bg-white border-t-2 border-gray-200; @apply lg:hidden flex justify-between items-center; } + .badge { @apply absolute left-3 bottom-2 bg-red-600 rounded-full flex justify-center items-center text-xs font-medium text-white scale-75 w-6 h-6; } diff --git a/web/src/ui/notifications/ListNotifications.vue b/web/src/ui/notifications/ListNotifications.vue index 75b6dee1..58f1c117 100644 --- a/web/src/ui/notifications/ListNotifications.vue +++ b/web/src/ui/notifications/ListNotifications.vue @@ -3,15 +3,13 @@ import type { PaginationRequest } from '@/proto/api/v1/shared_pb.ts' import { onMounted, ref } from 'vue' import { create } from '@bufbuild/protobuf' -import { UserClient } from '@/clients/clients.ts' -import { useNotificationStore } from '@/stores/notifications.ts' +import { NotificationClient } from '@/clients/clients.ts' import NotificationUserFollow from '@/ui/components/NotificationUserFollow.vue' import NotificationWorkoutComment from '@/ui/components/NotificationWorkoutComment.vue' -import { ListNotificationsRequestSchema, type Notification } from '@/proto/api/v1/users_pb.ts' +import { ListNotificationsRequestSchema, type Notification } from '@/proto/api/v1/notifications_pb.ts' const notifications = ref([] as Notification[]) const pageToken = ref(new Uint8Array(0)) -const notificationStore = useNotificationStore() const fetchUnreadNotifications = async () => { const req = create(ListNotificationsRequestSchema, { @@ -23,7 +21,7 @@ const fetchUnreadNotifications = async () => { unreadOnly: false, }) - const res = await UserClient.listNotifications(req) + const res = await NotificationClient.listNotifications(req) notifications.value = [...notifications.value, ...res.notifications] pageToken.value = res.pagination?.nextPageToken || new Uint8Array(0) if (pageToken.value.length > 0) { @@ -34,7 +32,6 @@ const fetchUnreadNotifications = async () => { onMounted(async () => { await fetchUnreadNotifications() - await notificationStore.fetchUnreadNotifications() })