mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
swapserverrpc: refactor reservation notifications
This commit refactors the proto definitions of the reservation notifications to use a more generic mechanism that can be used for other types of notifications as well.
This commit is contained in:
parent
96d321ece7
commit
cc1d942c7a
6 changed files with 847 additions and 600 deletions
|
|
@ -350,26 +350,26 @@ var file_reservation_proto_rawDesc = []byte{
|
||||||
0x6f, 0x6c, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x10, 0x52, 0x45, 0x53,
|
0x6f, 0x6c, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x10, 0x52, 0x45, 0x53,
|
||||||
0x45, 0x52, 0x56, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12,
|
0x45, 0x52, 0x56, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12,
|
||||||
0x1d, 0x0a, 0x19, 0x52, 0x45, 0x53, 0x45, 0x52, 0x56, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53,
|
0x1d, 0x0a, 0x19, 0x52, 0x45, 0x53, 0x45, 0x52, 0x56, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53,
|
||||||
0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x4e, 0x4f, 0x54, 0x49, 0x46, 0x59, 0x10, 0x01, 0x32, 0xea,
|
0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x4e, 0x4f, 0x54, 0x49, 0x46, 0x59, 0x10, 0x01, 0x32, 0xef,
|
||||||
0x01, 0x0a, 0x12, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65,
|
0x01, 0x0a, 0x12, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65,
|
||||||
0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x72, 0x0a, 0x1d, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61,
|
0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x77, 0x0a, 0x1d, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61,
|
||||||
0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
||||||
0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x27, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63,
|
0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x27, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63,
|
||||||
0x2e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x6f, 0x74, 0x69,
|
0x2e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x6f, 0x74, 0x69,
|
||||||
0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
||||||
0x26, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
|
0x26, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
|
||||||
0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x6f, 0x74, 0x69, 0x66,
|
0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x6f, 0x74, 0x69, 0x66,
|
||||||
0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x30, 0x01, 0x12, 0x60, 0x0a, 0x0f, 0x4f, 0x70, 0x65,
|
0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x03, 0x88, 0x02, 0x01, 0x30, 0x01, 0x12, 0x60,
|
||||||
0x6e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x2e, 0x6c,
|
0x0a, 0x0f, 0x4f, 0x70, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f,
|
||||||
0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4f, 0x70, 0x65,
|
0x6e, 0x12, 0x25, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76,
|
||||||
0x6e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75,
|
0x65, 0x72, 0x4f, 0x70, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f,
|
||||||
0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65,
|
0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72,
|
||||||
0x72, 0x76, 0x65, 0x72, 0x4f, 0x70, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74,
|
0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4f, 0x70, 0x65, 0x6e, 0x52, 0x65, 0x73,
|
||||||
0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x2d, 0x5a, 0x2b, 0x67,
|
0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||||
0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e,
|
0x42, 0x2d, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c,
|
||||||
0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x6c, 0x6f, 0x6f, 0x70, 0x2f, 0x73, 0x77, 0x61,
|
0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x6c, 0x6f, 0x6f,
|
||||||
0x70, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
|
0x70, 0x2f, 0x73, 0x77, 0x61, 0x70, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x62,
|
||||||
0x6f, 0x33,
|
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,9 @@ service ReservationService {
|
||||||
// ReservationNotificationStream is a server side stream that sends
|
// ReservationNotificationStream is a server side stream that sends
|
||||||
// notifications if the server wants to open a reservation to the client.
|
// notifications if the server wants to open a reservation to the client.
|
||||||
rpc ReservationNotificationStream (ReservationNotificationRequest)
|
rpc ReservationNotificationStream (ReservationNotificationRequest)
|
||||||
returns (stream ServerReservationNotification);
|
returns (stream ServerReservationNotification) {
|
||||||
|
option deprecated = true;
|
||||||
|
}
|
||||||
|
|
||||||
// OpenReservation requests a new reservation UTXO from the server.
|
// OpenReservation requests a new reservation UTXO from the server.
|
||||||
rpc OpenReservation (ServerOpenReservationRequest)
|
rpc OpenReservation (ServerOpenReservationRequest)
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ const _ = grpc.SupportPackageIsVersion7
|
||||||
//
|
//
|
||||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||||
type ReservationServiceClient interface {
|
type ReservationServiceClient interface {
|
||||||
|
// Deprecated: Do not use.
|
||||||
// ReservationNotificationStream is a server side stream that sends
|
// ReservationNotificationStream is a server side stream that sends
|
||||||
// notifications if the server wants to open a reservation to the client.
|
// notifications if the server wants to open a reservation to the client.
|
||||||
ReservationNotificationStream(ctx context.Context, in *ReservationNotificationRequest, opts ...grpc.CallOption) (ReservationService_ReservationNotificationStreamClient, error)
|
ReservationNotificationStream(ctx context.Context, in *ReservationNotificationRequest, opts ...grpc.CallOption) (ReservationService_ReservationNotificationStreamClient, error)
|
||||||
|
|
@ -33,6 +34,7 @@ func NewReservationServiceClient(cc grpc.ClientConnInterface) ReservationService
|
||||||
return &reservationServiceClient{cc}
|
return &reservationServiceClient{cc}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: Do not use.
|
||||||
func (c *reservationServiceClient) ReservationNotificationStream(ctx context.Context, in *ReservationNotificationRequest, opts ...grpc.CallOption) (ReservationService_ReservationNotificationStreamClient, error) {
|
func (c *reservationServiceClient) ReservationNotificationStream(ctx context.Context, in *ReservationNotificationRequest, opts ...grpc.CallOption) (ReservationService_ReservationNotificationStreamClient, error) {
|
||||||
stream, err := c.cc.NewStream(ctx, &ReservationService_ServiceDesc.Streams[0], "/looprpc.ReservationService/ReservationNotificationStream", opts...)
|
stream, err := c.cc.NewStream(ctx, &ReservationService_ServiceDesc.Streams[0], "/looprpc.ReservationService/ReservationNotificationStream", opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -78,6 +80,7 @@ func (c *reservationServiceClient) OpenReservation(ctx context.Context, in *Serv
|
||||||
// All implementations must embed UnimplementedReservationServiceServer
|
// All implementations must embed UnimplementedReservationServiceServer
|
||||||
// for forward compatibility
|
// for forward compatibility
|
||||||
type ReservationServiceServer interface {
|
type ReservationServiceServer interface {
|
||||||
|
// Deprecated: Do not use.
|
||||||
// ReservationNotificationStream is a server side stream that sends
|
// ReservationNotificationStream is a server side stream that sends
|
||||||
// notifications if the server wants to open a reservation to the client.
|
// notifications if the server wants to open a reservation to the client.
|
||||||
ReservationNotificationStream(*ReservationNotificationRequest, ReservationService_ReservationNotificationStreamServer) error
|
ReservationNotificationStream(*ReservationNotificationRequest, ReservationService_ReservationNotificationStreamServer) error
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -6,6 +6,7 @@ syntax = "proto3";
|
||||||
// different golang and RPC package names to fix protobuf namespace conflicts.
|
// different golang and RPC package names to fix protobuf namespace conflicts.
|
||||||
package looprpc;
|
package looprpc;
|
||||||
import "common.proto";
|
import "common.proto";
|
||||||
|
import "reservation.proto";
|
||||||
|
|
||||||
option go_package = "github.com/lightninglabs/loop/swapserverrpc";
|
option go_package = "github.com/lightninglabs/loop/swapserverrpc";
|
||||||
|
|
||||||
|
|
@ -50,6 +51,9 @@ service SwapServer {
|
||||||
// FetchL402 is a simple non-l402-allowlisted request that is required
|
// FetchL402 is a simple non-l402-allowlisted request that is required
|
||||||
// in order to force the creation of an l402.
|
// in order to force the creation of an l402.
|
||||||
rpc FetchL402 (FetchL402Request) returns (FetchL402Response);
|
rpc FetchL402 (FetchL402Request) returns (FetchL402Response);
|
||||||
|
|
||||||
|
rpc SubscribeNotifications (SubscribeNotificationsRequest)
|
||||||
|
returns (stream SubscribeNotificationsResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -655,6 +659,18 @@ message FetchL402Request {
|
||||||
message FetchL402Response {
|
message FetchL402Response {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SubscribeNotificationsRequest is a request to subscribe to notifications.
|
||||||
|
message SubscribeNotificationsRequest {
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubscribeNotificationsResponse is a response to a
|
||||||
|
// SubscribeNotificationsRequest.
|
||||||
|
message SubscribeNotificationsResponse {
|
||||||
|
oneof notification {
|
||||||
|
ServerReservationNotification reservation_notification = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// StaticAddressProtocolVersion represents the static address protocol version
|
// StaticAddressProtocolVersion represents the static address protocol version
|
||||||
// the client adheres to.
|
// the client adheres to.
|
||||||
enum StaticAddressProtocolVersion {
|
enum StaticAddressProtocolVersion {
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ type SwapServerClient interface {
|
||||||
// FetchL402 is a simple non-l402-allowlisted request that is required
|
// FetchL402 is a simple non-l402-allowlisted request that is required
|
||||||
// in order to force the creation of an l402.
|
// in order to force the creation of an l402.
|
||||||
FetchL402(ctx context.Context, in *FetchL402Request, opts ...grpc.CallOption) (*FetchL402Response, error)
|
FetchL402(ctx context.Context, in *FetchL402Request, opts ...grpc.CallOption) (*FetchL402Response, error)
|
||||||
|
SubscribeNotifications(ctx context.Context, in *SubscribeNotificationsRequest, opts ...grpc.CallOption) (SwapServer_SubscribeNotificationsClient, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type swapServerClient struct {
|
type swapServerClient struct {
|
||||||
|
|
@ -236,6 +237,38 @@ func (c *swapServerClient) FetchL402(ctx context.Context, in *FetchL402Request,
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *swapServerClient) SubscribeNotifications(ctx context.Context, in *SubscribeNotificationsRequest, opts ...grpc.CallOption) (SwapServer_SubscribeNotificationsClient, error) {
|
||||||
|
stream, err := c.cc.NewStream(ctx, &SwapServer_ServiceDesc.Streams[2], "/looprpc.SwapServer/SubscribeNotifications", opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
x := &swapServerSubscribeNotificationsClient{stream}
|
||||||
|
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := x.ClientStream.CloseSend(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return x, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type SwapServer_SubscribeNotificationsClient interface {
|
||||||
|
Recv() (*SubscribeNotificationsResponse, error)
|
||||||
|
grpc.ClientStream
|
||||||
|
}
|
||||||
|
|
||||||
|
type swapServerSubscribeNotificationsClient struct {
|
||||||
|
grpc.ClientStream
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *swapServerSubscribeNotificationsClient) Recv() (*SubscribeNotificationsResponse, error) {
|
||||||
|
m := new(SubscribeNotificationsResponse)
|
||||||
|
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
// SwapServerServer is the server API for SwapServer service.
|
// SwapServerServer is the server API for SwapServer service.
|
||||||
// All implementations must embed UnimplementedSwapServerServer
|
// All implementations must embed UnimplementedSwapServerServer
|
||||||
// for forward compatibility
|
// for forward compatibility
|
||||||
|
|
@ -258,6 +291,7 @@ type SwapServerServer interface {
|
||||||
// FetchL402 is a simple non-l402-allowlisted request that is required
|
// FetchL402 is a simple non-l402-allowlisted request that is required
|
||||||
// in order to force the creation of an l402.
|
// in order to force the creation of an l402.
|
||||||
FetchL402(context.Context, *FetchL402Request) (*FetchL402Response, error)
|
FetchL402(context.Context, *FetchL402Request) (*FetchL402Response, error)
|
||||||
|
SubscribeNotifications(*SubscribeNotificationsRequest, SwapServer_SubscribeNotificationsServer) error
|
||||||
mustEmbedUnimplementedSwapServerServer()
|
mustEmbedUnimplementedSwapServerServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -313,6 +347,9 @@ func (UnimplementedSwapServerServer) PushKey(context.Context, *ServerPushKeyReq)
|
||||||
func (UnimplementedSwapServerServer) FetchL402(context.Context, *FetchL402Request) (*FetchL402Response, error) {
|
func (UnimplementedSwapServerServer) FetchL402(context.Context, *FetchL402Request) (*FetchL402Response, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method FetchL402 not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method FetchL402 not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedSwapServerServer) SubscribeNotifications(*SubscribeNotificationsRequest, SwapServer_SubscribeNotificationsServer) error {
|
||||||
|
return status.Errorf(codes.Unimplemented, "method SubscribeNotifications not implemented")
|
||||||
|
}
|
||||||
func (UnimplementedSwapServerServer) mustEmbedUnimplementedSwapServerServer() {}
|
func (UnimplementedSwapServerServer) mustEmbedUnimplementedSwapServerServer() {}
|
||||||
|
|
||||||
// UnsafeSwapServerServer may be embedded to opt out of forward compatibility for this service.
|
// UnsafeSwapServerServer may be embedded to opt out of forward compatibility for this service.
|
||||||
|
|
@ -620,6 +657,27 @@ func _SwapServer_FetchL402_Handler(srv interface{}, ctx context.Context, dec fun
|
||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _SwapServer_SubscribeNotifications_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||||
|
m := new(SubscribeNotificationsRequest)
|
||||||
|
if err := stream.RecvMsg(m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return srv.(SwapServerServer).SubscribeNotifications(m, &swapServerSubscribeNotificationsServer{stream})
|
||||||
|
}
|
||||||
|
|
||||||
|
type SwapServer_SubscribeNotificationsServer interface {
|
||||||
|
Send(*SubscribeNotificationsResponse) error
|
||||||
|
grpc.ServerStream
|
||||||
|
}
|
||||||
|
|
||||||
|
type swapServerSubscribeNotificationsServer struct {
|
||||||
|
grpc.ServerStream
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *swapServerSubscribeNotificationsServer) Send(m *SubscribeNotificationsResponse) error {
|
||||||
|
return x.ServerStream.SendMsg(m)
|
||||||
|
}
|
||||||
|
|
||||||
// SwapServer_ServiceDesc is the grpc.ServiceDesc for SwapServer service.
|
// SwapServer_ServiceDesc is the grpc.ServiceDesc for SwapServer service.
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
// and not to be introspected or modified (even as a copy)
|
// and not to be introspected or modified (even as a copy)
|
||||||
|
|
@ -695,6 +753,11 @@ var SwapServer_ServiceDesc = grpc.ServiceDesc{
|
||||||
Handler: _SwapServer_SubscribeLoopInUpdates_Handler,
|
Handler: _SwapServer_SubscribeLoopInUpdates_Handler,
|
||||||
ServerStreams: true,
|
ServerStreams: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
StreamName: "SubscribeNotifications",
|
||||||
|
Handler: _SwapServer_SubscribeNotifications_Handler,
|
||||||
|
ServerStreams: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Metadata: "server.proto",
|
Metadata: "server.proto",
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue