From 5e2bac368d70043c58ce26346323a97c8afa2e3a Mon Sep 17 00:00:00 2001 From: benrobey Date: Sun, 17 Aug 2025 18:15:26 +1000 Subject: [PATCH 1/3] Adds `GetRace` --- api/proto/racing/racing.pb.go | 275 +++++++++-------------------- api/proto/racing/racing.pb.gw.go | 161 +++++++---------- api/proto/racing/racing_grpc.pb.go | 42 +++-- 3 files changed, 179 insertions(+), 299 deletions(-) diff --git a/api/proto/racing/racing.pb.go b/api/proto/racing/racing.pb.go index 0ee711e..e3e55f2 100644 --- a/api/proto/racing/racing.pb.go +++ b/api/proto/racing/racing.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.26.0 +// protoc-gen-go v1.36.7 // protoc v6.32.0 // source: racing/racing.proto @@ -13,6 +13,7 @@ import ( timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -124,20 +125,17 @@ func (RaceStatus) EnumDescriptor() ([]byte, []int) { // Request for ListRaces call. type ListRacesRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Filter *ListRacesRequestFilter `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` unknownFields protoimpl.UnknownFields - - Filter *ListRacesRequestFilter `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` + sizeCache protoimpl.SizeCache } func (x *ListRacesRequest) Reset() { *x = ListRacesRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_racing_racing_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_racing_racing_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ListRacesRequest) String() string { @@ -148,7 +146,7 @@ func (*ListRacesRequest) ProtoMessage() {} func (x *ListRacesRequest) ProtoReflect() protoreflect.Message { mi := &file_racing_racing_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -172,20 +170,17 @@ func (x *ListRacesRequest) GetFilter() *ListRacesRequestFilter { // Response to ListRaces call. type ListRacesResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Races []*Race `protobuf:"bytes,1,rep,name=races,proto3" json:"races,omitempty"` unknownFields protoimpl.UnknownFields - - Races []*Race `protobuf:"bytes,1,rep,name=races,proto3" json:"races,omitempty"` + sizeCache protoimpl.SizeCache } func (x *ListRacesResponse) Reset() { *x = ListRacesResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_racing_racing_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_racing_racing_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ListRacesResponse) String() string { @@ -196,7 +191,7 @@ func (*ListRacesResponse) ProtoMessage() {} func (x *ListRacesResponse) ProtoReflect() protoreflect.Message { mi := &file_racing_racing_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -219,22 +214,19 @@ func (x *ListRacesResponse) GetRaces() []*Race { } type GetRaceRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // The resource name of the race to retrieve. // Format: races/{race} - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // required + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // required + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *GetRaceRequest) Reset() { *x = GetRaceRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_racing_racing_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_racing_racing_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *GetRaceRequest) String() string { @@ -245,7 +237,7 @@ func (*GetRaceRequest) ProtoMessage() {} func (x *GetRaceRequest) ProtoReflect() protoreflect.Message { mi := &file_racing_racing_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -269,26 +261,23 @@ func (x *GetRaceRequest) GetName() string { // Filter for listing races. type ListRacesRequestFilter struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - MeetingIds []int64 `protobuf:"varint,1,rep,packed,name=meeting_ids,json=meetingIds,proto3" json:"meeting_ids,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + MeetingIds []int64 `protobuf:"varint,1,rep,packed,name=meeting_ids,json=meetingIds,proto3" json:"meeting_ids,omitempty"` // visible_only, when true, restricts results to only races marked visible. // When omitted or false, all races (visible or not) are returned. VisibleOnly *bool `protobuf:"varint,2,opt,name=visible_only,json=visibleOnly,proto3,oneof" json:"visible_only,omitempty"` // sort_direction chooses ascending or descending order by advertised_start_time. // If omitted or UNSPECIFIED the results are returned ascending (earliest first). SortDirection *SortDirection `protobuf:"varint,3,opt,name=sort_direction,json=sortDirection,proto3,enum=racing.SortDirection,oneof" json:"sort_direction,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ListRacesRequestFilter) Reset() { *x = ListRacesRequestFilter{} - if protoimpl.UnsafeEnabled { - mi := &file_racing_racing_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_racing_racing_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ListRacesRequestFilter) String() string { @@ -299,7 +288,7 @@ func (*ListRacesRequestFilter) ProtoMessage() {} func (x *ListRacesRequestFilter) ProtoReflect() protoreflect.Message { mi := &file_racing_racing_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -337,10 +326,7 @@ func (x *ListRacesRequestFilter) GetSortDirection() SortDirection { // A race resource. type Race struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // ID represents a unique identifier for the race. Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` // MeetingID represents a unique identifier for the races meeting. @@ -354,16 +340,16 @@ type Race struct { // AdvertisedStartTime is the time the race is advertised to run. AdvertisedStartTime *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=advertised_start_time,json=advertisedStartTime,proto3" json:"advertised_start_time,omitempty"` // Status is derived: OPEN if start time is in the future, otherwise CLOSED. - Status RaceStatus `protobuf:"varint,7,opt,name=status,proto3,enum=racing.RaceStatus" json:"status,omitempty"` + Status RaceStatus `protobuf:"varint,7,opt,name=status,proto3,enum=racing.RaceStatus" json:"status,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *Race) Reset() { *x = Race{} - if protoimpl.UnsafeEnabled { - mi := &file_racing_racing_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_racing_racing_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Race) String() string { @@ -374,7 +360,7 @@ func (*Race) ProtoMessage() {} func (x *Race) ProtoReflect() protoreflect.Message { mi := &file_racing_racing_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -440,93 +426,59 @@ func (x *Race) GetStatus() RaceStatus { var File_racing_racing_proto protoreflect.FileDescriptor -var file_racing_racing_proto_rawDesc = []byte{ - 0x0a, 0x13, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x2f, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x1a, 0x1f, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, - 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x4a, 0x0a, 0x10, - 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x36, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1e, 0x2e, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, - 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, - 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0x37, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, - 0x52, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x22, 0x0a, - 0x05, 0x72, 0x61, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x72, - 0x61, 0x63, 0x69, 0x6e, 0x67, 0x2e, 0x52, 0x61, 0x63, 0x65, 0x52, 0x05, 0x72, 0x61, 0x63, 0x65, - 0x73, 0x22, 0x24, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x52, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0xc8, 0x01, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, - 0x52, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x46, 0x69, 0x6c, 0x74, - 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x65, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0a, 0x6d, 0x65, 0x65, 0x74, 0x69, 0x6e, 0x67, - 0x49, 0x64, 0x73, 0x12, 0x26, 0x0a, 0x0c, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x6f, - 0x6e, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x0b, 0x76, 0x69, 0x73, - 0x69, 0x62, 0x6c, 0x65, 0x4f, 0x6e, 0x6c, 0x79, 0x88, 0x01, 0x01, 0x12, 0x41, 0x0a, 0x0e, 0x73, - 0x6f, 0x72, 0x74, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x2e, 0x53, 0x6f, 0x72, - 0x74, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x01, 0x52, 0x0d, 0x73, 0x6f, - 0x72, 0x74, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, 0x0f, - 0x0a, 0x0d, 0x5f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x42, - 0x11, 0x0a, 0x0f, 0x5f, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x22, 0xf7, 0x01, 0x0a, 0x04, 0x52, 0x61, 0x63, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, - 0x65, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x09, 0x6d, 0x65, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, - 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, - 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, - 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, - 0x12, 0x4e, 0x0a, 0x15, 0x61, 0x64, 0x76, 0x65, 0x72, 0x74, 0x69, 0x73, 0x65, 0x64, 0x5f, 0x73, - 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x13, 0x61, 0x64, 0x76, - 0x65, 0x72, 0x74, 0x69, 0x73, 0x65, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, - 0x12, 0x2a, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x12, 0x2e, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x2e, 0x52, 0x61, 0x63, 0x65, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2a, 0x60, 0x0a, 0x0d, - 0x53, 0x6f, 0x72, 0x74, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, - 0x1a, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, - 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x16, 0x0a, - 0x12, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, - 0x41, 0x53, 0x43, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x44, 0x49, - 0x52, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x53, 0x43, 0x10, 0x02, 0x2a, 0x57, - 0x0a, 0x0a, 0x52, 0x61, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1b, 0x0a, 0x17, - 0x52, 0x41, 0x43, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, - 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x52, 0x41, 0x43, - 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x4f, 0x50, 0x45, 0x4e, 0x10, 0x01, 0x12, - 0x16, 0x0a, 0x12, 0x52, 0x41, 0x43, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x43, - 0x4c, 0x4f, 0x53, 0x45, 0x44, 0x10, 0x02, 0x32, 0xb2, 0x01, 0x0a, 0x06, 0x52, 0x61, 0x63, 0x69, - 0x6e, 0x67, 0x12, 0x5b, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x65, 0x73, 0x12, - 0x18, 0x2e, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, - 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x61, 0x63, 0x69, - 0x6e, 0x67, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x22, 0x0e, 0x2f, 0x76, - 0x31, 0x2f, 0x6c, 0x69, 0x73, 0x74, 0x2d, 0x72, 0x61, 0x63, 0x65, 0x73, 0x3a, 0x01, 0x2a, 0x12, - 0x4b, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x52, 0x61, 0x63, 0x65, 0x12, 0x16, 0x2e, 0x72, 0x61, 0x63, - 0x69, 0x6e, 0x67, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x2e, 0x52, 0x61, 0x63, 0x65, - 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x12, 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x7b, 0x6e, - 0x61, 0x6d, 0x65, 0x3d, 0x72, 0x61, 0x63, 0x65, 0x73, 0x2f, 0x2a, 0x7d, 0x42, 0x09, 0x5a, 0x07, - 0x2f, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +const file_racing_racing_proto_rawDesc = "" + + "\n" + + "\x13racing/racing.proto\x12\x06racing\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1cgoogle/api/annotations.proto\"J\n" + + "\x10ListRacesRequest\x126\n" + + "\x06filter\x18\x01 \x01(\v2\x1e.racing.ListRacesRequestFilterR\x06filter\"7\n" + + "\x11ListRacesResponse\x12\"\n" + + "\x05races\x18\x01 \x03(\v2\f.racing.RaceR\x05races\"$\n" + + "\x0eGetRaceRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\"\xc8\x01\n" + + "\x16ListRacesRequestFilter\x12\x1f\n" + + "\vmeeting_ids\x18\x01 \x03(\x03R\n" + + "meetingIds\x12&\n" + + "\fvisible_only\x18\x02 \x01(\bH\x00R\vvisibleOnly\x88\x01\x01\x12A\n" + + "\x0esort_direction\x18\x03 \x01(\x0e2\x15.racing.SortDirectionH\x01R\rsortDirection\x88\x01\x01B\x0f\n" + + "\r_visible_onlyB\x11\n" + + "\x0f_sort_direction\"\xf7\x01\n" + + "\x04Race\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x03R\x02id\x12\x1d\n" + + "\n" + + "meeting_id\x18\x02 \x01(\x03R\tmeetingId\x12\x12\n" + + "\x04name\x18\x03 \x01(\tR\x04name\x12\x16\n" + + "\x06number\x18\x04 \x01(\x03R\x06number\x12\x18\n" + + "\avisible\x18\x05 \x01(\bR\avisible\x12N\n" + + "\x15advertised_start_time\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\x13advertisedStartTime\x12*\n" + + "\x06status\x18\a \x01(\x0e2\x12.racing.RaceStatusR\x06status*`\n" + + "\rSortDirection\x12\x1e\n" + + "\x1aSORT_DIRECTION_UNSPECIFIED\x10\x00\x12\x16\n" + + "\x12SORT_DIRECTION_ASC\x10\x01\x12\x17\n" + + "\x13SORT_DIRECTION_DESC\x10\x02*W\n" + + "\n" + + "RaceStatus\x12\x1b\n" + + "\x17RACE_STATUS_UNSPECIFIED\x10\x00\x12\x14\n" + + "\x10RACE_STATUS_OPEN\x10\x01\x12\x16\n" + + "\x12RACE_STATUS_CLOSED\x10\x022\xb2\x01\n" + + "\x06Racing\x12[\n" + + "\tListRaces\x12\x18.racing.ListRacesRequest\x1a\x19.racing.ListRacesResponse\"\x19\x82\xd3\xe4\x93\x02\x13:\x01*\"\x0e/v1/list-races\x12K\n" + + "\aGetRace\x12\x16.racing.GetRaceRequest\x1a\f.racing.Race\"\x1a\x82\xd3\xe4\x93\x02\x14\x12\x12/v1/{name=races/*}B\tZ\a/racingb\x06proto3" var ( file_racing_racing_proto_rawDescOnce sync.Once - file_racing_racing_proto_rawDescData = file_racing_racing_proto_rawDesc + file_racing_racing_proto_rawDescData []byte ) func file_racing_racing_proto_rawDescGZIP() []byte { file_racing_racing_proto_rawDescOnce.Do(func() { - file_racing_racing_proto_rawDescData = protoimpl.X.CompressGZIP(file_racing_racing_proto_rawDescData) + file_racing_racing_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_racing_racing_proto_rawDesc), len(file_racing_racing_proto_rawDesc))) }) return file_racing_racing_proto_rawDescData } var file_racing_racing_proto_enumTypes = make([]protoimpl.EnumInfo, 2) var file_racing_racing_proto_msgTypes = make([]protoimpl.MessageInfo, 5) -var file_racing_racing_proto_goTypes = []interface{}{ +var file_racing_racing_proto_goTypes = []any{ (SortDirection)(0), // 0: racing.SortDirection (RaceStatus)(0), // 1: racing.RaceStatus (*ListRacesRequest)(nil), // 2: racing.ListRacesRequest @@ -558,74 +510,12 @@ func file_racing_racing_proto_init() { if File_racing_racing_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_racing_racing_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListRacesRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_racing_racing_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListRacesResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_racing_racing_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetRaceRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_racing_racing_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListRacesRequestFilter); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_racing_racing_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Race); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - file_racing_racing_proto_msgTypes[3].OneofWrappers = []interface{}{} + file_racing_racing_proto_msgTypes[3].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_racing_racing_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_racing_racing_proto_rawDesc), len(file_racing_racing_proto_rawDesc)), NumEnums: 2, NumMessages: 5, NumExtensions: 0, @@ -637,7 +527,6 @@ func file_racing_racing_proto_init() { MessageInfos: file_racing_racing_proto_msgTypes, }.Build() File_racing_racing_proto = out.File - file_racing_racing_proto_rawDesc = nil file_racing_racing_proto_goTypes = nil file_racing_racing_proto_depIdxs = nil } diff --git a/api/proto/racing/racing.pb.gw.go b/api/proto/racing/racing.pb.gw.go index 115ef9c..631a160 100644 --- a/api/proto/racing/racing.pb.gw.go +++ b/api/proto/racing/racing.pb.gw.go @@ -10,6 +10,7 @@ package racing import ( "context" + "errors" "io" "net/http" @@ -24,149 +25,127 @@ import ( ) // Suppress "imported and not used" errors -var _ codes.Code -var _ io.Reader -var _ status.Status -var _ = runtime.String -var _ = utilities.NewDoubleArray -var _ = metadata.Join +var ( + _ codes.Code + _ io.Reader + _ status.Status + _ = errors.New + _ = runtime.String + _ = utilities.NewDoubleArray + _ = metadata.Join +) func request_Racing_ListRaces_0(ctx context.Context, marshaler runtime.Marshaler, client RacingClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq ListRacesRequest - var metadata runtime.ServerMetadata - - newReader, berr := utilities.IOReaderFactory(req.Body) - if berr != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) - } - if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + var ( + protoReq ListRacesRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ListRaces(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err - } func local_request_Racing_ListRaces_0(ctx context.Context, marshaler runtime.Marshaler, server RacingServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq ListRacesRequest - var metadata runtime.ServerMetadata - - newReader, berr := utilities.IOReaderFactory(req.Body) - if berr != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) - } - if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + var ( + protoReq ListRacesRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - msg, err := server.ListRaces(ctx, &protoReq) return msg, metadata, err - } func request_Racing_GetRace_0(ctx context.Context, marshaler runtime.Marshaler, client RacingClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GetRaceRequest - var metadata runtime.ServerMetadata - var ( - val string - ok bool - err error - _ = err + protoReq GetRaceRequest + metadata runtime.ServerMetadata + err error ) - - val, ok = pathParams["name"] + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + val, ok := pathParams["name"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "name") } - protoReq.Name, err = runtime.String(val) if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err) } - msg, err := client.GetRace(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err - } func local_request_Racing_GetRace_0(ctx context.Context, marshaler runtime.Marshaler, server RacingServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GetRaceRequest - var metadata runtime.ServerMetadata - var ( - val string - ok bool - err error - _ = err + protoReq GetRaceRequest + metadata runtime.ServerMetadata + err error ) - - val, ok = pathParams["name"] + val, ok := pathParams["name"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "name") } - protoReq.Name, err = runtime.String(val) if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err) } - msg, err := server.GetRace(ctx, &protoReq) return msg, metadata, err - } // RegisterRacingHandlerServer registers the http handlers for service Racing to "mux". // UnaryRPC :call RacingServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. // Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterRacingHandlerFromEndpoint instead. +// GRPC interceptors will not work for this type of registration. To use interceptors, you must use the "runtime.WithMiddlewares" option in the "runtime.NewServeMux" call. func RegisterRacingHandlerServer(ctx context.Context, mux *runtime.ServeMux, server RacingServer) error { - - mux.Handle("POST", pattern_Racing_ListRaces_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle(http.MethodPost, pattern_Racing_ListRaces_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/racing.Racing/ListRaces") + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/racing.Racing/ListRaces", runtime.WithHTTPPathPattern("/v1/list-races")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_Racing_ListRaces_0(rctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_Racing_ListRaces_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - - forward_Racing_ListRaces_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - + forward_Racing_ListRaces_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - - mux.Handle("GET", pattern_Racing_GetRace_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle(http.MethodGet, pattern_Racing_GetRace_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/racing.Racing/GetRace") + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/racing.Racing/GetRace", runtime.WithHTTPPathPattern("/v1/{name=races/*}")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_Racing_GetRace_0(rctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_Racing_GetRace_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - - forward_Racing_GetRace_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - + forward_Racing_GetRace_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) return nil @@ -175,25 +154,24 @@ func RegisterRacingHandlerServer(ctx context.Context, mux *runtime.ServeMux, ser // RegisterRacingHandlerFromEndpoint is same as RegisterRacingHandler but // automatically dials to "endpoint" and closes the connection when "ctx" gets done. func RegisterRacingHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { - conn, err := grpc.Dial(endpoint, opts...) + conn, err := grpc.NewClient(endpoint, opts...) if err != nil { return err } defer func() { if err != nil { if cerr := conn.Close(); cerr != nil { - grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + grpclog.Errorf("Failed to close conn to %s: %v", endpoint, cerr) } return } go func() { <-ctx.Done() if cerr := conn.Close(); cerr != nil { - grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + grpclog.Errorf("Failed to close conn to %s: %v", endpoint, cerr) } }() }() - return RegisterRacingHandler(ctx, mux, conn) } @@ -207,60 +185,51 @@ func RegisterRacingHandler(ctx context.Context, mux *runtime.ServeMux, conn *grp // to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "RacingClient". // Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "RacingClient" // doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in -// "RacingClient" to call the correct interceptors. +// "RacingClient" to call the correct interceptors. This client ignores the HTTP middlewares. func RegisterRacingHandlerClient(ctx context.Context, mux *runtime.ServeMux, client RacingClient) error { - - mux.Handle("POST", pattern_Racing_ListRaces_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle(http.MethodPost, pattern_Racing_ListRaces_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateContext(ctx, mux, req, "/racing.Racing/ListRaces") + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/racing.Racing/ListRaces", runtime.WithHTTPPathPattern("/v1/list-races")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_Racing_ListRaces_0(rctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_Racing_ListRaces_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - - forward_Racing_ListRaces_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - + forward_Racing_ListRaces_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - - mux.Handle("GET", pattern_Racing_GetRace_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle(http.MethodGet, pattern_Racing_GetRace_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateContext(ctx, mux, req, "/racing.Racing/GetRace") + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/racing.Racing/GetRace", runtime.WithHTTPPathPattern("/v1/{name=races/*}")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_Racing_GetRace_0(rctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_Racing_GetRace_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - - forward_Racing_GetRace_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - + forward_Racing_GetRace_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - return nil } var ( pattern_Racing_ListRaces_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "list-races"}, "")) - - pattern_Racing_GetRace_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 2, 5, 2}, []string{"v1", "races", "name"}, "")) + pattern_Racing_GetRace_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 2, 5, 2}, []string{"v1", "races", "name"}, "")) ) var ( forward_Racing_ListRaces_0 = runtime.ForwardResponseMessage - - forward_Racing_GetRace_0 = runtime.ForwardResponseMessage + forward_Racing_GetRace_0 = runtime.ForwardResponseMessage ) diff --git a/api/proto/racing/racing_grpc.pb.go b/api/proto/racing/racing_grpc.pb.go index 1b13fde..a9d6f86 100644 --- a/api/proto/racing/racing_grpc.pb.go +++ b/api/proto/racing/racing_grpc.pb.go @@ -1,4 +1,8 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc v6.32.0 +// source: racing/racing.proto package racing @@ -11,8 +15,13 @@ import ( // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.32.0 or later. -const _ = grpc.SupportPackageIsVersion7 +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + Racing_ListRaces_FullMethodName = "/racing.Racing/ListRaces" + Racing_GetRace_FullMethodName = "/racing.Racing/GetRace" +) // RacingClient is the client API for Racing service. // @@ -33,8 +42,9 @@ func NewRacingClient(cc grpc.ClientConnInterface) RacingClient { } func (c *racingClient) ListRaces(ctx context.Context, in *ListRacesRequest, opts ...grpc.CallOption) (*ListRacesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ListRacesResponse) - err := c.cc.Invoke(ctx, "/racing.Racing/ListRaces", in, out, opts...) + err := c.cc.Invoke(ctx, Racing_ListRaces_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -42,8 +52,9 @@ func (c *racingClient) ListRaces(ctx context.Context, in *ListRacesRequest, opts } func (c *racingClient) GetRace(ctx context.Context, in *GetRaceRequest, opts ...grpc.CallOption) (*Race, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Race) - err := c.cc.Invoke(ctx, "/racing.Racing/GetRace", in, out, opts...) + err := c.cc.Invoke(ctx, Racing_GetRace_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -52,7 +63,7 @@ func (c *racingClient) GetRace(ctx context.Context, in *GetRaceRequest, opts ... // RacingServer is the server API for Racing service. // All implementations must embed UnimplementedRacingServer -// for forward compatibility +// for forward compatibility. type RacingServer interface { // ListRaces returns a list of all races. ListRaces(context.Context, *ListRacesRequest) (*ListRacesResponse, error) @@ -61,9 +72,12 @@ type RacingServer interface { mustEmbedUnimplementedRacingServer() } -// UnimplementedRacingServer must be embedded to have forward compatible implementations. -type UnimplementedRacingServer struct { -} +// UnimplementedRacingServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedRacingServer struct{} func (UnimplementedRacingServer) ListRaces(context.Context, *ListRacesRequest) (*ListRacesResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ListRaces not implemented") @@ -72,6 +86,7 @@ func (UnimplementedRacingServer) GetRace(context.Context, *GetRaceRequest) (*Rac return nil, status.Errorf(codes.Unimplemented, "method GetRace not implemented") } func (UnimplementedRacingServer) mustEmbedUnimplementedRacingServer() {} +func (UnimplementedRacingServer) testEmbeddedByValue() {} // UnsafeRacingServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to RacingServer will @@ -81,6 +96,13 @@ type UnsafeRacingServer interface { } func RegisterRacingServer(s grpc.ServiceRegistrar, srv RacingServer) { + // If the following call pancis, it indicates UnimplementedRacingServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } s.RegisterService(&Racing_ServiceDesc, srv) } @@ -94,7 +116,7 @@ func _Racing_ListRaces_Handler(srv interface{}, ctx context.Context, dec func(in } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/racing.Racing/ListRaces", + FullMethod: Racing_ListRaces_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(RacingServer).ListRaces(ctx, req.(*ListRacesRequest)) @@ -112,7 +134,7 @@ func _Racing_GetRace_Handler(srv interface{}, ctx context.Context, dec func(inte } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/racing.Racing/GetRace", + FullMethod: Racing_GetRace_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(RacingServer).GetRace(ctx, req.(*GetRaceRequest)) From 643858131a66273b982ff1666131f610a2e290ec Mon Sep 17 00:00:00 2001 From: benrobey Date: Mon, 18 Aug 2025 00:06:08 +1000 Subject: [PATCH 2/3] Add `sports` service --- README.md | 459 +++++++++++++++-- api/go.mod | 26 +- api/go.sum | 614 ++--------------------- api/main.go | 16 +- api/proto/api.go | 2 +- api/proto/sporting/events.proto | 0 api/proto/sporting/sporting.pb.go | 481 ++++++++++++++++++ api/proto/sporting/sporting.pb.gw.go | 243 +++++++++ api/proto/sporting/sporting.proto | 59 +++ api/proto/sporting/sporting_grpc.pb.go | 159 ++++++ sport/db/db.go | 115 +++++ sport/db/events.db | Bin 0 -> 28672 bytes sport/db/events.go | 189 +++++++ sport/go.mod | 18 + sport/go.sum | 20 + sport/main.go | 47 ++ sport/proto/events.go | 3 + sport/proto/sporting.go | 1 + sport/proto/sporting/events.proto | 0 sport/proto/sporting/sporting.pb.go | 475 ++++++++++++++++++ sport/proto/sporting/sporting.proto | 59 +++ sport/proto/sporting/sporting_grpc.pb.go | 161 ++++++ sport/service/events.go | 41 ++ 23 files changed, 2548 insertions(+), 640 deletions(-) create mode 100644 api/proto/sporting/events.proto create mode 100644 api/proto/sporting/sporting.pb.go create mode 100644 api/proto/sporting/sporting.pb.gw.go create mode 100644 api/proto/sporting/sporting.proto create mode 100644 api/proto/sporting/sporting_grpc.pb.go create mode 100644 sport/db/db.go create mode 100644 sport/db/events.db create mode 100644 sport/db/events.go create mode 100644 sport/go.mod create mode 100644 sport/go.sum create mode 100644 sport/main.go create mode 100644 sport/proto/events.go create mode 100644 sport/proto/sporting.go create mode 100644 sport/proto/sporting/events.proto create mode 100644 sport/proto/sporting/sporting.pb.go create mode 100644 sport/proto/sporting/sporting.proto create mode 100644 sport/proto/sporting/sporting_grpc.pb.go create mode 100644 sport/service/events.go diff --git a/README.md b/README.md index 0eaf21e..9b1ae97 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,68 @@ -## Candidate 2 Solution – Usage Guide +# Entain BE Technical Test - Ben Robey -This guide explains the features added step‑by‑step so you can try them quickly. +## Step 1: visibility_only filter -### Step 1: Filter Races by Visibility -You can narrow the list of races to only those marked visible using the optional field `visible_only`. - -Field: `filter.visible_only` (boolean, optional) +Add optional visible_only filter to ListRaces Behavior: -* Omitted or false → all races returned (original behavior). -* true → only races where `visible` is true. +- Omit the field or set it to false => you get all races (same as before). +- Set it to true => you only get races where visible = true. -Endpoint: `POST http://localhost:8000/v1/list-races` +Upgraded out of date go 1.16 -> 1.22 for needed updated language features -Example – All races +### Example: Get All Races (no filtering) ```bash curl -s -X POST http://localhost:8000/v1/list-races \ - -H "Content-Type: application/json" \ - -d '{}' + -H "Content-Type: application/json" \ + -d '{}' ``` +Shows every race (visible + non‑visible). This matches legacy behavior. -Example – Only visible races +#### Example: Only Visible Races ```bash curl -s -X POST http://localhost:8000/v1/list-races \ - -H "Content-Type: application/json" \ - -d '{"filter":{"visible_only":true}}' + -H "Content-Type: application/json" \ + -d '{ + "filter": { + "visible_only": true + } + }' ``` +Narrows the list to races currently marked visible. -Example – Visible races for specific meetings +#### Example: Combine With Meeting IDs ```bash curl -s -X POST http://localhost:8000/v1/list-races \ - -H "Content-Type: application/json" \ - -d '{"filter":{"meeting_ids":[10,12],"visible_only":true}}' + -H "Content-Type: application/json" \ + -d '{ + "filter": { + "meeting_ids": [1,2], + "visible_only": true + } + }' ``` +Returns only visible races whose meeting_id is 1 or 2. -If nothing matches, you get an empty array: `{ "races": [] }`. +### Sample Truncated Response +```json +{ + "races": [ + { + "id": 42, + "meeting_id": 1, + "name": "Morning Dash", + "number": 3, + "visible": true, + "advertised_start_time": "2025-08-17T09:15:00Z" + } + ] +} +``` + +--- + + ## Step 2: Order Races by Advertised Start Time ---- -### Step 2: Order Races by Advertised Start Time Results are now always ordered by `advertised_start_time` ascending (earliest first) by default. You can reverse the order with `sort_direction`. Field: `filter.sort_direction` (enum, optional) @@ -47,21 +72,20 @@ Values: * `SORT_DIRECTION_ASC` → ascending * `SORT_DIRECTION_DESC` → descending (latest to earliest) -Example – Default ordering (ascending) +#### Example – Default ordering (ascending) ```bash curl -s -X POST http://localhost:8000/v1/list-races \ - -H "Content-Type: application/json" \ + -H "Content-Type: application/json" \ -d '{}' ``` -Explanation: No `sort_direction` provided → ascending order. +No `sort_direction` provided → ascending order. -Example – Explicit descending order +#### Example – Explicit descending order ```bash curl -s -X POST http://localhost:8000/v1/list-races \ -H "Content-Type: application/json" \ -d '{"filter":{"sort_direction":"SORT_DIRECTION_DESC"}}' ``` -Explanation: Returns the most recently scheduled (or already started) races first. Combine ordering with visibility: ```bash @@ -70,15 +94,380 @@ curl -s -X POST http://localhost:8000/v1/list-races \ -d '{"filter":{"visible_only":true, "sort_direction":"SORT_DIRECTION_ASC"}}' ``` -Smoke Test Checklist for Ordering: -1. Call ascending (default) – confirm earliest advertised_start_time first. -2. Call descending – confirm order reversed. -3. Add `visible_only:true` – ordering still holds within filtered subset. +--- + +## Step 3: Race Status (OPEN / CLOSED) + +Feature: Each race now includes a status based on advertised_start_time relative to "now". +- OPEN => start time is in the future +- CLOSED => start time is now or in the past + +Status field is always present. + +#### Example: Show first few races with status +```bash +curl -s -X POST localhost:8000/v1/list-races -d '{}' \ + | jq '.races[0:5] | map({id, status, start: .advertisedStartTime})' +``` + +#### Example: Only future (OPEN) races +```bash +curl -s -X POST localhost:8000/v1/list-races -d '{}' \ + | jq '.races | map(select(.status=="RACE_STATUS_OPEN"))' +``` + +--- + +## Step 4: GetRace +---------------------------------- +You can now fetch one race directly by its id. + +How it works: +* New RPC GetRace(id) returns the race or a NOT_FOUND error. +* Status field (OPEN/CLOSED) is still derived at read time, just like in the list. +* Follows AIP-131 conventions + +#### Example: Get race 1 + +``` +curl -s localhost:8000/v1/races/1 +``` + +Response: + +```json +{ + "id": "1", + "meetingId": "5", + "name": "North Dakota foes", + "number": "2", + "visible": false, + "advertisedStartTime": "2021-03-03T01:30:57Z", + "status": "RACE_STATUS_CLOSED" +} +``` --- -Upcoming Steps: -3. Add derived `status` (OPEN/CLOSED). -4. Add `GetRace` RPC for a single race. -5. Add separate sports service with `ListEvents`. -Feel free to copy/paste any curl above to exercise the API. \ No newline at end of file +## Step 5: Sports Service + +The `sport` microservice now exposes an AIP‑131 compliant `ListEvents` plus `GetEvent`. + +Current Event fields: +* `id`, `competition_id`, `name`, `visible`, `advertised_start_time`, derived `status` (OPEN/CLOSED), `sport_code`. + +`ListEvents` (HTTP GET, namespaced) – structured query params (no free‑form filter string): +* `page_size` (int, <=200, default 50) +* `page_token` (cursor based pagination) +* `visible` (optional bool) – filter when present +* `sport_code` (optional string) – equality +* `competition_id` (repeatable) – e.g. `&competition_id=1&competition_id=2` +* `status` (OPEN or CLOSED) – omit for both +* `order_by` – field name (currently only `advertised_start_time`) +* `order_direction` – `asc` (default) or `desc` + + +### Example: List first page (default order): +```bash +curl -s "http://localhost:8000/v1/sports/events?page_size=5" +``` + +Response: + +```json +{ + "events": [ + { + "id": "25", + "competitionId": "2", + "name": "Idaho ducks", + "visible": true, + "advertisedStartTime": "2025-08-16T14:20:40Z", + "status": "EVENT_STATUS_CLOSED", + "sportCode": "SOCCER" + }, + { + "id": "59", + "competitionId": "7", + "name": "West Virginia crows", + "visible": true, + "advertisedStartTime": "2025-08-16T15:30:45Z", + "status": "EVENT_STATUS_CLOSED", + "sportCode": "SOCCER" + }, + { + "id": "92", + "competitionId": "3", + "name": "Indiana werewolves", + "visible": true, + "advertisedStartTime": "2025-08-16T15:33:22Z", + "status": "EVENT_STATUS_CLOSED", + "sportCode": "SOCCER" + }, + { + "id": "18", + "competitionId": "1", + "name": "New Hampshire penguins", + "visible": true, + "advertisedStartTime": "2025-08-16T15:40:13Z", + "status": "EVENT_STATUS_CLOSED", + "sportCode": "SOCCER" + }, + { + "id": "21", + "competitionId": "3", + "name": "Alaska oxen", + "visible": true, + "advertisedStartTime": "2025-08-16T15:48:45Z", + "status": "EVENT_STATUS_CLOSED", + "sportCode": "SOCCER" + } + ], + "nextPageToken": "NQ==" +} +``` + +### Example: Filter + order (visible SOCCER events newest first): +```bash +curl -s "http://localhost:8000/v1/sports/events?visible=true&sport_code=SOCCER&order_by=advertised_start_time&order_direction=desc&page_size=5" +``` + +Response: + +```json +{ + "events": [ + { + "id": "23", + "competitionId": "8", + "name": "Vermont rabbits", + "visible": true, + "advertisedStartTime": "2025-08-19T13:41:48Z", + "status": "EVENT_STATUS_OPEN", + "sportCode": "SOCCER" + }, + { + "id": "64", + "competitionId": "5", + "name": "North Dakota birds", + "visible": true, + "advertisedStartTime": "2025-08-19T13:28:18Z", + "status": "EVENT_STATUS_OPEN", + "sportCode": "SOCCER" + }, + { + "id": "15", + "competitionId": "5", + "name": "Georgia gooses", + "visible": true, + "advertisedStartTime": "2025-08-19T11:43:19Z", + "status": "EVENT_STATUS_OPEN", + "sportCode": "SOCCER" + }, + { + "id": "27", + "competitionId": "3", + "name": "Maine oracles", + "visible": true, + "advertisedStartTime": "2025-08-19T10:58:52Z", + "status": "EVENT_STATUS_OPEN", + "sportCode": "SOCCER" + }, + { + "id": "40", + "competitionId": "7", + "name": "Connecticut vampires", + "visible": true, + "advertisedStartTime": "2025-08-19T10:43:55Z", + "status": "EVENT_STATUS_OPEN", + "sportCode": "SOCCER" + } + ], + "nextPageToken": "NQ==" +} +``` + +### Example: Filter by competition ids and status: +```bash +curl -s "http://localhost:8000/v1/sports/events?competition_id=1&competition_id=2&competition_id=3&status=OPEN&order_by=advertised_start_time&order_direction=asc&page_size=10" +``` + +Response: + +```json +{ + "events": [ + { + "id": "25", + "competitionId": "2", + "name": "Idaho ducks", + "visible": true, + "advertisedStartTime": "2025-08-16T14:20:40Z", + "status": "EVENT_STATUS_CLOSED", + "sportCode": "SOCCER" + }, + { + "id": "92", + "competitionId": "3", + "name": "Indiana werewolves", + "visible": true, + "advertisedStartTime": "2025-08-16T15:33:22Z", + "status": "EVENT_STATUS_CLOSED", + "sportCode": "SOCCER" + }, + { + "id": "18", + "competitionId": "1", + "name": "New Hampshire penguins", + "visible": true, + "advertisedStartTime": "2025-08-16T15:40:13Z", + "status": "EVENT_STATUS_CLOSED", + "sportCode": "SOCCER" + }, + { + "id": "21", + "competitionId": "3", + "name": "Alaska oxen", + "visible": true, + "advertisedStartTime": "2025-08-16T15:48:45Z", + "status": "EVENT_STATUS_CLOSED", + "sportCode": "SOCCER" + }, + { + "id": "68", + "competitionId": "3", + "name": "Pennsylvania banshees", + "visible": true, + "advertisedStartTime": "2025-08-16T15:55:03Z", + "status": "EVENT_STATUS_CLOSED", + "sportCode": "SOCCER" + }, + { + "id": "44", + "competitionId": "3", + "name": "Arizona bears", + "visible": false, + "advertisedStartTime": "2025-08-16T16:59:11Z", + "status": "EVENT_STATUS_CLOSED", + "sportCode": "SOCCER" + }, + { + "id": "11", + "competitionId": "3", + "name": "New Hampshire owls", + "visible": false, + "advertisedStartTime": "2025-08-16T17:04:23Z", + "status": "EVENT_STATUS_CLOSED", + "sportCode": "SOCCER" + }, + { + "id": "43", + "competitionId": "1", + "name": "Kansas penguins", + "visible": false, + "advertisedStartTime": "2025-08-16T17:59:21Z", + "status": "EVENT_STATUS_CLOSED", + "sportCode": "SOCCER" + }, + { + "id": "78", + "competitionId": "1", + "name": "Hawaii geese", + "visible": true, + "advertisedStartTime": "2025-08-16T21:01:40Z", + "status": "EVENT_STATUS_CLOSED", + "sportCode": "SOCCER" + }, + { + "id": "48", + "competitionId": "1", + "name": "Montana ducks", + "visible": false, + "advertisedStartTime": "2025-08-16T23:06:49Z", + "status": "EVENT_STATUS_CLOSED", + "sportCode": "SOCCER" + } + ], + "nextPageToken": "MTA=" +} +``` + +### Example: Paging ( next_page_token from previous response): +```bash +curl -s "http://localhost:8000/v1/sports/events?page_size=5&page_token=MTA=" +``` + +Response: + +```json +{ + "events": [ + { + "id": "24", + "competitionId": "5", + "name": "Idaho ghosts", + "visible": true, + "advertisedStartTime": "2025-08-16T17:05:57Z", + "status": "EVENT_STATUS_CLOSED", + "sportCode": "SOCCER" + }, + { + "id": "43", + "competitionId": "1", + "name": "Kansas penguins", + "visible": false, + "advertisedStartTime": "2025-08-16T17:59:21Z", + "status": "EVENT_STATUS_CLOSED", + "sportCode": "SOCCER" + }, + { + "id": "85", + "competitionId": "6", + "name": "Nebraska witches", + "visible": true, + "advertisedStartTime": "2025-08-16T20:51:43Z", + "status": "EVENT_STATUS_CLOSED", + "sportCode": "SOCCER" + }, + { + "id": "78", + "competitionId": "1", + "name": "Hawaii geese", + "visible": true, + "advertisedStartTime": "2025-08-16T21:01:40Z", + "status": "EVENT_STATUS_CLOSED", + "sportCode": "SOCCER" + }, + { + "id": "48", + "competitionId": "1", + "name": "Montana ducks", + "visible": false, + "advertisedStartTime": "2025-08-16T23:06:49Z", + "status": "EVENT_STATUS_CLOSED", + "sportCode": "SOCCER" + } + ], + "nextPageToken": "MTU=" +} +``` + + +### Example: Get event +```bash +curl -s "http://localhost:8000/v1/sports/events/123" +``` + +Response: + +```json +{ + "id": "5", + "competitionId": "2", + "name": "Missouri ogres", + "visible": false, + "advertisedStartTime": "2025-08-17T22:17:22Z", + "status": "EVENT_STATUS_CLOSED", + "sportCode": "SOCCER" +} +``` diff --git a/api/go.mod b/api/go.mod index c2dc52a..a9f210e 100644 --- a/api/go.mod +++ b/api/go.mod @@ -1,21 +1,21 @@ module git.neds.sh/matty/entain/api -go 1.22 +go 1.23 require ( - github.com/golang/protobuf v1.5.3 // indirect; indirect legacy proto - github.com/grpc-ecosystem/grpc-gateway/v2 v2.3.0 - google.golang.org/genproto v0.0.0-20210224155714-063164c882e6 - google.golang.org/grpc v1.46.0-dev - google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 - google.golang.org/protobuf v1.26.0 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 + google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 + google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect + google.golang.org/grpc v1.65.0 + google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 + google.golang.org/protobuf v1.34.2 ) require ( - github.com/ghodss/yaml v1.0.0 // indirect - github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect - golang.org/x/net v0.0.0-20210119194325-5f4716e94777 // indirect - golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect - golang.org/x/text v0.3.5 // indirect - gopkg.in/yaml.v2 v2.3.0 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.16.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/api/go.sum b/api/go.sum index aa941d4..d9bcc30 100644 --- a/api/go.sum +++ b/api/go.sum @@ -1,584 +1,32 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= -github.com/bufbuild/buf v0.37.0/go.mod h1:lQ1m2HkIaGOFba6w/aC3KYBHhKEOESP3gaAEpS3dAFM= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= -github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.3.0 h1:IvO4FbbQL6n3v3M1rQNobZ61SGL0gJLdvKA5KETM7Xs= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.3.0/go.mod h1:d2gYTOTUQklu06xp0AJYYmRdTVU1VKrqhkYfYag2L08= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jhump/protoreflect v1.8.1/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nishanths/predeclared v0.0.0-20200524104333-86fad755b4d3/go.mod h1:nt3d53pc1VYcphSCIaYAJtnPYnr3Zyn8fMq2wvPGPso= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v1.0.1-0.20201006035406-b97b5ead31f7/go.mod h1:yk5b0mALVusDL5fMM6Rd1wgnoO5jUPhwsQ6LQAJTidQ= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/twitchtv/twirp v7.1.0+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.6/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200717024301-6ddee64345a6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210207032614-bba0dbe2a9ea/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210224155714-063164c882e6 h1:bXUwz2WkXXrXgiLxww3vWmoSHLOGv4ipdPdTvKymcKw= -google.golang.org/genproto v0.0.0-20210224155714-063164c882e6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.35.0-dev.0.20201218190559-666aea1fb34c/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.46.0-dev h1:Lxftsnhovr5jHAxuFFCb3PMCJr8wSZ4CsJfvy+b2Lb8= -google.golang.org/grpc v1.46.0-dev/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.0.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 h1:M1YKkFIboKNieVO5DLUEVzQfGwJD30Nv2jfUgzb5UcE= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.25.1-0.20200805231151-a709e31e5d12/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.25.1-0.20201208041424-160c7477e0e8/go.mod h1:hFxJC2f0epmp1elRCiEGJTKAWbwxZ2nvqZdHl3FQXCY= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 h1:7whR9kGa5LUwFtpLm2ArCEejtnxlGeLbAyjFY8sGNFw= +google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/api/main.go b/api/main.go index 450dc65..93eb24c 100644 --- a/api/main.go +++ b/api/main.go @@ -7,13 +7,15 @@ import ( "net/http" "git.neds.sh/matty/entain/api/proto/racing" + "git.neds.sh/matty/entain/api/proto/sporting" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "google.golang.org/grpc" ) var ( - apiEndpoint = flag.String("api-endpoint", "localhost:8000", "API endpoint") - grpcEndpoint = flag.String("grpc-endpoint", "localhost:9000", "gRPC server endpoint") + apiEndpoint = flag.String("api-endpoint", "localhost:8000", "API endpoint") + grpcEndpoint = flag.String("grpc-endpoint", "localhost:9000", "Racing gRPC server endpoint") + sportingGrpcEndpoint = flag.String("sporting-grpc-endpoint", "localhost:9010", "Sporting gRPC server endpoint") ) func main() { @@ -30,12 +32,10 @@ func run() error { defer cancel() mux := runtime.NewServeMux() - if err := racing.RegisterRacingHandlerFromEndpoint( - ctx, - mux, - *grpcEndpoint, - []grpc.DialOption{grpc.WithInsecure()}, - ); err != nil { + if err := racing.RegisterRacingHandlerFromEndpoint(ctx, mux, *grpcEndpoint, []grpc.DialOption{grpc.WithInsecure()}); err != nil { + return err + } + if err := sporting.RegisterSportingHandlerFromEndpoint(ctx, mux, *sportingGrpcEndpoint, []grpc.DialOption{grpc.WithInsecure()}); err != nil { return err } diff --git a/api/proto/api.go b/api/proto/api.go index 09798bb..0619c53 100644 --- a/api/proto/api.go +++ b/api/proto/api.go @@ -1,3 +1,3 @@ package proto -//go:generate protoc -I . --go_out . --go_opt paths=source_relative --go-grpc_out . --go-grpc_opt paths=source_relative --grpc-gateway_out . --grpc-gateway_opt paths=source_relative racing/racing.proto --experimental_allow_proto3_optional +//go:generate protoc -I . --go_out . --go_opt paths=source_relative --go-grpc_out . --go-grpc_opt paths=source_relative --grpc-gateway_out . --grpc-gateway_opt paths=source_relative racing/racing.proto sporting/sporting.proto --experimental_allow_proto3_optional diff --git a/api/proto/sporting/events.proto b/api/proto/sporting/events.proto new file mode 100644 index 0000000..e69de29 diff --git a/api/proto/sporting/sporting.pb.go b/api/proto/sporting/sporting.pb.go new file mode 100644 index 0000000..d2a8ef5 --- /dev/null +++ b/api/proto/sporting/sporting.pb.go @@ -0,0 +1,481 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.7 +// protoc v6.32.0 +// source: sporting/sporting.proto + +package sporting + +import ( + _ "google.golang.org/genproto/googleapis/api/annotations" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +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) +) + +// EventStatus derived from advertised_start_time relative to now. +type EventStatus int32 + +const ( + EventStatus_EVENT_STATUS_UNSPECIFIED EventStatus = 0 + EventStatus_EVENT_STATUS_OPEN EventStatus = 1 + EventStatus_EVENT_STATUS_CLOSED EventStatus = 2 + // Aliases to support status=OPEN&CLOSED query params (gRPC-Gateway accepts enum names). + EventStatus_OPEN EventStatus = 1 + EventStatus_CLOSED EventStatus = 2 +) + +// Enum value maps for EventStatus. +var ( + EventStatus_name = map[int32]string{ + 0: "EVENT_STATUS_UNSPECIFIED", + 1: "EVENT_STATUS_OPEN", + 2: "EVENT_STATUS_CLOSED", + // Duplicate value: 1: "OPEN", + // Duplicate value: 2: "CLOSED", + } + EventStatus_value = map[string]int32{ + "EVENT_STATUS_UNSPECIFIED": 0, + "EVENT_STATUS_OPEN": 1, + "EVENT_STATUS_CLOSED": 2, + "OPEN": 1, + "CLOSED": 2, + } +) + +func (x EventStatus) Enum() *EventStatus { + p := new(EventStatus) + *p = x + return p +} + +func (x EventStatus) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (EventStatus) Descriptor() protoreflect.EnumDescriptor { + return file_sporting_sporting_proto_enumTypes[0].Descriptor() +} + +func (EventStatus) Type() protoreflect.EnumType { + return &file_sporting_sporting_proto_enumTypes[0] +} + +func (x EventStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use EventStatus.Descriptor instead. +func (EventStatus) EnumDescriptor() ([]byte, []int) { + return file_sporting_sporting_proto_rawDescGZIP(), []int{0} +} + +// AIP-131 style list request. +// Supported filter fields: visible, sport_code, competition_id (with IN), status. +// Example: ?filter=visible=true AND sport_code="SOCCER"&order_by=advertised_start_time desc&page_size=20 +type ListEventsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + PageSize int32 `protobuf:"varint,1,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + PageToken string `protobuf:"bytes,2,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` + CompetitionId []int64 `protobuf:"varint,3,rep,packed,name=competition_id,json=competitionId,proto3" json:"competition_id,omitempty"` // repeated query param competition_id=1 + Visible *bool `protobuf:"varint,4,opt,name=visible,proto3,oneof" json:"visible,omitempty"` + SportCode string `protobuf:"bytes,5,opt,name=sport_code,json=sportCode,proto3" json:"sport_code,omitempty"` + Status EventStatus `protobuf:"varint,6,opt,name=status,proto3,enum=sporting.EventStatus" json:"status,omitempty"` // OPEN/CLOSED + OrderBy string `protobuf:"bytes,7,opt,name=order_by,json=orderBy,proto3" json:"order_by,omitempty"` // currently only advertised_start_time + OrderDirection string `protobuf:"bytes,8,opt,name=order_direction,json=orderDirection,proto3" json:"order_direction,omitempty"` // asc|desc (default asc) + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListEventsRequest) Reset() { + *x = ListEventsRequest{} + mi := &file_sporting_sporting_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListEventsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListEventsRequest) ProtoMessage() {} + +func (x *ListEventsRequest) ProtoReflect() protoreflect.Message { + mi := &file_sporting_sporting_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 ListEventsRequest.ProtoReflect.Descriptor instead. +func (*ListEventsRequest) Descriptor() ([]byte, []int) { + return file_sporting_sporting_proto_rawDescGZIP(), []int{0} +} + +func (x *ListEventsRequest) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +func (x *ListEventsRequest) GetPageToken() string { + if x != nil { + return x.PageToken + } + return "" +} + +func (x *ListEventsRequest) GetCompetitionId() []int64 { + if x != nil { + return x.CompetitionId + } + return nil +} + +func (x *ListEventsRequest) GetVisible() bool { + if x != nil && x.Visible != nil { + return *x.Visible + } + return false +} + +func (x *ListEventsRequest) GetSportCode() string { + if x != nil { + return x.SportCode + } + return "" +} + +func (x *ListEventsRequest) GetStatus() EventStatus { + if x != nil { + return x.Status + } + return EventStatus_EVENT_STATUS_UNSPECIFIED +} + +func (x *ListEventsRequest) GetOrderBy() string { + if x != nil { + return x.OrderBy + } + return "" +} + +func (x *ListEventsRequest) GetOrderDirection() string { + if x != nil { + return x.OrderDirection + } + return "" +} + +type ListEventsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Events []*Event `protobuf:"bytes,1,rep,name=events,proto3" json:"events,omitempty"` + NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListEventsResponse) Reset() { + *x = ListEventsResponse{} + mi := &file_sporting_sporting_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListEventsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListEventsResponse) ProtoMessage() {} + +func (x *ListEventsResponse) ProtoReflect() protoreflect.Message { + mi := &file_sporting_sporting_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 ListEventsResponse.ProtoReflect.Descriptor instead. +func (*ListEventsResponse) Descriptor() ([]byte, []int) { + return file_sporting_sporting_proto_rawDescGZIP(), []int{1} +} + +func (x *ListEventsResponse) GetEvents() []*Event { + if x != nil { + return x.Events + } + return nil +} + +func (x *ListEventsResponse) GetNextPageToken() string { + if x != nil { + return x.NextPageToken + } + return "" +} + +type GetEventRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetEventRequest) Reset() { + *x = GetEventRequest{} + mi := &file_sporting_sporting_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetEventRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetEventRequest) ProtoMessage() {} + +func (x *GetEventRequest) ProtoReflect() protoreflect.Message { + mi := &file_sporting_sporting_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 GetEventRequest.ProtoReflect.Descriptor instead. +func (*GetEventRequest) Descriptor() ([]byte, []int) { + return file_sporting_sporting_proto_rawDescGZIP(), []int{2} +} + +func (x *GetEventRequest) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +// Event resource. +type Event struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + CompetitionId int64 `protobuf:"varint,2,opt,name=competition_id,json=competitionId,proto3" json:"competition_id,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + Visible bool `protobuf:"varint,4,opt,name=visible,proto3" json:"visible,omitempty"` + AdvertisedStartTime *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=advertised_start_time,json=advertisedStartTime,proto3" json:"advertised_start_time,omitempty"` + Status EventStatus `protobuf:"varint,6,opt,name=status,proto3,enum=sporting.EventStatus" json:"status,omitempty"` + SportCode string `protobuf:"bytes,7,opt,name=sport_code,json=sportCode,proto3" json:"sport_code,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Event) Reset() { + *x = Event{} + mi := &file_sporting_sporting_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Event) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Event) ProtoMessage() {} + +func (x *Event) ProtoReflect() protoreflect.Message { + mi := &file_sporting_sporting_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 Event.ProtoReflect.Descriptor instead. +func (*Event) Descriptor() ([]byte, []int) { + return file_sporting_sporting_proto_rawDescGZIP(), []int{3} +} + +func (x *Event) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *Event) GetCompetitionId() int64 { + if x != nil { + return x.CompetitionId + } + return 0 +} + +func (x *Event) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Event) GetVisible() bool { + if x != nil { + return x.Visible + } + return false +} + +func (x *Event) GetAdvertisedStartTime() *timestamppb.Timestamp { + if x != nil { + return x.AdvertisedStartTime + } + return nil +} + +func (x *Event) GetStatus() EventStatus { + if x != nil { + return x.Status + } + return EventStatus_EVENT_STATUS_UNSPECIFIED +} + +func (x *Event) GetSportCode() string { + if x != nil { + return x.SportCode + } + return "" +} + +var File_sporting_sporting_proto protoreflect.FileDescriptor + +const file_sporting_sporting_proto_rawDesc = "" + + "\n" + + "\x17sporting/sporting.proto\x12\bsporting\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1cgoogle/api/annotations.proto\"\xb3\x02\n" + + "\x11ListEventsRequest\x12\x1b\n" + + "\tpage_size\x18\x01 \x01(\x05R\bpageSize\x12\x1d\n" + + "\n" + + "page_token\x18\x02 \x01(\tR\tpageToken\x12%\n" + + "\x0ecompetition_id\x18\x03 \x03(\x03R\rcompetitionId\x12\x1d\n" + + "\avisible\x18\x04 \x01(\bH\x00R\avisible\x88\x01\x01\x12\x1d\n" + + "\n" + + "sport_code\x18\x05 \x01(\tR\tsportCode\x12-\n" + + "\x06status\x18\x06 \x01(\x0e2\x15.sporting.EventStatusR\x06status\x12\x19\n" + + "\border_by\x18\a \x01(\tR\aorderBy\x12'\n" + + "\x0forder_direction\x18\b \x01(\tR\x0eorderDirectionB\n" + + "\n" + + "\b_visible\"e\n" + + "\x12ListEventsResponse\x12'\n" + + "\x06events\x18\x01 \x03(\v2\x0f.sporting.EventR\x06events\x12&\n" + + "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"!\n" + + "\x0fGetEventRequest\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x03R\x02id\"\x8a\x02\n" + + "\x05Event\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x03R\x02id\x12%\n" + + "\x0ecompetition_id\x18\x02 \x01(\x03R\rcompetitionId\x12\x12\n" + + "\x04name\x18\x03 \x01(\tR\x04name\x12\x18\n" + + "\avisible\x18\x04 \x01(\bR\avisible\x12N\n" + + "\x15advertised_start_time\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\x13advertisedStartTime\x12-\n" + + "\x06status\x18\x06 \x01(\x0e2\x15.sporting.EventStatusR\x06status\x12\x1d\n" + + "\n" + + "sport_code\x18\a \x01(\tR\tsportCode*u\n" + + "\vEventStatus\x12\x1c\n" + + "\x18EVENT_STATUS_UNSPECIFIED\x10\x00\x12\x15\n" + + "\x11EVENT_STATUS_OPEN\x10\x01\x12\x17\n" + + "\x13EVENT_STATUS_CLOSED\x10\x02\x12\b\n" + + "\x04OPEN\x10\x01\x12\n" + + "\n" + + "\x06CLOSED\x10\x02\x1a\x02\x10\x012\xc6\x01\n" + + "\bSporting\x12V\n" + + "\bGetEvent\x12\x19.sporting.GetEventRequest\x1a\x0f.sporting.Event\"\x1e\x82\xd3\xe4\x93\x02\x18\x12\x16/v1/sports/events/{id}\x12b\n" + + "\n" + + "ListEvents\x12\x1b.sporting.ListEventsRequest\x1a\x1c.sporting.ListEventsResponse\"\x19\x82\xd3\xe4\x93\x02\x13\x12\x11/v1/sports/eventsB\vZ\t/sportingb\x06proto3" + +var ( + file_sporting_sporting_proto_rawDescOnce sync.Once + file_sporting_sporting_proto_rawDescData []byte +) + +func file_sporting_sporting_proto_rawDescGZIP() []byte { + file_sporting_sporting_proto_rawDescOnce.Do(func() { + file_sporting_sporting_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_sporting_sporting_proto_rawDesc), len(file_sporting_sporting_proto_rawDesc))) + }) + return file_sporting_sporting_proto_rawDescData +} + +var file_sporting_sporting_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_sporting_sporting_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_sporting_sporting_proto_goTypes = []any{ + (EventStatus)(0), // 0: sporting.EventStatus + (*ListEventsRequest)(nil), // 1: sporting.ListEventsRequest + (*ListEventsResponse)(nil), // 2: sporting.ListEventsResponse + (*GetEventRequest)(nil), // 3: sporting.GetEventRequest + (*Event)(nil), // 4: sporting.Event + (*timestamppb.Timestamp)(nil), // 5: google.protobuf.Timestamp +} +var file_sporting_sporting_proto_depIdxs = []int32{ + 0, // 0: sporting.ListEventsRequest.status:type_name -> sporting.EventStatus + 4, // 1: sporting.ListEventsResponse.events:type_name -> sporting.Event + 5, // 2: sporting.Event.advertised_start_time:type_name -> google.protobuf.Timestamp + 0, // 3: sporting.Event.status:type_name -> sporting.EventStatus + 3, // 4: sporting.Sporting.GetEvent:input_type -> sporting.GetEventRequest + 1, // 5: sporting.Sporting.ListEvents:input_type -> sporting.ListEventsRequest + 4, // 6: sporting.Sporting.GetEvent:output_type -> sporting.Event + 2, // 7: sporting.Sporting.ListEvents:output_type -> sporting.ListEventsResponse + 6, // [6:8] is the sub-list for method output_type + 4, // [4:6] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name +} + +func init() { file_sporting_sporting_proto_init() } +func file_sporting_sporting_proto_init() { + if File_sporting_sporting_proto != nil { + return + } + file_sporting_sporting_proto_msgTypes[0].OneofWrappers = []any{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_sporting_sporting_proto_rawDesc), len(file_sporting_sporting_proto_rawDesc)), + NumEnums: 1, + NumMessages: 4, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_sporting_sporting_proto_goTypes, + DependencyIndexes: file_sporting_sporting_proto_depIdxs, + EnumInfos: file_sporting_sporting_proto_enumTypes, + MessageInfos: file_sporting_sporting_proto_msgTypes, + }.Build() + File_sporting_sporting_proto = out.File + file_sporting_sporting_proto_goTypes = nil + file_sporting_sporting_proto_depIdxs = nil +} diff --git a/api/proto/sporting/sporting.pb.gw.go b/api/proto/sporting/sporting.pb.gw.go new file mode 100644 index 0000000..ca80982 --- /dev/null +++ b/api/proto/sporting/sporting.pb.gw.go @@ -0,0 +1,243 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: sporting/sporting.proto + +/* +Package sporting is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package sporting + +import ( + "context" + "errors" + "io" + "net/http" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/grpc-ecosystem/grpc-gateway/v2/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" +) + +// Suppress "imported and not used" errors +var ( + _ codes.Code + _ io.Reader + _ status.Status + _ = errors.New + _ = runtime.String + _ = utilities.NewDoubleArray + _ = metadata.Join +) + +func request_Sporting_GetEvent_0(ctx context.Context, marshaler runtime.Marshaler, client SportingClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq GetEventRequest + metadata runtime.ServerMetadata + err error + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + val, ok := pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + protoReq.Id, err = runtime.Int64(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + msg, err := client.GetEvent(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_Sporting_GetEvent_0(ctx context.Context, marshaler runtime.Marshaler, server SportingServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq GetEventRequest + metadata runtime.ServerMetadata + err error + ) + val, ok := pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + protoReq.Id, err = runtime.Int64(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + msg, err := server.GetEvent(ctx, &protoReq) + return msg, metadata, err +} + +var filter_Sporting_ListEvents_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} + +func request_Sporting_ListEvents_0(ctx context.Context, marshaler runtime.Marshaler, client SportingClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ListEventsRequest + metadata runtime.ServerMetadata + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Sporting_ListEvents_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := client.ListEvents(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_Sporting_ListEvents_0(ctx context.Context, marshaler runtime.Marshaler, server SportingServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ListEventsRequest + metadata runtime.ServerMetadata + ) + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Sporting_ListEvents_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.ListEvents(ctx, &protoReq) + return msg, metadata, err +} + +// RegisterSportingHandlerServer registers the http handlers for service Sporting to "mux". +// UnaryRPC :call SportingServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterSportingHandlerFromEndpoint instead. +// GRPC interceptors will not work for this type of registration. To use interceptors, you must use the "runtime.WithMiddlewares" option in the "runtime.NewServeMux" call. +func RegisterSportingHandlerServer(ctx context.Context, mux *runtime.ServeMux, server SportingServer) error { + mux.Handle(http.MethodGet, pattern_Sporting_GetEvent_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/sporting.Sporting/GetEvent", runtime.WithHTTPPathPattern("/v1/sports/events/{id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Sporting_GetEvent_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_Sporting_GetEvent_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_Sporting_ListEvents_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/sporting.Sporting/ListEvents", runtime.WithHTTPPathPattern("/v1/sports/events")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Sporting_ListEvents_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_Sporting_ListEvents_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + + return nil +} + +// RegisterSportingHandlerFromEndpoint is same as RegisterSportingHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterSportingHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.NewClient(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Errorf("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Errorf("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + return RegisterSportingHandler(ctx, mux, conn) +} + +// RegisterSportingHandler registers the http handlers for service Sporting to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterSportingHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterSportingHandlerClient(ctx, mux, NewSportingClient(conn)) +} + +// RegisterSportingHandlerClient registers the http handlers for service Sporting +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "SportingClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "SportingClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "SportingClient" to call the correct interceptors. This client ignores the HTTP middlewares. +func RegisterSportingHandlerClient(ctx context.Context, mux *runtime.ServeMux, client SportingClient) error { + mux.Handle(http.MethodGet, pattern_Sporting_GetEvent_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/sporting.Sporting/GetEvent", runtime.WithHTTPPathPattern("/v1/sports/events/{id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Sporting_GetEvent_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_Sporting_GetEvent_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_Sporting_ListEvents_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/sporting.Sporting/ListEvents", runtime.WithHTTPPathPattern("/v1/sports/events")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Sporting_ListEvents_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_Sporting_ListEvents_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + return nil +} + +var ( + pattern_Sporting_GetEvent_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"v1", "sports", "events", "id"}, "")) + pattern_Sporting_ListEvents_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "sports", "events"}, "")) +) + +var ( + forward_Sporting_GetEvent_0 = runtime.ForwardResponseMessage + forward_Sporting_ListEvents_0 = runtime.ForwardResponseMessage +) diff --git a/api/proto/sporting/sporting.proto b/api/proto/sporting/sporting.proto new file mode 100644 index 0000000..b034c75 --- /dev/null +++ b/api/proto/sporting/sporting.proto @@ -0,0 +1,59 @@ +syntax = "proto3"; +package sporting; + +option go_package = "/sporting"; + +import "google/protobuf/timestamp.proto"; +import "google/api/annotations.proto"; + +service Sporting { + rpc GetEvent(GetEventRequest) returns (Event) { + option (google.api.http) = { get: "/v1/sports/events/{id}" }; + } + rpc ListEvents(ListEventsRequest) returns (ListEventsResponse) { + option (google.api.http) = { get: "/v1/sports/events" }; + } +} + +// EventStatus derived from advertised_start_time relative to now. +enum EventStatus { + option allow_alias = true; // allow OPEN/CLOSED aliases for friendlier query values + EVENT_STATUS_UNSPECIFIED = 0; + EVENT_STATUS_OPEN = 1; + EVENT_STATUS_CLOSED = 2; + // Aliases to support status=OPEN&CLOSED query params (gRPC-Gateway accepts enum names). + OPEN = 1; + CLOSED = 2; +} + +// AIP-131 style list request. +// Supported filter fields: visible, sport_code, competition_id (with IN), status. +// Example: ?filter=visible=true AND sport_code="SOCCER"&order_by=advertised_start_time desc&page_size=20 +message ListEventsRequest { + int32 page_size = 1; + string page_token = 2; + repeated int64 competition_id = 3; // repeated query param competition_id=1 + optional bool visible = 4; + string sport_code = 5; + EventStatus status = 6; // OPEN/CLOSED + string order_by = 7; // currently only advertised_start_time + string order_direction = 8; // asc|desc (default asc) +} + +message ListEventsResponse { + repeated Event events = 1; + string next_page_token = 2; +} + +message GetEventRequest { int64 id = 1; } + +// Event resource. +message Event { + int64 id = 1; + int64 competition_id = 2; + string name = 3; + bool visible = 4; + google.protobuf.Timestamp advertised_start_time = 5; + EventStatus status = 6; + string sport_code = 7; +} diff --git a/api/proto/sporting/sporting_grpc.pb.go b/api/proto/sporting/sporting_grpc.pb.go new file mode 100644 index 0000000..1a8b729 --- /dev/null +++ b/api/proto/sporting/sporting_grpc.pb.go @@ -0,0 +1,159 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc v6.32.0 +// source: sporting/sporting.proto + +package sporting + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + Sporting_GetEvent_FullMethodName = "/sporting.Sporting/GetEvent" + Sporting_ListEvents_FullMethodName = "/sporting.Sporting/ListEvents" +) + +// SportingClient is the client API for Sporting service. +// +// 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 SportingClient interface { + GetEvent(ctx context.Context, in *GetEventRequest, opts ...grpc.CallOption) (*Event, error) + ListEvents(ctx context.Context, in *ListEventsRequest, opts ...grpc.CallOption) (*ListEventsResponse, error) +} + +type sportingClient struct { + cc grpc.ClientConnInterface +} + +func NewSportingClient(cc grpc.ClientConnInterface) SportingClient { + return &sportingClient{cc} +} + +func (c *sportingClient) GetEvent(ctx context.Context, in *GetEventRequest, opts ...grpc.CallOption) (*Event, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Event) + err := c.cc.Invoke(ctx, Sporting_GetEvent_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *sportingClient) ListEvents(ctx context.Context, in *ListEventsRequest, opts ...grpc.CallOption) (*ListEventsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListEventsResponse) + err := c.cc.Invoke(ctx, Sporting_ListEvents_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// SportingServer is the server API for Sporting service. +// All implementations must embed UnimplementedSportingServer +// for forward compatibility. +type SportingServer interface { + GetEvent(context.Context, *GetEventRequest) (*Event, error) + ListEvents(context.Context, *ListEventsRequest) (*ListEventsResponse, error) + mustEmbedUnimplementedSportingServer() +} + +// UnimplementedSportingServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedSportingServer struct{} + +func (UnimplementedSportingServer) GetEvent(context.Context, *GetEventRequest) (*Event, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetEvent not implemented") +} +func (UnimplementedSportingServer) ListEvents(context.Context, *ListEventsRequest) (*ListEventsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListEvents not implemented") +} +func (UnimplementedSportingServer) mustEmbedUnimplementedSportingServer() {} +func (UnimplementedSportingServer) testEmbeddedByValue() {} + +// UnsafeSportingServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to SportingServer will +// result in compilation errors. +type UnsafeSportingServer interface { + mustEmbedUnimplementedSportingServer() +} + +func RegisterSportingServer(s grpc.ServiceRegistrar, srv SportingServer) { + // If the following call pancis, it indicates UnimplementedSportingServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&Sporting_ServiceDesc, srv) +} + +func _Sporting_GetEvent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetEventRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SportingServer).GetEvent(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Sporting_GetEvent_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SportingServer).GetEvent(ctx, req.(*GetEventRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Sporting_ListEvents_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListEventsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SportingServer).ListEvents(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Sporting_ListEvents_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SportingServer).ListEvents(ctx, req.(*ListEventsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// Sporting_ServiceDesc is the grpc.ServiceDesc for Sporting service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Sporting_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "sporting.Sporting", + HandlerType: (*SportingServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetEvent", + Handler: _Sporting_GetEvent_Handler, + }, + { + MethodName: "ListEvents", + Handler: _Sporting_ListEvents_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "sporting/sporting.proto", +} diff --git a/sport/db/db.go b/sport/db/db.go new file mode 100644 index 0000000..9b21e91 --- /dev/null +++ b/sport/db/db.go @@ -0,0 +1,115 @@ +package db + +import ( + "database/sql" + "log" + "time" + + "syreclabs.com/go/faker" +) + +// seed ensures schema exists (with latest shape) and seeds initial data. +func (r *eventsRepo) seed() error { + if err := r.ensureSchema(); err != nil { + return err + } + + // If table already has rows, assume seeded. + var count int + if err := r.db.QueryRow(`SELECT COUNT(1) FROM events`).Scan(&count); err == nil && count > 0 { + return nil + } + + sportCodes := []string{"SOCCER", "TENNIS", "BASKETBALL", "CRICKET"} + for i := 1; i <= 100; i++ { + if _, err := r.db.Exec(`INSERT OR IGNORE INTO events(id, competition_id, name, visible, advertised_start_time, sport_code) VALUES (?,?,?,?,?,?)`, + i, + faker.Number().Between(1, 8), + faker.Team().Name(), + faker.Number().Between(0, 1), + faker.Time().Between(time.Now().Add(-24*time.Hour), time.Now().Add(48*time.Hour)).Format(time.RFC3339), + sportCodes[(i-1)%len(sportCodes)], + ); err != nil { + return err + } + } + return nil +} + +// ensureSchema creates or migrates the events table to the latest schema. +func (r *eventsRepo) ensureSchema() error { + // Create table if it does not exist with latest columns. + if _, err := r.db.Exec(`CREATE TABLE IF NOT EXISTS events (id INTEGER PRIMARY KEY, competition_id INTEGER, name TEXT, visible INTEGER, advertised_start_time DATETIME, sport_code TEXT)`); err != nil { + return err + } + + // Detect legacy schema (league_id column, no competition_id). + cols, err := r.loadColumns("events") + if err != nil { + return err + } + hasCompetition := false + hasLeague := false + for _, c := range cols { + if c == "competition_id" { + hasCompetition = true + } + if c == "league_id" { + hasLeague = true + } + } + if !hasCompetition && hasLeague { + log.Printf("[sport][migrate] migrating events table: league_id -> competition_id, adding sport_code") + tx, err := r.db.Begin() + if err != nil { + return err + } + // Rename old table. + if _, err = tx.Exec(`ALTER TABLE events RENAME TO events_old`); err != nil { + tx.Rollback() + return err + } + if _, err = tx.Exec(`CREATE TABLE events (id INTEGER PRIMARY KEY, competition_id INTEGER, name TEXT, visible INTEGER, advertised_start_time DATETIME, sport_code TEXT)`); err != nil { + tx.Rollback() + return err + } + if _, err = tx.Exec(`INSERT INTO events(id, competition_id, name, visible, advertised_start_time, sport_code) + SELECT id, league_id, name, visible, advertised_start_time, 'SOCCER' FROM events_old`); err != nil { + tx.Rollback() + return err + } + if _, err = tx.Exec(`DROP TABLE events_old`); err != nil { + tx.Rollback() + return err + } + if err = tx.Commit(); err != nil { + return err + } + } + return nil +} + +// loadColumns returns column names for a table. +func (r *eventsRepo) loadColumns(table string) ([]string, error) { + rows, err := r.db.Query(`PRAGMA table_info(` + table + `)`) + if err != nil { + return nil, err + } + defer rows.Close() + var out []string + for rows.Next() { + var ( + cid int + name string + ctype string + notnull int + dflt sql.NullString + pk int + ) + if err := rows.Scan(&cid, &name, &ctype, ¬null, &dflt, &pk); err != nil { + return nil, err + } + out = append(out, name) + } + return out, rows.Err() +} diff --git a/sport/db/events.db b/sport/db/events.db new file mode 100644 index 0000000000000000000000000000000000000000..62f22fff4c89a132bfe652c8d8d9eda30c1a1dc3 GIT binary patch literal 28672 zcmeHPTZ|jk8D8J-cDM$I+XM&@_GXXA_Ik3Z6E+Jh$tEE&gnKe;&+ZQKdV=lECOj0u zOGVYE^3W@#sHLiUL2cDaeQT+Dc_?aC3aGqP>O+wb4@KgU-oAg%nVY>$W~!>~!>n=> zoX`J%zW+b}`H#;xbMk>B+E5i%_5Ox36e<%tCPJZ!1A;IyF|mr@3H*-m-%-{)JrOv58RrwTb*|_#^lU7zh{$7zh{$7zh{$7zh{$7zh{$7zh{$7`QA0 z&)pSH>?{^T&(98(m9DCuS9`+&`!lw1V)0;WQD_~!`^cie{+n>8))AJDwifSQJRuxE zv9x^f#3RDt#Yd)uw!U#r9cn{O@13^CObI<@Lls(!54Wa-^V&dz4HG{lD4p|af2a-A z&gsEW=?_m2H4HukqphXo#VKKMPRD<2>mBw9doi0=ckbRDdcNRr_O#yZFz4uBVVBdl z*#F~}f9-l#Rn|7u|B%;vbHw=$gZOCS|$9 zW_wvVo5}n!^R3Lq%;C&U>A$3Zn*MaUmA*OkLF$L8XH!R0yOMuSelPjS-iUoE_EfA9OGV#~ek1yHv=$9V-i*8)IUA`)lHs?) zFNIgbQaBWPBlLyPYN$H#kBQ$JzIpv)W;s3)+M8-dj-Bl)>-vTwbP?i#SQ4w#rMc;H zqg5)&VneQ!Cd(zcRFaNhd^OdK9#?z4!Nu-*rKc%~q}EaUPE@%rSF3VaVyM}}Kuso_ ziKFUDUm2WLgteZ&q1u4C7CybwkgFmCl?$h?yi&XBxpf7}>+np3L!w-& za>x=v3>#a*{qRP;C~WE7^A0QZ7D1|VwZb5k`v9q=n#qIxvr2EE3}(stvh3#~`J9U}rknOdeIYgk|-D z*49U82BI#*xpJK$s*6C>lg;Rfby^-vgRau+2f z+FcjJWngR@jd@_2sb-u_WkKodU9E?-9?v(dI1pEtB{NC)0g5C|xD&43*SBmM>McYR z_9UJN(=tGJ0JPNWAT(f2RUAwsr{SJt4Hp|UB`kz{x_V#hD8iQ7S4S4lTuWq0Q{%MF z0R!41CT5&wR1Q&=%o2Yhk*)DZ9RF=xYqSjV&(sg*J;RFh3t#&^J$vy1^$rD7@a zbuv`~=#FG_S?>)g)}77vSyyT_sD7EWmS*ACG%C0wecfvcWm3ePt*SCR0|a=r7f4$f z4!f$O-XU@l)*qV4$?uHwWr)I1u!$u8Sm*wmA#4 zmoP}yBl;%V8e(#6Ob*4`Wn=amiAIakD~$iYFrQm5`< z!NW#J`{Gbrb8bGcQI#pnc`+#64jTww!lG)lwxfL1u#L)P{`r+XK#Zl~*wVvPkiHcY zbQ^(C58O3pG_h#U#)fM0d@DeVXRW6S-HUDP3G3Lh9J5F0QQ7Q#pS=Ybq*n6OMSVl- ztqG&K4gidR1D@-Z-ORIy9T@e3-fwFIcg2QVcGFT70K#r+RFeA;k-<7*B0QsStY{AB zj9rV0Hm_LX&1^!^7J40ZFi_R;a3a|nvV>J{R_mL{Kcv1h2{!cJS@ohb+sepj?7y5j z=|+H%IWZ?_t*@=FI`i6S#b}lB9znW+P}qT81!hYdw%1*gLR@M2a|frd2MRkwWLcpo zjc#11sc2p1tVV2l7Yzq>)<)vmepy$5$(UV15yNlAM$cFNXeGjqKwCU3c2n13@E%N? z*VA>~8AM|lQ?tgWJG&F3DODa)`yg~E^p%wr%@K8TJCJx;5w8V+x|(HeFwi&q+BQuj zjKKM_Eb{t0c@6VF!k43~t;1k!2UzgtCVMpogPWMuJEJbzQAWH_wCu)Fyo&jQ?P*J} zv(QzCkb)>eaEbT9)hkgQ8mXomGFFP1XJ{XJkY}%eDU`^hHT99r8Ow@djKtuZ`3?ZE znTO4FIKKI#q@Zf?IL{PlJVIwSbRz`?u>e&S)D-slVxG-C=4wpcJ&dszV)>aHXf+26 zR&C^zg7w;48|pn_rK_~h3ND&K8A^&|nW|&~!wQT#n(b{mU~EB-=0wae{~~UpU9n*a z37A=n)4)(yZBY*Y)sm0I5Lt=khK*;rQrN;e4iSe z|I5MoKREvf=l{~o|K<4~{cNd}!=Zm?W-w9sec{W6)k3up&;Ks})qF2s&nI(l=Dw2a z=Jw}O*>|&F&kk|_pU%9Uc@@KhpMZgYfq;R4fq;R4fq;R4fq;R40dEYHI=HDL(MwCc z0FCArZVuPAb8eXo6j73`5}9e!1tl(8&FkzD*%dBkA&S6tAivJ8tYAF!E>X8cXcgE0 z_C>29MS-xh#03d63K_u#;<85a5Yi^PE-7*UKPQ(Y>yCBq494%@MgusewFD9ZFQ^i@ z&|u~?n@-#k8jRkzlAsE|Op(l!Y&r>-GUORU73UCb(OhtEY#Moj8Hw5YJZMI3kr$kT zun5yJy1G*k2O%S7jk)uQOAiA>>Km6fVN^$B z+~w1ahN*Eog)3*I*kLH@p>nw>xIM@|`O5cqdRZJi@hH$OSjZGd!D4|2T53*EE6V_a54}R+qYFXNc7;09i z@G(`c1oZ#c<^Pob4%Gj1xxeLpo_is;oGWDC%D$9c&CX;#%={?xER_E{(tk*QEv=^; z=}79AsTWeqsVkFjB|o1$liZVdKk?nfFwsne|7`eFSO~or`gW)nlA-p0-CGHDjG8}`07(UWAHVfGya25YnEPdUzgJwUz!>_dQ`D<)Rc)b z^?L5;VLQQ=WsBJvsyYcqC^@}q+8L~I7mU#=@*{2M(7gnOML!C62Hkfjpj4Cn3EdgE zatDvSH@!Op)o+JIioJK)I|Isl036To5wR+|6_@&*V{aoYjQSG6Gi>8lV0mCXYvCER zb_<|XH#|$?xx2f$yS|k13_C9Xi@vGoQy|YLdo!P`ADujR`X+Lk)|gZIemS{3!|uBg zSV}{Gs(B8+fxs~APdv}S()9q-mgiAJ&r!PwMI@UCCU^BRbi9ycn`csn5{p1VP+#<< zsArhUb-pz<67X1hR_lNk2i#@>e(`TKDTT5+&ro68AiGq z7-G?*=$<=y6?f8)_MSoKuSCIu0>9`!Q(RVp&(O6iV3-QKXC*$vRd)bKO^)>q#$^Qg z99AG0Mc$(>pTQb=PLKNtd}(<;gU;swrPAS3rO(iE7HC@kzQp?6=?r&zG>J95(gHl{-QTo zKLb@`02!%gEok>w8`v&g>V7`ID4(Bakv}&Vq1K15FE3hu25W=?BU&8Y;AJKM3_2fz zr$)+pR{k3||0i*Xjvu1?|3f%-7k*awRN;7V{>L-(W8Yu0t;~K_obeu<|BW{);Oj@% zG%~J-=h@l`&i||*W*0)i`QK>k@D&GL5?YVo^UWtX|Fidqp~kwN(r5Gp=l@GQ z$j>PX&i{st!g|)9(el4I{~Ps=&i_L47j*uAp>V8lRsP-l%lWnZUAd2PKgm6pJCw`f z-G5)qu4MOSKEVC|h0Oj;H2s_Oi|NPHLhAk0H&ajJ+U)Q6fZ!)!AYdS1AYdS1AYdS1 z;J?X0y@M+dLr~#aQq9_~YMU;H=mN~6vYI!sD}a%f!k56Bn^edoZhE}xtQo9%1~90t z$5nA-*A=63Yi{>6pQ=~YHJ|E}e5$_0*WBb2+@x0xHiI=D2aIkd{YbGHRC)|hk}`Ny zWHVI#QJ^S&ybH6rr;l(?{phn9bl<~(8gdU`GHpK1Q@AapwDqXgW~ljxV3IBheTudj zy8a+1$-8!&0c$M)4JC$G2{*&ZCxJ7>G=7xa3@e=gmShzk1>M}!2gp-enqGC?47cw% zaHPKRC+}w9`C|YZ*SBs(#^`nWsLGq6#iPgs(#CiadoxUR85SvfyuKuz&+-V(5-@-7 zN@w8OVStHR&#z2pxVig*!;90Q&-VQ0bdFpiB(rF}IB-ODGWr5_hLrCE(oo9T?++h; zuQeW^aJ@&JTfGgoA~a?^*VKitltBxhKPgmz|&jR8E}3PKuRE=FIQ*i Fe*&*39dQ5v literal 0 HcmV?d00001 diff --git a/sport/db/events.go b/sport/db/events.go new file mode 100644 index 0000000..fa607b8 --- /dev/null +++ b/sport/db/events.go @@ -0,0 +1,189 @@ +package db + +import ( + "database/sql" + "encoding/base64" + "fmt" + "strconv" + "strings" + "sync" + "time" + + "sport/proto/sporting" + + "github.com/golang/protobuf/ptypes" + _ "github.com/mattn/go-sqlite3" +) + +// EventsRepo provides repository access to events. +type EventsRepo interface { + Init() error + List(req *sporting.ListEventsRequest) ([]*sporting.Event, string, error) + Get(id int64) (*sporting.Event, error) +} + +type eventsRepo struct { + db *sql.DB + init sync.Once +} + +func NewEventsRepo(db *sql.DB) EventsRepo { return &eventsRepo{db: db} } + +func (r *eventsRepo) Init() error { + var err error + r.init.Do(func() { err = r.seed() }) + return err +} + +func (r *eventsRepo) List(req *sporting.ListEventsRequest) ([]*sporting.Event, string, error) { + q := `SELECT id, competition_id, name, visible, advertised_start_time, sport_code FROM events` + var clauses []string + var args []interface{} + + // visible filter + if req.Visible != nil { + if req.GetVisible() { + clauses = append(clauses, "visible = 1") + } else { + clauses = append(clauses, "visible = 0") + } + } + // sport_code filter + if sc := strings.TrimSpace(req.GetSportCode()); sc != "" { + clauses = append(clauses, "sport_code = ?") + args = append(args, sc) + } + // competition_id repeated filter + if len(req.GetCompetitionId()) > 0 { + placeholders := make([]string, 0, len(req.GetCompetitionId())) + for _, id := range req.GetCompetitionId() { + placeholders = append(placeholders, "?") + args = append(args, id) + } + clauses = append(clauses, "competition_id IN ("+strings.Join(placeholders, ",")+")") + } + // status filter (OPEN/CLOSED only) + switch req.GetStatus() { + case sporting.EventStatus_EVENT_STATUS_OPEN: + clauses = append(clauses, "advertised_start_time > CURRENT_TIMESTAMP") + case sporting.EventStatus_EVENT_STATUS_CLOSED: + clauses = append(clauses, "advertised_start_time <= CURRENT_TIMESTAMP") + } + if len(clauses) > 0 { + q += " WHERE " + strings.Join(clauses, " AND ") + } + q += buildOrderBy(req.GetOrderBy(), req.GetOrderDirection()) + limit, offset, nextToken, err := interpretPaging(req.GetPageSize(), req.GetPageToken()) + if err != nil { + return nil, "", err + } + q += fmt.Sprintf(" LIMIT %d OFFSET %d", limit, offset) + rows, err := r.db.Query(q, args...) + if err != nil { + return nil, "", err + } + events, err := r.scan(rows) + if err != nil { + return nil, "", err + } + if len(events) == limit { + return events, nextToken, nil + } + return events, "", nil +} + +// interpretPaging converts AIP page_size+token to SQL limit/offset. +func interpretPaging(pageSize int32, token string) (limit int, offset int, nextToken string, err error) { + const maxPage = 200 + if pageSize <= 0 { + pageSize = 50 + } + if pageSize > maxPage { + pageSize = maxPage + } + if token != "" { + b, decErr := base64.URLEncoding.DecodeString(token) + if decErr != nil { + return 0, 0, "", fmt.Errorf("invalid page_token") + } + o, convErr := strconv.Atoi(string(b)) + if convErr != nil { + return 0, 0, "", fmt.Errorf("invalid page_token") + } + offset = o + } + limit = int(pageSize) + nextToken = base64.URLEncoding.EncodeToString([]byte(strconv.Itoa(offset + limit))) + return +} + +// (Removed legacy free-form filter parser in favor of structured query params.) + +func buildOrderBy(field, direction string) string { + f := strings.TrimSpace(strings.ToLower(field)) + if f == "" { + f = "advertised_start_time" + } + if f != "advertised_start_time" { // enforce whitelist + f = "advertised_start_time" + } + d := strings.ToLower(strings.TrimSpace(direction)) + if d != "desc" { // default asc + d = "asc" + } + if d == "desc" { + return " ORDER BY " + f + " DESC" + } + return " ORDER BY " + f + " ASC" +} + +func (r *eventsRepo) Get(id int64) (*sporting.Event, error) { + row := r.db.QueryRow(`SELECT id, competition_id, name, visible, advertised_start_time, sport_code FROM events WHERE id = ? LIMIT 1`, id) + var ( + e sporting.Event + start time.Time + ) + if err := row.Scan(&e.Id, &e.CompetitionId, &e.Name, &e.Visible, &start, &e.SportCode); err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, err + } + ts, err := ptypes.TimestampProto(start) + if err != nil { + return nil, err + } + e.AdvertisedStartTime = ts + if start.After(time.Now()) { + e.Status = sporting.EventStatus_EVENT_STATUS_OPEN + } else { + e.Status = sporting.EventStatus_EVENT_STATUS_CLOSED + } + return &e, nil +} + +func (r *eventsRepo) scan(rows *sql.Rows) ([]*sporting.Event, error) { + var out []*sporting.Event + for rows.Next() { + var e sporting.Event + var start time.Time + if err := rows.Scan(&e.Id, &e.CompetitionId, &e.Name, &e.Visible, &start, &e.SportCode); err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, err + } + ts, err := ptypes.TimestampProto(start) + if err != nil { + return nil, err + } + e.AdvertisedStartTime = ts + if start.After(time.Now()) { + e.Status = sporting.EventStatus_EVENT_STATUS_OPEN + } else { + e.Status = sporting.EventStatus_EVENT_STATUS_CLOSED + } + out = append(out, &e) + } + return out, nil +} diff --git a/sport/go.mod b/sport/go.mod new file mode 100644 index 0000000..82d215e --- /dev/null +++ b/sport/go.mod @@ -0,0 +1,18 @@ +module sport + +go 1.23 + +require ( + github.com/golang/protobuf v1.5.4 + github.com/mattn/go-sqlite3 v1.14.22 + golang.org/x/net v0.26.0 + google.golang.org/grpc v1.65.0 + google.golang.org/protobuf v1.34.2 + syreclabs.com/go/faker v1.2.3 +) + +require ( + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.16.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect +) diff --git a/sport/go.sum b/sport/go.sum new file mode 100644 index 0000000..768d5b6 --- /dev/null +++ b/sport/go.sum @@ -0,0 +1,20 @@ +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +syreclabs.com/go/faker v1.2.3 h1:HPrWtnHazIf0/bVuPZJLFrtHlBHk10hS0SB+mV8v6R4= +syreclabs.com/go/faker v1.2.3/go.mod h1:NAXInmkPsC2xuO5MKZFe80PUXX5LU8cFdJIHGs+nSBE= diff --git a/sport/main.go b/sport/main.go new file mode 100644 index 0000000..83d7634 --- /dev/null +++ b/sport/main.go @@ -0,0 +1,47 @@ +package main + +import ( + "database/sql" + "flag" + "log" + "net" + + "sport/db" + "sport/proto/sporting" + "sport/service" + + "google.golang.org/grpc" +) + +var ( + grpcEndpoint = flag.String("grpc-endpoint", "localhost:9010", "gRPC server endpoint") +) + +func main() { + flag.Parse() + if err := run(); err != nil { + log.Fatalf("failed running sport grpc server: %v", err) + } +} + +func run() error { + ln, err := net.Listen("tcp", ":9010") + if err != nil { + return err + } + + eventsDB, err := sql.Open("sqlite3", "./db/events.db") + if err != nil { + return err + } + + eventsRepo := db.NewEventsRepo(eventsDB) + if err := eventsRepo.Init(); err != nil { + return err + } + + grpcServer := grpc.NewServer() + sporting.RegisterSportingServer(grpcServer, service.NewSportingService(eventsRepo)) + log.Printf("Sport gRPC server listening on: %s", *grpcEndpoint) + return grpcServer.Serve(ln) +} diff --git a/sport/proto/events.go b/sport/proto/events.go new file mode 100644 index 0000000..dd298a0 --- /dev/null +++ b/sport/proto/events.go @@ -0,0 +1,3 @@ +package proto + +//go:generate protoc --go_out=. --go-grpc_out=require_unimplemented_servers=false:. sporting/sporting.proto --experimental_allow_proto3_optional diff --git a/sport/proto/sporting.go b/sport/proto/sporting.go new file mode 100644 index 0000000..92256db --- /dev/null +++ b/sport/proto/sporting.go @@ -0,0 +1 @@ +package proto diff --git a/sport/proto/sporting/events.proto b/sport/proto/sporting/events.proto new file mode 100644 index 0000000..e69de29 diff --git a/sport/proto/sporting/sporting.pb.go b/sport/proto/sporting/sporting.pb.go new file mode 100644 index 0000000..d3ff07b --- /dev/null +++ b/sport/proto/sporting/sporting.pb.go @@ -0,0 +1,475 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.7 +// protoc v6.32.0 +// source: sporting/sporting.proto + +package sporting + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +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) +) + +// EventStatus derived from advertised_start_time relative to now. +type EventStatus int32 + +const ( + EventStatus_EVENT_STATUS_UNSPECIFIED EventStatus = 0 + EventStatus_EVENT_STATUS_OPEN EventStatus = 1 // start time in future + EventStatus_EVENT_STATUS_CLOSED EventStatus = 2 // start time <= now +) + +// Enum value maps for EventStatus. +var ( + EventStatus_name = map[int32]string{ + 0: "EVENT_STATUS_UNSPECIFIED", + 1: "EVENT_STATUS_OPEN", + 2: "EVENT_STATUS_CLOSED", + } + EventStatus_value = map[string]int32{ + "EVENT_STATUS_UNSPECIFIED": 0, + "EVENT_STATUS_OPEN": 1, + "EVENT_STATUS_CLOSED": 2, + } +) + +func (x EventStatus) Enum() *EventStatus { + p := new(EventStatus) + *p = x + return p +} + +func (x EventStatus) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (EventStatus) Descriptor() protoreflect.EnumDescriptor { + return file_sporting_sporting_proto_enumTypes[0].Descriptor() +} + +func (EventStatus) Type() protoreflect.EnumType { + return &file_sporting_sporting_proto_enumTypes[0] +} + +func (x EventStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use EventStatus.Descriptor instead. +func (EventStatus) EnumDescriptor() ([]byte, []int) { + return file_sporting_sporting_proto_rawDescGZIP(), []int{0} +} + +// ListEventsRequest follows https://google.aip.dev/131. +// Supported filter fields (AND combinations only): +// visible (true/false), sport_code (string equality), competition_id (number or IN list), status (OPEN/CLOSED) +// Examples: +// +// visible = true AND sport_code = "SOCCER" +// competition_id IN (1,2,3) AND status = OPEN +type ListEventsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + PageSize int32 `protobuf:"varint,1,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` // optional; server-enforced max. + PageToken string `protobuf:"bytes,2,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` // opaque token from a previous ListEventsResponse. + CompetitionId []int64 `protobuf:"varint,3,rep,packed,name=competition_id,json=competitionId,proto3" json:"competition_id,omitempty"` // repeated query param competition_id=1&competition_id=2 + Visible *bool `protobuf:"varint,4,opt,name=visible,proto3,oneof" json:"visible,omitempty"` // visible=true to filter; omitted = no filter + SportCode string `protobuf:"bytes,5,opt,name=sport_code,json=sportCode,proto3" json:"sport_code,omitempty"` // equality match if provided + Status EventStatus `protobuf:"varint,6,opt,name=status,proto3,enum=sporting.EventStatus" json:"status,omitempty"` // OPEN/CLOSED filter if set (UNSPECIFIED = no filter) + OrderBy string `protobuf:"bytes,7,opt,name=order_by,json=orderBy,proto3" json:"order_by,omitempty"` // field name (currently only advertised_start_time) + OrderDirection string `protobuf:"bytes,8,opt,name=order_direction,json=orderDirection,proto3" json:"order_direction,omitempty"` // asc|desc (default asc) + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListEventsRequest) Reset() { + *x = ListEventsRequest{} + mi := &file_sporting_sporting_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListEventsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListEventsRequest) ProtoMessage() {} + +func (x *ListEventsRequest) ProtoReflect() protoreflect.Message { + mi := &file_sporting_sporting_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 ListEventsRequest.ProtoReflect.Descriptor instead. +func (*ListEventsRequest) Descriptor() ([]byte, []int) { + return file_sporting_sporting_proto_rawDescGZIP(), []int{0} +} + +func (x *ListEventsRequest) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +func (x *ListEventsRequest) GetPageToken() string { + if x != nil { + return x.PageToken + } + return "" +} + +func (x *ListEventsRequest) GetCompetitionId() []int64 { + if x != nil { + return x.CompetitionId + } + return nil +} + +func (x *ListEventsRequest) GetVisible() bool { + if x != nil && x.Visible != nil { + return *x.Visible + } + return false +} + +func (x *ListEventsRequest) GetSportCode() string { + if x != nil { + return x.SportCode + } + return "" +} + +func (x *ListEventsRequest) GetStatus() EventStatus { + if x != nil { + return x.Status + } + return EventStatus_EVENT_STATUS_UNSPECIFIED +} + +func (x *ListEventsRequest) GetOrderBy() string { + if x != nil { + return x.OrderBy + } + return "" +} + +func (x *ListEventsRequest) GetOrderDirection() string { + if x != nil { + return x.OrderDirection + } + return "" +} + +type ListEventsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Events []*Event `protobuf:"bytes,1,rep,name=events,proto3" json:"events,omitempty"` + NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` // empty when no further pages. + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListEventsResponse) Reset() { + *x = ListEventsResponse{} + mi := &file_sporting_sporting_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListEventsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListEventsResponse) ProtoMessage() {} + +func (x *ListEventsResponse) ProtoReflect() protoreflect.Message { + mi := &file_sporting_sporting_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 ListEventsResponse.ProtoReflect.Descriptor instead. +func (*ListEventsResponse) Descriptor() ([]byte, []int) { + return file_sporting_sporting_proto_rawDescGZIP(), []int{1} +} + +func (x *ListEventsResponse) GetEvents() []*Event { + if x != nil { + return x.Events + } + return nil +} + +func (x *ListEventsResponse) GetNextPageToken() string { + if x != nil { + return x.NextPageToken + } + return "" +} + +// Get single event by numeric id. +type GetEventRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetEventRequest) Reset() { + *x = GetEventRequest{} + mi := &file_sporting_sporting_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetEventRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetEventRequest) ProtoMessage() {} + +func (x *GetEventRequest) ProtoReflect() protoreflect.Message { + mi := &file_sporting_sporting_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 GetEventRequest.ProtoReflect.Descriptor instead. +func (*GetEventRequest) Descriptor() ([]byte, []int) { + return file_sporting_sporting_proto_rawDescGZIP(), []int{2} +} + +func (x *GetEventRequest) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +// Event resource. +type Event struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` // unique id + CompetitionId int64 `protobuf:"varint,2,opt,name=competition_id,json=competitionId,proto3" json:"competition_id,omitempty"` // grouping id + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` // event/fixture name + Visible bool `protobuf:"varint,4,opt,name=visible,proto3" json:"visible,omitempty"` // visibility flag + AdvertisedStartTime *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=advertised_start_time,json=advertisedStartTime,proto3" json:"advertised_start_time,omitempty"` // kickoff/start + Status EventStatus `protobuf:"varint,6,opt,name=status,proto3,enum=sporting.EventStatus" json:"status,omitempty"` // derived open/closed + SportCode string `protobuf:"bytes,7,opt,name=sport_code,json=sportCode,proto3" json:"sport_code,omitempty"` // e.g. SOCCER, TENNIS + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Event) Reset() { + *x = Event{} + mi := &file_sporting_sporting_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Event) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Event) ProtoMessage() {} + +func (x *Event) ProtoReflect() protoreflect.Message { + mi := &file_sporting_sporting_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 Event.ProtoReflect.Descriptor instead. +func (*Event) Descriptor() ([]byte, []int) { + return file_sporting_sporting_proto_rawDescGZIP(), []int{3} +} + +func (x *Event) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *Event) GetCompetitionId() int64 { + if x != nil { + return x.CompetitionId + } + return 0 +} + +func (x *Event) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Event) GetVisible() bool { + if x != nil { + return x.Visible + } + return false +} + +func (x *Event) GetAdvertisedStartTime() *timestamppb.Timestamp { + if x != nil { + return x.AdvertisedStartTime + } + return nil +} + +func (x *Event) GetStatus() EventStatus { + if x != nil { + return x.Status + } + return EventStatus_EVENT_STATUS_UNSPECIFIED +} + +func (x *Event) GetSportCode() string { + if x != nil { + return x.SportCode + } + return "" +} + +var File_sporting_sporting_proto protoreflect.FileDescriptor + +const file_sporting_sporting_proto_rawDesc = "" + + "\n" + + "\x17sporting/sporting.proto\x12\bsporting\x1a\x1fgoogle/protobuf/timestamp.proto\"\xb3\x02\n" + + "\x11ListEventsRequest\x12\x1b\n" + + "\tpage_size\x18\x01 \x01(\x05R\bpageSize\x12\x1d\n" + + "\n" + + "page_token\x18\x02 \x01(\tR\tpageToken\x12%\n" + + "\x0ecompetition_id\x18\x03 \x03(\x03R\rcompetitionId\x12\x1d\n" + + "\avisible\x18\x04 \x01(\bH\x00R\avisible\x88\x01\x01\x12\x1d\n" + + "\n" + + "sport_code\x18\x05 \x01(\tR\tsportCode\x12-\n" + + "\x06status\x18\x06 \x01(\x0e2\x15.sporting.EventStatusR\x06status\x12\x19\n" + + "\border_by\x18\a \x01(\tR\aorderBy\x12'\n" + + "\x0forder_direction\x18\b \x01(\tR\x0eorderDirectionB\n" + + "\n" + + "\b_visible\"e\n" + + "\x12ListEventsResponse\x12'\n" + + "\x06events\x18\x01 \x03(\v2\x0f.sporting.EventR\x06events\x12&\n" + + "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"!\n" + + "\x0fGetEventRequest\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x03R\x02id\"\x8a\x02\n" + + "\x05Event\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x03R\x02id\x12%\n" + + "\x0ecompetition_id\x18\x02 \x01(\x03R\rcompetitionId\x12\x12\n" + + "\x04name\x18\x03 \x01(\tR\x04name\x12\x18\n" + + "\avisible\x18\x04 \x01(\bR\avisible\x12N\n" + + "\x15advertised_start_time\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\x13advertisedStartTime\x12-\n" + + "\x06status\x18\x06 \x01(\x0e2\x15.sporting.EventStatusR\x06status\x12\x1d\n" + + "\n" + + "sport_code\x18\a \x01(\tR\tsportCode*[\n" + + "\vEventStatus\x12\x1c\n" + + "\x18EVENT_STATUS_UNSPECIFIED\x10\x00\x12\x15\n" + + "\x11EVENT_STATUS_OPEN\x10\x01\x12\x17\n" + + "\x13EVENT_STATUS_CLOSED\x10\x022\x8f\x01\n" + + "\bSporting\x12I\n" + + "\n" + + "ListEvents\x12\x1b.sporting.ListEventsRequest\x1a\x1c.sporting.ListEventsResponse\"\x00\x128\n" + + "\bGetEvent\x12\x19.sporting.GetEventRequest\x1a\x0f.sporting.Event\"\x00B\vZ\t/sportingb\x06proto3" + +var ( + file_sporting_sporting_proto_rawDescOnce sync.Once + file_sporting_sporting_proto_rawDescData []byte +) + +func file_sporting_sporting_proto_rawDescGZIP() []byte { + file_sporting_sporting_proto_rawDescOnce.Do(func() { + file_sporting_sporting_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_sporting_sporting_proto_rawDesc), len(file_sporting_sporting_proto_rawDesc))) + }) + return file_sporting_sporting_proto_rawDescData +} + +var file_sporting_sporting_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_sporting_sporting_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_sporting_sporting_proto_goTypes = []any{ + (EventStatus)(0), // 0: sporting.EventStatus + (*ListEventsRequest)(nil), // 1: sporting.ListEventsRequest + (*ListEventsResponse)(nil), // 2: sporting.ListEventsResponse + (*GetEventRequest)(nil), // 3: sporting.GetEventRequest + (*Event)(nil), // 4: sporting.Event + (*timestamppb.Timestamp)(nil), // 5: google.protobuf.Timestamp +} +var file_sporting_sporting_proto_depIdxs = []int32{ + 0, // 0: sporting.ListEventsRequest.status:type_name -> sporting.EventStatus + 4, // 1: sporting.ListEventsResponse.events:type_name -> sporting.Event + 5, // 2: sporting.Event.advertised_start_time:type_name -> google.protobuf.Timestamp + 0, // 3: sporting.Event.status:type_name -> sporting.EventStatus + 1, // 4: sporting.Sporting.ListEvents:input_type -> sporting.ListEventsRequest + 3, // 5: sporting.Sporting.GetEvent:input_type -> sporting.GetEventRequest + 2, // 6: sporting.Sporting.ListEvents:output_type -> sporting.ListEventsResponse + 4, // 7: sporting.Sporting.GetEvent:output_type -> sporting.Event + 6, // [6:8] is the sub-list for method output_type + 4, // [4:6] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name +} + +func init() { file_sporting_sporting_proto_init() } +func file_sporting_sporting_proto_init() { + if File_sporting_sporting_proto != nil { + return + } + file_sporting_sporting_proto_msgTypes[0].OneofWrappers = []any{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_sporting_sporting_proto_rawDesc), len(file_sporting_sporting_proto_rawDesc)), + NumEnums: 1, + NumMessages: 4, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_sporting_sporting_proto_goTypes, + DependencyIndexes: file_sporting_sporting_proto_depIdxs, + EnumInfos: file_sporting_sporting_proto_enumTypes, + MessageInfos: file_sporting_sporting_proto_msgTypes, + }.Build() + File_sporting_sporting_proto = out.File + file_sporting_sporting_proto_goTypes = nil + file_sporting_sporting_proto_depIdxs = nil +} diff --git a/sport/proto/sporting/sporting.proto b/sport/proto/sporting/sporting.proto new file mode 100644 index 0000000..d105e42 --- /dev/null +++ b/sport/proto/sporting/sporting.proto @@ -0,0 +1,59 @@ +syntax = "proto3"; +package sporting; + +option go_package = "/sporting"; + +import "google/protobuf/timestamp.proto"; + +// EventStatus derived from advertised_start_time relative to now. +enum EventStatus { + option allow_alias = true; + EVENT_STATUS_UNSPECIFIED = 0; + EVENT_STATUS_OPEN = 1; // start time in future + EVENT_STATUS_CLOSED = 2; // start time <= now + OPEN = 1; // alias + CLOSED = 2; // alias +} + +service Sporting { + // AIP-131 compliant list method. + rpc ListEvents(ListEventsRequest) returns (ListEventsResponse) {} + // GetEvent returns a single event by numeric id. + rpc GetEvent(GetEventRequest) returns (Event) {} +} + +// ListEventsRequest follows https://google.aip.dev/131. +// Supported filter fields (AND combinations only): +// visible (true/false), sport_code (string equality), competition_id (number or IN list), status (OPEN/CLOSED) +// Examples: +// visible = true AND sport_code = "SOCCER" +// competition_id IN (1,2,3) AND status = OPEN +message ListEventsRequest { + int32 page_size = 1; // optional; server-enforced max. + string page_token = 2; // opaque token from a previous ListEventsResponse. + repeated int64 competition_id = 3; // repeated query param competition_id=1&competition_id=2 + optional bool visible = 4; // visible=true to filter; omitted = no filter + string sport_code = 5; // equality match if provided + EventStatus status = 6; // OPEN/CLOSED filter if set (UNSPECIFIED = no filter) + string order_by = 7; // field name (currently only advertised_start_time) + string order_direction = 8; // asc|desc (default asc) +} + +message ListEventsResponse { + repeated Event events = 1; + string next_page_token = 2; // empty when no further pages. +} + +// Get single event by numeric id. +message GetEventRequest { int64 id = 1; } + +// Event resource. +message Event { + int64 id = 1; // unique id + int64 competition_id = 2; // grouping id + string name = 3; // event/fixture name + bool visible = 4; // visibility flag + google.protobuf.Timestamp advertised_start_time = 5; // kickoff/start + EventStatus status = 6; // derived open/closed + string sport_code = 7; // e.g. SOCCER, TENNIS +} diff --git a/sport/proto/sporting/sporting_grpc.pb.go b/sport/proto/sporting/sporting_grpc.pb.go new file mode 100644 index 0000000..87e7777 --- /dev/null +++ b/sport/proto/sporting/sporting_grpc.pb.go @@ -0,0 +1,161 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc v6.32.0 +// source: sporting/sporting.proto + +package sporting + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + Sporting_ListEvents_FullMethodName = "/sporting.Sporting/ListEvents" + Sporting_GetEvent_FullMethodName = "/sporting.Sporting/GetEvent" +) + +// SportingClient is the client API for Sporting service. +// +// 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 SportingClient interface { + // AIP-131 compliant list method. + ListEvents(ctx context.Context, in *ListEventsRequest, opts ...grpc.CallOption) (*ListEventsResponse, error) + // GetEvent returns a single event by numeric id. + GetEvent(ctx context.Context, in *GetEventRequest, opts ...grpc.CallOption) (*Event, error) +} + +type sportingClient struct { + cc grpc.ClientConnInterface +} + +func NewSportingClient(cc grpc.ClientConnInterface) SportingClient { + return &sportingClient{cc} +} + +func (c *sportingClient) ListEvents(ctx context.Context, in *ListEventsRequest, opts ...grpc.CallOption) (*ListEventsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListEventsResponse) + err := c.cc.Invoke(ctx, Sporting_ListEvents_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *sportingClient) GetEvent(ctx context.Context, in *GetEventRequest, opts ...grpc.CallOption) (*Event, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Event) + err := c.cc.Invoke(ctx, Sporting_GetEvent_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// SportingServer is the server API for Sporting service. +// All implementations should embed UnimplementedSportingServer +// for forward compatibility. +type SportingServer interface { + // AIP-131 compliant list method. + ListEvents(context.Context, *ListEventsRequest) (*ListEventsResponse, error) + // GetEvent returns a single event by numeric id. + GetEvent(context.Context, *GetEventRequest) (*Event, error) +} + +// UnimplementedSportingServer should be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedSportingServer struct{} + +func (UnimplementedSportingServer) ListEvents(context.Context, *ListEventsRequest) (*ListEventsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListEvents not implemented") +} +func (UnimplementedSportingServer) GetEvent(context.Context, *GetEventRequest) (*Event, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetEvent not implemented") +} +func (UnimplementedSportingServer) testEmbeddedByValue() {} + +// UnsafeSportingServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to SportingServer will +// result in compilation errors. +type UnsafeSportingServer interface { + mustEmbedUnimplementedSportingServer() +} + +func RegisterSportingServer(s grpc.ServiceRegistrar, srv SportingServer) { + // If the following call pancis, it indicates UnimplementedSportingServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&Sporting_ServiceDesc, srv) +} + +func _Sporting_ListEvents_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListEventsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SportingServer).ListEvents(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Sporting_ListEvents_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SportingServer).ListEvents(ctx, req.(*ListEventsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Sporting_GetEvent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetEventRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SportingServer).GetEvent(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Sporting_GetEvent_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SportingServer).GetEvent(ctx, req.(*GetEventRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// Sporting_ServiceDesc is the grpc.ServiceDesc for Sporting service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Sporting_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "sporting.Sporting", + HandlerType: (*SportingServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ListEvents", + Handler: _Sporting_ListEvents_Handler, + }, + { + MethodName: "GetEvent", + Handler: _Sporting_GetEvent_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "sporting/sporting.proto", +} diff --git a/sport/service/events.go b/sport/service/events.go new file mode 100644 index 0000000..6dc0019 --- /dev/null +++ b/sport/service/events.go @@ -0,0 +1,41 @@ +package service + +import ( + "sport/db" + "sport/proto/sporting" + + "golang.org/x/net/context" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +type Sporting interface { + ListEvents(ctx context.Context, in *sporting.ListEventsRequest) (*sporting.ListEventsResponse, error) + GetEvent(ctx context.Context, in *sporting.GetEventRequest) (*sporting.Event, error) +} + +type sportingService struct{ repo db.EventsRepo } + +func NewSportingService(r db.EventsRepo) Sporting { return &sportingService{repo: r} } + +func (s *sportingService) ListEvents(ctx context.Context, in *sporting.ListEventsRequest) (*sporting.ListEventsResponse, error) { + events, nextToken, err := s.repo.List(in) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "list events: %v", err) + } + return &sporting.ListEventsResponse{Events: events, NextPageToken: nextToken}, nil +} + +func (s *sportingService) GetEvent(ctx context.Context, in *sporting.GetEventRequest) (*sporting.Event, error) { + if in.GetId() <= 0 { + return nil, status.Error(codes.InvalidArgument, "id must be positive") + } + ev, err := s.repo.Get(in.GetId()) + if err != nil { + return nil, status.Errorf(codes.Internal, "get event: %v", err) + } + if ev == nil { + return nil, status.Error(codes.NotFound, "event not found") + } + return ev, nil +} From 4966e9f1461da1545895ae9889ab5347500b72fb Mon Sep 17 00:00:00 2001 From: benrobey Date: Mon, 18 Aug 2025 10:44:57 +1000 Subject: [PATCH 3/3] Add unit tests for event listing and retrieval in service layer --- sport/db/events_test.go | 290 +++++++++++++++++++++++++++++++++++ sport/go.mod | 1 + sport/go.sum | 2 + sport/service/events_test.go | 81 ++++++++++ 4 files changed, 374 insertions(+) create mode 100644 sport/db/events_test.go create mode 100644 sport/service/events_test.go diff --git a/sport/db/events_test.go b/sport/db/events_test.go new file mode 100644 index 0000000..eea3a85 --- /dev/null +++ b/sport/db/events_test.go @@ -0,0 +1,290 @@ +package db + +import ( + "fmt" + "regexp" + "testing" + "time" + + "sport/proto/sporting" + "github.com/DATA-DOG/go-sqlmock" +) + +func TestListEvents_DefaultOrdering(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { t.Fatalf("mock: %v", err) } + defer db.Close() + repo := NewEventsRepo(db) + + future1 := time.Now().Add(10 * time.Minute) + future2 := time.Now().Add(20 * time.Minute) + + rows := sqlmock.NewRows([]string{"id","competition_id","name","visible","advertised_start_time","sport_code"}). + AddRow(1, 10, "Match A", 1, future1, "SOCCER"). + AddRow(2, 10, "Match B", 1, future2, "SOCCER") + + mock.ExpectQuery(regexp.QuoteMeta("SELECT id, competition_id, name, visible, advertised_start_time, sport_code FROM events ORDER BY advertised_start_time ASC LIMIT 50 OFFSET 0")). + WillReturnRows(rows) + + out, token, err := repo.List(&sporting.ListEventsRequest{}) + if err != nil { t.Fatalf("list: %v", err) } + if token != "" { t.Fatalf("did not expect next page token for non-full page") } + if len(out) != 2 { t.Fatalf("expected 2 got %d", len(out)) } + if out[0].AdvertisedStartTime.AsTime().After(out[1].AdvertisedStartTime.AsTime()) { t.Fatalf("expected ascending order") } +} + +func TestListEvents_VisibleAndSportCode(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { t.Fatalf("mock: %v", err) } + defer db.Close() + repo := NewEventsRepo(db) + visible := true + req := &sporting.ListEventsRequest{Visible: &visible, SportCode: "SOCCER"} + + future := time.Now().Add(5 * time.Minute) + rows := sqlmock.NewRows([]string{"id","competition_id","name","visible","advertised_start_time","sport_code"}). + AddRow(5, 22, "Derby", 1, future, "SOCCER") + + mock.ExpectQuery(regexp.QuoteMeta("SELECT id, competition_id, name, visible, advertised_start_time, sport_code FROM events WHERE visible = 1 AND sport_code = ? ORDER BY advertised_start_time ASC LIMIT 50 OFFSET 0")). + WithArgs("SOCCER"). + WillReturnRows(rows) + + out, token, err := repo.List(req) + if err != nil { t.Fatalf("list: %v", err) } + if token != "" { t.Fatalf("unexpected token when results < page size") } + if len(out) != 1 { t.Fatalf("expected 1 got %d", len(out)) } + if out[0].SportCode != "SOCCER" || !out[0].Visible { t.Fatalf("filter mismatch") } +} + +func TestListEvents_StatusFilter(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { t.Fatalf("mock: %v", err) } + defer db.Close() + repo := NewEventsRepo(db) + req := &sporting.ListEventsRequest{Status: sporting.EventStatus_EVENT_STATUS_OPEN} + + future := time.Now().Add(30 * time.Minute) + rows := sqlmock.NewRows([]string{"id","competition_id","name","visible","advertised_start_time","sport_code"}). + AddRow(9, 5, "Final", 1, future, "TENNIS") + + mock.ExpectQuery(regexp.QuoteMeta("SELECT id, competition_id, name, visible, advertised_start_time, sport_code FROM events WHERE advertised_start_time > CURRENT_TIMESTAMP ORDER BY advertised_start_time ASC LIMIT 50 OFFSET 0")). + WillReturnRows(rows) + + out, _, err := repo.List(req) + if err != nil { t.Fatalf("list: %v", err) } + if len(out) != 1 { t.Fatalf("expected 1 got %d", len(out)) } + if out[0].Status != sporting.EventStatus_EVENT_STATUS_OPEN { t.Fatalf("expected OPEN got %v", out[0].Status) } +} + +func TestListEvents_PaginationOffset(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { t.Fatalf("mock: %v", err) } + defer db.Close() + repo := NewEventsRepo(db) + + // Build a full page of 50 rows to trigger token creation + rows1 := sqlmock.NewRows([]string{"id","competition_id","name","visible","advertised_start_time","sport_code"}) + for i := 1; i <= 50; i++ { + rows1.AddRow(i, 1, "M"+fmt.Sprint(i), 1, time.Now().Add(time.Duration(i)*time.Minute), "SOCCER") + } + mock.ExpectQuery(regexp.QuoteMeta("SELECT id, competition_id, name, visible, advertised_start_time, sport_code FROM events ORDER BY advertised_start_time ASC LIMIT 50 OFFSET 0")). + WillReturnRows(rows1) + first, token, err := repo.List(&sporting.ListEventsRequest{}) + if err != nil { t.Fatalf("first: %v", err) } + if len(first) != 50 { t.Fatalf("expected 50 got %d", len(first)) } + if token == "" { t.Fatalf("expected token for next page when full page returned") } + + // second page (offset 50) + rows2 := sqlmock.NewRows([]string{"id","competition_id","name","visible","advertised_start_time","sport_code"}). + AddRow(51, 1, "M51", 1, time.Now().Add(51*time.Minute), "SOCCER") + mock.ExpectQuery(regexp.QuoteMeta("SELECT id, competition_id, name, visible, advertised_start_time, sport_code FROM events ORDER BY advertised_start_time ASC LIMIT 50 OFFSET 50")). + WillReturnRows(rows2) + second, _, err := repo.List(&sporting.ListEventsRequest{PageToken: token}) + if err != nil { t.Fatalf("second: %v", err) } + if len(second) != 1 || second[0].Id != 51 { t.Fatalf("unexpected second page result") } +} + +func TestListEvents_CustomPageSize(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { t.Fatalf("mock: %v", err) } + defer db.Close() + repo := NewEventsRepo(db) + + rows := sqlmock.NewRows([]string{"id","competition_id","name","visible","advertised_start_time","sport_code"}). + AddRow(1, 1, "A", 1, time.Now().Add(1*time.Hour), "SOCCER"). + AddRow(2, 1, "B", 1, time.Now().Add(2*time.Hour), "SOCCER") + + mock.ExpectQuery(regexp.QuoteMeta("SELECT id, competition_id, name, visible, advertised_start_time, sport_code FROM events ORDER BY advertised_start_time ASC LIMIT 2 OFFSET 0")). + WillReturnRows(rows) + + list, token, err := repo.List(&sporting.ListEventsRequest{PageSize: 2}) + if err != nil { t.Fatalf("list: %v", err) } + if len(list) != 2 { t.Fatalf("expected 2 got %d", len(list)) } + if token == "" { t.Fatalf("expected next token for full custom page") } +} + +func TestListEvents_MaxPageSizeCap(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { t.Fatalf("mock: %v", err) } + defer db.Close() + repo := NewEventsRepo(db) + + // request 500 but expect 200 cap + rows := sqlmock.NewRows([]string{"id","competition_id","name","visible","advertised_start_time","sport_code"}) + for i := 1; i <= 199; i++ { // not full page -> no token + rows.AddRow(i, 1, fmt.Sprintf("E%d", i), 1, time.Now().Add(time.Duration(i)*time.Minute), "SOCCER") + } + mock.ExpectQuery(regexp.QuoteMeta("SELECT id, competition_id, name, visible, advertised_start_time, sport_code FROM events ORDER BY advertised_start_time ASC LIMIT 200 OFFSET 0")). + WillReturnRows(rows) + + list, token, err := repo.List(&sporting.ListEventsRequest{PageSize: 500}) + if err != nil { t.Fatalf("list: %v", err) } + if len(list) != 199 { t.Fatalf("expected 199 got %d", len(list)) } + if token != "" { t.Fatalf("did not expect token when page not full (cap 200)") } +} + +func TestListEvents_LastPageNoToken(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { t.Fatalf("mock: %v", err) } + defer db.Close() + repo := NewEventsRepo(db) + + rows := sqlmock.NewRows([]string{"id","competition_id","name","visible","advertised_start_time","sport_code"}). + AddRow(1, 1, "Only", 1, time.Now().Add(1*time.Hour), "SOCCER") + mock.ExpectQuery(regexp.QuoteMeta("SELECT id, competition_id, name, visible, advertised_start_time, sport_code FROM events ORDER BY advertised_start_time ASC LIMIT 3 OFFSET 0")). + WillReturnRows(rows) + + list, token, err := repo.List(&sporting.ListEventsRequest{PageSize: 3}) + if err != nil { t.Fatalf("list: %v", err) } + if len(list) != 1 { t.Fatalf("expected 1 got %d", len(list)) } + if token != "" { t.Fatalf("expected empty token on last page") } +} + +func TestListEvents_CompetitionIDIn(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { t.Fatalf("mock: %v", err) } + defer db.Close() + repo := NewEventsRepo(db) + + rows := sqlmock.NewRows([]string{"id","competition_id","name","visible","advertised_start_time","sport_code"}). + AddRow(10, 10, "Comp10", 1, time.Now().Add(1*time.Hour), "SOCCER") + + mock.ExpectQuery(regexp.QuoteMeta("SELECT id, competition_id, name, visible, advertised_start_time, sport_code FROM events WHERE competition_id IN (?,?,?) ORDER BY advertised_start_time ASC LIMIT 50 OFFSET 0")). + WillReturnRows(rows) + + list, _, err := repo.List(&sporting.ListEventsRequest{CompetitionId: []int64{10,20,30}}) + if err != nil { t.Fatalf("list: %v", err) } + if len(list) != 1 || list[0].CompetitionId != 10 { t.Fatalf("expected competition 10 result") } +} + +func TestListEvents_StatusClosed(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { t.Fatalf("mock: %v", err) } + defer db.Close() + repo := NewEventsRepo(db) + + rows := sqlmock.NewRows([]string{"id","competition_id","name","visible","advertised_start_time","sport_code"}). + AddRow(2, 1, "Past", 1, time.Now().Add(-1*time.Hour), "SOCCER") + + mock.ExpectQuery(regexp.QuoteMeta("SELECT id, competition_id, name, visible, advertised_start_time, sport_code FROM events WHERE advertised_start_time <= CURRENT_TIMESTAMP ORDER BY advertised_start_time ASC LIMIT 50 OFFSET 0")). + WillReturnRows(rows) + + list, _, err := repo.List(&sporting.ListEventsRequest{Status: sporting.EventStatus_EVENT_STATUS_CLOSED}) + if err != nil { t.Fatalf("list: %v", err) } + if len(list) != 1 { t.Fatalf("expected 1 got %d", len(list)) } +} + +func TestListEvents_VisibleFalse(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { t.Fatalf("mock: %v", err) } + defer db.Close() + repo := NewEventsRepo(db) + visFalse := false + + rows := sqlmock.NewRows([]string{"id","competition_id","name","visible","advertised_start_time","sport_code"}). + AddRow(3, 5, "Hidden", 0, time.Now().Add(1*time.Hour), "SOCCER") + + mock.ExpectQuery(regexp.QuoteMeta("SELECT id, competition_id, name, visible, advertised_start_time, sport_code FROM events WHERE visible = 0 ORDER BY advertised_start_time ASC LIMIT 50 OFFSET 0")). + WillReturnRows(rows) + + list, _, err := repo.List(&sporting.ListEventsRequest{Visible: &visFalse}) + if err != nil { t.Fatalf("list: %v", err) } + if len(list) != 1 || list[0].Visible { t.Fatalf("expected 1 hidden event") } +} + +func TestListEvents_CombinedFilters(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { t.Fatalf("mock: %v", err) } + defer db.Close() + repo := NewEventsRepo(db) + visTrue := true + + rows := sqlmock.NewRows([]string{"id","competition_id","name","visible","advertised_start_time","sport_code"}). + AddRow(42, 7, "Combo", 1, time.Now().Add(-10*time.Minute), "SOCCER") + + // Clause order: visible, sport_code, competition_id IN, status (CLOSED) + mock.ExpectQuery(regexp.QuoteMeta("SELECT id, competition_id, name, visible, advertised_start_time, sport_code FROM events WHERE visible = 1 AND sport_code = ? AND competition_id IN (?,?,?) AND advertised_start_time <= CURRENT_TIMESTAMP ORDER BY advertised_start_time ASC LIMIT 50 OFFSET 0")). + WithArgs("SOCCER", int64(7), int64(8), int64(9)). + WillReturnRows(rows) + + list, _, err := repo.List(&sporting.ListEventsRequest{Visible: &visTrue, SportCode: "SOCCER", CompetitionId: []int64{7,8,9}, Status: sporting.EventStatus_EVENT_STATUS_CLOSED}) + if err != nil { t.Fatalf("list: %v", err) } + if len(list) != 1 || list[0].Id != 42 { t.Fatalf("expected combined filter match") } +} + +func TestListEvents_OrderDesc(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { t.Fatalf("mock: %v", err) } + defer db.Close() + repo := NewEventsRepo(db) + + rows := sqlmock.NewRows([]string{"id","competition_id","name","visible","advertised_start_time","sport_code"}). + AddRow(1, 1, "Late", 1, time.Now().Add(2*time.Hour), "SOCCER"). + AddRow(2, 1, "Earlier", 1, time.Now().Add(1*time.Hour), "SOCCER") + + mock.ExpectQuery(regexp.QuoteMeta("SELECT id, competition_id, name, visible, advertised_start_time, sport_code FROM events ORDER BY advertised_start_time DESC LIMIT 50 OFFSET 0")). + WillReturnRows(rows) + + list, _, err := repo.List(&sporting.ListEventsRequest{OrderDirection: "DESC"}) + if err != nil { t.Fatalf("list: %v", err) } + if len(list) != 2 { t.Fatalf("expected 2 got %d", len(list)) } + if list[0].AdvertisedStartTime.AsTime().Before(list[1].AdvertisedStartTime.AsTime()) { t.Fatalf("expected descending order") } +} + +func TestListEvents_InvalidPageToken(t *testing.T) { + db, _, err := sqlmock.New() + if err != nil { t.Fatalf("mock: %v", err) } + defer db.Close() + repo := NewEventsRepo(db) + _, _, err = repo.List(&sporting.ListEventsRequest{PageToken: "***not-base64***"}) + if err == nil { t.Fatalf("expected error for invalid page token") } +} + +func TestGetEvent_OpenClosed(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { t.Fatalf("mock: %v", err) } + defer db.Close() + repo := NewEventsRepo(db) + + future := time.Now().Add(1 * time.Hour) + past := time.Now().Add(-1 * time.Hour) + + // Expect two separate queries + mock.ExpectQuery(regexp.QuoteMeta("SELECT id, competition_id, name, visible, advertised_start_time, sport_code FROM events WHERE id = ? LIMIT 1")). + WithArgs(int64(10)). + WillReturnRows(sqlmock.NewRows([]string{"id","competition_id","name","visible","advertised_start_time","sport_code"}). + AddRow(10, 1, "Future Event", 1, future, "SOCCER")) + + mock.ExpectQuery(regexp.QuoteMeta("SELECT id, competition_id, name, visible, advertised_start_time, sport_code FROM events WHERE id = ? LIMIT 1")). + WithArgs(int64(11)). + WillReturnRows(sqlmock.NewRows([]string{"id","competition_id","name","visible","advertised_start_time","sport_code"}). + AddRow(11, 1, "Past Event", 1, past, "SOCCER")) + + e, err := repo.Get(10) + if err != nil || e == nil { t.Fatalf("get open: %v", err) } + if e.Status != sporting.EventStatus_EVENT_STATUS_OPEN { t.Fatalf("expected OPEN got %v", e.Status) } + + e2, err := repo.Get(11) + if err != nil || e2 == nil { t.Fatalf("get closed: %v", err) } + if e2.Status != sporting.EventStatus_EVENT_STATUS_CLOSED { t.Fatalf("expected CLOSED got %v", e2.Status) } +} diff --git a/sport/go.mod b/sport/go.mod index 82d215e..cbbb656 100644 --- a/sport/go.mod +++ b/sport/go.mod @@ -4,6 +4,7 @@ go 1.23 require ( github.com/golang/protobuf v1.5.4 + github.com/DATA-DOG/go-sqlmock v1.5.0 github.com/mattn/go-sqlite3 v1.14.22 golang.org/x/net v0.26.0 google.golang.org/grpc v1.65.0 diff --git a/sport/go.sum b/sport/go.sum index 768d5b6..3b35fd6 100644 --- a/sport/go.sum +++ b/sport/go.sum @@ -1,3 +1,5 @@ +github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= +github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= diff --git a/sport/service/events_test.go b/sport/service/events_test.go new file mode 100644 index 0000000..ecafa61 --- /dev/null +++ b/sport/service/events_test.go @@ -0,0 +1,81 @@ +package service + +import ( + "errors" + "testing" + "time" + + "sport/proto/sporting" + "golang.org/x/net/context" + "google.golang.org/protobuf/types/known/timestamppb" +) + +// mock repo implementing db.EventsRepo minimal surface +type mockEventsRepo struct { + listFn func(req *sporting.ListEventsRequest) ([]*sporting.Event, string, error) + getFn func(id int64) (*sporting.Event, error) +} + +func (m *mockEventsRepo) Init() error { return nil } +func (m *mockEventsRepo) List(req *sporting.ListEventsRequest) ([]*sporting.Event, string, error) { + return m.listFn(req) +} +func (m *mockEventsRepo) Get(id int64) (*sporting.Event, error) { return m.getFn(id) } + +func TestService_ListEvents_Success(t *testing.T) { + mockRepo := &mockEventsRepo{listFn: func(req *sporting.ListEventsRequest) ([]*sporting.Event, string, error) { + return []*sporting.Event{{Id: 1, Name: "Match"}}, "NEXT", nil + }} + svc := NewSportingService(mockRepo) + resp, err := svc.ListEvents(context.Background(), &sporting.ListEventsRequest{}) + if err != nil { t.Fatalf("unexpected err: %v", err) } + if len(resp.Events) != 1 || resp.Events[0].Id != 1 { t.Fatalf("unexpected events resp") } + if resp.NextPageToken != "NEXT" { t.Fatalf("expected NEXT token") } +} + +func TestService_ListEvents_InvalidArgumentPropagated(t *testing.T) { + mockRepo := &mockEventsRepo{listFn: func(req *sporting.ListEventsRequest) ([]*sporting.Event, string, error) { + return nil, "", errors.New("invalid page_token") + }} + svc := NewSportingService(mockRepo) + _, err := svc.ListEvents(context.Background(), &sporting.ListEventsRequest{PageToken: "bad"}) + if err == nil { t.Fatalf("expected error") } +} + +func TestService_GetEvent_Validation(t *testing.T) { + mockRepo := &mockEventsRepo{} + svc := NewSportingService(mockRepo) + _, err := svc.GetEvent(context.Background(), &sporting.GetEventRequest{Id: 0}) + if err == nil { t.Fatalf("expected validation error") } +} + +func TestService_GetEvent_NotFound(t *testing.T) { + mockRepo := &mockEventsRepo{getFn: func(id int64) (*sporting.Event, error) { return nil, nil }} + svc := NewSportingService(mockRepo) + _, err := svc.GetEvent(context.Background(), &sporting.GetEventRequest{Id: 5}) + if err == nil { t.Fatalf("expected not found error") } +} + +func TestService_GetEvent_InternalError(t *testing.T) { + mockRepo := &mockEventsRepo{getFn: func(id int64) (*sporting.Event, error) { return nil, errors.New("db explode") }} + svc := NewSportingService(mockRepo) + _, err := svc.GetEvent(context.Background(), &sporting.GetEventRequest{Id: 5}) + if err == nil { t.Fatalf("expected internal error") } +} + +func TestService_GetEvent_SuccessStatusDerivation(t *testing.T) { + future := time.Now().Add(1 * time.Hour) + past := time.Now().Add(-1 * time.Hour) + mockRepo := &mockEventsRepo{getFn: func(id int64) (*sporting.Event, error) { + if id == 1 { return &sporting.Event{Id: 1, Name: "Future", AdvertisedStartTime: ts(future), Status: sporting.EventStatus_EVENT_STATUS_OPEN}, nil } + return &sporting.Event{Id: 2, Name: "Past", AdvertisedStartTime: ts(past), Status: sporting.EventStatus_EVENT_STATUS_CLOSED}, nil + }} + svc := NewSportingService(mockRepo) + e1, err := svc.GetEvent(context.Background(), &sporting.GetEventRequest{Id: 1}) + if err != nil || e1.Status != sporting.EventStatus_EVENT_STATUS_OPEN { t.Fatalf("expected open") } + e2, err := svc.GetEvent(context.Background(), &sporting.GetEventRequest{Id: 2}) + if err != nil || e2.Status != sporting.EventStatus_EVENT_STATUS_CLOSED { t.Fatalf("expected closed") } +} + +// helper to create timestamp proto using existing types in generated code +func ts(t time.Time) *timestamppb.Timestamp { return timestamppb.New(t) }