diff --git a/server/api/tumdev/campus_backend.pb.go b/server/api/tumdev/campus_backend.pb.go index b6bbb802..d9bb7f5d 100644 --- a/server/api/tumdev/campus_backend.pb.go +++ b/server/api/tumdev/campus_backend.pb.go @@ -3419,7 +3419,7 @@ func (x *GetCanteenHeadCountReply) GetTimestamp() *timestamppb.Timestamp { return nil } -type ListStudentClubRequest struct { +type ListStudentGroupRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields @@ -3429,20 +3429,20 @@ type ListStudentClubRequest struct { Language *Language `protobuf:"varint,1,opt,name=language,proto3,enum=api.Language,oneof" json:"language,omitempty"` } -func (x *ListStudentClubRequest) Reset() { - *x = ListStudentClubRequest{} +func (x *ListStudentGroupRequest) Reset() { + *x = ListStudentGroupRequest{} mi := &file_tumdev_campus_backend_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ListStudentClubRequest) String() string { +func (x *ListStudentGroupRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListStudentClubRequest) ProtoMessage() {} +func (*ListStudentGroupRequest) ProtoMessage() {} -func (x *ListStudentClubRequest) ProtoReflect() protoreflect.Message { +func (x *ListStudentGroupRequest) ProtoReflect() protoreflect.Message { mi := &file_tumdev_campus_backend_proto_msgTypes[53] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -3454,40 +3454,41 @@ func (x *ListStudentClubRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListStudentClubRequest.ProtoReflect.Descriptor instead. -func (*ListStudentClubRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use ListStudentGroupRequest.ProtoReflect.Descriptor instead. +func (*ListStudentGroupRequest) Descriptor() ([]byte, []int) { return file_tumdev_campus_backend_proto_rawDescGZIP(), []int{53} } -func (x *ListStudentClubRequest) GetLanguage() Language { +func (x *ListStudentGroupRequest) GetLanguage() Language { if x != nil && x.Language != nil { return *x.Language } return Language_German } -type ListStudentClubReply struct { +type ListStudentGroupReply struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Collections []*StudentClubCollection `protobuf:"bytes,1,rep,name=collections,proto3" json:"collections,omitempty"` + StudentClubs []*StudentClubCollection `protobuf:"bytes,1,rep,name=student_clubs,json=studentClubs,proto3" json:"student_clubs,omitempty"` + StudentCouncils []*StudentCouncilCollection `protobuf:"bytes,2,rep,name=student_councils,json=studentCouncils,proto3" json:"student_councils,omitempty"` } -func (x *ListStudentClubReply) Reset() { - *x = ListStudentClubReply{} +func (x *ListStudentGroupReply) Reset() { + *x = ListStudentGroupReply{} mi := &file_tumdev_campus_backend_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ListStudentClubReply) String() string { +func (x *ListStudentGroupReply) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListStudentClubReply) ProtoMessage() {} +func (*ListStudentGroupReply) ProtoMessage() {} -func (x *ListStudentClubReply) ProtoReflect() protoreflect.Message { +func (x *ListStudentGroupReply) ProtoReflect() protoreflect.Message { mi := &file_tumdev_campus_backend_proto_msgTypes[54] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -3499,14 +3500,21 @@ func (x *ListStudentClubReply) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListStudentClubReply.ProtoReflect.Descriptor instead. -func (*ListStudentClubReply) Descriptor() ([]byte, []int) { +// Deprecated: Use ListStudentGroupReply.ProtoReflect.Descriptor instead. +func (*ListStudentGroupReply) Descriptor() ([]byte, []int) { return file_tumdev_campus_backend_proto_rawDescGZIP(), []int{54} } -func (x *ListStudentClubReply) GetCollections() []*StudentClubCollection { +func (x *ListStudentGroupReply) GetStudentClubs() []*StudentClubCollection { if x != nil { - return x.Collections + return x.StudentClubs + } + return nil +} + +func (x *ListStudentGroupReply) GetStudentCouncils() []*StudentCouncilCollection { + if x != nil { + return x.StudentCouncils } return nil } @@ -3592,8 +3600,9 @@ type StudentClubCollection struct { Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"` Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` Clubs []*StudentClub `protobuf:"bytes,3,rep,name=clubs,proto3" json:"clubs,omitempty"` - // id of the collection. + // ID of the collection. // Might not be stable over time because of scraping + // May not be disjunct not unique in relation to student councils UnstableCollectionId uint64 `protobuf:"varint,4,opt,name=unstable_collection_id,json=unstableCollectionId,proto3" json:"unstable_collection_id,omitempty"` } @@ -3655,6 +3664,151 @@ func (x *StudentClubCollection) GetUnstableCollectionId() uint64 { return 0 } +type StudentCouncil struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The name of the council + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // How the council describes itsself + Description *string `protobuf:"bytes,2,opt,name=description,proto3,oneof" json:"description,omitempty"` + // Where the council main internet presence is + LinkUrl *string `protobuf:"bytes,3,opt,name=link_url,json=linkUrl,proto3,oneof" json:"link_url,omitempty"` + // Where to find a image for this council + CoverUrl *string `protobuf:"bytes,4,opt,name=cover_url,json=coverUrl,proto3,oneof" json:"cover_url,omitempty"` +} + +func (x *StudentCouncil) Reset() { + *x = StudentCouncil{} + mi := &file_tumdev_campus_backend_proto_msgTypes[57] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StudentCouncil) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StudentCouncil) ProtoMessage() {} + +func (x *StudentCouncil) ProtoReflect() protoreflect.Message { + mi := &file_tumdev_campus_backend_proto_msgTypes[57] + 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 StudentCouncil.ProtoReflect.Descriptor instead. +func (*StudentCouncil) Descriptor() ([]byte, []int) { + return file_tumdev_campus_backend_proto_rawDescGZIP(), []int{57} +} + +func (x *StudentCouncil) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *StudentCouncil) GetDescription() string { + if x != nil && x.Description != nil { + return *x.Description + } + return "" +} + +func (x *StudentCouncil) GetLinkUrl() string { + if x != nil && x.LinkUrl != nil { + return *x.LinkUrl + } + return "" +} + +func (x *StudentCouncil) GetCoverUrl() string { + if x != nil && x.CoverUrl != nil { + return *x.CoverUrl + } + return "" +} + +type StudentCouncilCollection struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"` + Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` + Councils []*StudentCouncil `protobuf:"bytes,3,rep,name=councils,proto3" json:"councils,omitempty"` + // ID of the collection. + // Might not be stable over time because of scraping + // May not be disjunct not unique in relation to student clubs + UnstableCollectionId uint64 `protobuf:"varint,4,opt,name=unstable_collection_id,json=unstableCollectionId,proto3" json:"unstable_collection_id,omitempty"` +} + +func (x *StudentCouncilCollection) Reset() { + *x = StudentCouncilCollection{} + mi := &file_tumdev_campus_backend_proto_msgTypes[58] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StudentCouncilCollection) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StudentCouncilCollection) ProtoMessage() {} + +func (x *StudentCouncilCollection) ProtoReflect() protoreflect.Message { + mi := &file_tumdev_campus_backend_proto_msgTypes[58] + 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 StudentCouncilCollection.ProtoReflect.Descriptor instead. +func (*StudentCouncilCollection) Descriptor() ([]byte, []int) { + return file_tumdev_campus_backend_proto_rawDescGZIP(), []int{58} +} + +func (x *StudentCouncilCollection) GetTitle() string { + if x != nil { + return x.Title + } + return "" +} + +func (x *StudentCouncilCollection) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *StudentCouncilCollection) GetCouncils() []*StudentCouncil { + if x != nil { + return x.Councils + } + return nil +} + +func (x *StudentCouncilCollection) GetUnstableCollectionId() uint64 { + if x != nil { + return x.UnstableCollectionId + } + return 0 +} + var File_tumdev_campus_backend_proto protoreflect.FileDescriptor var file_tumdev_campus_backend_proto_rawDesc = []byte{ @@ -4043,17 +4197,22 @@ var file_tumdev_campus_backend_proto_rawDesc = []byte{ 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 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, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, - 0x6d, 0x70, 0x22, 0x55, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x75, 0x64, 0x65, 0x6e, - 0x74, 0x43, 0x6c, 0x75, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x08, - 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0d, - 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x48, 0x00, 0x52, - 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, - 0x5f, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x22, 0x54, 0x0a, 0x14, 0x4c, 0x69, 0x73, - 0x74, 0x53, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x43, 0x6c, 0x75, 0x62, 0x52, 0x65, 0x70, 0x6c, - 0x79, 0x12, 0x3c, 0x0a, 0x0b, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x75, - 0x64, 0x65, 0x6e, 0x74, 0x43, 0x6c, 0x75, 0x62, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x0b, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, + 0x6d, 0x70, 0x22, 0x56, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x75, 0x64, 0x65, 0x6e, + 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, + 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x48, 0x00, + 0x52, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, + 0x09, 0x5f, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x22, 0xa2, 0x01, 0x0a, 0x15, 0x4c, + 0x69, 0x73, 0x74, 0x53, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, + 0x65, 0x70, 0x6c, 0x79, 0x12, 0x3f, 0x0a, 0x0d, 0x73, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x5f, + 0x63, 0x6c, 0x75, 0x62, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x53, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x43, 0x6c, 0x75, 0x62, 0x43, 0x6f, 0x6c, + 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x73, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, + 0x43, 0x6c, 0x75, 0x62, 0x73, 0x12, 0x48, 0x0a, 0x10, 0x73, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, + 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x63, 0x69, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x1d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x75, + 0x6e, 0x63, 0x69, 0x6c, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0f, + 0x73, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x63, 0x69, 0x6c, 0x73, 0x22, 0xb5, 0x01, 0x0a, 0x0b, 0x53, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x43, 0x6c, 0x75, 0x62, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x25, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, @@ -4076,146 +4235,176 @@ var file_tumdev_campus_backend_proto_rawDesc = []byte{ 0x73, 0x12, 0x34, 0x0a, 0x16, 0x75, 0x6e, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x14, 0x75, 0x6e, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x2a, 0x2f, 0x0a, 0x0a, 0x44, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x07, 0x0a, 0x03, 0x49, 0x4f, 0x53, 0x10, 0x00, 0x12, 0x0b, - 0x0a, 0x07, 0x41, 0x4e, 0x44, 0x52, 0x4f, 0x49, 0x44, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x57, - 0x49, 0x4e, 0x44, 0x4f, 0x57, 0x53, 0x10, 0x02, 0x2a, 0x23, 0x0a, 0x08, 0x4c, 0x61, 0x6e, 0x67, - 0x75, 0x61, 0x67, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x47, 0x65, 0x72, 0x6d, 0x61, 0x6e, 0x10, 0x00, - 0x12, 0x0b, 0x0a, 0x07, 0x45, 0x6e, 0x67, 0x6c, 0x69, 0x73, 0x68, 0x10, 0x01, 0x32, 0xf2, 0x0f, - 0x0a, 0x06, 0x43, 0x61, 0x6d, 0x70, 0x75, 0x73, 0x12, 0x64, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, - 0x4e, 0x65, 0x77, 0x73, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x12, 0x1a, 0x2e, 0x61, 0x70, 0x69, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x65, 0x77, 0x73, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, - 0x74, 0x4e, 0x65, 0x77, 0x73, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, - 0x22, 0x1c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x62, 0x06, 0x61, 0x6c, 0x65, 0x72, 0x74, 0x73, - 0x12, 0x0c, 0x2f, 0x6e, 0x65, 0x77, 0x73, 0x2f, 0x61, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x12, 0x69, - 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x65, 0x77, 0x73, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x73, 0x12, 0x1b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x65, 0x77, 0x73, - 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, - 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x65, 0x77, 0x73, 0x53, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x18, 0x62, 0x07, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x0d, 0x2f, 0x6e, 0x65, 0x77, - 0x73, 0x2f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x49, 0x0a, 0x08, 0x4c, 0x69, 0x73, - 0x74, 0x4e, 0x65, 0x77, 0x73, 0x12, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x4e, 0x65, 0x77, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x65, 0x77, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, - 0x13, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0d, 0x62, 0x04, 0x6e, 0x65, 0x77, 0x73, 0x12, 0x05, 0x2f, - 0x6e, 0x65, 0x77, 0x73, 0x12, 0x72, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x6e, 0x74, - 0x65, 0x65, 0x6e, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x1e, 0x2e, 0x61, 0x70, 0x69, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x52, 0x61, 0x74, 0x69, - 0x6e, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x61, 0x70, 0x69, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x52, 0x61, 0x74, 0x69, - 0x6e, 0x67, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, - 0x3a, 0x01, 0x2a, 0x22, 0x13, 0x2f, 0x63, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x2f, 0x72, 0x61, - 0x74, 0x69, 0x6e, 0x67, 0x2f, 0x67, 0x65, 0x74, 0x12, 0x63, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x44, - 0x69, 0x73, 0x68, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x1a, 0x2e, 0x61, 0x70, 0x69, - 0x2e, 0x47, 0x65, 0x74, 0x44, 0x69, 0x73, 0x68, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, - 0x44, 0x69, 0x73, 0x68, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, - 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x3a, 0x01, 0x2a, 0x22, 0x10, 0x2f, 0x64, 0x69, - 0x73, 0x68, 0x2f, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x2f, 0x67, 0x65, 0x74, 0x12, 0x75, 0x0a, - 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x52, 0x61, - 0x74, 0x69, 0x6e, 0x67, 0x12, 0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x43, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x43, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x52, - 0x65, 0x70, 0x6c, 0x79, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x3a, 0x01, 0x2a, 0x22, - 0x13, 0x2f, 0x63, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x2f, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, - 0x2f, 0x6e, 0x65, 0x77, 0x12, 0x69, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x69, - 0x73, 0x68, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x69, 0x73, 0x68, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x44, 0x69, 0x73, 0x68, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x70, - 0x6c, 0x79, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x3a, 0x01, 0x2a, 0x22, 0x10, 0x2f, - 0x64, 0x69, 0x73, 0x68, 0x2f, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x2f, 0x6e, 0x65, 0x77, 0x12, - 0x8c, 0x01, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, - 0x65, 0x44, 0x69, 0x73, 0x68, 0x54, 0x61, 0x67, 0x73, 0x12, 0x21, 0x2e, 0x61, 0x70, 0x69, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x44, 0x69, 0x73, - 0x68, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x61, - 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, - 0x44, 0x69, 0x73, 0x68, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x2f, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x29, 0x62, 0x0b, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x61, - 0x67, 0x73, 0x12, 0x1a, 0x2f, 0x64, 0x69, 0x73, 0x68, 0x2f, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, - 0x2f, 0x61, 0x6c, 0x6c, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x54, 0x61, 0x67, 0x73, 0x12, 0x6f, - 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x54, 0x61, 0x67, 0x73, 0x12, 0x18, - 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x54, 0x61, 0x67, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, - 0x22, 0x2d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x27, 0x62, 0x0b, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, - 0x5f, 0x74, 0x61, 0x67, 0x73, 0x12, 0x18, 0x2f, 0x64, 0x69, 0x73, 0x68, 0x2f, 0x72, 0x61, 0x74, - 0x69, 0x6e, 0x67, 0x2f, 0x61, 0x6c, 0x6c, 0x44, 0x69, 0x73, 0x68, 0x54, 0x61, 0x67, 0x73, 0x12, - 0x98, 0x01, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, - 0x65, 0x43, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x54, 0x61, 0x67, 0x73, 0x12, 0x24, 0x2e, 0x61, - 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, - 0x43, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x76, 0x61, - 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x54, 0x61, 0x67, - 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x32, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2c, 0x62, 0x0b, - 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x12, 0x1d, 0x2f, 0x63, 0x61, - 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x2f, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x2f, 0x61, 0x6c, 0x6c, - 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x54, 0x61, 0x67, 0x73, 0x12, 0x67, 0x0a, 0x0c, 0x4c, 0x69, - 0x73, 0x74, 0x43, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x73, 0x12, 0x18, 0x2e, 0x61, 0x70, 0x69, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, - 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x25, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x1f, 0x62, 0x07, 0x63, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x12, 0x14, 0x2f, - 0x63, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x2f, 0x61, 0x6c, 0x6c, 0x43, 0x61, 0x6e, 0x74, 0x65, - 0x65, 0x6e, 0x73, 0x12, 0x51, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x69, 0x73, 0x68, 0x65, - 0x73, 0x12, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x69, 0x73, 0x68, - 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x44, 0x69, 0x73, 0x68, 0x65, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, - 0x15, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x62, 0x04, 0x64, 0x69, 0x73, 0x68, 0x12, 0x07, 0x2f, - 0x64, 0x69, 0x73, 0x68, 0x65, 0x73, 0x12, 0x62, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x74, 0x65, 0x12, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, - 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x4e, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x1d, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x17, 0x12, 0x15, 0x2f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x6e, 0x6f, 0x74, 0x65, - 0x2f, 0x7b, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x7d, 0x12, 0x55, 0x0a, 0x0a, 0x4c, 0x69, - 0x73, 0x74, 0x4d, 0x6f, 0x76, 0x69, 0x65, 0x73, 0x12, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x4d, 0x6f, 0x76, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x6f, 0x76, 0x69, 0x65, - 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, 0x11, - 0x2f, 0x6d, 0x6f, 0x76, 0x69, 0x65, 0x73, 0x2f, 0x7b, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x69, 0x64, - 0x7d, 0x12, 0x67, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x64, 0x62, - 0x61, 0x63, 0x6b, 0x12, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x46, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x64, - 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x17, 0x3a, 0x0a, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x09, 0x2f, - 0x66, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x28, 0x01, 0x12, 0x7e, 0x0a, 0x13, 0x47, 0x65, - 0x74, 0x43, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x48, 0x65, 0x61, 0x64, 0x43, 0x6f, 0x75, 0x6e, - 0x74, 0x12, 0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x61, 0x6e, 0x74, 0x65, - 0x65, 0x6e, 0x48, 0x65, 0x61, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x61, 0x6e, 0x74, - 0x65, 0x65, 0x6e, 0x48, 0x65, 0x61, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c, - 0x79, 0x22, 0x27, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x21, 0x12, 0x1f, 0x2f, 0x63, 0x61, 0x6e, 0x74, - 0x65, 0x65, 0x6e, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x2f, 0x7b, 0x63, - 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x54, 0x0a, 0x0c, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x18, 0x2e, 0x61, 0x70, 0x69, - 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x12, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x0c, 0x3a, 0x01, 0x2a, 0x22, 0x07, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x12, 0x5d, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x12, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x61, 0x70, 0x69, - 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x70, - 0x6c, 0x79, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x2a, 0x13, 0x2f, 0x64, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x2f, 0x7b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x12, - 0x61, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x43, 0x6c, - 0x75, 0x62, 0x12, 0x1b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x75, - 0x64, 0x65, 0x6e, 0x74, 0x43, 0x6c, 0x75, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x75, 0x64, 0x65, 0x6e, - 0x74, 0x43, 0x6c, 0x75, 0x62, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x16, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x10, 0x12, 0x0e, 0x2f, 0x73, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x6c, 0x75, - 0x62, 0x73, 0x42, 0x5e, 0x0a, 0x12, 0x61, 0x70, 0x70, 0x2e, 0x74, 0x75, 0x6d, 0x2e, 0x63, 0x61, - 0x6d, 0x70, 0x75, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x42, 0x0e, 0x43, 0x61, 0x6d, 0x70, 0x75, 0x73, - 0x41, 0x70, 0x69, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x54, 0x55, 0x4d, 0x2d, 0x44, 0x65, 0x76, 0x2f, 0x43, - 0x61, 0x6d, 0x70, 0x75, 0x73, 0x2d, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2f, 0x61, 0x70, - 0x69, 0xaa, 0x02, 0x0e, 0x43, 0x61, 0x6d, 0x70, 0x75, 0x73, 0x41, 0x70, 0x69, 0x50, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0xb8, 0x01, 0x0a, 0x0e, 0x53, 0x74, 0x75, 0x64, + 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x63, 0x69, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x25, + 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x1e, 0x0a, 0x08, 0x6c, 0x69, 0x6e, 0x6b, 0x5f, 0x75, 0x72, + 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x07, 0x6c, 0x69, 0x6e, 0x6b, 0x55, + 0x72, 0x6c, 0x88, 0x01, 0x01, 0x12, 0x20, 0x0a, 0x09, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x75, + 0x72, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x08, 0x63, 0x6f, 0x76, 0x65, + 0x72, 0x55, 0x72, 0x6c, 0x88, 0x01, 0x01, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x64, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x6c, 0x69, 0x6e, 0x6b, + 0x5f, 0x75, 0x72, 0x6c, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x75, + 0x72, 0x6c, 0x22, 0xb9, 0x01, 0x0a, 0x18, 0x53, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x43, 0x6f, + 0x75, 0x6e, 0x63, 0x69, 0x6c, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2f, 0x0a, 0x08, 0x63, 0x6f, 0x75, 0x6e, 0x63, + 0x69, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x53, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x63, 0x69, 0x6c, 0x52, 0x08, + 0x63, 0x6f, 0x75, 0x6e, 0x63, 0x69, 0x6c, 0x73, 0x12, 0x34, 0x0a, 0x16, 0x75, 0x6e, 0x73, 0x74, + 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x14, 0x75, 0x6e, 0x73, 0x74, 0x61, 0x62, + 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x2a, 0x2f, + 0x0a, 0x0a, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x07, 0x0a, 0x03, + 0x49, 0x4f, 0x53, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x4e, 0x44, 0x52, 0x4f, 0x49, 0x44, + 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x57, 0x49, 0x4e, 0x44, 0x4f, 0x57, 0x53, 0x10, 0x02, 0x2a, + 0x23, 0x0a, 0x08, 0x4c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x47, + 0x65, 0x72, 0x6d, 0x61, 0x6e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x45, 0x6e, 0x67, 0x6c, 0x69, + 0x73, 0x68, 0x10, 0x01, 0x32, 0xde, 0x10, 0x0a, 0x06, 0x43, 0x61, 0x6d, 0x70, 0x75, 0x73, 0x12, + 0x64, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x65, 0x77, 0x73, 0x41, 0x6c, 0x65, 0x72, 0x74, + 0x73, 0x12, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x65, 0x77, 0x73, + 0x41, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x65, 0x77, 0x73, 0x41, 0x6c, 0x65, 0x72, + 0x74, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x1c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x62, + 0x06, 0x61, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x12, 0x0c, 0x2f, 0x6e, 0x65, 0x77, 0x73, 0x2f, 0x61, + 0x6c, 0x65, 0x72, 0x74, 0x73, 0x12, 0x69, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x65, 0x77, + 0x73, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x1b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x4e, 0x65, 0x77, 0x73, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x4e, 0x65, 0x77, 0x73, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, + 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x62, 0x07, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x73, 0x12, 0x0d, 0x2f, 0x6e, 0x65, 0x77, 0x73, 0x2f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, + 0x12, 0x49, 0x0a, 0x08, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x65, 0x77, 0x73, 0x12, 0x14, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x65, 0x77, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x65, 0x77, + 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x13, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0d, 0x62, 0x04, + 0x6e, 0x65, 0x77, 0x73, 0x12, 0x05, 0x2f, 0x6e, 0x65, 0x77, 0x73, 0x12, 0x72, 0x0a, 0x12, 0x4c, + 0x69, 0x73, 0x74, 0x43, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, + 0x73, 0x12, 0x1e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x6e, 0x74, + 0x65, 0x65, 0x6e, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x6e, 0x74, + 0x65, 0x65, 0x6e, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, + 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x3a, 0x01, 0x2a, 0x22, 0x13, 0x2f, 0x63, 0x61, 0x6e, + 0x74, 0x65, 0x65, 0x6e, 0x2f, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x2f, 0x67, 0x65, 0x74, 0x12, + 0x63, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x44, 0x69, 0x73, 0x68, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, + 0x73, 0x12, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x69, 0x73, 0x68, 0x52, + 0x61, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x69, 0x73, 0x68, 0x52, 0x61, 0x74, 0x69, 0x6e, + 0x67, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x3a, + 0x01, 0x2a, 0x22, 0x10, 0x2f, 0x64, 0x69, 0x73, 0x68, 0x2f, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, + 0x2f, 0x67, 0x65, 0x74, 0x12, 0x75, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x61, + 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x1f, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x52, + 0x61, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, + 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x1e, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x18, 0x3a, 0x01, 0x2a, 0x22, 0x13, 0x2f, 0x63, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, + 0x2f, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x2f, 0x6e, 0x65, 0x77, 0x12, 0x69, 0x0a, 0x10, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x69, 0x73, 0x68, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x12, + 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x69, 0x73, 0x68, + 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x69, 0x73, 0x68, 0x52, 0x61, + 0x74, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x15, 0x3a, 0x01, 0x2a, 0x22, 0x10, 0x2f, 0x64, 0x69, 0x73, 0x68, 0x2f, 0x72, 0x61, 0x74, 0x69, + 0x6e, 0x67, 0x2f, 0x6e, 0x65, 0x77, 0x12, 0x8c, 0x01, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x41, + 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x44, 0x69, 0x73, 0x68, 0x54, 0x61, 0x67, 0x73, + 0x12, 0x21, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, + 0x61, 0x62, 0x6c, 0x65, 0x44, 0x69, 0x73, 0x68, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x76, + 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x44, 0x69, 0x73, 0x68, 0x54, 0x61, 0x67, 0x73, 0x52, + 0x65, 0x70, 0x6c, 0x79, 0x22, 0x2f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x29, 0x62, 0x0b, 0x72, 0x61, + 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x12, 0x1a, 0x2f, 0x64, 0x69, 0x73, 0x68, + 0x2f, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x2f, 0x61, 0x6c, 0x6c, 0x52, 0x61, 0x74, 0x69, 0x6e, + 0x67, 0x54, 0x61, 0x67, 0x73, 0x12, 0x6f, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x61, 0x6d, + 0x65, 0x54, 0x61, 0x67, 0x73, 0x12, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x4e, 0x61, 0x6d, 0x65, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x54, 0x61, + 0x67, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x2d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x27, 0x62, + 0x0b, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x12, 0x18, 0x2f, 0x64, + 0x69, 0x73, 0x68, 0x2f, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x2f, 0x61, 0x6c, 0x6c, 0x44, 0x69, + 0x73, 0x68, 0x54, 0x61, 0x67, 0x73, 0x12, 0x98, 0x01, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x41, + 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x54, + 0x61, 0x67, 0x73, 0x12, 0x24, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x76, + 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x54, 0x61, + 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x61, 0x6e, + 0x74, 0x65, 0x65, 0x6e, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x32, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x2c, 0x62, 0x0b, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x61, + 0x67, 0x73, 0x12, 0x1d, 0x2f, 0x63, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x2f, 0x72, 0x61, 0x74, + 0x69, 0x6e, 0x67, 0x2f, 0x61, 0x6c, 0x6c, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x54, 0x61, 0x67, + 0x73, 0x12, 0x67, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, + 0x73, 0x12, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x6e, 0x74, + 0x65, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x73, 0x52, 0x65, + 0x70, 0x6c, 0x79, 0x22, 0x25, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x62, 0x07, 0x63, 0x61, 0x6e, + 0x74, 0x65, 0x65, 0x6e, 0x12, 0x14, 0x2f, 0x63, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x2f, 0x61, + 0x6c, 0x6c, 0x43, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x73, 0x12, 0x51, 0x0a, 0x0a, 0x4c, 0x69, + 0x73, 0x74, 0x44, 0x69, 0x73, 0x68, 0x65, 0x73, 0x12, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x44, 0x69, 0x73, 0x68, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x69, 0x73, 0x68, 0x65, + 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x15, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x62, 0x04, + 0x64, 0x69, 0x73, 0x68, 0x12, 0x07, 0x2f, 0x64, 0x69, 0x73, 0x68, 0x65, 0x73, 0x12, 0x62, 0x0a, + 0x0d, 0x47, 0x65, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x74, 0x65, 0x12, 0x19, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4e, 0x6f, + 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x47, 0x65, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x70, + 0x6c, 0x79, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x12, 0x15, 0x2f, 0x75, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x6e, 0x6f, 0x74, 0x65, 0x2f, 0x7b, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x7d, 0x12, 0x55, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x6f, 0x76, 0x69, 0x65, 0x73, 0x12, + 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x6f, 0x76, 0x69, 0x65, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x4d, 0x6f, 0x76, 0x69, 0x65, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x19, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, 0x11, 0x2f, 0x6d, 0x6f, 0x76, 0x69, 0x65, 0x73, 0x2f, 0x7b, + 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x67, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x46, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x1a, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x70, 0x6c, 0x79, + 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x3a, 0x0a, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, + 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x09, 0x2f, 0x66, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x28, + 0x01, 0x12, 0x7e, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x43, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x48, + 0x65, 0x61, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, + 0x65, 0x74, 0x43, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x48, 0x65, 0x61, 0x64, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x47, 0x65, 0x74, 0x43, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x48, 0x65, 0x61, 0x64, 0x43, 0x6f, + 0x75, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x27, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x21, + 0x12, 0x1f, 0x2f, 0x63, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x2f, 0x7b, 0x63, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x5f, 0x69, 0x64, + 0x7d, 0x12, 0x54, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x12, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, + 0x70, 0x6c, 0x79, 0x22, 0x12, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0c, 0x3a, 0x01, 0x2a, 0x22, 0x07, + 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5d, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x15, 0x2a, 0x13, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x7b, 0x64, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x66, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, + 0x75, 0x64, 0x65, 0x6e, 0x74, 0x43, 0x6c, 0x75, 0x62, 0x12, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x53, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, + 0x70, 0x6c, 0x79, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x12, 0x0e, 0x2f, 0x73, 0x74, + 0x75, 0x64, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x6c, 0x75, 0x62, 0x73, 0x88, 0x02, 0x01, 0x12, 0x65, + 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x47, 0x72, 0x6f, + 0x75, 0x70, 0x12, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x75, + 0x64, 0x65, 0x6e, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x75, 0x64, 0x65, + 0x6e, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x17, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x11, 0x12, 0x0f, 0x2f, 0x73, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x5f, 0x67, + 0x72, 0x6f, 0x75, 0x70, 0x73, 0x42, 0x5e, 0x0a, 0x12, 0x61, 0x70, 0x70, 0x2e, 0x74, 0x75, 0x6d, + 0x2e, 0x63, 0x61, 0x6d, 0x70, 0x75, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x42, 0x0e, 0x43, 0x61, 0x6d, + 0x70, 0x75, 0x73, 0x41, 0x70, 0x69, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x25, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x54, 0x55, 0x4d, 0x2d, 0x44, 0x65, + 0x76, 0x2f, 0x43, 0x61, 0x6d, 0x70, 0x75, 0x73, 0x2d, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, + 0x2f, 0x61, 0x70, 0x69, 0xaa, 0x02, 0x0e, 0x43, 0x61, 0x6d, 0x70, 0x75, 0x73, 0x41, 0x70, 0x69, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -4231,7 +4420,7 @@ func file_tumdev_campus_backend_proto_rawDescGZIP() []byte { } var file_tumdev_campus_backend_proto_enumTypes = make([]protoimpl.EnumInfo, 3) -var file_tumdev_campus_backend_proto_msgTypes = make([]protoimpl.MessageInfo, 57) +var file_tumdev_campus_backend_proto_msgTypes = make([]protoimpl.MessageInfo, 59) var file_tumdev_campus_backend_proto_goTypes = []any{ (DeviceType)(0), // 0: api.DeviceType (Language)(0), // 1: api.Language @@ -4289,94 +4478,100 @@ var file_tumdev_campus_backend_proto_goTypes = []any{ (*GetUploadStatusReply)(nil), // 53: api.GetUploadStatusReply (*GetCanteenHeadCountRequest)(nil), // 54: api.GetCanteenHeadCountRequest (*GetCanteenHeadCountReply)(nil), // 55: api.GetCanteenHeadCountReply - (*ListStudentClubRequest)(nil), // 56: api.ListStudentClubRequest - (*ListStudentClubReply)(nil), // 57: api.ListStudentClubReply + (*ListStudentGroupRequest)(nil), // 56: api.ListStudentGroupRequest + (*ListStudentGroupReply)(nil), // 57: api.ListStudentGroupReply (*StudentClub)(nil), // 58: api.StudentClub (*StudentClubCollection)(nil), // 59: api.StudentClubCollection - (*timestamppb.Timestamp)(nil), // 60: google.protobuf.Timestamp + (*StudentCouncil)(nil), // 60: api.StudentCouncil + (*StudentCouncilCollection)(nil), // 61: api.StudentCouncilCollection + (*timestamppb.Timestamp)(nil), // 62: google.protobuf.Timestamp } var file_tumdev_campus_backend_proto_depIdxs = []int32{ 0, // 0: api.CreateDeviceRequest.device_type:type_name -> api.DeviceType 0, // 1: api.DeleteDeviceRequest.device_type:type_name -> api.DeviceType - 60, // 2: api.News.created:type_name -> google.protobuf.Timestamp - 60, // 3: api.News.date:type_name -> google.protobuf.Timestamp + 62, // 2: api.News.created:type_name -> google.protobuf.Timestamp + 62, // 3: api.News.date:type_name -> google.protobuf.Timestamp 7, // 4: api.ListNewsReply.news:type_name -> api.News - 60, // 5: api.ListNewsRequest.oldest_date_at:type_name -> google.protobuf.Timestamp + 62, // 5: api.ListNewsRequest.oldest_date_at:type_name -> google.protobuf.Timestamp 12, // 6: api.ListNewsSourcesReply.sources:type_name -> api.NewsSource 15, // 7: api.ListNewsAlertsReply.alerts:type_name -> api.NewsAlert - 60, // 8: api.NewsAlert.created:type_name -> google.protobuf.Timestamp - 60, // 9: api.NewsAlert.from:type_name -> google.protobuf.Timestamp - 60, // 10: api.NewsAlert.to:type_name -> google.protobuf.Timestamp - 60, // 11: api.ListCanteenRatingsRequest.from:type_name -> google.protobuf.Timestamp - 60, // 12: api.ListCanteenRatingsRequest.to:type_name -> google.protobuf.Timestamp + 62, // 8: api.NewsAlert.created:type_name -> google.protobuf.Timestamp + 62, // 9: api.NewsAlert.from:type_name -> google.protobuf.Timestamp + 62, // 10: api.NewsAlert.to:type_name -> google.protobuf.Timestamp + 62, // 11: api.ListCanteenRatingsRequest.from:type_name -> google.protobuf.Timestamp + 62, // 12: api.ListCanteenRatingsRequest.to:type_name -> google.protobuf.Timestamp 20, // 13: api.ListCanteenRatingsReply.rating:type_name -> api.SingleRatingReply 34, // 14: api.ListCanteenRatingsReply.rating_tags:type_name -> api.RatingTagResult - 60, // 15: api.GetDishRatingsRequest.from:type_name -> google.protobuf.Timestamp - 60, // 16: api.GetDishRatingsRequest.to:type_name -> google.protobuf.Timestamp + 62, // 15: api.GetDishRatingsRequest.from:type_name -> google.protobuf.Timestamp + 62, // 16: api.GetDishRatingsRequest.to:type_name -> google.protobuf.Timestamp 20, // 17: api.GetDishRatingsReply.rating:type_name -> api.SingleRatingReply 34, // 18: api.GetDishRatingsReply.rating_tags:type_name -> api.RatingTagResult 34, // 19: api.GetDishRatingsReply.name_tags:type_name -> api.RatingTagResult 33, // 20: api.SingleRatingReply.rating_tags:type_name -> api.RatingTagNewRequest - 60, // 21: api.SingleRatingReply.visited:type_name -> google.protobuf.Timestamp + 62, // 21: api.SingleRatingReply.visited:type_name -> google.protobuf.Timestamp 32, // 22: api.CreateCanteenRatingRequest.rating_tags:type_name -> api.RatingTag 32, // 23: api.CreateDishRatingRequest.rating_tags:type_name -> api.RatingTag 31, // 24: api.ListAvailableDishTagsReply.rating_tags:type_name -> api.TagsOverview 31, // 25: api.ListNameTagsReply.rating_tags:type_name -> api.TagsOverview 31, // 26: api.ListAvailableCanteenTagsReply.rating_tags:type_name -> api.TagsOverview 37, // 27: api.ListCanteensReply.canteen:type_name -> api.Canteen - 60, // 28: api.ListMoviesRequest.oldest_date_at:type_name -> google.protobuf.Timestamp + 62, // 28: api.ListMoviesRequest.oldest_date_at:type_name -> google.protobuf.Timestamp 45, // 29: api.ListMoviesReply.movies:type_name -> api.Movie - 60, // 30: api.Movie.date:type_name -> google.protobuf.Timestamp - 60, // 31: api.Movie.created:type_name -> google.protobuf.Timestamp + 62, // 30: api.Movie.date:type_name -> google.protobuf.Timestamp + 62, // 31: api.Movie.created:type_name -> google.protobuf.Timestamp 2, // 32: api.CreateFeedbackRequest.recipient:type_name -> api.CreateFeedbackRequest.Recipient 47, // 33: api.CreateFeedbackRequest.location:type_name -> api.Coordinate - 60, // 34: api.GetCanteenHeadCountReply.timestamp:type_name -> google.protobuf.Timestamp - 1, // 35: api.ListStudentClubRequest.language:type_name -> api.Language - 59, // 36: api.ListStudentClubReply.collections:type_name -> api.StudentClubCollection - 58, // 37: api.StudentClubCollection.clubs:type_name -> api.StudentClub - 13, // 38: api.Campus.ListNewsAlerts:input_type -> api.ListNewsAlertsRequest - 10, // 39: api.Campus.ListNewsSources:input_type -> api.ListNewsSourcesRequest - 9, // 40: api.Campus.ListNews:input_type -> api.ListNewsRequest - 16, // 41: api.Campus.ListCanteenRatings:input_type -> api.ListCanteenRatingsRequest - 18, // 42: api.Campus.GetDishRatings:input_type -> api.GetDishRatingsRequest - 22, // 43: api.Campus.CreateCanteenRating:input_type -> api.CreateCanteenRatingRequest - 24, // 44: api.Campus.CreateDishRating:input_type -> api.CreateDishRatingRequest - 25, // 45: api.Campus.ListAvailableDishTags:input_type -> api.ListAvailableDishTagsRequest - 27, // 46: api.Campus.ListNameTags:input_type -> api.ListNameTagsRequest - 29, // 47: api.Campus.ListAvailableCanteenTags:input_type -> api.ListAvailableCanteenTagsRequest - 35, // 48: api.Campus.ListCanteens:input_type -> api.ListCanteensRequest - 38, // 49: api.Campus.ListDishes:input_type -> api.ListDishesRequest - 41, // 50: api.Campus.GetUpdateNote:input_type -> api.GetUpdateNoteRequest - 43, // 51: api.Campus.ListMovies:input_type -> api.ListMoviesRequest - 46, // 52: api.Campus.CreateFeedback:input_type -> api.CreateFeedbackRequest - 54, // 53: api.Campus.GetCanteenHeadCount:input_type -> api.GetCanteenHeadCountRequest - 3, // 54: api.Campus.CreateDevice:input_type -> api.CreateDeviceRequest - 5, // 55: api.Campus.DeleteDevice:input_type -> api.DeleteDeviceRequest - 56, // 56: api.Campus.ListStudentClub:input_type -> api.ListStudentClubRequest - 14, // 57: api.Campus.ListNewsAlerts:output_type -> api.ListNewsAlertsReply - 11, // 58: api.Campus.ListNewsSources:output_type -> api.ListNewsSourcesReply - 8, // 59: api.Campus.ListNews:output_type -> api.ListNewsReply - 17, // 60: api.Campus.ListCanteenRatings:output_type -> api.ListCanteenRatingsReply - 19, // 61: api.Campus.GetDishRatings:output_type -> api.GetDishRatingsReply - 21, // 62: api.Campus.CreateCanteenRating:output_type -> api.CreateCanteenRatingReply - 23, // 63: api.Campus.CreateDishRating:output_type -> api.CreateDishRatingReply - 26, // 64: api.Campus.ListAvailableDishTags:output_type -> api.ListAvailableDishTagsReply - 28, // 65: api.Campus.ListNameTags:output_type -> api.ListNameTagsReply - 30, // 66: api.Campus.ListAvailableCanteenTags:output_type -> api.ListAvailableCanteenTagsReply - 36, // 67: api.Campus.ListCanteens:output_type -> api.ListCanteensReply - 39, // 68: api.Campus.ListDishes:output_type -> api.ListDishesReply - 42, // 69: api.Campus.GetUpdateNote:output_type -> api.GetUpdateNoteReply - 44, // 70: api.Campus.ListMovies:output_type -> api.ListMoviesReply - 48, // 71: api.Campus.CreateFeedback:output_type -> api.CreateFeedbackReply - 55, // 72: api.Campus.GetCanteenHeadCount:output_type -> api.GetCanteenHeadCountReply - 4, // 73: api.Campus.CreateDevice:output_type -> api.CreateDeviceReply - 6, // 74: api.Campus.DeleteDevice:output_type -> api.DeleteDeviceReply - 57, // 75: api.Campus.ListStudentClub:output_type -> api.ListStudentClubReply - 57, // [57:76] is the sub-list for method output_type - 38, // [38:57] is the sub-list for method input_type - 38, // [38:38] is the sub-list for extension type_name - 38, // [38:38] is the sub-list for extension extendee - 0, // [0:38] is the sub-list for field type_name + 62, // 34: api.GetCanteenHeadCountReply.timestamp:type_name -> google.protobuf.Timestamp + 1, // 35: api.ListStudentGroupRequest.language:type_name -> api.Language + 59, // 36: api.ListStudentGroupReply.student_clubs:type_name -> api.StudentClubCollection + 61, // 37: api.ListStudentGroupReply.student_councils:type_name -> api.StudentCouncilCollection + 58, // 38: api.StudentClubCollection.clubs:type_name -> api.StudentClub + 60, // 39: api.StudentCouncilCollection.councils:type_name -> api.StudentCouncil + 13, // 40: api.Campus.ListNewsAlerts:input_type -> api.ListNewsAlertsRequest + 10, // 41: api.Campus.ListNewsSources:input_type -> api.ListNewsSourcesRequest + 9, // 42: api.Campus.ListNews:input_type -> api.ListNewsRequest + 16, // 43: api.Campus.ListCanteenRatings:input_type -> api.ListCanteenRatingsRequest + 18, // 44: api.Campus.GetDishRatings:input_type -> api.GetDishRatingsRequest + 22, // 45: api.Campus.CreateCanteenRating:input_type -> api.CreateCanteenRatingRequest + 24, // 46: api.Campus.CreateDishRating:input_type -> api.CreateDishRatingRequest + 25, // 47: api.Campus.ListAvailableDishTags:input_type -> api.ListAvailableDishTagsRequest + 27, // 48: api.Campus.ListNameTags:input_type -> api.ListNameTagsRequest + 29, // 49: api.Campus.ListAvailableCanteenTags:input_type -> api.ListAvailableCanteenTagsRequest + 35, // 50: api.Campus.ListCanteens:input_type -> api.ListCanteensRequest + 38, // 51: api.Campus.ListDishes:input_type -> api.ListDishesRequest + 41, // 52: api.Campus.GetUpdateNote:input_type -> api.GetUpdateNoteRequest + 43, // 53: api.Campus.ListMovies:input_type -> api.ListMoviesRequest + 46, // 54: api.Campus.CreateFeedback:input_type -> api.CreateFeedbackRequest + 54, // 55: api.Campus.GetCanteenHeadCount:input_type -> api.GetCanteenHeadCountRequest + 3, // 56: api.Campus.CreateDevice:input_type -> api.CreateDeviceRequest + 5, // 57: api.Campus.DeleteDevice:input_type -> api.DeleteDeviceRequest + 56, // 58: api.Campus.ListStudentClub:input_type -> api.ListStudentGroupRequest + 56, // 59: api.Campus.ListStudentGroup:input_type -> api.ListStudentGroupRequest + 14, // 60: api.Campus.ListNewsAlerts:output_type -> api.ListNewsAlertsReply + 11, // 61: api.Campus.ListNewsSources:output_type -> api.ListNewsSourcesReply + 8, // 62: api.Campus.ListNews:output_type -> api.ListNewsReply + 17, // 63: api.Campus.ListCanteenRatings:output_type -> api.ListCanteenRatingsReply + 19, // 64: api.Campus.GetDishRatings:output_type -> api.GetDishRatingsReply + 21, // 65: api.Campus.CreateCanteenRating:output_type -> api.CreateCanteenRatingReply + 23, // 66: api.Campus.CreateDishRating:output_type -> api.CreateDishRatingReply + 26, // 67: api.Campus.ListAvailableDishTags:output_type -> api.ListAvailableDishTagsReply + 28, // 68: api.Campus.ListNameTags:output_type -> api.ListNameTagsReply + 30, // 69: api.Campus.ListAvailableCanteenTags:output_type -> api.ListAvailableCanteenTagsReply + 36, // 70: api.Campus.ListCanteens:output_type -> api.ListCanteensReply + 39, // 71: api.Campus.ListDishes:output_type -> api.ListDishesReply + 42, // 72: api.Campus.GetUpdateNote:output_type -> api.GetUpdateNoteReply + 44, // 73: api.Campus.ListMovies:output_type -> api.ListMoviesReply + 48, // 74: api.Campus.CreateFeedback:output_type -> api.CreateFeedbackReply + 55, // 75: api.Campus.GetCanteenHeadCount:output_type -> api.GetCanteenHeadCountReply + 4, // 76: api.Campus.CreateDevice:output_type -> api.CreateDeviceReply + 6, // 77: api.Campus.DeleteDevice:output_type -> api.DeleteDeviceReply + 57, // 78: api.Campus.ListStudentClub:output_type -> api.ListStudentGroupReply + 57, // 79: api.Campus.ListStudentGroup:output_type -> api.ListStudentGroupReply + 60, // [60:80] is the sub-list for method output_type + 40, // [40:60] is the sub-list for method input_type + 40, // [40:40] is the sub-list for extension type_name + 40, // [40:40] is the sub-list for extension extendee + 0, // [0:40] is the sub-list for field type_name } func init() { file_tumdev_campus_backend_proto_init() } @@ -4386,13 +4581,14 @@ func file_tumdev_campus_backend_proto_init() { } file_tumdev_campus_backend_proto_msgTypes[53].OneofWrappers = []any{} file_tumdev_campus_backend_proto_msgTypes[55].OneofWrappers = []any{} + file_tumdev_campus_backend_proto_msgTypes[57].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_tumdev_campus_backend_proto_rawDesc, NumEnums: 3, - NumMessages: 57, + NumMessages: 59, NumExtensions: 0, NumServices: 1, }, diff --git a/server/api/tumdev/campus_backend.pb.gw.go b/server/api/tumdev/campus_backend.pb.gw.go index 914d9003..e0982e72 100644 --- a/server/api/tumdev/campus_backend.pb.gw.go +++ b/server/api/tumdev/campus_backend.pb.gw.go @@ -652,7 +652,7 @@ var ( ) func request_Campus_ListStudentClub_0(ctx context.Context, marshaler runtime.Marshaler, client CampusClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq ListStudentClubRequest + var protoReq ListStudentGroupRequest var metadata runtime.ServerMetadata if err := req.ParseForm(); err != nil { @@ -668,7 +668,7 @@ func request_Campus_ListStudentClub_0(ctx context.Context, marshaler runtime.Mar } func local_request_Campus_ListStudentClub_0(ctx context.Context, marshaler runtime.Marshaler, server CampusServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq ListStudentClubRequest + var protoReq ListStudentGroupRequest var metadata runtime.ServerMetadata if err := req.ParseForm(); err != nil { @@ -683,6 +683,42 @@ func local_request_Campus_ListStudentClub_0(ctx context.Context, marshaler runti } +var ( + filter_Campus_ListStudentGroup_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_Campus_ListStudentGroup_0(ctx context.Context, marshaler runtime.Marshaler, client CampusClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ListStudentGroupRequest + var 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_Campus_ListStudentGroup_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.ListStudentGroup(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Campus_ListStudentGroup_0(ctx context.Context, marshaler runtime.Marshaler, server CampusServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ListStudentGroupRequest + var 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_Campus_ListStudentGroup_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.ListStudentGroup(ctx, &protoReq) + return msg, metadata, err + +} + // RegisterCampusHandlerServer registers the http handlers for service Campus to "mux". // UnaryRPC :call CampusServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -1147,6 +1183,31 @@ func RegisterCampusHandlerServer(ctx context.Context, mux *runtime.ServeMux, ser }) + mux.Handle("GET", pattern_Campus_ListStudentGroup_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) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/api.Campus/ListStudentGroup", runtime.WithHTTPPathPattern("/student_groups")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Campus_ListStudentGroup_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_Campus_ListStudentGroup_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -1606,6 +1667,28 @@ func RegisterCampusHandlerClient(ctx context.Context, mux *runtime.ServeMux, cli }) + mux.Handle("GET", pattern_Campus_ListStudentGroup_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) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/api.Campus/ListStudentGroup", runtime.WithHTTPPathPattern("/student_groups")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Campus_ListStudentGroup_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_Campus_ListStudentGroup_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -1711,6 +1794,8 @@ var ( pattern_Campus_DeleteDevice_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1}, []string{"device", "device_id"}, "")) pattern_Campus_ListStudentClub_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0}, []string{"student_clubs"}, "")) + + pattern_Campus_ListStudentGroup_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0}, []string{"student_groups"}, "")) ) var ( @@ -1751,4 +1836,6 @@ var ( forward_Campus_DeleteDevice_0 = runtime.ForwardResponseMessage forward_Campus_ListStudentClub_0 = runtime.ForwardResponseMessage + + forward_Campus_ListStudentGroup_0 = runtime.ForwardResponseMessage ) diff --git a/server/api/tumdev/campus_backend.proto b/server/api/tumdev/campus_backend.proto index 2304ff41..a0d88965 100644 --- a/server/api/tumdev/campus_backend.proto +++ b/server/api/tumdev/campus_backend.proto @@ -132,8 +132,14 @@ service Campus { } // List all avaliable student clubs - rpc ListStudentClub(ListStudentClubRequest) returns (ListStudentClubReply) { + rpc ListStudentClub(ListStudentGroupRequest) returns (ListStudentGroupReply) { option (google.api.http) = {get: "/student_clubs"}; + option deprecated = true; + } + + // List student groups information necessary for the "Campus" tab + rpc ListStudentGroup(ListStudentGroupRequest) returns (ListStudentGroupReply) { + option (google.api.http) = {get: "/student_groups"}; } } @@ -532,13 +538,14 @@ enum Language { English = 1; } -message ListStudentClubRequest { +message ListStudentGroupRequest { // Language of the student clubs and categories // Defaults to german optional Language language = 1; } -message ListStudentClubReply { - repeated StudentClubCollection collections = 1; +message ListStudentGroupReply { + repeated StudentClubCollection student_clubs = 1; + repeated StudentCouncilCollection student_councils = 2; } message StudentClub { // The name of the club @@ -554,7 +561,27 @@ message StudentClubCollection { string title = 1; string description = 2; repeated StudentClub clubs = 3; - // id of the collection. + // ID of the collection. + // Might not be stable over time because of scraping + // May not be disjunct not unique in relation to student councils + uint64 unstable_collection_id = 4; +} +message StudentCouncil { + // The name of the council + string name = 1; + // How the council describes itsself + optional string description = 2; + // Where the council main internet presence is + optional string link_url = 3; + // Where to find a image for this council + optional string cover_url = 4; +} +message StudentCouncilCollection { + string title = 1; + string description = 2; + repeated StudentCouncil councils = 3; + // ID of the collection. // Might not be stable over time because of scraping + // May not be disjunct not unique in relation to student clubs uint64 unstable_collection_id = 4; } diff --git a/server/api/tumdev/campus_backend.swagger.json b/server/api/tumdev/campus_backend.swagger.json index 62ccb209..ff82a8a5 100644 --- a/server/api/tumdev/campus_backend.swagger.json +++ b/server/api/tumdev/campus_backend.swagger.json @@ -673,7 +673,44 @@ "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/apiListStudentClubReply" + "$ref": "#/definitions/apiListStudentGroupReply" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "language", + "description": "Language of the student clubs and categories\nDefaults to german", + "in": "query", + "required": false, + "type": "string", + "enum": [ + "German", + "English" + ], + "default": "German" + } + ], + "tags": [ + "Campus" + ] + } + }, + "/student_groups": { + "get": { + "summary": "List all student groups information", + "operationId": "Campus_ListStudentGroup", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/apiListStudentGroupReply" } }, "default": { @@ -1173,15 +1210,22 @@ } } }, - "apiListStudentClubReply": { + "apiListStudentGroupReply": { "type": "object", "properties": { - "collections": { + "studentClubs": { "type": "array", "items": { "type": "object", "$ref": "#/definitions/apiStudentClubCollection" } + }, + "studentCouncils": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/apiStudentCouncilCollection" + } } } }, @@ -1461,7 +1505,51 @@ "unstableCollectionId": { "type": "string", "format": "uint64", - "title": "id of the collection.\nMight not be stable over time because of scraping" + "title": "ID of the collection.\nMight not be stable over time because of scraping\nMay not be disjunct not unique in relation to student councils" + } + } + }, + "apiStudentCouncil": { + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "The name of the council" + }, + "description": { + "type": "string", + "title": "How the council describes itsself" + }, + "linkUrl": { + "type": "string", + "title": "Where the council main internet presence is" + }, + "coverUrl": { + "type": "string", + "title": "Where to find a image for this council" + } + } + }, + "apiStudentCouncilCollection": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "councils": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/apiStudentCouncil" + } + }, + "unstableCollectionId": { + "type": "string", + "format": "uint64", + "title": "ID of the collection.\nMight not be stable over time because of scraping\nMay not be disjunct not unique in relation to student clubs" } } }, diff --git a/server/api/tumdev/campus_backend_grpc.pb.go b/server/api/tumdev/campus_backend_grpc.pb.go index 6e8cb6c6..7a99f022 100644 --- a/server/api/tumdev/campus_backend_grpc.pb.go +++ b/server/api/tumdev/campus_backend_grpc.pb.go @@ -39,6 +39,7 @@ const ( Campus_CreateDevice_FullMethodName = "/api.Campus/CreateDevice" Campus_DeleteDevice_FullMethodName = "/api.Campus/DeleteDevice" Campus_ListStudentClub_FullMethodName = "/api.Campus/ListStudentClub" + Campus_ListStudentGroup_FullMethodName = "/api.Campus/ListStudentGroup" ) // CampusClient is the client API for Campus service. @@ -68,8 +69,11 @@ type CampusClient interface { CreateDevice(ctx context.Context, in *CreateDeviceRequest, opts ...grpc.CallOption) (*CreateDeviceReply, error) // Delete a device from push notifications DeleteDevice(ctx context.Context, in *DeleteDeviceRequest, opts ...grpc.CallOption) (*DeleteDeviceReply, error) + // Deprecated: Do not use. // List all avaliable student clubs - ListStudentClub(ctx context.Context, in *ListStudentClubRequest, opts ...grpc.CallOption) (*ListStudentClubReply, error) + ListStudentClub(ctx context.Context, in *ListStudentGroupRequest, opts ...grpc.CallOption) (*ListStudentGroupReply, error) + // List all student groups information + ListStudentGroup(ctx context.Context, in *ListStudentGroupRequest, opts ...grpc.CallOption) (*ListStudentGroupReply, error) } type campusClient struct { @@ -263,9 +267,10 @@ func (c *campusClient) DeleteDevice(ctx context.Context, in *DeleteDeviceRequest return out, nil } -func (c *campusClient) ListStudentClub(ctx context.Context, in *ListStudentClubRequest, opts ...grpc.CallOption) (*ListStudentClubReply, error) { +// Deprecated: Do not use. +func (c *campusClient) ListStudentClub(ctx context.Context, in *ListStudentGroupRequest, opts ...grpc.CallOption) (*ListStudentGroupReply, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(ListStudentClubReply) + out := new(ListStudentGroupReply) err := c.cc.Invoke(ctx, Campus_ListStudentClub_FullMethodName, in, out, cOpts...) if err != nil { return nil, err @@ -273,6 +278,16 @@ func (c *campusClient) ListStudentClub(ctx context.Context, in *ListStudentClubR return out, nil } +func (c *campusClient) ListStudentGroup(ctx context.Context, in *ListStudentGroupRequest, opts ...grpc.CallOption) (*ListStudentGroupReply, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListStudentGroupReply) + err := c.cc.Invoke(ctx, Campus_ListStudentGroup_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // CampusServer is the server API for Campus service. // All implementations must embed UnimplementedCampusServer // for forward compatibility. @@ -300,8 +315,11 @@ type CampusServer interface { CreateDevice(context.Context, *CreateDeviceRequest) (*CreateDeviceReply, error) // Delete a device from push notifications DeleteDevice(context.Context, *DeleteDeviceRequest) (*DeleteDeviceReply, error) - // List all avaliable student clubs - ListStudentClub(context.Context, *ListStudentClubRequest) (*ListStudentClubReply, error) + // Deprecated: Do not use. + // List all information necessary for the "Campus" tab + ListStudentClub(context.Context, *ListStudentGroupRequest) (*ListStudentGroupReply, error) + // List all student groups information + ListStudentGroup(context.Context, *ListStudentGroupRequest) (*ListStudentGroupReply, error) mustEmbedUnimplementedCampusServer() } @@ -366,9 +384,12 @@ func (UnimplementedCampusServer) CreateDevice(context.Context, *CreateDeviceRequ func (UnimplementedCampusServer) DeleteDevice(context.Context, *DeleteDeviceRequest) (*DeleteDeviceReply, error) { return nil, status.Errorf(codes.Unimplemented, "method DeleteDevice not implemented") } -func (UnimplementedCampusServer) ListStudentClub(context.Context, *ListStudentClubRequest) (*ListStudentClubReply, error) { +func (UnimplementedCampusServer) ListStudentClub(context.Context, *ListStudentGroupRequest) (*ListStudentGroupReply, error) { return nil, status.Errorf(codes.Unimplemented, "method ListStudentClub not implemented") } +func (UnimplementedCampusServer) ListStudentGroup(context.Context, *ListStudentGroupRequest) (*ListStudentGroupReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListStudentGroup not implemented") +} func (UnimplementedCampusServer) mustEmbedUnimplementedCampusServer() {} func (UnimplementedCampusServer) testEmbeddedByValue() {} @@ -704,7 +725,7 @@ func _Campus_DeleteDevice_Handler(srv interface{}, ctx context.Context, dec func } func _Campus_ListStudentClub_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ListStudentClubRequest) + in := new(ListStudentGroupRequest) if err := dec(in); err != nil { return nil, err } @@ -716,7 +737,25 @@ func _Campus_ListStudentClub_Handler(srv interface{}, ctx context.Context, dec f FullMethod: Campus_ListStudentClub_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(CampusServer).ListStudentClub(ctx, req.(*ListStudentClubRequest)) + return srv.(CampusServer).ListStudentClub(ctx, req.(*ListStudentGroupRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Campus_ListStudentGroup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListStudentGroupRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CampusServer).ListStudentGroup(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Campus_ListStudentGroup_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CampusServer).ListStudentGroup(ctx, req.(*ListStudentGroupRequest)) } return interceptor(ctx, in, info, handler) } @@ -800,6 +839,10 @@ var Campus_ServiceDesc = grpc.ServiceDesc{ MethodName: "ListStudentClub", Handler: _Campus_ListStudentClub_Handler, }, + { + MethodName: "ListStudentGroup", + Handler: _Campus_ListStudentGroup_Handler, + }, }, Streams: []grpc.StreamDesc{ { diff --git a/server/backend/migration/20241024000000.go b/server/backend/migration/20241024000000.go new file mode 100644 index 00000000..69765222 --- /dev/null +++ b/server/backend/migration/20241024000000.go @@ -0,0 +1,57 @@ +package migration + +import ( + "github.com/go-gormigrate/gormigrate/v2" + "github.com/guregu/null" + "gorm.io/gorm" +) + +type newStudentCouncil struct { + gorm.Model + Name string + Language string `gorm:"type:enum('German','English');default:'German'"` + Description null.String + LinkUrl null.String `gorm:"type:varchar(190);unique;"` + ImageID null.Int + Image *File `gorm:"foreignKey:ImageID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"` + ImageCaption null.String + StudentCouncilCollectionID uint + StudentCouncilCollection newStudentCouncilCollection `gorm:"foreignKey:StudentCouncilCollectionID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"` +} + +// TableName sets the insert table name for this struct type +func (n *newStudentCouncil) TableName() string { + return "student_councils" +} + +type newStudentCouncilCollection struct { + gorm.Model + Name string `gorm:"type:varchar(100)"` + Language string `gorm:"type:enum('German','English');default:'German'"` + Description string +} + +// TableName sets the insert table name for this struct type +func (n *newStudentCouncilCollection) TableName() string { + return "student_council_collections" +} + +// migrate20241024000000 +// - made sure that student councils are supported +func migrate20241024000000() *gormigrate.Migration { + return &gormigrate.Migration{ + ID: "20241024000000", + Migrate: func(tx *gorm.DB) error { + if err := tx.Migrator().AutoMigrate(&newStudentCouncilCollection{}, &newStudentCouncil{}); err != nil { + return err + } + return nil + }, + Rollback: func(tx *gorm.DB) error { + if err := tx.Migrator().DropTable(&newStudentCouncil{}, &newStudentCouncilCollection{}); err != nil { + return err + } + return nil + }, + } +} diff --git a/server/backend/migration/migration.go b/server/backend/migration/migration.go index 4ff90a37..bb0eab3c 100644 --- a/server/backend/migration/migration.go +++ b/server/backend/migration/migration.go @@ -42,6 +42,8 @@ func autoMigrate(db *gorm.DB) error { &model.UpdateNote{}, &model.StudentClub{}, &model.StudentClubCollection{}, + &model.StudentCouncil{}, + &model.StudentCouncilCollection{}, ) return err } @@ -89,6 +91,7 @@ func manualMigrate(db *gorm.DB) error { migrate20240706000000(), migrate20240824000000(), migrate20241023000000(), + migrate20241024000000(), } return gormigrate.New(db, gormigrateOptions, migrations).Migrate() } diff --git a/server/backend/student_club.go b/server/backend/student_club.go deleted file mode 100644 index 15b4363b..00000000 --- a/server/backend/student_club.go +++ /dev/null @@ -1,62 +0,0 @@ -package backend - -import ( - "context" - - pb "github.com/TUM-Dev/Campus-Backend/server/api/tumdev" - "github.com/TUM-Dev/Campus-Backend/server/model" - log "github.com/sirupsen/logrus" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" -) - -func (s *CampusServer) ListStudentClub(ctx context.Context, req *pb.ListStudentClubRequest) (*pb.ListStudentClubReply, error) { - var dbClubs []model.StudentClub - if err := s.db.WithContext(ctx). - Where(&model.StudentClub{Language: req.GetLanguage().String()}). - Where(&model.StudentClubCollection{Language: req.GetLanguage().String()}). - Joins("Image"). - Joins("StudentClubCollection"). - Find(&dbClubs).Error; err != nil { - log.WithError(err).Error("Error while querying student clubs") - return nil, status.Error(codes.Internal, "could not query the student clubs. Please retry later") - } - - var dbClubCollections []model.StudentClubCollection - if err := s.db.WithContext(ctx). - Where(&model.StudentClubCollection{Language: req.GetLanguage().String()}). - Find(&dbClubCollections).Error; err != nil { - log.WithError(err).Error("Error while querying student club collections") - return nil, status.Error(codes.Internal, "could not query the student club collections. Please retry later") - } - // map from the db to the response - collections := make([]*pb.StudentClubCollection, 0) - for _, dbCollection := range dbClubCollections { - collections = append(collections, &pb.StudentClubCollection{ - Title: dbCollection.Name, - Description: dbCollection.Description, - Clubs: make([]*pb.StudentClub, 0), - UnstableCollectionId: uint64(dbCollection.ID), - }) - } - for _, dbClub := range dbClubs { - resClub := &pb.StudentClub{ - Name: dbClub.Name, - Description: dbClub.Description.Ptr(), - LinkUrl: dbClub.LinkUrl.Ptr(), - } - if dbClub.Image != nil { - cov := dbClub.Image.FullExternalUrl() - resClub.CoverUrl = &cov // go does not allow inlining here - } - - for _, collection := range collections { - if collection.UnstableCollectionId == uint64(dbClub.StudentClubCollectionID) { - collection.Clubs = append(collection.Clubs, resClub) - break - } - } - } - - return &pb.ListStudentClubReply{Collections: collections}, nil -} diff --git a/server/backend/student_club_test.go b/server/backend/student_club_test.go deleted file mode 100644 index 38913381..00000000 --- a/server/backend/student_club_test.go +++ /dev/null @@ -1,114 +0,0 @@ -package backend - -import ( - "context" - "fmt" - "testing" - - pb "github.com/TUM-Dev/Campus-Backend/server/api/tumdev" - "github.com/TUM-Dev/Campus-Backend/server/model" - "github.com/TUM-Dev/Campus-Backend/server/utils" - "github.com/guregu/null" - "github.com/stretchr/testify/require" - "gorm.io/gorm" -) - -func TestCampusServer_ListStudentClub(t *testing.T) { - t.Parallel() - ctx := context.Background() - db := utils.SetupTestContainer(ctx, t) - exampleClubs := genExampleClubData(db, t) - server := CampusServer{db: db} - language := pb.Language_German - response, err := server.ListStudentClub(ctx, &pb.ListStudentClubRequest{Language: &language}) - require.NoError(t, err) - url0 := exampleClubs[0].Image.FullExternalUrl() - expectedResp := &pb.ListStudentClubReply{ - Collections: []*pb.StudentClubCollection{ - { - UnstableCollectionId: uint64(exampleClubs[0].StudentClubCollection.ID), - Title: exampleClubs[0].StudentClubCollection.Name, - Description: exampleClubs[0].StudentClubCollection.Description, - Clubs: []*pb.StudentClub{ - { - Name: exampleClubs[0].Name, - Description: exampleClubs[0].Description.Ptr(), - LinkUrl: exampleClubs[0].LinkUrl.Ptr(), - CoverUrl: &url0, - }, - { - Name: exampleClubs[1].Name, - }, - }, - }, - { - UnstableCollectionId: uint64(exampleClubs[2].StudentClubCollection.ID), - Title: exampleClubs[2].StudentClubCollection.Name, - Description: exampleClubs[2].StudentClubCollection.Description, - Clubs: []*pb.StudentClub{ - { - Name: exampleClubs[2].Name, - }, - }, - }, - }, - } - require.Equal(t, expectedResp, response) -} - -func genExampleClubData(db *gorm.DB, t *testing.T) []*model.StudentClub { - col1 := model.StudentClubCollection{ - Name: "col1", - Language: pb.Language_German.String(), - Description: "Awesome collection", - } - err := db.Create(&col1).Error - require.NoError(t, err) - col2 := model.StudentClubCollection{ - Name: "col2", - Description: "Terrible collection", - Language: pb.Language_German.String(), - } - err = db.Create(&col2).Error - require.NoError(t, err) - file1 := &model.File{ - File: 1, - Name: fmt.Sprintf("src_%d.png", 1), - Path: "student_club/", - Downloads: 1, - URL: null.String{}, - Downloaded: null.BoolFrom(true), - } - err = db.Create(file1).Error - require.NoError(t, err) - club1 := &model.StudentClub{ - Name: "Student Club 1", - Language: pb.Language_German.String(), - Description: null.StringFrom("With Description"), - LinkUrl: null.StringFrom("https://example.com"), - ImageID: null.IntFrom(file1.File), - Image: file1, - ImageCaption: null.StringFrom("source: idk, something"), - StudentClubCollectionID: col1.ID, - StudentClubCollection: col1, - } - err = db.Create(club1).Error - require.NoError(t, err) - club2 := &model.StudentClub{ - Name: "Student Club 2", - Language: pb.Language_German.String(), - StudentClubCollectionID: col1.ID, - StudentClubCollection: col1, - } - err = db.Create(club2).Error - require.NoError(t, err) - club3 := &model.StudentClub{ - Name: "Student Club 3", - Language: pb.Language_German.String(), - StudentClubCollectionID: col2.ID, - StudentClubCollection: col2, - } - err = db.Create(club3).Error - require.NoError(t, err) - return []*model.StudentClub{club1, club2, club3} -} diff --git a/server/backend/student_groups.go b/server/backend/student_groups.go new file mode 100644 index 00000000..1619dd2d --- /dev/null +++ b/server/backend/student_groups.go @@ -0,0 +1,129 @@ +package backend + +import ( + "context" + + pb "github.com/TUM-Dev/Campus-Backend/server/api/tumdev" + "github.com/TUM-Dev/Campus-Backend/server/model" + log "github.com/sirupsen/logrus" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// ListStudentClub is deprecated, use ListStudentGroup instead +func (s *CampusServer) ListStudentClub(ctx context.Context, req *pb.ListStudentGroupRequest) (*pb.ListStudentGroupReply, error) { + return s.ListStudentGroup(ctx, req) +} + +func (s *CampusServer) ListStudentGroup(ctx context.Context, req *pb.ListStudentGroupRequest) (*pb.ListStudentGroupReply, error) { + studentClubs, err := s.getAllStudentClubs(ctx, req.GetLanguage()) + if err != nil { + return nil, status.Error(codes.Internal, "could not query the student clubs. Please retry later") + } + studentCouncils, err := s.getAllStudentCouncils(ctx, req.GetLanguage()) + if err != nil { + return nil, status.Error(codes.Internal, "could not query the student clubs. Please retry later") + } + + return &pb.ListStudentGroupReply{StudentClubs: studentClubs, StudentCouncils: studentCouncils}, nil +} + +func (s *CampusServer) getAllStudentClubs(ctx context.Context, lang pb.Language) ([]*pb.StudentClubCollection, error) { + var dbClubs []model.StudentClub + if err := s.db.WithContext(ctx). + Where(&model.StudentClub{Language: lang.String()}). + Where(&model.StudentClubCollection{Language: lang.String()}). + Joins("Image"). + Joins("StudentClubCollection"). + Find(&dbClubs).Error; err != nil { + log.WithError(err).Error("Error while querying student clubs") + return nil, err + } + + var dbClubCollections []model.StudentClubCollection + if err := s.db.WithContext(ctx). + Where(&model.StudentClubCollection{Language: lang.String()}). + Find(&dbClubCollections).Error; err != nil { + log.WithError(err).Error("Error while querying student club collections") + return nil, err + } + // map from the db to the response + collections := make([]*pb.StudentClubCollection, 0) + for _, dbCollection := range dbClubCollections { + collections = append(collections, &pb.StudentClubCollection{ + Title: dbCollection.Name, + Description: dbCollection.Description, + Clubs: make([]*pb.StudentClub, 0), + UnstableCollectionId: uint64(dbCollection.ID), + }) + } + for _, dbClub := range dbClubs { + resClub := &pb.StudentClub{ + Name: dbClub.Name, + Description: dbClub.Description.Ptr(), + LinkUrl: dbClub.LinkUrl.Ptr(), + } + if dbClub.Image != nil { + cov := dbClub.Image.FullExternalUrl() + resClub.CoverUrl = &cov // go does not allow inlining here + } + + for _, collection := range collections { + if collection.UnstableCollectionId == uint64(dbClub.StudentClubCollectionID) { + collection.Clubs = append(collection.Clubs, resClub) + break + } + } + } + return collections, nil +} + +func (s *CampusServer) getAllStudentCouncils(ctx context.Context, lang pb.Language) ([]*pb.StudentCouncilCollection, error) { + var dbCouncils []model.StudentCouncil + if err := s.db.WithContext(ctx). + Where(&model.StudentCouncil{Language: lang.String()}). + Where(&model.StudentCouncilCollection{Language: lang.String()}). + Joins("Image"). + Joins("StudentCouncilCollection"). + Find(&dbCouncils).Error; err != nil { + log.WithError(err).Error("Error while querying student councils") + return nil, err + } + + var dbCouncilCollections []model.StudentCouncilCollection + if err := s.db.WithContext(ctx). + Where(&model.StudentCouncilCollection{Language: lang.String()}). + Find(&dbCouncilCollections).Error; err != nil { + log.WithError(err).Error("Error while querying student council collections") + return nil, err + } + // map from the db to the response + collections := make([]*pb.StudentCouncilCollection, 0) + for _, dbCollection := range dbCouncilCollections { + collections = append(collections, &pb.StudentCouncilCollection{ + Title: dbCollection.Name, + Description: dbCollection.Description, + Councils: make([]*pb.StudentCouncil, 0), + UnstableCollectionId: uint64(dbCollection.ID), + }) + } + for _, dbCouncil := range dbCouncils { + resCouncil := &pb.StudentCouncil{ + Name: dbCouncil.Name, + Description: dbCouncil.Description.Ptr(), + LinkUrl: dbCouncil.LinkUrl.Ptr(), + } + if dbCouncil.Image != nil { + cov := dbCouncil.Image.FullExternalUrl() + resCouncil.CoverUrl = &cov // go does not allow inlining here + } + + for _, collection := range collections { + if collection.UnstableCollectionId == uint64(dbCouncil.StudentCouncilCollectionID) { + collection.Councils = append(collection.Councils, resCouncil) + break + } + } + } + return collections, nil +} diff --git a/server/backend/student_groups_test.go b/server/backend/student_groups_test.go new file mode 100644 index 00000000..e772523c --- /dev/null +++ b/server/backend/student_groups_test.go @@ -0,0 +1,201 @@ +package backend + +import ( + "context" + "fmt" + "testing" + + pb "github.com/TUM-Dev/Campus-Backend/server/api/tumdev" + "github.com/TUM-Dev/Campus-Backend/server/model" + "github.com/TUM-Dev/Campus-Backend/server/utils" + "github.com/guregu/null" + "github.com/stretchr/testify/require" + "gorm.io/gorm" +) + +func TestCampusServer_ListStudentGroup(t *testing.T) { + t.Parallel() + ctx := context.Background() + db := utils.SetupTestContainer(ctx, t) + exampleClubs := genExampleClubData(db, t) + exampleCouncils := genExampleCouncilData(db, t) + server := CampusServer{db: db} + language := pb.Language_German + response, err := server.ListStudentGroup(ctx, &pb.ListStudentGroupRequest{Language: &language}) + require.NoError(t, err) + urlClub0 := exampleClubs[0].Image.FullExternalUrl() + urlCouncil0 := exampleCouncils[0].Image.FullExternalUrl() + expectedResp := &pb.ListStudentGroupReply{ + StudentCouncils: []*pb.StudentCouncilCollection{ + { + UnstableCollectionId: uint64(exampleCouncils[0].StudentCouncilCollection.ID), + Title: exampleCouncils[0].StudentCouncilCollection.Name, + Description: exampleCouncils[0].StudentCouncilCollection.Description, + Councils: []*pb.StudentCouncil{ + { + Name: exampleCouncils[0].Name, + Description: exampleCouncils[0].Description.Ptr(), + LinkUrl: exampleCouncils[0].LinkUrl.Ptr(), + CoverUrl: &urlCouncil0, + }, + { + Name: exampleCouncils[1].Name, + }, + }, + }, + { + UnstableCollectionId: uint64(exampleCouncils[2].StudentCouncilCollection.ID), + Title: exampleCouncils[2].StudentCouncilCollection.Name, + Description: exampleCouncils[2].StudentCouncilCollection.Description, + Councils: []*pb.StudentCouncil{ + { + Name: exampleCouncils[2].Name, + }, + }, + }, + }, + StudentClubs: []*pb.StudentClubCollection{ + { + UnstableCollectionId: uint64(exampleClubs[0].StudentClubCollection.ID), + Title: exampleClubs[0].StudentClubCollection.Name, + Description: exampleClubs[0].StudentClubCollection.Description, + Clubs: []*pb.StudentClub{ + { + Name: exampleClubs[0].Name, + Description: exampleClubs[0].Description.Ptr(), + LinkUrl: exampleClubs[0].LinkUrl.Ptr(), + CoverUrl: &urlClub0, + }, + { + Name: exampleClubs[1].Name, + }, + }, + }, + { + UnstableCollectionId: uint64(exampleClubs[2].StudentClubCollection.ID), + Title: exampleClubs[2].StudentClubCollection.Name, + Description: exampleClubs[2].StudentClubCollection.Description, + Clubs: []*pb.StudentClub{ + { + Name: exampleClubs[2].Name, + }, + }, + }, + }, + } + require.Equal(t, expectedResp, response) +} + +func genExampleClubData(db *gorm.DB, t *testing.T) []*model.StudentClub { + col1 := model.StudentClubCollection{ + Name: "col1", + Language: pb.Language_German.String(), + Description: "Awesome collection", + } + err := db.Create(&col1).Error + require.NoError(t, err) + col2 := model.StudentClubCollection{ + Name: "col2", + Description: "Terrible collection", + Language: pb.Language_German.String(), + } + err = db.Create(&col2).Error + require.NoError(t, err) + file1 := &model.File{ + File: 1, + Name: fmt.Sprintf("src_%d.png", 1), + Path: "student_club/", + Downloads: 1, + URL: null.String{}, + Downloaded: null.BoolFrom(true), + } + err = db.Create(file1).Error + require.NoError(t, err) + club1 := &model.StudentClub{ + Name: "Student Club 1", + Language: pb.Language_German.String(), + Description: null.StringFrom("With Description"), + LinkUrl: null.StringFrom("https://example.com"), + ImageID: null.IntFrom(file1.File), + Image: file1, + ImageCaption: null.StringFrom("source: idk, something"), + StudentClubCollectionID: col1.ID, + StudentClubCollection: col1, + } + err = db.Create(club1).Error + require.NoError(t, err) + club2 := &model.StudentClub{ + Name: "Student Club 2", + Language: pb.Language_German.String(), + StudentClubCollectionID: col1.ID, + StudentClubCollection: col1, + } + err = db.Create(club2).Error + require.NoError(t, err) + club3 := &model.StudentClub{ + Name: "Student Club 3", + Language: pb.Language_German.String(), + StudentClubCollectionID: col2.ID, + StudentClubCollection: col2, + } + err = db.Create(club3).Error + require.NoError(t, err) + return []*model.StudentClub{club1, club2, club3} +} + +func genExampleCouncilData(db *gorm.DB, t *testing.T) []*model.StudentCouncil { + col1 := model.StudentCouncilCollection{ + Name: "col1", + Language: pb.Language_German.String(), + Description: "Awesome council col", + } + err := db.Create(&col1).Error + require.NoError(t, err) + col2 := model.StudentCouncilCollection{ + Name: "col2", + Description: "Terrible council col", + Language: pb.Language_German.String(), + } + err = db.Create(&col2).Error + require.NoError(t, err) + file1 := &model.File{ + File: 2, + Name: fmt.Sprintf("src_%d.png", 1), + Path: "council/", + Downloads: 1, + URL: null.String{}, + Downloaded: null.BoolFrom(true), + } + err = db.Create(file1).Error + require.NoError(t, err) + club1 := &model.StudentCouncil{ + Name: "Student Council 1", + Language: pb.Language_German.String(), + Description: null.StringFrom("With Description"), + LinkUrl: null.StringFrom("https://example.com"), + ImageID: null.IntFrom(file1.File), + Image: file1, + ImageCaption: null.StringFrom("source: idk, something"), + StudentCouncilCollectionID: col1.ID, + StudentCouncilCollection: col1, + } + err = db.Create(club1).Error + require.NoError(t, err) + club2 := &model.StudentCouncil{ + Name: "Student Council 2", + Language: pb.Language_German.String(), + StudentCouncilCollectionID: col1.ID, + StudentCouncilCollection: col1, + } + err = db.Create(club2).Error + require.NoError(t, err) + club3 := &model.StudentCouncil{ + Name: "Student Council 3", + Language: pb.Language_German.String(), + StudentCouncilCollectionID: col2.ID, + StudentCouncilCollection: col2, + } + err = db.Create(club3).Error + require.NoError(t, err) + return []*model.StudentCouncil{club1, club2, club3} +} diff --git a/server/model/student_council.go b/server/model/student_council.go new file mode 100644 index 00000000..199b177d --- /dev/null +++ b/server/model/student_council.go @@ -0,0 +1,20 @@ +package model + +import ( + "github.com/guregu/null" + "gorm.io/gorm" +) + +// StudentCouncil stores a student Council +type StudentCouncil struct { + gorm.Model + Name string + Language string `gorm:"type:enum('German','English');default:'German'"` + Description null.String + LinkUrl null.String `gorm:"type:varchar(190);unique;"` + ImageID null.Int + Image *File `gorm:"foreignKey:ImageID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"` + ImageCaption null.String + StudentCouncilCollectionID uint + StudentCouncilCollection StudentCouncilCollection `gorm:"foreignKey:StudentCouncilCollectionID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"` +} diff --git a/server/model/student_council_collection.go b/server/model/student_council_collection.go new file mode 100644 index 00000000..8b9163ff --- /dev/null +++ b/server/model/student_council_collection.go @@ -0,0 +1,11 @@ +package model + +import "gorm.io/gorm" + +// StudentCouncilCollection stores what collection a ćouncil belongs to +type StudentCouncilCollection struct { + gorm.Model + Name string `gorm:"type:varchar(100)"` + Language string `gorm:"type:enum('German','English');default:'German'"` + Description string +}