diff --git a/client/go.mod b/client/go.mod index 3355bfde..ccac2926 100644 --- a/client/go.mod +++ b/client/go.mod @@ -11,8 +11,8 @@ require ( require ( github.com/golang/protobuf v1.5.3 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 // indirect - golang.org/x/net v0.15.0 // indirect - golang.org/x/sys v0.12.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb // indirect diff --git a/client/go.sum b/client/go.sum index 9c0ae582..446d3bea 100644 --- a/client/go.sum +++ b/client/go.sum @@ -21,11 +21,11 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/client/local/client.go b/client/local/client.go index 77c73453..88df04ca 100644 --- a/client/local/client.go +++ b/client/local/client.go @@ -285,17 +285,14 @@ func storeImage(path string, i []byte) (string, error) { } } - out, errFile := os.Create(imgPath) - if errFile != nil { - log.WithError(errFile).Error("Unable to create the new testfile") + out, err := os.Create(imgPath) + if err != nil { + log.WithError(err).Error("Unable to create the new testfile") } defer func(out *os.File) { if err := out.Close(); err != nil { log.WithError(err).Error("File was not closed successfully") } }(out) - var opts jpeg.Options - opts.Quality = 100 - errFile = jpeg.Encode(out, img, &opts) - return imgPath, errFile + return imgPath, jpeg.Encode(out, img, &jpeg.Options{Quality: 100}) } diff --git a/deployment/charts/backend/files/envoy.yaml b/deployment/charts/backend/files/envoy.yaml index 1c029c45..b81cf80d 100644 --- a/deployment/charts/backend/files/envoy.yaml +++ b/deployment/charts/backend/files/envoy.yaml @@ -28,9 +28,9 @@ static_resources: cors: allow_origin_string_match: - prefix: "*" - allow_methods: GET, PUT, DELETE, POST, OPTIONS - allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout - max_age: "1728000" + allow_methods: GET, DELETE, POST + allow_headers: "*" + max_age: "86400" expose_headers: custom-header-1,grpc-status,grpc-message http_filters: - name: envoy.grpc_web diff --git a/deployment/charts/backend/templates/deployments/backend-v1.yaml b/deployment/charts/backend/templates/deployments/backend-v1.yaml index 5d572c7e..fdf2b976 100644 --- a/deployment/charts/backend/templates/deployments/backend-v1.yaml +++ b/deployment/charts/backend/templates/deployments/backend-v1.yaml @@ -40,18 +40,6 @@ spec: - name: logs emptyDir: { } containers: - - name: access-legacybackend - image: busybox:1.36 - args: [/bin/sh, -c, 'tail -n+1 -F /var/log/apache2/access.log'] - volumeMounts: - - name: logs - mountPath: /var/log - - name: error-legacybackend - image: busybox:1.36 - args: [/bin/sh, -c, 'tail -n+1 -F /var/log/apache2/error.log'] - volumeMounts: - - name: logs - mountPath: /var/log - name: tca-legacybackend image: ghcr.io/kordianbruck/tca-backend/tca-server:{{ $.Values.tag }} imagePullPolicy: Always @@ -75,6 +63,18 @@ spec: name: https securityContext: readOnlyRootFilesystem: true + - name: access-legacybackend + image: busybox:1.36 + args: [/bin/sh, -c, 'tail -n+1 -F /var/log/apache2/access.log'] + volumeMounts: + - name: logs + mountPath: /var/log + - name: error-legacybackend + image: busybox:1.36 + args: [/bin/sh, -c, 'tail -n+1 -F /var/log/apache2/error.log'] + volumeMounts: + - name: logs + mountPath: /var/log imagePullSecrets: - name: regcred --- diff --git a/deployment/charts/backend/templates/deployments/backend-v2.yaml b/deployment/charts/backend/templates/deployments/backend-v2.yaml index 686268f1..4ff23739 100644 --- a/deployment/charts/backend/templates/deployments/backend-v2.yaml +++ b/deployment/charts/backend/templates/deployments/backend-v2.yaml @@ -36,40 +36,6 @@ spec: configMap: name: backend-grpc-web-config containers: - - name: grpc-web-proxy - image: envoyproxy/envoy:v1.27-latest - imagePullPolicy: IfNotPresent - args: - - --config-path - - /etc/envoy/envoy.yaml - - --service-cluster - - backend-v2 - - --service-node - - backend-v2 - - --log-level - - info - ports: - - containerPort: 8081 - name: http - - containerPort: 9901 - name: admin - volumeMounts: - - mountPath: /etc/envoy/envoy.yaml - subPath: envoy.yaml - name: backend-grpc-web-config - readOnly: true - livenessProbe: - httpGet: - path: /ready - port: admin - failureThreshold: 5 - periodSeconds: 1 - startupProbe: - httpGet: - path: /ready - port: admin - failureThreshold: 60 - periodSeconds: 1 - name: tca-backend image: ghcr.io/tum-dev/campus-backend/backend-server:{{ $.Values.tag }} imagePullPolicy: Always @@ -136,6 +102,40 @@ spec: port: http failureThreshold: 60 periodSeconds: 1 + - name: grpc-web-proxy + image: envoyproxy/envoy:v1.27-latest + imagePullPolicy: IfNotPresent + args: + - --config-path + - /etc/envoy/envoy.yaml + - --service-cluster + - backend-v2 + - --service-node + - backend-v2 + - --log-level + - info + ports: + - containerPort: 8081 + name: http + - containerPort: 9901 + name: admin + volumeMounts: + - mountPath: /etc/envoy/envoy.yaml + subPath: envoy.yaml + name: backend-grpc-web-config + readOnly: true + livenessProbe: + httpGet: + path: /ready + port: admin + failureThreshold: 5 + periodSeconds: 1 + startupProbe: + httpGet: + path: /ready + port: admin + failureThreshold: 60 + periodSeconds: 1 --- apiVersion: v1 kind: Secret diff --git a/deployment/charts/backend/templates/networking/cors-middleware.yaml b/deployment/charts/backend/templates/networking/cors-middleware.yaml new file mode 100644 index 00000000..f42b1674 --- /dev/null +++ b/deployment/charts/backend/templates/networking/cors-middleware.yaml @@ -0,0 +1,19 @@ +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: cors + labels: + app.kubernetes.io/part-of: tum-campus-app + namespace: {{ $.Values.namespace }} +spec: + headers: + accessControlAllowMethods: + - GET + - DELETE + - POST + accessControlAllowHeaders: + - "*" + accessControlAllowOriginList: + - "*" + accessControlMaxAge: 86400 + addVaryHeader: true diff --git a/deployment/charts/backend/templates/networking/ingress.yaml b/deployment/charts/backend/templates/networking/ingress.yaml index 0ddf2cbb..0932f040 100644 --- a/deployment/charts/backend/templates/networking/ingress.yaml +++ b/deployment/charts/backend/templates/networking/ingress.yaml @@ -16,16 +16,22 @@ spec: port: 80 - kind: Rule match: Host(`{{ join "`) || Host(`" $.Values.urls.v2 }}`) + middlewares: + - name: cors services: - name: backend-v2-svc port: 50051 - kind: Rule match: Host(`{{ join "`) || Host(`" $.Values.urls.v2WebGrpc }}`) + middlewares: + - name: cors services: - name: backend-v2-svc port: 8081 - kind: Rule match: (Host(`{{ join "`) || Host(`" $.Values.urls.v2 }}`)) && Headers(`Content-Type`, `application/grpc`) + middlewares: + - name: cors services: - name: backend-v2-svc port: 50051 diff --git a/server/api/tumdev/campus_backend.pb.go b/server/api/tumdev/campus_backend.pb.go index c01b0f90..db845a21 100644 --- a/server/api/tumdev/campus_backend.pb.go +++ b/server/api/tumdev/campus_backend.pb.go @@ -72,6 +72,54 @@ func (DeviceType) EnumDescriptor() ([]byte, []int) { return file_tumdev_campus_backend_proto_rawDescGZIP(), []int{0} } +type CreateFeedbackRequest_Recipient int32 + +const ( + // Feedback for the TUM Dev Team + CreateFeedbackRequest_TUM_DEV CreateFeedbackRequest_Recipient = 0 + // Feedback for the general TUM Contact Form + CreateFeedbackRequest_TUM_CONTACT CreateFeedbackRequest_Recipient = 1 +) + +// Enum value maps for CreateFeedbackRequest_Recipient. +var ( + CreateFeedbackRequest_Recipient_name = map[int32]string{ + 0: "TUM_DEV", + 1: "TUM_CONTACT", + } + CreateFeedbackRequest_Recipient_value = map[string]int32{ + "TUM_DEV": 0, + "TUM_CONTACT": 1, + } +) + +func (x CreateFeedbackRequest_Recipient) Enum() *CreateFeedbackRequest_Recipient { + p := new(CreateFeedbackRequest_Recipient) + *p = x + return p +} + +func (x CreateFeedbackRequest_Recipient) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (CreateFeedbackRequest_Recipient) Descriptor() protoreflect.EnumDescriptor { + return file_tumdev_campus_backend_proto_enumTypes[1].Descriptor() +} + +func (CreateFeedbackRequest_Recipient) Type() protoreflect.EnumType { + return &file_tumdev_campus_backend_proto_enumTypes[1] +} + +func (x CreateFeedbackRequest_Recipient) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use CreateFeedbackRequest_Recipient.Descriptor instead. +func (CreateFeedbackRequest_Recipient) EnumDescriptor() ([]byte, []int) { + return file_tumdev_campus_backend_proto_rawDescGZIP(), []int{60, 0} +} + type CreateDeviceRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -596,14 +644,22 @@ type News struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` - Title string `protobuf:"bytes,2,opt,name=title,proto3" json:"title,omitempty"` - Text string `protobuf:"bytes,3,opt,name=text,proto3" json:"text,omitempty"` - Link string `protobuf:"bytes,4,opt,name=link,proto3" json:"link,omitempty"` - ImageUrl string `protobuf:"bytes,5,opt,name=image_url,json=imageUrl,proto3" json:"image_url,omitempty"` - Source string `protobuf:"bytes,6,opt,name=source,proto3" json:"source,omitempty"` - Created *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=created,proto3" json:"created,omitempty"` - Date *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=date,proto3" json:"date,omitempty"` + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Title string `protobuf:"bytes,2,opt,name=title,proto3" json:"title,omitempty"` + Text string `protobuf:"bytes,3,opt,name=text,proto3" json:"text,omitempty"` + Link string `protobuf:"bytes,4,opt,name=link,proto3" json:"link,omitempty"` + // where a news thumbnail is stored. empty string means no image is available + ImageUrl string `protobuf:"bytes,5,opt,name=image_url,json=imageUrl,proto3" json:"image_url,omitempty"` + // the id of the news source + SourceId string `protobuf:"bytes,6,opt,name=source_id,json=sourceId,proto3" json:"source_id,omitempty"` + // where the icon can be found + SourceIconUrl string `protobuf:"bytes,9,opt,name=source_icon_url,json=sourceIconUrl,proto3" json:"source_icon_url,omitempty"` + // human readable title of the news source + SourceTitle string `protobuf:"bytes,10,opt,name=source_title,json=sourceTitle,proto3" json:"source_title,omitempty"` + // when the news item was created in OUR database + Created *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=created,proto3" json:"created,omitempty"` + // the date of the news item + Date *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=date,proto3" json:"date,omitempty"` } func (x *News) Reset() { @@ -673,9 +729,23 @@ func (x *News) GetImageUrl() string { return "" } -func (x *News) GetSource() string { +func (x *News) GetSourceId() string { if x != nil { - return x.Source + return x.SourceId + } + return "" +} + +func (x *News) GetSourceIconUrl() string { + if x != nil { + return x.SourceIconUrl + } + return "" +} + +func (x *News) GetSourceTitle() string { + if x != nil { + return x.SourceTitle } return "" } @@ -899,7 +969,8 @@ type NewsSource struct { Source string `protobuf:"bytes,1,opt,name=source,proto3" json:"source,omitempty"` Title string `protobuf:"bytes,2,opt,name=title,proto3" json:"title,omitempty"` - Icon string `protobuf:"bytes,3,opt,name=icon,proto3" json:"icon,omitempty"` + // where the icon can be found + IconUrl string `protobuf:"bytes,3,opt,name=icon_url,json=iconUrl,proto3" json:"icon_url,omitempty"` } func (x *NewsSource) Reset() { @@ -948,9 +1019,9 @@ func (x *NewsSource) GetTitle() string { return "" } -func (x *NewsSource) GetIcon() string { +func (x *NewsSource) GetIconUrl() string { if x != nil { - return x.Icon + return x.IconUrl } return "" } @@ -3614,12 +3685,10 @@ type Movie struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - CoverName string `protobuf:"bytes,1,opt,name=cover_name,json=coverName,proto3" json:"cover_name,omitempty"` - CoverPath string `protobuf:"bytes,2,opt,name=cover_path,json=coverPath,proto3" json:"cover_path,omitempty"` - MovieId int64 `protobuf:"varint,3,opt,name=movie_id,json=movieId,proto3" json:"movie_id,omitempty"` - Date *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=date,proto3" json:"date,omitempty"` - Created *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=created,proto3" json:"created,omitempty"` - Title string `protobuf:"bytes,6,opt,name=title,proto3" json:"title,omitempty"` + MovieId int64 `protobuf:"varint,3,opt,name=movie_id,json=movieId,proto3" json:"movie_id,omitempty"` + Date *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=date,proto3" json:"date,omitempty"` + Created *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=created,proto3" json:"created,omitempty"` + Title string `protobuf:"bytes,6,opt,name=title,proto3" json:"title,omitempty"` // release year of the movie ReleaseYear string `protobuf:"bytes,7,opt,name=release_year,json=releaseYear,proto3" json:"release_year,omitempty"` Runtime string `protobuf:"bytes,8,opt,name=runtime,proto3" json:"runtime,omitempty"` @@ -3632,6 +3701,8 @@ type Movie struct { CoverId int64 `protobuf:"varint,14,opt,name=cover_id,json=coverId,proto3" json:"cover_id,omitempty"` // Where to find additional information about this movie Link string `protobuf:"bytes,16,opt,name=link,proto3" json:"link,omitempty"` + // Where to find a cover image for this movie + CoverUrl string `protobuf:"bytes,17,opt,name=cover_url,json=coverUrl,proto3" json:"cover_url,omitempty"` } func (x *Movie) Reset() { @@ -3666,20 +3737,6 @@ func (*Movie) Descriptor() ([]byte, []int) { return file_tumdev_campus_backend_proto_rawDescGZIP(), []int{59} } -func (x *Movie) GetCoverName() string { - if x != nil { - return x.CoverName - } - return "" -} - -func (x *Movie) GetCoverPath() string { - if x != nil { - return x.CoverPath - } - return "" -} - func (x *Movie) GetMovieId() int64 { if x != nil { return x.MovieId @@ -3771,14 +3828,38 @@ func (x *Movie) GetLink() string { return "" } -type CreateFeedbackReply struct { +func (x *Movie) GetCoverUrl() string { + if x != nil { + return x.CoverUrl + } + return "" +} + +type CreateFeedbackRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields + + // who is the feedback for + Recipient CreateFeedbackRequest_Recipient `protobuf:"varint,1,opt,name=recipient,proto3,enum=api.CreateFeedbackRequest_Recipient" json:"recipient,omitempty"` + // the email address of the user + FromEmail string `protobuf:"bytes,2,opt,name=from_email,json=fromEmail,proto3" json:"from_email,omitempty"` + // The actual message + Message string `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"` + // Optional location which the user can choose (data protection) to attach or not + Location *Coordinate `protobuf:"bytes,4,opt,name=location,proto3" json:"location,omitempty"` + // Optional os information which the user can choose (data protection) to attach or not + OsVersion string `protobuf:"bytes,5,opt,name=os_version,json=osVersion,proto3" json:"os_version,omitempty"` + // Optional app information which the user can choose (data protection) to attach or not + AppVersion string `protobuf:"bytes,6,opt,name=app_version,json=appVersion,proto3" json:"app_version,omitempty"` + // Optional file in Base64. + // Accepted file formats: jpeg, jpg, png, webp, md, txt, pdf + // Maximum file size 4MB as by Protobuf maximum per request message size + Attachment []byte `protobuf:"bytes,7,opt,name=attachment,proto3" json:"attachment,omitempty"` } -func (x *CreateFeedbackReply) Reset() { - *x = CreateFeedbackReply{} +func (x *CreateFeedbackRequest) Reset() { + *x = CreateFeedbackRequest{} if protoimpl.UnsafeEnabled { mi := &file_tumdev_campus_backend_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -3786,13 +3867,13 @@ func (x *CreateFeedbackReply) Reset() { } } -func (x *CreateFeedbackReply) String() string { +func (x *CreateFeedbackRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*CreateFeedbackReply) ProtoMessage() {} +func (*CreateFeedbackRequest) ProtoMessage() {} -func (x *CreateFeedbackReply) ProtoReflect() protoreflect.Message { +func (x *CreateFeedbackRequest) ProtoReflect() protoreflect.Message { mi := &file_tumdev_campus_backend_proto_msgTypes[60] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -3804,29 +3885,71 @@ func (x *CreateFeedbackReply) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use CreateFeedbackReply.ProtoReflect.Descriptor instead. -func (*CreateFeedbackReply) Descriptor() ([]byte, []int) { +// Deprecated: Use CreateFeedbackRequest.ProtoReflect.Descriptor instead. +func (*CreateFeedbackRequest) Descriptor() ([]byte, []int) { return file_tumdev_campus_backend_proto_rawDescGZIP(), []int{60} } -type CreateFeedbackRequest struct { +func (x *CreateFeedbackRequest) GetRecipient() CreateFeedbackRequest_Recipient { + if x != nil { + return x.Recipient + } + return CreateFeedbackRequest_TUM_DEV +} + +func (x *CreateFeedbackRequest) GetFromEmail() string { + if x != nil { + return x.FromEmail + } + return "" +} + +func (x *CreateFeedbackRequest) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *CreateFeedbackRequest) GetLocation() *Coordinate { + if x != nil { + return x.Location + } + return nil +} + +func (x *CreateFeedbackRequest) GetOsVersion() string { + if x != nil { + return x.OsVersion + } + return "" +} + +func (x *CreateFeedbackRequest) GetAppVersion() string { + if x != nil { + return x.AppVersion + } + return "" +} + +func (x *CreateFeedbackRequest) GetAttachment() []byte { + if x != nil { + return x.Attachment + } + return nil +} + +type Coordinate struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Topic string `protobuf:"bytes,1,opt,name=topic,proto3" json:"topic,omitempty"` - Email string `protobuf:"bytes,2,opt,name=email,proto3" json:"email,omitempty"` - EmailId string `protobuf:"bytes,3,opt,name=email_id,json=emailId,proto3" json:"email_id,omitempty"` - Message string `protobuf:"bytes,4,opt,name=message,proto3" json:"message,omitempty"` - ImageCount int32 `protobuf:"varint,5,opt,name=image_count,json=imageCount,proto3" json:"image_count,omitempty"` - Latitude float64 `protobuf:"fixed64,6,opt,name=latitude,proto3" json:"latitude,omitempty"` - Longitude float64 `protobuf:"fixed64,7,opt,name=longitude,proto3" json:"longitude,omitempty"` - OsVersion string `protobuf:"bytes,8,opt,name=os_version,json=osVersion,proto3" json:"os_version,omitempty"` - AppVersion string `protobuf:"bytes,9,opt,name=app_version,json=appVersion,proto3" json:"app_version,omitempty"` + Latitude float64 `protobuf:"fixed64,1,opt,name=latitude,proto3" json:"latitude,omitempty"` + Longitude float64 `protobuf:"fixed64,2,opt,name=longitude,proto3" json:"longitude,omitempty"` } -func (x *CreateFeedbackRequest) Reset() { - *x = CreateFeedbackRequest{} +func (x *Coordinate) Reset() { + *x = Coordinate{} if protoimpl.UnsafeEnabled { mi := &file_tumdev_campus_backend_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -3834,13 +3957,13 @@ func (x *CreateFeedbackRequest) Reset() { } } -func (x *CreateFeedbackRequest) String() string { +func (x *Coordinate) String() string { return protoimpl.X.MessageStringOf(x) } -func (*CreateFeedbackRequest) ProtoMessage() {} +func (*Coordinate) ProtoMessage() {} -func (x *CreateFeedbackRequest) ProtoReflect() protoreflect.Message { +func (x *Coordinate) ProtoReflect() protoreflect.Message { mi := &file_tumdev_campus_backend_proto_msgTypes[61] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -3852,72 +3975,61 @@ func (x *CreateFeedbackRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use CreateFeedbackRequest.ProtoReflect.Descriptor instead. -func (*CreateFeedbackRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use Coordinate.ProtoReflect.Descriptor instead. +func (*Coordinate) Descriptor() ([]byte, []int) { return file_tumdev_campus_backend_proto_rawDescGZIP(), []int{61} } -func (x *CreateFeedbackRequest) GetTopic() string { - if x != nil { - return x.Topic - } - return "" -} - -func (x *CreateFeedbackRequest) GetEmail() string { +func (x *Coordinate) GetLatitude() float64 { if x != nil { - return x.Email + return x.Latitude } - return "" + return 0 } -func (x *CreateFeedbackRequest) GetEmailId() string { +func (x *Coordinate) GetLongitude() float64 { if x != nil { - return x.EmailId + return x.Longitude } - return "" + return 0 } -func (x *CreateFeedbackRequest) GetMessage() string { - if x != nil { - return x.Message - } - return "" +type CreateFeedbackReply struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } -func (x *CreateFeedbackRequest) GetImageCount() int32 { - if x != nil { - return x.ImageCount +func (x *CreateFeedbackReply) Reset() { + *x = CreateFeedbackReply{} + if protoimpl.UnsafeEnabled { + mi := &file_tumdev_campus_backend_proto_msgTypes[62] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } - return 0 } -func (x *CreateFeedbackRequest) GetLatitude() float64 { - if x != nil { - return x.Latitude - } - return 0 +func (x *CreateFeedbackReply) String() string { + return protoimpl.X.MessageStringOf(x) } -func (x *CreateFeedbackRequest) GetLongitude() float64 { - if x != nil { - return x.Longitude - } - return 0 -} +func (*CreateFeedbackReply) ProtoMessage() {} -func (x *CreateFeedbackRequest) GetOsVersion() string { - if x != nil { - return x.OsVersion +func (x *CreateFeedbackReply) ProtoReflect() protoreflect.Message { + mi := &file_tumdev_campus_backend_proto_msgTypes[62] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms } - return "" + return mi.MessageOf(x) } -func (x *CreateFeedbackRequest) GetAppVersion() string { - if x != nil { - return x.AppVersion - } - return "" +// Deprecated: Use CreateFeedbackReply.ProtoReflect.Descriptor instead. +func (*CreateFeedbackReply) Descriptor() ([]byte, []int) { + return file_tumdev_campus_backend_proto_rawDescGZIP(), []int{62} } type CreateFeedbackImageReply struct { @@ -3931,7 +4043,7 @@ type CreateFeedbackImageReply struct { func (x *CreateFeedbackImageReply) Reset() { *x = CreateFeedbackImageReply{} if protoimpl.UnsafeEnabled { - mi := &file_tumdev_campus_backend_proto_msgTypes[62] + mi := &file_tumdev_campus_backend_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3944,7 +4056,7 @@ func (x *CreateFeedbackImageReply) String() string { func (*CreateFeedbackImageReply) ProtoMessage() {} func (x *CreateFeedbackImageReply) ProtoReflect() protoreflect.Message { - mi := &file_tumdev_campus_backend_proto_msgTypes[62] + mi := &file_tumdev_campus_backend_proto_msgTypes[63] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3957,7 +4069,7 @@ func (x *CreateFeedbackImageReply) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateFeedbackImageReply.ProtoReflect.Descriptor instead. func (*CreateFeedbackImageReply) Descriptor() ([]byte, []int) { - return file_tumdev_campus_backend_proto_rawDescGZIP(), []int{62} + return file_tumdev_campus_backend_proto_rawDescGZIP(), []int{63} } func (x *CreateFeedbackImageReply) GetStatus() string { @@ -3979,7 +4091,7 @@ type CreateFeedbackImageRequest struct { func (x *CreateFeedbackImageRequest) Reset() { *x = CreateFeedbackImageRequest{} if protoimpl.UnsafeEnabled { - mi := &file_tumdev_campus_backend_proto_msgTypes[63] + mi := &file_tumdev_campus_backend_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3992,7 +4104,7 @@ func (x *CreateFeedbackImageRequest) String() string { func (*CreateFeedbackImageRequest) ProtoMessage() {} func (x *CreateFeedbackImageRequest) ProtoReflect() protoreflect.Message { - mi := &file_tumdev_campus_backend_proto_msgTypes[63] + mi := &file_tumdev_campus_backend_proto_msgTypes[64] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4005,7 +4117,7 @@ func (x *CreateFeedbackImageRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateFeedbackImageRequest.ProtoReflect.Descriptor instead. func (*CreateFeedbackImageRequest) Descriptor() ([]byte, []int) { - return file_tumdev_campus_backend_proto_rawDescGZIP(), []int{63} + return file_tumdev_campus_backend_proto_rawDescGZIP(), []int{64} } func (x *CreateFeedbackImageRequest) GetId() int32 { @@ -4033,7 +4145,7 @@ type GetMemberRequest struct { func (x *GetMemberRequest) Reset() { *x = GetMemberRequest{} if protoimpl.UnsafeEnabled { - mi := &file_tumdev_campus_backend_proto_msgTypes[64] + mi := &file_tumdev_campus_backend_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4046,7 +4158,7 @@ func (x *GetMemberRequest) String() string { func (*GetMemberRequest) ProtoMessage() {} func (x *GetMemberRequest) ProtoReflect() protoreflect.Message { - mi := &file_tumdev_campus_backend_proto_msgTypes[64] + mi := &file_tumdev_campus_backend_proto_msgTypes[65] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4059,7 +4171,7 @@ func (x *GetMemberRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetMemberRequest.ProtoReflect.Descriptor instead. func (*GetMemberRequest) Descriptor() ([]byte, []int) { - return file_tumdev_campus_backend_proto_rawDescGZIP(), []int{64} + return file_tumdev_campus_backend_proto_rawDescGZIP(), []int{65} } func (x *GetMemberRequest) GetLrzId() string { @@ -4082,7 +4194,7 @@ type GetMemberReply struct { func (x *GetMemberReply) Reset() { *x = GetMemberReply{} if protoimpl.UnsafeEnabled { - mi := &file_tumdev_campus_backend_proto_msgTypes[65] + mi := &file_tumdev_campus_backend_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4095,7 +4207,7 @@ func (x *GetMemberReply) String() string { func (*GetMemberReply) ProtoMessage() {} func (x *GetMemberReply) ProtoReflect() protoreflect.Message { - mi := &file_tumdev_campus_backend_proto_msgTypes[65] + mi := &file_tumdev_campus_backend_proto_msgTypes[66] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4108,7 +4220,7 @@ func (x *GetMemberReply) ProtoReflect() protoreflect.Message { // Deprecated: Use GetMemberReply.ProtoReflect.Descriptor instead. func (*GetMemberReply) Descriptor() ([]byte, []int) { - return file_tumdev_campus_backend_proto_rawDescGZIP(), []int{65} + return file_tumdev_campus_backend_proto_rawDescGZIP(), []int{66} } func (x *GetMemberReply) GetLrzId() string { @@ -4143,7 +4255,7 @@ type GetUploadStatusRequest struct { func (x *GetUploadStatusRequest) Reset() { *x = GetUploadStatusRequest{} if protoimpl.UnsafeEnabled { - mi := &file_tumdev_campus_backend_proto_msgTypes[66] + mi := &file_tumdev_campus_backend_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4156,7 +4268,7 @@ func (x *GetUploadStatusRequest) String() string { func (*GetUploadStatusRequest) ProtoMessage() {} func (x *GetUploadStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_tumdev_campus_backend_proto_msgTypes[66] + mi := &file_tumdev_campus_backend_proto_msgTypes[67] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4169,7 +4281,7 @@ func (x *GetUploadStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetUploadStatusRequest.ProtoReflect.Descriptor instead. func (*GetUploadStatusRequest) Descriptor() ([]byte, []int) { - return file_tumdev_campus_backend_proto_rawDescGZIP(), []int{66} + return file_tumdev_campus_backend_proto_rawDescGZIP(), []int{67} } func (x *GetUploadStatusRequest) GetLrzId() string { @@ -4194,7 +4306,7 @@ type GetUploadStatusReply struct { func (x *GetUploadStatusReply) Reset() { *x = GetUploadStatusReply{} if protoimpl.UnsafeEnabled { - mi := &file_tumdev_campus_backend_proto_msgTypes[67] + mi := &file_tumdev_campus_backend_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4207,7 +4319,7 @@ func (x *GetUploadStatusReply) String() string { func (*GetUploadStatusReply) ProtoMessage() {} func (x *GetUploadStatusReply) ProtoReflect() protoreflect.Message { - mi := &file_tumdev_campus_backend_proto_msgTypes[67] + mi := &file_tumdev_campus_backend_proto_msgTypes[68] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4220,7 +4332,7 @@ func (x *GetUploadStatusReply) ProtoReflect() protoreflect.Message { // Deprecated: Use GetUploadStatusReply.ProtoReflect.Descriptor instead. func (*GetUploadStatusReply) Descriptor() ([]byte, []int) { - return file_tumdev_campus_backend_proto_rawDescGZIP(), []int{67} + return file_tumdev_campus_backend_proto_rawDescGZIP(), []int{68} } func (x *GetUploadStatusReply) GetFcmToken() string { @@ -4269,7 +4381,7 @@ type GetNotificationRequest struct { func (x *GetNotificationRequest) Reset() { *x = GetNotificationRequest{} if protoimpl.UnsafeEnabled { - mi := &file_tumdev_campus_backend_proto_msgTypes[68] + mi := &file_tumdev_campus_backend_proto_msgTypes[69] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4282,7 +4394,7 @@ func (x *GetNotificationRequest) String() string { func (*GetNotificationRequest) ProtoMessage() {} func (x *GetNotificationRequest) ProtoReflect() protoreflect.Message { - mi := &file_tumdev_campus_backend_proto_msgTypes[68] + mi := &file_tumdev_campus_backend_proto_msgTypes[69] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4295,7 +4407,7 @@ func (x *GetNotificationRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetNotificationRequest.ProtoReflect.Descriptor instead. func (*GetNotificationRequest) Descriptor() ([]byte, []int) { - return file_tumdev_campus_backend_proto_rawDescGZIP(), []int{68} + return file_tumdev_campus_backend_proto_rawDescGZIP(), []int{69} } func (x *GetNotificationRequest) GetNotificationId() int32 { @@ -4320,7 +4432,7 @@ type GetNotificationReply struct { func (x *GetNotificationReply) Reset() { *x = GetNotificationReply{} if protoimpl.UnsafeEnabled { - mi := &file_tumdev_campus_backend_proto_msgTypes[69] + mi := &file_tumdev_campus_backend_proto_msgTypes[70] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4333,7 +4445,7 @@ func (x *GetNotificationReply) String() string { func (*GetNotificationReply) ProtoMessage() {} func (x *GetNotificationReply) ProtoReflect() protoreflect.Message { - mi := &file_tumdev_campus_backend_proto_msgTypes[69] + mi := &file_tumdev_campus_backend_proto_msgTypes[70] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4346,7 +4458,7 @@ func (x *GetNotificationReply) ProtoReflect() protoreflect.Message { // Deprecated: Use GetNotificationReply.ProtoReflect.Descriptor instead. func (*GetNotificationReply) Descriptor() ([]byte, []int) { - return file_tumdev_campus_backend_proto_rawDescGZIP(), []int{69} + return file_tumdev_campus_backend_proto_rawDescGZIP(), []int{70} } func (x *GetNotificationReply) GetNotificationId() int32 { @@ -4395,7 +4507,7 @@ type GetNotificationConfirmRequest struct { func (x *GetNotificationConfirmRequest) Reset() { *x = GetNotificationConfirmRequest{} if protoimpl.UnsafeEnabled { - mi := &file_tumdev_campus_backend_proto_msgTypes[70] + mi := &file_tumdev_campus_backend_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4408,7 +4520,7 @@ func (x *GetNotificationConfirmRequest) String() string { func (*GetNotificationConfirmRequest) ProtoMessage() {} func (x *GetNotificationConfirmRequest) ProtoReflect() protoreflect.Message { - mi := &file_tumdev_campus_backend_proto_msgTypes[70] + mi := &file_tumdev_campus_backend_proto_msgTypes[71] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4421,7 +4533,7 @@ func (x *GetNotificationConfirmRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetNotificationConfirmRequest.ProtoReflect.Descriptor instead. func (*GetNotificationConfirmRequest) Descriptor() ([]byte, []int) { - return file_tumdev_campus_backend_proto_rawDescGZIP(), []int{70} + return file_tumdev_campus_backend_proto_rawDescGZIP(), []int{71} } func (x *GetNotificationConfirmRequest) GetNotificationId() int32 { @@ -4442,7 +4554,7 @@ type GetNotificationConfirmReply struct { func (x *GetNotificationConfirmReply) Reset() { *x = GetNotificationConfirmReply{} if protoimpl.UnsafeEnabled { - mi := &file_tumdev_campus_backend_proto_msgTypes[71] + mi := &file_tumdev_campus_backend_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4455,7 +4567,7 @@ func (x *GetNotificationConfirmReply) String() string { func (*GetNotificationConfirmReply) ProtoMessage() {} func (x *GetNotificationConfirmReply) ProtoReflect() protoreflect.Message { - mi := &file_tumdev_campus_backend_proto_msgTypes[71] + mi := &file_tumdev_campus_backend_proto_msgTypes[72] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4468,7 +4580,7 @@ func (x *GetNotificationConfirmReply) ProtoReflect() protoreflect.Message { // Deprecated: Use GetNotificationConfirmReply.ProtoReflect.Descriptor instead. func (*GetNotificationConfirmReply) Descriptor() ([]byte, []int) { - return file_tumdev_campus_backend_proto_rawDescGZIP(), []int{71} + return file_tumdev_campus_backend_proto_rawDescGZIP(), []int{72} } func (x *GetNotificationConfirmReply) GetStatus() string { @@ -4490,7 +4602,7 @@ type GetCanteenHeadCountRequest struct { func (x *GetCanteenHeadCountRequest) Reset() { *x = GetCanteenHeadCountRequest{} if protoimpl.UnsafeEnabled { - mi := &file_tumdev_campus_backend_proto_msgTypes[72] + mi := &file_tumdev_campus_backend_proto_msgTypes[73] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4503,7 +4615,7 @@ func (x *GetCanteenHeadCountRequest) String() string { func (*GetCanteenHeadCountRequest) ProtoMessage() {} func (x *GetCanteenHeadCountRequest) ProtoReflect() protoreflect.Message { - mi := &file_tumdev_campus_backend_proto_msgTypes[72] + mi := &file_tumdev_campus_backend_proto_msgTypes[73] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4516,7 +4628,7 @@ func (x *GetCanteenHeadCountRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetCanteenHeadCountRequest.ProtoReflect.Descriptor instead. func (*GetCanteenHeadCountRequest) Descriptor() ([]byte, []int) { - return file_tumdev_campus_backend_proto_rawDescGZIP(), []int{72} + return file_tumdev_campus_backend_proto_rawDescGZIP(), []int{73} } func (x *GetCanteenHeadCountRequest) GetCanteenId() string { @@ -4544,7 +4656,7 @@ type GetCanteenHeadCountReply struct { func (x *GetCanteenHeadCountReply) Reset() { *x = GetCanteenHeadCountReply{} if protoimpl.UnsafeEnabled { - mi := &file_tumdev_campus_backend_proto_msgTypes[73] + mi := &file_tumdev_campus_backend_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4557,7 +4669,7 @@ func (x *GetCanteenHeadCountReply) String() string { func (*GetCanteenHeadCountReply) ProtoMessage() {} func (x *GetCanteenHeadCountReply) ProtoReflect() protoreflect.Message { - mi := &file_tumdev_campus_backend_proto_msgTypes[73] + mi := &file_tumdev_campus_backend_proto_msgTypes[74] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4570,7 +4682,7 @@ func (x *GetCanteenHeadCountReply) ProtoReflect() protoreflect.Message { // Deprecated: Use GetCanteenHeadCountReply.ProtoReflect.Descriptor instead. func (*GetCanteenHeadCountReply) Descriptor() ([]byte, []int) { - return file_tumdev_campus_backend_proto_rawDescGZIP(), []int{73} + return file_tumdev_campus_backend_proto_rawDescGZIP(), []int{74} } func (x *GetCanteenHeadCountReply) GetCount() uint32 { @@ -4614,7 +4726,7 @@ type ListMoreInformationReply_MoreInformation struct { func (x *ListMoreInformationReply_MoreInformation) Reset() { *x = ListMoreInformationReply_MoreInformation{} if protoimpl.UnsafeEnabled { - mi := &file_tumdev_campus_backend_proto_msgTypes[74] + mi := &file_tumdev_campus_backend_proto_msgTypes[75] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4627,7 +4739,7 @@ func (x *ListMoreInformationReply_MoreInformation) String() string { func (*ListMoreInformationReply_MoreInformation) ProtoMessage() {} func (x *ListMoreInformationReply_MoreInformation) ProtoReflect() protoreflect.Message { - mi := &file_tumdev_campus_backend_proto_msgTypes[74] + mi := &file_tumdev_campus_backend_proto_msgTypes[75] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4725,16 +4837,21 @@ var file_tumdev_campus_backend_proto_rawDesc = []byte{ 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x75, 0x72, 0x70, 0x6f, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x61, 0x6d, 0x70, 0x75, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x61, 0x6d, 0x70, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x09, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0xef, 0x01, 0x0a, 0x04, 0x4e, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0xbf, 0x02, 0x0a, 0x04, 0x4e, 0x65, 0x77, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x55, 0x72, 0x6c, 0x12, 0x16, - 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x55, 0x72, 0x6c, 0x12, 0x1b, + 0x0a, 0x09, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x63, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x63, 0x6f, 0x6e, + 0x55, 0x72, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x74, 0x69, + 0x74, 0x6c, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x54, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x34, 0x0a, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x18, 0x07, 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, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x12, 0x2e, 0x0a, 0x04, @@ -4759,625 +4876,632 @@ var file_tumdev_campus_backend_proto_rawDesc = []byte{ 0x65, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x29, 0x0a, 0x07, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4e, 0x65, 0x77, 0x73, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x07, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x73, 0x22, 0x4e, 0x0a, 0x0a, 0x4e, 0x65, 0x77, 0x73, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x65, 0x73, 0x22, 0x55, 0x0a, 0x0a, 0x4e, 0x65, 0x77, 0x73, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x12, - 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, - 0x6f, 0x6e, 0x22, 0x44, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x65, 0x77, 0x73, 0x41, 0x6c, - 0x65, 0x72, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x12, 0x6c, - 0x61, 0x73, 0x74, 0x5f, 0x6e, 0x65, 0x77, 0x73, 0x5f, 0x61, 0x6c, 0x65, 0x72, 0x74, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x4e, 0x65, 0x77, - 0x73, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x49, 0x64, 0x22, 0x3d, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, - 0x4e, 0x65, 0x77, 0x73, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, - 0x26, 0x0a, 0x06, 0x61, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x0e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4e, 0x65, 0x77, 0x73, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x52, - 0x06, 0x61, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x22, 0xce, 0x01, 0x0a, 0x09, 0x4e, 0x65, 0x77, 0x73, - 0x41, 0x6c, 0x65, 0x72, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x5f, 0x75, - 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x55, - 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x34, 0x0a, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x64, 0x18, 0x03, 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, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x12, 0x2e, 0x0a, 0x04, - 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x19, + 0x0a, 0x08, 0x69, 0x63, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x69, 0x63, 0x6f, 0x6e, 0x55, 0x72, 0x6c, 0x22, 0x44, 0x0a, 0x15, 0x4c, 0x69, 0x73, + 0x74, 0x4e, 0x65, 0x77, 0x73, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x12, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x6e, 0x65, 0x77, 0x73, 0x5f, + 0x61, 0x6c, 0x65, 0x72, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, + 0x6c, 0x61, 0x73, 0x74, 0x4e, 0x65, 0x77, 0x73, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x49, 0x64, 0x22, + 0x3d, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x65, 0x77, 0x73, 0x41, 0x6c, 0x65, 0x72, 0x74, + 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x26, 0x0a, 0x06, 0x61, 0x6c, 0x65, 0x72, 0x74, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4e, 0x65, 0x77, + 0x73, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x52, 0x06, 0x61, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x22, 0xce, + 0x01, 0x0a, 0x09, 0x4e, 0x65, 0x77, 0x73, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x12, 0x1b, 0x0a, 0x09, + 0x69, 0x6d, 0x61, 0x67, 0x65, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x55, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, + 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x34, 0x0a, + 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x18, 0x03, 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, 0x07, 0x63, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x64, 0x12, 0x2e, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 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, 0x04, 0x66, + 0x72, 0x6f, 0x6d, 0x12, 0x2a, 0x0a, 0x02, 0x74, 0x6f, 0x18, 0x05, 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, 0x02, 0x74, 0x6f, 0x22, + 0xac, 0x01, 0x0a, 0x19, 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, 0x12, 0x1d, 0x0a, + 0x0a, 0x63, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x63, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x49, 0x64, 0x12, 0x2e, 0x0a, 0x04, + 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x02, 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, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x2a, 0x0a, 0x02, - 0x74, 0x6f, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x74, 0x6f, 0x18, 0x03, 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, 0x02, 0x74, 0x6f, 0x22, 0xac, 0x01, 0x0a, 0x19, 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, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x61, 0x6e, 0x74, 0x65, 0x65, - 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, 0x6e, 0x74, - 0x65, 0x65, 0x6e, 0x49, 0x64, 0x12, 0x2e, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x02, 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, - 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x2a, 0x0a, 0x02, 0x74, 0x6f, 0x18, 0x03, 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, 0x02, 0x74, - 0x6f, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0xc8, 0x01, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, - 0x43, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x65, - 0x70, 0x6c, 0x79, 0x12, 0x2e, 0x0a, 0x06, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, - 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x52, 0x06, 0x72, 0x61, 0x74, - 0x69, 0x6e, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x76, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, - 0x52, 0x03, 0x61, 0x76, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x74, 0x64, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x01, 0x52, 0x03, 0x73, 0x74, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x69, 0x6e, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6d, 0x69, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x61, 0x78, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6d, 0x61, 0x78, 0x12, 0x35, 0x0a, 0x0b, 0x72, - 0x61, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x54, 0x61, 0x67, - 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x0a, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x54, 0x61, - 0x67, 0x73, 0x22, 0xbc, 0x01, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x44, 0x69, 0x73, 0x68, 0x52, 0x61, - 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, - 0x63, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x63, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x64, - 0x69, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x69, 0x73, 0x68, 0x12, - 0x2e, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x03, 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, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, - 0x2a, 0x0a, 0x02, 0x74, 0x6f, 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, 0x02, 0x74, 0x6f, 0x12, 0x14, 0x0a, 0x05, 0x6c, - 0x69, 0x6d, 0x69, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, - 0x74, 0x22, 0xf7, 0x01, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x44, 0x69, 0x73, 0x68, 0x52, 0x61, 0x74, - 0x69, 0x6e, 0x67, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x2e, 0x0a, 0x06, 0x72, 0x61, 0x74, - 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, - 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x70, 0x6c, - 0x79, 0x52, 0x06, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x76, 0x67, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x61, 0x76, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x73, - 0x74, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x73, 0x74, 0x64, 0x12, 0x10, 0x0a, - 0x03, 0x6d, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6d, 0x69, 0x6e, 0x12, - 0x10, 0x0a, 0x03, 0x6d, 0x61, 0x78, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6d, 0x61, - 0x78, 0x12, 0x35, 0x0a, 0x0b, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x61, 0x67, 0x73, - 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x61, 0x74, - 0x69, 0x6e, 0x67, 0x54, 0x61, 0x67, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x0a, 0x72, 0x61, - 0x74, 0x69, 0x6e, 0x67, 0x54, 0x61, 0x67, 0x73, 0x12, 0x31, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, - 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x54, 0x61, 0x67, 0x52, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x52, 0x08, 0x6e, 0x61, 0x6d, 0x65, 0x54, 0x61, 0x67, 0x73, 0x22, 0xcc, 0x01, 0x0a, 0x11, - 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x70, 0x6c, - 0x79, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x06, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6d, 0x61, - 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, - 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x39, 0x0a, 0x0b, 0x72, 0x61, 0x74, - 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, - 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x54, 0x61, 0x67, 0x4e, 0x65, - 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x0a, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, - 0x54, 0x61, 0x67, 0x73, 0x12, 0x34, 0x0a, 0x07, 0x76, 0x69, 0x73, 0x69, 0x74, 0x65, 0x64, 0x18, - 0x05, 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, 0x07, 0x76, 0x69, 0x73, 0x69, 0x74, 0x65, 0x64, 0x22, 0x1a, 0x0a, 0x18, 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, 0xb4, 0x01, 0x0a, 0x1a, 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, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x1d, 0x0a, - 0x0a, 0x63, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x09, 0x63, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, - 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x69, 0x6d, 0x61, - 0x67, 0x65, 0x12, 0x2f, 0x0a, 0x0b, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x61, 0x67, - 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x61, - 0x74, 0x69, 0x6e, 0x67, 0x54, 0x61, 0x67, 0x52, 0x0a, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x54, - 0x61, 0x67, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x17, 0x0a, - 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x69, 0x73, 0x68, 0x52, 0x61, 0x74, 0x69, 0x6e, - 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0xc5, 0x01, 0x0a, 0x17, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x44, 0x69, 0x73, 0x68, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x06, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x61, - 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x63, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x69, 0x73, - 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x69, 0x73, 0x68, 0x12, 0x14, 0x0a, - 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x69, 0x6d, - 0x61, 0x67, 0x65, 0x12, 0x2f, 0x0a, 0x0b, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x61, - 0x67, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, - 0x61, 0x74, 0x69, 0x6e, 0x67, 0x54, 0x61, 0x67, 0x52, 0x0a, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, - 0x54, 0x61, 0x67, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x1e, - 0x0a, 0x1c, 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, 0x22, 0x50, - 0x0a, 0x1a, 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, 0x12, 0x32, 0x0a, 0x0b, - 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x11, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x54, 0x61, 0x67, 0x73, 0x4f, 0x76, 0x65, 0x72, - 0x76, 0x69, 0x65, 0x77, 0x52, 0x0a, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x54, 0x61, 0x67, 0x73, - 0x22, 0x15, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x54, 0x61, 0x67, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x47, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x4e, - 0x61, 0x6d, 0x65, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x32, 0x0a, 0x0b, - 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x11, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x54, 0x61, 0x67, 0x73, 0x4f, 0x76, 0x65, 0x72, - 0x76, 0x69, 0x65, 0x77, 0x52, 0x0a, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x54, 0x61, 0x67, 0x73, - 0x22, 0x21, 0x0a, 0x1f, 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, 0x22, 0x53, 0x0a, 0x1d, 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, 0x12, 0x32, 0x0a, 0x0b, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x74, - 0x61, 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x61, 0x70, 0x69, 0x2e, - 0x54, 0x61, 0x67, 0x73, 0x4f, 0x76, 0x65, 0x72, 0x76, 0x69, 0x65, 0x77, 0x52, 0x0a, 0x72, 0x61, - 0x74, 0x69, 0x6e, 0x67, 0x54, 0x61, 0x67, 0x73, 0x22, 0x45, 0x0a, 0x0c, 0x54, 0x61, 0x67, 0x73, - 0x4f, 0x76, 0x65, 0x72, 0x76, 0x69, 0x65, 0x77, 0x12, 0x15, 0x0a, 0x06, 0x74, 0x61, 0x67, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x74, 0x61, 0x67, 0x49, 0x64, 0x12, - 0x0e, 0x0a, 0x02, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x64, 0x65, 0x12, - 0x0e, 0x0a, 0x02, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x65, 0x6e, 0x22, - 0x3a, 0x0a, 0x09, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x54, 0x61, 0x67, 0x12, 0x15, 0x0a, 0x06, - 0x74, 0x61, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x61, - 0x67, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x01, 0x52, 0x06, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x22, 0x44, 0x0a, 0x13, 0x52, - 0x61, 0x74, 0x69, 0x6e, 0x67, 0x54, 0x61, 0x67, 0x4e, 0x65, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x74, 0x61, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x05, 0x74, 0x61, 0x67, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6f, 0x69, - 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x70, 0x6f, 0x69, 0x6e, 0x74, - 0x73, 0x22, 0x70, 0x0a, 0x0f, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x54, 0x61, 0x67, 0x52, 0x65, - 0x73, 0x75, 0x6c, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x74, 0x61, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x74, 0x61, 0x67, 0x49, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x61, - 0x76, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x61, 0x76, 0x67, 0x12, 0x10, 0x0a, - 0x03, 0x73, 0x74, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x73, 0x74, 0x64, 0x12, - 0x10, 0x0a, 0x03, 0x6d, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6d, 0x69, - 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x61, 0x78, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, - 0x6d, 0x61, 0x78, 0x22, 0x15, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x6e, 0x74, 0x65, - 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x3b, 0x0a, 0x11, 0x4c, 0x69, - 0x73, 0x74, 0x43, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, - 0x26, 0x0a, 0x07, 0x63, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x0c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x52, 0x07, - 0x63, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x22, 0x6d, 0x0a, 0x07, 0x43, 0x61, 0x6e, 0x74, 0x65, - 0x65, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, - 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1c, 0x0a, 0x09, - 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, - 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, - 0x74, 0x69, 0x74, 0x75, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x6c, 0x61, - 0x74, 0x69, 0x74, 0x75, 0x64, 0x65, 0x22, 0x6c, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x69, - 0x73, 0x68, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, - 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x63, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x79, 0x65, - 0x61, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x79, 0x65, 0x61, 0x72, 0x12, 0x12, - 0x0a, 0x04, 0x77, 0x65, 0x65, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x77, 0x65, - 0x65, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x64, 0x61, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x03, 0x64, 0x61, 0x79, 0x22, 0x25, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x69, 0x73, 0x68, - 0x65, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x69, 0x73, 0x68, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x64, 0x69, 0x73, 0x68, 0x22, 0x1e, 0x0a, 0x1c, 0x4c, - 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x50, 0x65, - 0x72, 0x73, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x63, 0x0a, 0x1a, 0x4c, - 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x50, 0x65, - 0x72, 0x73, 0x6f, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x45, 0x0a, 0x12, 0x72, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x52, 0x11, 0x72, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, - 0x22, 0x8c, 0x01, 0x0a, 0x11, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x69, 0x62, 0x6c, 0x65, - 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x65, - 0x6c, 0x65, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, - 0x65, 0x6c, 0x65, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, - 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x18, - 0x0a, 0x07, 0x66, 0x61, 0x63, 0x75, 0x6c, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x66, 0x61, 0x63, 0x75, 0x6c, 0x74, 0x79, 0x12, 0x15, 0x0a, 0x06, 0x74, 0x75, 0x6d, 0x5f, - 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x75, 0x6d, 0x49, 0x64, 0x22, - 0xfc, 0x01, 0x0a, 0x16, 0x52, 0x6f, 0x6f, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, - 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x72, 0x6f, 0x6f, - 0x6d, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x63, 0x6f, 0x64, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x6f, 0x6f, 0x6d, 0x43, 0x6f, 0x64, 0x65, - 0x12, 0x1f, 0x0a, 0x0b, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x6e, 0x72, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x4e, - 0x72, 0x12, 0x17, 0x0a, 0x07, 0x61, 0x72, 0x63, 0x68, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x61, 0x72, 0x63, 0x68, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x6e, - 0x66, 0x6f, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x18, - 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x75, 0x72, 0x70, - 0x6f, 0x73, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x75, 0x72, 0x70, 0x6f, - 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x61, 0x6d, 0x70, 0x75, 0x73, 0x18, 0x08, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x63, 0x61, 0x6d, 0x70, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x1c, - 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x6f, 0x72, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xb6, 0x01, 0x0a, - 0x18, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x6f, 0x72, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x43, 0x0a, 0x05, 0x69, 0x6e, 0x66, - 0x6f, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x4d, 0x6f, 0x72, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x2e, 0x4d, 0x6f, 0x72, 0x65, 0x49, 0x6e, 0x66, 0x6f, - 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x69, 0x6e, 0x66, 0x6f, 0x73, 0x1a, 0x55, - 0x0a, 0x0f, 0x4d, 0x6f, 0x72, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 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, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, - 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, - 0x6f, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x35, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x70, 0x65, - 0x6e, 0x69, 0x6e, 0x67, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x22, 0x54, 0x0a, 0x15, + 0x74, 0x61, 0x6d, 0x70, 0x52, 0x02, 0x74, 0x6f, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, + 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0xc8, + 0x01, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x52, 0x61, + 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x2e, 0x0a, 0x06, 0x72, 0x61, + 0x74, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x70, + 0x6c, 0x79, 0x52, 0x06, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x76, + 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x61, 0x76, 0x67, 0x12, 0x10, 0x0a, 0x03, + 0x73, 0x74, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x73, 0x74, 0x64, 0x12, 0x10, + 0x0a, 0x03, 0x6d, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6d, 0x69, 0x6e, + 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x61, 0x78, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6d, + 0x61, 0x78, 0x12, 0x35, 0x0a, 0x0b, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x61, 0x67, + 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x61, + 0x74, 0x69, 0x6e, 0x67, 0x54, 0x61, 0x67, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x0a, 0x72, + 0x61, 0x74, 0x69, 0x6e, 0x67, 0x54, 0x61, 0x67, 0x73, 0x22, 0xbc, 0x01, 0x0a, 0x15, 0x47, 0x65, + 0x74, 0x44, 0x69, 0x73, 0x68, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, + 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x69, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x64, 0x69, 0x73, 0x68, 0x12, 0x2e, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x03, + 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, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x2a, 0x0a, 0x02, 0x74, 0x6f, 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, 0x02, + 0x74, 0x6f, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0xf7, 0x01, 0x0a, 0x13, 0x47, 0x65, 0x74, + 0x44, 0x69, 0x73, 0x68, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, + 0x12, 0x2e, 0x0a, 0x06, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x52, 0x61, 0x74, + 0x69, 0x6e, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x52, 0x06, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, + 0x12, 0x10, 0x0a, 0x03, 0x61, 0x76, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x61, + 0x76, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x74, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, + 0x03, 0x73, 0x74, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x03, 0x6d, 0x69, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x61, 0x78, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x03, 0x6d, 0x61, 0x78, 0x12, 0x35, 0x0a, 0x0b, 0x72, 0x61, 0x74, 0x69, + 0x6e, 0x67, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x54, 0x61, 0x67, 0x52, 0x65, 0x73, + 0x75, 0x6c, 0x74, 0x52, 0x0a, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x54, 0x61, 0x67, 0x73, 0x12, + 0x31, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x07, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x54, + 0x61, 0x67, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x08, 0x6e, 0x61, 0x6d, 0x65, 0x54, 0x61, + 0x67, 0x73, 0x22, 0xcc, 0x01, 0x0a, 0x11, 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x52, 0x61, 0x74, + 0x69, 0x6e, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, + 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, + 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, + 0x12, 0x39, 0x0a, 0x0b, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, + 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x61, 0x74, 0x69, + 0x6e, 0x67, 0x54, 0x61, 0x67, 0x4e, 0x65, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, + 0x0a, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x54, 0x61, 0x67, 0x73, 0x12, 0x34, 0x0a, 0x07, 0x76, + 0x69, 0x73, 0x69, 0x74, 0x65, 0x64, 0x18, 0x05, 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, 0x07, 0x76, 0x69, 0x73, 0x69, 0x74, 0x65, + 0x64, 0x22, 0x1a, 0x0a, 0x18, 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, 0xb4, 0x01, + 0x0a, 0x1a, 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, 0x12, 0x16, 0x0a, 0x06, + 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, 0x6e, 0x74, 0x65, 0x65, + 0x6e, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x2f, 0x0a, 0x0b, 0x72, 0x61, 0x74, + 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x54, 0x61, 0x67, 0x52, 0x0a, + 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x54, 0x61, 0x67, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, + 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, + 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x17, 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x69, + 0x73, 0x68, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0xc5, 0x01, + 0x0a, 0x17, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x69, 0x73, 0x68, 0x52, 0x61, 0x74, 0x69, + 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6f, 0x69, + 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x5f, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x49, 0x64, + 0x12, 0x12, 0x0a, 0x04, 0x64, 0x69, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x64, 0x69, 0x73, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x2f, 0x0a, 0x0b, 0x72, 0x61, + 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x0e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x54, 0x61, 0x67, 0x52, + 0x0a, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x54, 0x61, 0x67, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x63, + 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, + 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x1e, 0x0a, 0x1c, 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, 0x22, 0x50, 0x0a, 0x1a, 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, 0x12, 0x32, 0x0a, 0x0b, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x61, + 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x54, + 0x61, 0x67, 0x73, 0x4f, 0x76, 0x65, 0x72, 0x76, 0x69, 0x65, 0x77, 0x52, 0x0a, 0x72, 0x61, 0x74, + 0x69, 0x6e, 0x67, 0x54, 0x61, 0x67, 0x73, 0x22, 0x15, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x4e, + 0x61, 0x6d, 0x65, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x47, + 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, + 0x70, 0x6c, 0x79, 0x12, 0x32, 0x0a, 0x0b, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x61, + 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x54, + 0x61, 0x67, 0x73, 0x4f, 0x76, 0x65, 0x72, 0x76, 0x69, 0x65, 0x77, 0x52, 0x0a, 0x72, 0x61, 0x74, + 0x69, 0x6e, 0x67, 0x54, 0x61, 0x67, 0x73, 0x22, 0x21, 0x0a, 0x1f, 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, 0x22, 0x53, 0x0a, 0x1d, 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, 0x12, 0x32, 0x0a, 0x0b, 0x72, + 0x61, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x11, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x54, 0x61, 0x67, 0x73, 0x4f, 0x76, 0x65, 0x72, 0x76, + 0x69, 0x65, 0x77, 0x52, 0x0a, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x54, 0x61, 0x67, 0x73, 0x22, + 0x45, 0x0a, 0x0c, 0x54, 0x61, 0x67, 0x73, 0x4f, 0x76, 0x65, 0x72, 0x76, 0x69, 0x65, 0x77, 0x12, + 0x15, 0x0a, 0x06, 0x74, 0x61, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x05, 0x74, 0x61, 0x67, 0x49, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x02, 0x64, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x02, 0x65, 0x6e, 0x22, 0x3a, 0x0a, 0x09, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, + 0x54, 0x61, 0x67, 0x12, 0x15, 0x0a, 0x06, 0x74, 0x61, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x61, 0x67, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x06, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x73, 0x22, 0x44, 0x0a, 0x13, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x54, 0x61, 0x67, 0x4e, + 0x65, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x74, 0x61, 0x67, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x74, 0x61, 0x67, 0x49, 0x64, + 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x06, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x22, 0x70, 0x0a, 0x0f, 0x52, 0x61, 0x74, 0x69, + 0x6e, 0x67, 0x54, 0x61, 0x67, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x74, + 0x61, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x74, 0x61, 0x67, + 0x49, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x76, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, + 0x03, 0x61, 0x76, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x74, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x01, 0x52, 0x03, 0x73, 0x74, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x69, 0x6e, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x03, 0x6d, 0x69, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x61, 0x78, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6d, 0x61, 0x78, 0x22, 0x15, 0x0a, 0x13, 0x4c, 0x69, + 0x73, 0x74, 0x43, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x22, 0x3b, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, + 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x26, 0x0a, 0x07, 0x63, 0x61, 0x6e, 0x74, 0x65, 0x65, + 0x6e, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x61, + 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x52, 0x07, 0x63, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x22, 0x6d, + 0x0a, 0x07, 0x43, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, + 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x01, 0x52, 0x08, 0x6c, 0x61, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65, 0x22, 0x6c, 0x0a, + 0x11, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x69, 0x73, 0x68, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x49, + 0x64, 0x12, 0x12, 0x0a, 0x04, 0x79, 0x65, 0x61, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x04, 0x79, 0x65, 0x61, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x77, 0x65, 0x65, 0x6b, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x04, 0x77, 0x65, 0x65, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x64, 0x61, 0x79, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x64, 0x61, 0x79, 0x22, 0x25, 0x0a, 0x0f, 0x4c, + 0x69, 0x73, 0x74, 0x44, 0x69, 0x73, 0x68, 0x65, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x12, + 0x0a, 0x04, 0x64, 0x69, 0x73, 0x68, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x64, 0x69, + 0x73, 0x68, 0x22, 0x1e, 0x0a, 0x1c, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x69, 0x62, 0x6c, 0x65, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x22, 0x63, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x69, 0x62, 0x6c, 0x65, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, + 0x12, 0x45, 0x0a, 0x12, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, + 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x50, 0x65, + 0x72, 0x73, 0x6f, 0x6e, 0x52, 0x11, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x69, 0x62, 0x6c, + 0x65, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x22, 0x8c, 0x01, 0x0a, 0x11, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x12, 0x12, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x12, + 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x66, 0x61, 0x63, 0x75, 0x6c, 0x74, 0x79, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, 0x61, 0x63, 0x75, 0x6c, 0x74, 0x79, 0x12, + 0x15, 0x0a, 0x06, 0x74, 0x75, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x74, 0x75, 0x6d, 0x49, 0x64, 0x22, 0xfc, 0x01, 0x0a, 0x16, 0x52, 0x6f, 0x6f, 0x6d, 0x49, + 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f, + 0x6f, 0x6d, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, + 0x6f, 0x6f, 0x6d, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x62, 0x75, 0x69, 0x6c, 0x64, + 0x69, 0x6e, 0x67, 0x5f, 0x6e, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x62, 0x75, + 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x4e, 0x72, 0x12, 0x17, 0x0a, 0x07, 0x61, 0x72, 0x63, 0x68, + 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x72, 0x63, 0x68, 0x49, + 0x64, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, + 0x18, 0x0a, 0x07, 0x70, 0x75, 0x72, 0x70, 0x6f, 0x73, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x70, 0x75, 0x72, 0x70, 0x6f, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x61, 0x6d, + 0x70, 0x75, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x61, 0x6d, 0x70, 0x75, + 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x1c, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x6f, 0x72, + 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x22, 0xb6, 0x01, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x6f, 0x72, 0x65, + 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, + 0x12, 0x43, 0x0a, 0x05, 0x69, 0x6e, 0x66, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x2d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x6f, 0x72, 0x65, 0x49, 0x6e, + 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x2e, 0x4d, + 0x6f, 0x72, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x05, + 0x69, 0x6e, 0x66, 0x6f, 0x73, 0x1a, 0x55, 0x0a, 0x0f, 0x4d, 0x6f, 0x72, 0x65, 0x49, 0x6e, 0x66, + 0x6f, 0x72, 0x6d, 0x61, 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, 0x1a, + 0x0a, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, + 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x35, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x54, 0x69, 0x6d, 0x65, 0x73, - 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x3b, 0x0a, 0x0a, 0x66, 0x61, 0x63, 0x69, 0x6c, 0x69, 0x74, - 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x61, 0x70, 0x69, 0x2e, - 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x4d, 0x73, 0x67, 0x45, - 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x0a, 0x66, 0x61, 0x63, 0x69, 0x6c, 0x69, 0x74, 0x69, - 0x65, 0x73, 0x22, 0xbf, 0x02, 0x0a, 0x16, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x54, 0x69, - 0x6d, 0x65, 0x73, 0x4d, 0x73, 0x67, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, - 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1a, 0x0a, - 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, - 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x12, 0x2b, 0x0a, 0x11, 0x74, - 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, - 0x74, 0x53, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x70, 0x65, 0x6e, - 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x6f, 0x75, 0x72, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0c, 0x6f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x48, 0x6f, 0x75, 0x72, 0x73, 0x12, 0x14, 0x0a, - 0x05, 0x69, 0x6e, 0x66, 0x6f, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x69, 0x6e, - 0x66, 0x6f, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, - 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, - 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x69, - 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, - 0x63, 0x65, 0x49, 0x64, 0x22, 0x30, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x4e, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x51, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, - 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x76, 0x65, - 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x17, 0x0a, 0x15, 0x4c, 0x69, 0x73, - 0x74, 0x53, 0x74, 0x75, 0x64, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x22, 0x45, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x75, 0x64, 0x79, 0x52, - 0x6f, 0x6f, 0x6d, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x2e, 0x0a, 0x05, 0x72, 0x6f, 0x6f, - 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, - 0x74, 0x75, 0x64, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x4d, 0x73, 0x67, 0x45, 0x6c, 0x65, 0x6d, 0x65, - 0x6e, 0x74, 0x52, 0x05, 0x72, 0x6f, 0x6f, 0x6d, 0x73, 0x22, 0x79, 0x0a, 0x13, 0x53, 0x74, 0x75, - 0x64, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x4d, 0x73, 0x67, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, - 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, - 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x24, - 0x0a, 0x05, 0x72, 0x6f, 0x6f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x75, 0x64, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x05, 0x72, - 0x6f, 0x6f, 0x6d, 0x73, 0x22, 0x9e, 0x01, 0x0a, 0x09, 0x53, 0x74, 0x75, 0x64, 0x79, 0x52, 0x6f, - 0x6f, 0x6d, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x12, 0x17, 0x0a, - 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, - 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x63, - 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x6f, 0x6f, 0x6d, 0x43, - 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x6f, 0x6f, 0x6d, 0x4e, 0x61, 0x6d, 0x65, - 0x12, 0x23, 0x0a, 0x0d, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, - 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x6e, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x6f, 0x76, - 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x6c, 0x61, - 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x6c, 0x61, 0x73, - 0x74, 0x49, 0x64, 0x12, 0x40, 0x0a, 0x0e, 0x6f, 0x6c, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x64, 0x61, - 0x74, 0x65, 0x5f, 0x61, 0x74, 0x18, 0x02, 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, 0x0c, 0x6f, 0x6c, 0x64, 0x65, 0x73, 0x74, 0x44, - 0x61, 0x74, 0x65, 0x41, 0x74, 0x22, 0x35, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x6f, 0x76, - 0x69, 0x65, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x22, 0x0a, 0x06, 0x6d, 0x6f, 0x76, 0x69, - 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, - 0x6f, 0x76, 0x69, 0x65, 0x52, 0x06, 0x6d, 0x6f, 0x76, 0x69, 0x65, 0x73, 0x22, 0xdb, 0x03, 0x0a, - 0x05, 0x4d, 0x6f, 0x76, 0x69, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x76, 0x65, - 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x70, - 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x76, 0x65, 0x72, - 0x50, 0x61, 0x74, 0x68, 0x12, 0x19, 0x0a, 0x08, 0x6d, 0x6f, 0x76, 0x69, 0x65, 0x5f, 0x69, 0x64, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x6d, 0x6f, 0x76, 0x69, 0x65, 0x49, 0x64, 0x12, - 0x2e, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, + 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, + 0x61, 0x67, 0x65, 0x22, 0x54, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x70, 0x65, 0x6e, 0x69, + 0x6e, 0x67, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x3b, 0x0a, 0x0a, + 0x66, 0x61, 0x63, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x1b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x54, 0x69, + 0x6d, 0x65, 0x73, 0x4d, 0x73, 0x67, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x0a, 0x66, + 0x61, 0x63, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x22, 0xbf, 0x02, 0x0a, 0x16, 0x4f, 0x70, + 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x4d, 0x73, 0x67, 0x45, 0x6c, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x02, 0x69, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, + 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, + 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x72, 0x6f, + 0x6f, 0x6d, 0x12, 0x2b, 0x0a, 0x11, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x5f, + 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x74, + 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x23, 0x0a, 0x0d, 0x6f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x6f, 0x75, 0x72, 0x73, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x48, + 0x6f, 0x75, 0x72, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x66, 0x6f, 0x73, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x69, 0x6e, 0x66, 0x6f, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, + 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, + 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x66, 0x65, + 0x72, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, + 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x22, 0x30, 0x0a, 0x14, 0x47, + 0x65, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x51, 0x0a, + 0x12, 0x47, 0x65, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x74, 0x65, 0x52, 0x65, + 0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x21, 0x0a, + 0x0c, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, + 0x22, 0x17, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x75, 0x64, 0x79, 0x52, 0x6f, 0x6f, + 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x45, 0x0a, 0x13, 0x4c, 0x69, 0x73, + 0x74, 0x53, 0x74, 0x75, 0x64, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, + 0x12, 0x2e, 0x0a, 0x05, 0x72, 0x6f, 0x6f, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x75, 0x64, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x4d, + 0x73, 0x67, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x05, 0x72, 0x6f, 0x6f, 0x6d, 0x73, + 0x22, 0x79, 0x0a, 0x13, 0x53, 0x74, 0x75, 0x64, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x4d, 0x73, 0x67, + 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x64, + 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x64, 0x65, + 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x24, 0x0a, 0x05, 0x72, 0x6f, 0x6f, 0x6d, 0x73, 0x18, 0x04, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x75, 0x64, 0x79, + 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x05, 0x72, 0x6f, 0x6f, 0x6d, 0x73, 0x22, 0x9e, 0x01, 0x0a, 0x09, + 0x53, 0x74, 0x75, 0x64, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x6f, + 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x67, 0x72, 0x6f, + 0x75, 0x70, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x1b, 0x0a, + 0x09, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x72, 0x6f, 0x6f, 0x6d, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f, + 0x6f, 0x6d, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, + 0x6f, 0x6f, 0x6d, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x62, 0x75, 0x69, 0x6c, 0x64, + 0x69, 0x6e, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x6e, 0x0a, 0x11, + 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x6f, 0x76, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x17, 0x0a, 0x07, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x06, 0x6c, 0x61, 0x73, 0x74, 0x49, 0x64, 0x12, 0x40, 0x0a, 0x0e, 0x6f, 0x6c, + 0x64, 0x65, 0x73, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x61, 0x74, 0x18, 0x02, 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, 0x0c, + 0x6f, 0x6c, 0x64, 0x65, 0x73, 0x74, 0x44, 0x61, 0x74, 0x65, 0x41, 0x74, 0x22, 0x35, 0x0a, 0x0f, + 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x6f, 0x76, 0x69, 0x65, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, + 0x22, 0x0a, 0x06, 0x6d, 0x6f, 0x76, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x0a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x6f, 0x76, 0x69, 0x65, 0x52, 0x06, 0x6d, 0x6f, 0x76, + 0x69, 0x65, 0x73, 0x22, 0xc6, 0x03, 0x0a, 0x05, 0x4d, 0x6f, 0x76, 0x69, 0x65, 0x12, 0x19, 0x0a, + 0x08, 0x6d, 0x6f, 0x76, 0x69, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x07, 0x6d, 0x6f, 0x76, 0x69, 0x65, 0x49, 0x64, 0x12, 0x2e, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x65, + 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, 0x04, 0x64, 0x61, 0x74, 0x65, 0x12, 0x34, 0x0a, 0x07, 0x63, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x64, 0x18, 0x05, 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, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x12, 0x14, + 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, + 0x69, 0x74, 0x6c, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, + 0x79, 0x65, 0x61, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x6c, 0x65, + 0x61, 0x73, 0x65, 0x59, 0x65, 0x61, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x75, 0x6e, 0x74, 0x69, + 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, + 0x65, 0x12, 0x14, 0x0a, 0x05, 0x67, 0x65, 0x6e, 0x72, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x67, 0x65, 0x6e, 0x72, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x69, 0x72, 0x65, 0x63, + 0x74, 0x6f, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x69, 0x72, 0x65, 0x63, + 0x74, 0x6f, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x0b, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x69, + 0x6d, 0x64, 0x62, 0x5f, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x69, 0x6d, 0x64, 0x62, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x20, 0x0a, 0x0b, + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0d, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, + 0x0a, 0x08, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x07, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, + 0x6b, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x1b, 0x0a, + 0x09, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, + 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x4a, 0x04, 0x08, 0x0f, 0x10, 0x10, 0x22, 0xcc, 0x02, 0x0a, + 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x42, 0x0a, 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, + 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x24, 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, 0x2e, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x52, + 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x66, 0x72, + 0x6f, 0x6d, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x66, 0x72, 0x6f, 0x6d, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x12, 0x2b, 0x0a, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6f, 0x72, + 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, + 0x1f, 0x0a, 0x0b, 0x61, 0x70, 0x70, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x70, 0x70, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, + 0x22, 0x29, 0x0a, 0x09, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x0b, 0x0a, + 0x07, 0x54, 0x55, 0x4d, 0x5f, 0x44, 0x45, 0x56, 0x10, 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x54, 0x55, + 0x4d, 0x5f, 0x43, 0x4f, 0x4e, 0x54, 0x41, 0x43, 0x54, 0x10, 0x01, 0x22, 0x46, 0x0a, 0x0a, 0x43, + 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, 0x74, + 0x69, 0x74, 0x75, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x6c, 0x61, 0x74, + 0x69, 0x74, 0x75, 0x64, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, 0x75, + 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, + 0x75, 0x64, 0x65, 0x22, 0x15, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, + 0x64, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x32, 0x0a, 0x18, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x49, 0x6d, 0x61, 0x67, + 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x47, + 0x0a, 0x1a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, + 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, + 0x69, 0x6d, 0x61, 0x67, 0x65, 0x5f, 0x6e, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, + 0x69, 0x6d, 0x61, 0x67, 0x65, 0x4e, 0x72, 0x22, 0x29, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x4d, 0x65, + 0x6d, 0x62, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x6c, + 0x72, 0x7a, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x72, 0x7a, + 0x49, 0x64, 0x22, 0x58, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x52, + 0x65, 0x70, 0x6c, 0x79, 0x12, 0x15, 0x0a, 0x06, 0x6c, 0x72, 0x7a, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x72, 0x7a, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, + 0x1b, 0x0a, 0x09, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x08, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x49, 0x64, 0x22, 0x2f, 0x0a, 0x16, + 0x47, 0x65, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x6c, 0x72, 0x7a, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x72, 0x7a, 0x49, 0x64, 0x22, 0xb3, 0x01, + 0x0a, 0x14, 0x47, 0x65, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x63, 0x6d, 0x5f, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x63, 0x6d, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, + 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, + 0x65, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x49, + 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x6d, 0x70, 0x6c, 0x6f, 0x79, 0x65, 0x65, 0x5f, 0x69, 0x64, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x65, 0x6d, 0x70, 0x6c, 0x6f, 0x79, 0x65, 0x65, + 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x69, + 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, + 0x6c, 0x49, 0x64, 0x22, 0x41, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, + 0x0f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0xa9, 0x01, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x4e, 0x6f, + 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, + 0x27, 0x0a, 0x0f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, + 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x03, 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, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x22, 0x48, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x6e, 0x6f, + 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x35, 0x0a, 0x1b, + 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x22, 0x3b, 0x0a, 0x1a, 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, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x49, 0x64, + 0x22, 0xa1, 0x01, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x43, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x48, + 0x65, 0x61, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x14, 0x0a, + 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x43, 0x6f, 0x75, 0x6e, 0x74, + 0x12, 0x18, 0x0a, 0x07, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x02, 0x52, 0x07, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, + 0x6d, 0x65, 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, 0x04, 0x64, 0x61, 0x74, 0x65, 0x12, - 0x34, 0x0a, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x18, 0x05, 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, 0x07, 0x63, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x72, - 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x79, 0x65, 0x61, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0b, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x59, 0x65, 0x61, 0x72, 0x12, 0x18, - 0x0a, 0x07, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x67, 0x65, 0x6e, 0x72, - 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x67, 0x65, 0x6e, 0x72, 0x65, 0x12, 0x1a, - 0x0a, 0x08, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, - 0x74, 0x6f, 0x72, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x63, 0x74, 0x6f, - 0x72, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6d, 0x64, 0x62, 0x5f, 0x72, 0x61, 0x74, 0x69, 0x6e, - 0x67, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6d, 0x64, 0x62, 0x52, 0x61, 0x74, - 0x69, 0x6e, 0x67, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x49, 0x64, - 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x6c, 0x69, 0x6e, 0x6b, 0x4a, 0x04, 0x08, 0x0f, 0x10, 0x10, 0x22, 0x15, 0x0a, 0x13, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x70, 0x6c, - 0x79, 0x22, 0x93, 0x02, 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x64, - 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x74, - 0x6f, 0x70, 0x69, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x70, 0x69, - 0x63, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6d, 0x61, 0x69, 0x6c, - 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x6d, 0x61, 0x69, 0x6c, - 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1f, 0x0a, 0x0b, - 0x69, 0x6d, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x0a, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, - 0x08, 0x6c, 0x61, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, - 0x08, 0x6c, 0x61, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6c, 0x6f, 0x6e, - 0x67, 0x69, 0x74, 0x75, 0x64, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x01, 0x52, 0x09, 0x6c, 0x6f, - 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x73, 0x5f, 0x76, 0x65, - 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x73, 0x56, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x70, 0x70, 0x5f, 0x76, 0x65, - 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x70, 0x70, - 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x32, 0x0a, 0x18, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x46, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, - 0x70, 0x6c, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x47, 0x0a, 0x1a, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x49, 0x6d, 0x61, - 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x6d, 0x61, - 0x67, 0x65, 0x5f, 0x6e, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x69, 0x6d, 0x61, - 0x67, 0x65, 0x4e, 0x72, 0x22, 0x29, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, - 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x6c, 0x72, 0x7a, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x72, 0x7a, 0x49, 0x64, 0x22, - 0x58, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x65, 0x70, 0x6c, - 0x79, 0x12, 0x15, 0x0a, 0x06, 0x6c, 0x72, 0x7a, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x6c, 0x72, 0x7a, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, - 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x08, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x49, 0x64, 0x22, 0x2f, 0x0a, 0x16, 0x47, 0x65, 0x74, - 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x6c, 0x72, 0x7a, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x72, 0x7a, 0x49, 0x64, 0x22, 0xb3, 0x01, 0x0a, 0x14, 0x47, - 0x65, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, - 0x70, 0x6c, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x63, 0x6d, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x63, 0x6d, 0x54, 0x6f, 0x6b, 0x65, 0x6e, - 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, - 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1f, - 0x0a, 0x0b, 0x65, 0x6d, 0x70, 0x6c, 0x6f, 0x79, 0x65, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x0a, 0x65, 0x6d, 0x70, 0x6c, 0x6f, 0x79, 0x65, 0x65, 0x49, 0x64, 0x12, - 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x49, 0x64, - 0x22, 0x41, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x6e, 0x6f, - 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x0e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x49, 0x64, 0x22, 0xa9, 0x01, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x27, 0x0a, 0x0f, - 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, - 0x6c, 0x65, 0x18, 0x03, 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, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, - 0x48, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x27, 0x0a, 0x0f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x6e, 0x6f, 0x74, 0x69, 0x66, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x35, 0x0a, 0x1b, 0x47, 0x65, 0x74, - 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x72, 0x6d, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x22, 0x3b, 0x0a, 0x1a, 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, 0x12, 0x1d, - 0x0a, 0x0a, 0x63, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x49, 0x64, 0x22, 0xa1, 0x01, - 0x0a, 0x18, 0x47, 0x65, 0x74, 0x43, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x6e, 0x48, 0x65, 0x61, 0x64, - 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0d, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x18, 0x0a, - 0x07, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, 0x52, 0x07, - 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 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, 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, 0x32, 0xae, 0x18, 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, 0x58, - 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, 0x22, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x62, 0x04, 0x6e, 0x65, - 0x77, 0x73, 0x12, 0x14, 0x2f, 0x6e, 0x65, 0x77, 0x73, 0x2f, 0x7b, 0x6c, 0x61, 0x73, 0x74, 0x5f, - 0x6e, 0x65, 0x77, 0x73, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x68, 0x0a, 0x0b, 0x53, 0x65, 0x61, 0x72, - 0x63, 0x68, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x12, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x65, - 0x61, 0x72, 0x63, 0x68, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x6f, 0x6f, - 0x6d, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x29, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x23, 0x3a, - 0x01, 0x2a, 0x62, 0x05, 0x72, 0x6f, 0x6f, 0x6d, 0x73, 0x22, 0x17, 0x2f, 0x72, 0x6f, 0x6f, 0x6d, - 0x66, 0x69, 0x6e, 0x64, 0x65, 0x72, 0x2f, 0x72, 0x6f, 0x6f, 0x6d, 0x2f, 0x73, 0x65, 0x61, 0x72, - 0x63, 0x68, 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, + 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 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, 0x32, 0xad, 0x18, 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, 0x68, 0x0a, 0x0b, + 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x12, 0x17, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, + 0x68, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x29, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x23, 0x3a, 0x01, 0x2a, 0x62, 0x05, 0x72, 0x6f, 0x6f, 0x6d, 0x73, 0x22, 0x17, 0x2f, + 0x72, 0x6f, 0x6f, 0x6d, 0x66, 0x69, 0x6e, 0x64, 0x65, 0x72, 0x2f, 0x72, 0x6f, 0x6f, 0x6d, 0x2f, + 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 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, 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, + 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, 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, 0x59, 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, 0x1d, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x62, 0x04, 0x64, 0x69, 0x73, 0x68, 0x12, 0x0f, 0x2f, 0x64, 0x69, - 0x73, 0x68, 0x2f, 0x61, 0x6c, 0x6c, 0x44, 0x69, 0x73, 0x68, 0x65, 0x73, 0x12, 0x7a, 0x0a, 0x15, + 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, 0x59, 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, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x62, 0x04, 0x64, 0x69, 0x73, 0x68, 0x12, + 0x0f, 0x2f, 0x64, 0x69, 0x73, 0x68, 0x2f, 0x61, 0x6c, 0x6c, 0x44, 0x69, 0x73, 0x68, 0x65, 0x73, + 0x12, 0x7a, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x69, + 0x62, 0x6c, 0x65, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x12, 0x21, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x50, - 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x12, 0x21, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x50, 0x65, 0x72, 0x73, 0x6f, - 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x50, 0x65, - 0x72, 0x73, 0x6f, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x17, 0x12, 0x15, 0x2f, 0x62, 0x61, 0x72, 0x72, 0x69, 0x65, 0x72, 0x66, 0x72, 0x65, 0x65, 0x2f, - 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x73, 0x12, 0x7b, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, - 0x4d, 0x6f, 0x72, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, - 0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x6f, 0x72, 0x65, 0x49, 0x6e, - 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x6f, 0x72, 0x65, 0x49, - 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, - 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x12, 0x1c, 0x2f, 0x62, 0x61, 0x72, 0x72, 0x69, 0x65, - 0x72, 0x66, 0x72, 0x65, 0x65, 0x2f, 0x6d, 0x6f, 0x72, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x6e, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x70, 0x65, - 0x6e, 0x69, 0x6e, 0x67, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x12, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x54, 0x69, 0x6d, 0x65, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x52, 0x65, - 0x70, 0x6c, 0x79, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x12, 0x18, 0x2f, 0x6f, 0x70, - 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x2f, 0x7b, 0x6c, 0x61, 0x6e, 0x67, - 0x75, 0x61, 0x67, 0x65, 0x7d, 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, 0x5b, 0x0a, 0x0e, 0x4c, 0x69, 0x73, - 0x74, 0x53, 0x74, 0x75, 0x64, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x12, 0x1a, 0x2e, 0x61, 0x70, + 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x69, 0x62, + 0x6c, 0x65, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x1d, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x12, 0x15, 0x2f, 0x62, 0x61, 0x72, 0x72, 0x69, 0x65, 0x72, 0x66, + 0x72, 0x65, 0x65, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x73, 0x12, 0x7b, 0x0a, 0x13, + 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x6f, 0x72, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x6f, + 0x72, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, + 0x6f, 0x72, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x70, 0x6c, 0x79, 0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x12, 0x1c, 0x2f, 0x62, 0x61, + 0x72, 0x72, 0x69, 0x65, 0x72, 0x66, 0x72, 0x65, 0x65, 0x2f, 0x6d, 0x6f, 0x72, 0x65, 0x49, 0x6e, + 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x6e, 0x0a, 0x10, 0x4c, 0x69, 0x73, + 0x74, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x12, 0x1c, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x54, + 0x69, 0x6d, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x54, 0x69, 0x6d, + 0x65, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x12, + 0x18, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x2f, 0x7b, + 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x7d, 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, 0x5b, 0x0a, + 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x75, 0x64, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x12, + 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x75, 0x64, 0x79, 0x52, + 0x6f, 0x6f, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x75, 0x64, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x53, 0x74, 0x75, 0x64, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x73, 0x52, 0x65, 0x70, 0x6c, - 0x79, 0x22, 0x13, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0d, 0x12, 0x0b, 0x2f, 0x73, 0x74, 0x75, 0x64, - 0x79, 0x72, 0x6f, 0x6f, 0x6d, 0x73, 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, 0x59, 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, 0x11, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0b, 0x22, 0x09, 0x2f, - 0x66, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x6c, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x55, - 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1b, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, - 0x65, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, - 0x70, 0x6c, 0x79, 0x22, 0x21, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x12, 0x19, 0x2f, 0x64, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x2f, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x2f, 0x7b, 0x6c, - 0x72, 0x7a, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x73, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x74, - 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x2e, 0x61, 0x70, 0x69, 0x2e, - 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, - 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x70, 0x6c, - 0x79, 0x22, 0x28, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x22, 0x12, 0x20, 0x2f, 0x6e, 0x6f, 0x74, 0x69, - 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x7b, 0x6e, 0x6f, 0x74, 0x69, 0x66, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x90, 0x01, 0x0a, 0x16, - 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x12, 0x22, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, - 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x72, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x61, 0x70, 0x69, - 0x2e, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x30, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x2a, 0x12, 0x28, 0x2f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x2f, 0x7b, 0x6e, 0x6f, - 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x52, - 0x0a, 0x09, 0x47, 0x65, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x15, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x65, 0x6d, 0x62, - 0x65, 0x72, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, - 0x11, 0x2f, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x6c, 0x72, 0x7a, 0x5f, 0x69, - 0x64, 0x7d, 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, 0x99, 0x01, 0x0a, 0x18, 0x49, 0x4f, 0x53, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x24, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x4f, 0x53, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x4f, 0x53, 0x44, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x33, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x2d, 0x3a, 0x01, 0x2a, 0x22, 0x28, 0x2f, 0x69, 0x6f, 0x73, 0x2f, 0x6e, 0x6f, 0x74, 0x69, 0x66, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 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, 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, + 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x13, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0d, 0x12, 0x0b, 0x2f, + 0x73, 0x74, 0x75, 0x64, 0x79, 0x72, 0x6f, 0x6f, 0x6d, 0x73, 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, 0x6c, 0x0a, 0x0f, 0x47, 0x65, + 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1b, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x47, 0x65, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x21, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x12, 0x19, 0x2f, + 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x2f, + 0x7b, 0x6c, 0x72, 0x7a, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x73, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x4e, + 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, + 0x65, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x70, 0x6c, 0x79, 0x22, 0x28, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x22, 0x12, 0x20, 0x2f, 0x6e, 0x6f, + 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x7b, 0x6e, 0x6f, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x90, 0x01, + 0x0a, 0x16, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x12, 0x22, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, + 0x65, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x30, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2a, 0x12, 0x28, 0x2f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x2f, 0x7b, + 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x7d, + 0x12, 0x52, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x15, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x65, + 0x6d, 0x62, 0x65, 0x72, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x13, 0x12, 0x11, 0x2f, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x6c, 0x72, 0x7a, + 0x5f, 0x69, 0x64, 0x7d, 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, 0x99, 0x01, 0x0a, 0x18, 0x49, 0x4f, 0x53, 0x44, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x24, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x4f, 0x53, 0x44, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x4f, + 0x53, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x33, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x2d, 0x3a, 0x01, 0x2a, 0x22, 0x28, 0x2f, 0x69, 0x6f, 0x73, 0x2f, 0x6e, 0x6f, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 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, 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 ( @@ -5392,188 +5516,192 @@ func file_tumdev_campus_backend_proto_rawDescGZIP() []byte { return file_tumdev_campus_backend_proto_rawDescData } -var file_tumdev_campus_backend_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_tumdev_campus_backend_proto_msgTypes = make([]protoimpl.MessageInfo, 75) +var file_tumdev_campus_backend_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_tumdev_campus_backend_proto_msgTypes = make([]protoimpl.MessageInfo, 76) var file_tumdev_campus_backend_proto_goTypes = []interface{}{ (DeviceType)(0), // 0: api.DeviceType - (*CreateDeviceRequest)(nil), // 1: api.CreateDeviceRequest - (*CreateDeviceReply)(nil), // 2: api.CreateDeviceReply - (*DeleteDeviceRequest)(nil), // 3: api.DeleteDeviceRequest - (*DeleteDeviceReply)(nil), // 4: api.DeleteDeviceReply - (*IOSDeviceRequestResponseRequest)(nil), // 5: api.IOSDeviceRequestResponseRequest - (*IOSDeviceRequestResponseReply)(nil), // 6: api.IOSDeviceRequestResponseReply - (*SearchRoomsRequest)(nil), // 7: api.SearchRoomsRequest - (*SearchRoomsReply)(nil), // 8: api.SearchRoomsReply - (*Room)(nil), // 9: api.Room - (*News)(nil), // 10: api.News - (*ListNewsReply)(nil), // 11: api.ListNewsReply - (*ListNewsRequest)(nil), // 12: api.ListNewsRequest - (*ListNewsSourcesRequest)(nil), // 13: api.ListNewsSourcesRequest - (*ListNewsSourcesReply)(nil), // 14: api.ListNewsSourcesReply - (*NewsSource)(nil), // 15: api.NewsSource - (*ListNewsAlertsRequest)(nil), // 16: api.ListNewsAlertsRequest - (*ListNewsAlertsReply)(nil), // 17: api.ListNewsAlertsReply - (*NewsAlert)(nil), // 18: api.NewsAlert - (*ListCanteenRatingsRequest)(nil), // 19: api.ListCanteenRatingsRequest - (*ListCanteenRatingsReply)(nil), // 20: api.ListCanteenRatingsReply - (*GetDishRatingsRequest)(nil), // 21: api.GetDishRatingsRequest - (*GetDishRatingsReply)(nil), // 22: api.GetDishRatingsReply - (*SingleRatingReply)(nil), // 23: api.SingleRatingReply - (*CreateCanteenRatingReply)(nil), // 24: api.CreateCanteenRatingReply - (*CreateCanteenRatingRequest)(nil), // 25: api.CreateCanteenRatingRequest - (*CreateDishRatingReply)(nil), // 26: api.CreateDishRatingReply - (*CreateDishRatingRequest)(nil), // 27: api.CreateDishRatingRequest - (*ListAvailableDishTagsRequest)(nil), // 28: api.ListAvailableDishTagsRequest - (*ListAvailableDishTagsReply)(nil), // 29: api.ListAvailableDishTagsReply - (*ListNameTagsRequest)(nil), // 30: api.ListNameTagsRequest - (*ListNameTagsReply)(nil), // 31: api.ListNameTagsReply - (*ListAvailableCanteenTagsRequest)(nil), // 32: api.ListAvailableCanteenTagsRequest - (*ListAvailableCanteenTagsReply)(nil), // 33: api.ListAvailableCanteenTagsReply - (*TagsOverview)(nil), // 34: api.TagsOverview - (*RatingTag)(nil), // 35: api.RatingTag - (*RatingTagNewRequest)(nil), // 36: api.RatingTagNewRequest - (*RatingTagResult)(nil), // 37: api.RatingTagResult - (*ListCanteensRequest)(nil), // 38: api.ListCanteensRequest - (*ListCanteensReply)(nil), // 39: api.ListCanteensReply - (*Canteen)(nil), // 40: api.Canteen - (*ListDishesRequest)(nil), // 41: api.ListDishesRequest - (*ListDishesReply)(nil), // 42: api.ListDishesReply - (*ListResponsiblePersonRequest)(nil), // 43: api.ListResponsiblePersonRequest - (*ListResponsiblePersonReply)(nil), // 44: api.ListResponsiblePersonReply - (*ResponsiblePerson)(nil), // 45: api.ResponsiblePerson - (*RoomInformationElement)(nil), // 46: api.RoomInformationElement - (*ListMoreInformationRequest)(nil), // 47: api.ListMoreInformationRequest - (*ListMoreInformationReply)(nil), // 48: api.ListMoreInformationReply - (*ListOpeningTimesRequest)(nil), // 49: api.ListOpeningTimesRequest - (*ListOpeningTimesReply)(nil), // 50: api.ListOpeningTimesReply - (*OpeningTimesMsgElement)(nil), // 51: api.OpeningTimesMsgElement - (*GetUpdateNoteRequest)(nil), // 52: api.GetUpdateNoteRequest - (*GetUpdateNoteReply)(nil), // 53: api.GetUpdateNoteReply - (*ListStudyRoomsRequest)(nil), // 54: api.ListStudyRoomsRequest - (*ListStudyRoomsReply)(nil), // 55: api.ListStudyRoomsReply - (*StudyRoomMsgElement)(nil), // 56: api.StudyRoomMsgElement - (*StudyRoom)(nil), // 57: api.StudyRoom - (*ListMoviesRequest)(nil), // 58: api.ListMoviesRequest - (*ListMoviesReply)(nil), // 59: api.ListMoviesReply - (*Movie)(nil), // 60: api.Movie - (*CreateFeedbackReply)(nil), // 61: api.CreateFeedbackReply + (CreateFeedbackRequest_Recipient)(0), // 1: api.CreateFeedbackRequest.Recipient + (*CreateDeviceRequest)(nil), // 2: api.CreateDeviceRequest + (*CreateDeviceReply)(nil), // 3: api.CreateDeviceReply + (*DeleteDeviceRequest)(nil), // 4: api.DeleteDeviceRequest + (*DeleteDeviceReply)(nil), // 5: api.DeleteDeviceReply + (*IOSDeviceRequestResponseRequest)(nil), // 6: api.IOSDeviceRequestResponseRequest + (*IOSDeviceRequestResponseReply)(nil), // 7: api.IOSDeviceRequestResponseReply + (*SearchRoomsRequest)(nil), // 8: api.SearchRoomsRequest + (*SearchRoomsReply)(nil), // 9: api.SearchRoomsReply + (*Room)(nil), // 10: api.Room + (*News)(nil), // 11: api.News + (*ListNewsReply)(nil), // 12: api.ListNewsReply + (*ListNewsRequest)(nil), // 13: api.ListNewsRequest + (*ListNewsSourcesRequest)(nil), // 14: api.ListNewsSourcesRequest + (*ListNewsSourcesReply)(nil), // 15: api.ListNewsSourcesReply + (*NewsSource)(nil), // 16: api.NewsSource + (*ListNewsAlertsRequest)(nil), // 17: api.ListNewsAlertsRequest + (*ListNewsAlertsReply)(nil), // 18: api.ListNewsAlertsReply + (*NewsAlert)(nil), // 19: api.NewsAlert + (*ListCanteenRatingsRequest)(nil), // 20: api.ListCanteenRatingsRequest + (*ListCanteenRatingsReply)(nil), // 21: api.ListCanteenRatingsReply + (*GetDishRatingsRequest)(nil), // 22: api.GetDishRatingsRequest + (*GetDishRatingsReply)(nil), // 23: api.GetDishRatingsReply + (*SingleRatingReply)(nil), // 24: api.SingleRatingReply + (*CreateCanteenRatingReply)(nil), // 25: api.CreateCanteenRatingReply + (*CreateCanteenRatingRequest)(nil), // 26: api.CreateCanteenRatingRequest + (*CreateDishRatingReply)(nil), // 27: api.CreateDishRatingReply + (*CreateDishRatingRequest)(nil), // 28: api.CreateDishRatingRequest + (*ListAvailableDishTagsRequest)(nil), // 29: api.ListAvailableDishTagsRequest + (*ListAvailableDishTagsReply)(nil), // 30: api.ListAvailableDishTagsReply + (*ListNameTagsRequest)(nil), // 31: api.ListNameTagsRequest + (*ListNameTagsReply)(nil), // 32: api.ListNameTagsReply + (*ListAvailableCanteenTagsRequest)(nil), // 33: api.ListAvailableCanteenTagsRequest + (*ListAvailableCanteenTagsReply)(nil), // 34: api.ListAvailableCanteenTagsReply + (*TagsOverview)(nil), // 35: api.TagsOverview + (*RatingTag)(nil), // 36: api.RatingTag + (*RatingTagNewRequest)(nil), // 37: api.RatingTagNewRequest + (*RatingTagResult)(nil), // 38: api.RatingTagResult + (*ListCanteensRequest)(nil), // 39: api.ListCanteensRequest + (*ListCanteensReply)(nil), // 40: api.ListCanteensReply + (*Canteen)(nil), // 41: api.Canteen + (*ListDishesRequest)(nil), // 42: api.ListDishesRequest + (*ListDishesReply)(nil), // 43: api.ListDishesReply + (*ListResponsiblePersonRequest)(nil), // 44: api.ListResponsiblePersonRequest + (*ListResponsiblePersonReply)(nil), // 45: api.ListResponsiblePersonReply + (*ResponsiblePerson)(nil), // 46: api.ResponsiblePerson + (*RoomInformationElement)(nil), // 47: api.RoomInformationElement + (*ListMoreInformationRequest)(nil), // 48: api.ListMoreInformationRequest + (*ListMoreInformationReply)(nil), // 49: api.ListMoreInformationReply + (*ListOpeningTimesRequest)(nil), // 50: api.ListOpeningTimesRequest + (*ListOpeningTimesReply)(nil), // 51: api.ListOpeningTimesReply + (*OpeningTimesMsgElement)(nil), // 52: api.OpeningTimesMsgElement + (*GetUpdateNoteRequest)(nil), // 53: api.GetUpdateNoteRequest + (*GetUpdateNoteReply)(nil), // 54: api.GetUpdateNoteReply + (*ListStudyRoomsRequest)(nil), // 55: api.ListStudyRoomsRequest + (*ListStudyRoomsReply)(nil), // 56: api.ListStudyRoomsReply + (*StudyRoomMsgElement)(nil), // 57: api.StudyRoomMsgElement + (*StudyRoom)(nil), // 58: api.StudyRoom + (*ListMoviesRequest)(nil), // 59: api.ListMoviesRequest + (*ListMoviesReply)(nil), // 60: api.ListMoviesReply + (*Movie)(nil), // 61: api.Movie (*CreateFeedbackRequest)(nil), // 62: api.CreateFeedbackRequest - (*CreateFeedbackImageReply)(nil), // 63: api.CreateFeedbackImageReply - (*CreateFeedbackImageRequest)(nil), // 64: api.CreateFeedbackImageRequest - (*GetMemberRequest)(nil), // 65: api.GetMemberRequest - (*GetMemberReply)(nil), // 66: api.GetMemberReply - (*GetUploadStatusRequest)(nil), // 67: api.GetUploadStatusRequest - (*GetUploadStatusReply)(nil), // 68: api.GetUploadStatusReply - (*GetNotificationRequest)(nil), // 69: api.GetNotificationRequest - (*GetNotificationReply)(nil), // 70: api.GetNotificationReply - (*GetNotificationConfirmRequest)(nil), // 71: api.GetNotificationConfirmRequest - (*GetNotificationConfirmReply)(nil), // 72: api.GetNotificationConfirmReply - (*GetCanteenHeadCountRequest)(nil), // 73: api.GetCanteenHeadCountRequest - (*GetCanteenHeadCountReply)(nil), // 74: api.GetCanteenHeadCountReply - (*ListMoreInformationReply_MoreInformation)(nil), // 75: api.ListMoreInformationReply.MoreInformation - (*timestamppb.Timestamp)(nil), // 76: google.protobuf.Timestamp + (*Coordinate)(nil), // 63: api.Coordinate + (*CreateFeedbackReply)(nil), // 64: api.CreateFeedbackReply + (*CreateFeedbackImageReply)(nil), // 65: api.CreateFeedbackImageReply + (*CreateFeedbackImageRequest)(nil), // 66: api.CreateFeedbackImageRequest + (*GetMemberRequest)(nil), // 67: api.GetMemberRequest + (*GetMemberReply)(nil), // 68: api.GetMemberReply + (*GetUploadStatusRequest)(nil), // 69: api.GetUploadStatusRequest + (*GetUploadStatusReply)(nil), // 70: api.GetUploadStatusReply + (*GetNotificationRequest)(nil), // 71: api.GetNotificationRequest + (*GetNotificationReply)(nil), // 72: api.GetNotificationReply + (*GetNotificationConfirmRequest)(nil), // 73: api.GetNotificationConfirmRequest + (*GetNotificationConfirmReply)(nil), // 74: api.GetNotificationConfirmReply + (*GetCanteenHeadCountRequest)(nil), // 75: api.GetCanteenHeadCountRequest + (*GetCanteenHeadCountReply)(nil), // 76: api.GetCanteenHeadCountReply + (*ListMoreInformationReply_MoreInformation)(nil), // 77: api.ListMoreInformationReply.MoreInformation + (*timestamppb.Timestamp)(nil), // 78: 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 - 9, // 2: api.SearchRoomsReply.rooms:type_name -> api.Room - 76, // 3: api.News.created:type_name -> google.protobuf.Timestamp - 76, // 4: api.News.date:type_name -> google.protobuf.Timestamp - 10, // 5: api.ListNewsReply.news:type_name -> api.News - 76, // 6: api.ListNewsRequest.oldest_date_at:type_name -> google.protobuf.Timestamp - 15, // 7: api.ListNewsSourcesReply.sources:type_name -> api.NewsSource - 18, // 8: api.ListNewsAlertsReply.alerts:type_name -> api.NewsAlert - 76, // 9: api.NewsAlert.created:type_name -> google.protobuf.Timestamp - 76, // 10: api.NewsAlert.from:type_name -> google.protobuf.Timestamp - 76, // 11: api.NewsAlert.to:type_name -> google.protobuf.Timestamp - 76, // 12: api.ListCanteenRatingsRequest.from:type_name -> google.protobuf.Timestamp - 76, // 13: api.ListCanteenRatingsRequest.to:type_name -> google.protobuf.Timestamp - 23, // 14: api.ListCanteenRatingsReply.rating:type_name -> api.SingleRatingReply - 37, // 15: api.ListCanteenRatingsReply.rating_tags:type_name -> api.RatingTagResult - 76, // 16: api.GetDishRatingsRequest.from:type_name -> google.protobuf.Timestamp - 76, // 17: api.GetDishRatingsRequest.to:type_name -> google.protobuf.Timestamp - 23, // 18: api.GetDishRatingsReply.rating:type_name -> api.SingleRatingReply - 37, // 19: api.GetDishRatingsReply.rating_tags:type_name -> api.RatingTagResult - 37, // 20: api.GetDishRatingsReply.name_tags:type_name -> api.RatingTagResult - 36, // 21: api.SingleRatingReply.rating_tags:type_name -> api.RatingTagNewRequest - 76, // 22: api.SingleRatingReply.visited:type_name -> google.protobuf.Timestamp - 35, // 23: api.CreateCanteenRatingRequest.rating_tags:type_name -> api.RatingTag - 35, // 24: api.CreateDishRatingRequest.rating_tags:type_name -> api.RatingTag - 34, // 25: api.ListAvailableDishTagsReply.rating_tags:type_name -> api.TagsOverview - 34, // 26: api.ListNameTagsReply.rating_tags:type_name -> api.TagsOverview - 34, // 27: api.ListAvailableCanteenTagsReply.rating_tags:type_name -> api.TagsOverview - 40, // 28: api.ListCanteensReply.canteen:type_name -> api.Canteen - 45, // 29: api.ListResponsiblePersonReply.responsible_person:type_name -> api.ResponsiblePerson - 75, // 30: api.ListMoreInformationReply.infos:type_name -> api.ListMoreInformationReply.MoreInformation - 51, // 31: api.ListOpeningTimesReply.facilities:type_name -> api.OpeningTimesMsgElement - 56, // 32: api.ListStudyRoomsReply.rooms:type_name -> api.StudyRoomMsgElement - 57, // 33: api.StudyRoomMsgElement.rooms:type_name -> api.StudyRoom - 76, // 34: api.ListMoviesRequest.oldest_date_at:type_name -> google.protobuf.Timestamp - 60, // 35: api.ListMoviesReply.movies:type_name -> api.Movie - 76, // 36: api.Movie.date:type_name -> google.protobuf.Timestamp - 76, // 37: api.Movie.created:type_name -> google.protobuf.Timestamp - 76, // 38: api.GetCanteenHeadCountReply.timestamp:type_name -> google.protobuf.Timestamp - 16, // 39: api.Campus.ListNewsAlerts:input_type -> api.ListNewsAlertsRequest - 13, // 40: api.Campus.ListNewsSources:input_type -> api.ListNewsSourcesRequest - 12, // 41: api.Campus.ListNews:input_type -> api.ListNewsRequest - 7, // 42: api.Campus.SearchRooms:input_type -> api.SearchRoomsRequest - 19, // 43: api.Campus.ListCanteenRatings:input_type -> api.ListCanteenRatingsRequest - 21, // 44: api.Campus.GetDishRatings:input_type -> api.GetDishRatingsRequest - 25, // 45: api.Campus.CreateCanteenRating:input_type -> api.CreateCanteenRatingRequest - 27, // 46: api.Campus.CreateDishRating:input_type -> api.CreateDishRatingRequest - 28, // 47: api.Campus.ListAvailableDishTags:input_type -> api.ListAvailableDishTagsRequest - 30, // 48: api.Campus.ListNameTags:input_type -> api.ListNameTagsRequest - 32, // 49: api.Campus.ListAvailableCanteenTags:input_type -> api.ListAvailableCanteenTagsRequest - 38, // 50: api.Campus.ListCanteens:input_type -> api.ListCanteensRequest - 41, // 51: api.Campus.ListDishes:input_type -> api.ListDishesRequest - 43, // 52: api.Campus.ListResponsiblePerson:input_type -> api.ListResponsiblePersonRequest - 47, // 53: api.Campus.ListMoreInformation:input_type -> api.ListMoreInformationRequest - 49, // 54: api.Campus.ListOpeningTimes:input_type -> api.ListOpeningTimesRequest - 52, // 55: api.Campus.GetUpdateNote:input_type -> api.GetUpdateNoteRequest - 54, // 56: api.Campus.ListStudyRooms:input_type -> api.ListStudyRoomsRequest - 58, // 57: api.Campus.ListMovies:input_type -> api.ListMoviesRequest - 62, // 58: api.Campus.CreateFeedback:input_type -> api.CreateFeedbackRequest - 67, // 59: api.Campus.GetUploadStatus:input_type -> api.GetUploadStatusRequest - 69, // 60: api.Campus.GetNotification:input_type -> api.GetNotificationRequest - 71, // 61: api.Campus.GetNotificationConfirm:input_type -> api.GetNotificationConfirmRequest - 65, // 62: api.Campus.GetMember:input_type -> api.GetMemberRequest - 73, // 63: api.Campus.GetCanteenHeadCount:input_type -> api.GetCanteenHeadCountRequest - 5, // 64: api.Campus.IOSDeviceRequestResponse:input_type -> api.IOSDeviceRequestResponseRequest - 1, // 65: api.Campus.CreateDevice:input_type -> api.CreateDeviceRequest - 3, // 66: api.Campus.DeleteDevice:input_type -> api.DeleteDeviceRequest - 17, // 67: api.Campus.ListNewsAlerts:output_type -> api.ListNewsAlertsReply - 14, // 68: api.Campus.ListNewsSources:output_type -> api.ListNewsSourcesReply - 11, // 69: api.Campus.ListNews:output_type -> api.ListNewsReply - 8, // 70: api.Campus.SearchRooms:output_type -> api.SearchRoomsReply - 20, // 71: api.Campus.ListCanteenRatings:output_type -> api.ListCanteenRatingsReply - 22, // 72: api.Campus.GetDishRatings:output_type -> api.GetDishRatingsReply - 24, // 73: api.Campus.CreateCanteenRating:output_type -> api.CreateCanteenRatingReply - 26, // 74: api.Campus.CreateDishRating:output_type -> api.CreateDishRatingReply - 29, // 75: api.Campus.ListAvailableDishTags:output_type -> api.ListAvailableDishTagsReply - 31, // 76: api.Campus.ListNameTags:output_type -> api.ListNameTagsReply - 33, // 77: api.Campus.ListAvailableCanteenTags:output_type -> api.ListAvailableCanteenTagsReply - 39, // 78: api.Campus.ListCanteens:output_type -> api.ListCanteensReply - 42, // 79: api.Campus.ListDishes:output_type -> api.ListDishesReply - 44, // 80: api.Campus.ListResponsiblePerson:output_type -> api.ListResponsiblePersonReply - 48, // 81: api.Campus.ListMoreInformation:output_type -> api.ListMoreInformationReply - 50, // 82: api.Campus.ListOpeningTimes:output_type -> api.ListOpeningTimesReply - 53, // 83: api.Campus.GetUpdateNote:output_type -> api.GetUpdateNoteReply - 55, // 84: api.Campus.ListStudyRooms:output_type -> api.ListStudyRoomsReply - 59, // 85: api.Campus.ListMovies:output_type -> api.ListMoviesReply - 61, // 86: api.Campus.CreateFeedback:output_type -> api.CreateFeedbackReply - 68, // 87: api.Campus.GetUploadStatus:output_type -> api.GetUploadStatusReply - 70, // 88: api.Campus.GetNotification:output_type -> api.GetNotificationReply - 72, // 89: api.Campus.GetNotificationConfirm:output_type -> api.GetNotificationConfirmReply - 66, // 90: api.Campus.GetMember:output_type -> api.GetMemberReply - 74, // 91: api.Campus.GetCanteenHeadCount:output_type -> api.GetCanteenHeadCountReply - 6, // 92: api.Campus.IOSDeviceRequestResponse:output_type -> api.IOSDeviceRequestResponseReply - 2, // 93: api.Campus.CreateDevice:output_type -> api.CreateDeviceReply - 4, // 94: api.Campus.DeleteDevice:output_type -> api.DeleteDeviceReply - 67, // [67:95] is the sub-list for method output_type - 39, // [39:67] is the sub-list for method input_type - 39, // [39:39] is the sub-list for extension type_name - 39, // [39:39] is the sub-list for extension extendee - 0, // [0:39] is the sub-list for field type_name + 10, // 2: api.SearchRoomsReply.rooms:type_name -> api.Room + 78, // 3: api.News.created:type_name -> google.protobuf.Timestamp + 78, // 4: api.News.date:type_name -> google.protobuf.Timestamp + 11, // 5: api.ListNewsReply.news:type_name -> api.News + 78, // 6: api.ListNewsRequest.oldest_date_at:type_name -> google.protobuf.Timestamp + 16, // 7: api.ListNewsSourcesReply.sources:type_name -> api.NewsSource + 19, // 8: api.ListNewsAlertsReply.alerts:type_name -> api.NewsAlert + 78, // 9: api.NewsAlert.created:type_name -> google.protobuf.Timestamp + 78, // 10: api.NewsAlert.from:type_name -> google.protobuf.Timestamp + 78, // 11: api.NewsAlert.to:type_name -> google.protobuf.Timestamp + 78, // 12: api.ListCanteenRatingsRequest.from:type_name -> google.protobuf.Timestamp + 78, // 13: api.ListCanteenRatingsRequest.to:type_name -> google.protobuf.Timestamp + 24, // 14: api.ListCanteenRatingsReply.rating:type_name -> api.SingleRatingReply + 38, // 15: api.ListCanteenRatingsReply.rating_tags:type_name -> api.RatingTagResult + 78, // 16: api.GetDishRatingsRequest.from:type_name -> google.protobuf.Timestamp + 78, // 17: api.GetDishRatingsRequest.to:type_name -> google.protobuf.Timestamp + 24, // 18: api.GetDishRatingsReply.rating:type_name -> api.SingleRatingReply + 38, // 19: api.GetDishRatingsReply.rating_tags:type_name -> api.RatingTagResult + 38, // 20: api.GetDishRatingsReply.name_tags:type_name -> api.RatingTagResult + 37, // 21: api.SingleRatingReply.rating_tags:type_name -> api.RatingTagNewRequest + 78, // 22: api.SingleRatingReply.visited:type_name -> google.protobuf.Timestamp + 36, // 23: api.CreateCanteenRatingRequest.rating_tags:type_name -> api.RatingTag + 36, // 24: api.CreateDishRatingRequest.rating_tags:type_name -> api.RatingTag + 35, // 25: api.ListAvailableDishTagsReply.rating_tags:type_name -> api.TagsOverview + 35, // 26: api.ListNameTagsReply.rating_tags:type_name -> api.TagsOverview + 35, // 27: api.ListAvailableCanteenTagsReply.rating_tags:type_name -> api.TagsOverview + 41, // 28: api.ListCanteensReply.canteen:type_name -> api.Canteen + 46, // 29: api.ListResponsiblePersonReply.responsible_person:type_name -> api.ResponsiblePerson + 77, // 30: api.ListMoreInformationReply.infos:type_name -> api.ListMoreInformationReply.MoreInformation + 52, // 31: api.ListOpeningTimesReply.facilities:type_name -> api.OpeningTimesMsgElement + 57, // 32: api.ListStudyRoomsReply.rooms:type_name -> api.StudyRoomMsgElement + 58, // 33: api.StudyRoomMsgElement.rooms:type_name -> api.StudyRoom + 78, // 34: api.ListMoviesRequest.oldest_date_at:type_name -> google.protobuf.Timestamp + 61, // 35: api.ListMoviesReply.movies:type_name -> api.Movie + 78, // 36: api.Movie.date:type_name -> google.protobuf.Timestamp + 78, // 37: api.Movie.created:type_name -> google.protobuf.Timestamp + 1, // 38: api.CreateFeedbackRequest.recipient:type_name -> api.CreateFeedbackRequest.Recipient + 63, // 39: api.CreateFeedbackRequest.location:type_name -> api.Coordinate + 78, // 40: api.GetCanteenHeadCountReply.timestamp:type_name -> google.protobuf.Timestamp + 17, // 41: api.Campus.ListNewsAlerts:input_type -> api.ListNewsAlertsRequest + 14, // 42: api.Campus.ListNewsSources:input_type -> api.ListNewsSourcesRequest + 13, // 43: api.Campus.ListNews:input_type -> api.ListNewsRequest + 8, // 44: api.Campus.SearchRooms:input_type -> api.SearchRoomsRequest + 20, // 45: api.Campus.ListCanteenRatings:input_type -> api.ListCanteenRatingsRequest + 22, // 46: api.Campus.GetDishRatings:input_type -> api.GetDishRatingsRequest + 26, // 47: api.Campus.CreateCanteenRating:input_type -> api.CreateCanteenRatingRequest + 28, // 48: api.Campus.CreateDishRating:input_type -> api.CreateDishRatingRequest + 29, // 49: api.Campus.ListAvailableDishTags:input_type -> api.ListAvailableDishTagsRequest + 31, // 50: api.Campus.ListNameTags:input_type -> api.ListNameTagsRequest + 33, // 51: api.Campus.ListAvailableCanteenTags:input_type -> api.ListAvailableCanteenTagsRequest + 39, // 52: api.Campus.ListCanteens:input_type -> api.ListCanteensRequest + 42, // 53: api.Campus.ListDishes:input_type -> api.ListDishesRequest + 44, // 54: api.Campus.ListResponsiblePerson:input_type -> api.ListResponsiblePersonRequest + 48, // 55: api.Campus.ListMoreInformation:input_type -> api.ListMoreInformationRequest + 50, // 56: api.Campus.ListOpeningTimes:input_type -> api.ListOpeningTimesRequest + 53, // 57: api.Campus.GetUpdateNote:input_type -> api.GetUpdateNoteRequest + 55, // 58: api.Campus.ListStudyRooms:input_type -> api.ListStudyRoomsRequest + 59, // 59: api.Campus.ListMovies:input_type -> api.ListMoviesRequest + 62, // 60: api.Campus.CreateFeedback:input_type -> api.CreateFeedbackRequest + 69, // 61: api.Campus.GetUploadStatus:input_type -> api.GetUploadStatusRequest + 71, // 62: api.Campus.GetNotification:input_type -> api.GetNotificationRequest + 73, // 63: api.Campus.GetNotificationConfirm:input_type -> api.GetNotificationConfirmRequest + 67, // 64: api.Campus.GetMember:input_type -> api.GetMemberRequest + 75, // 65: api.Campus.GetCanteenHeadCount:input_type -> api.GetCanteenHeadCountRequest + 6, // 66: api.Campus.IOSDeviceRequestResponse:input_type -> api.IOSDeviceRequestResponseRequest + 2, // 67: api.Campus.CreateDevice:input_type -> api.CreateDeviceRequest + 4, // 68: api.Campus.DeleteDevice:input_type -> api.DeleteDeviceRequest + 18, // 69: api.Campus.ListNewsAlerts:output_type -> api.ListNewsAlertsReply + 15, // 70: api.Campus.ListNewsSources:output_type -> api.ListNewsSourcesReply + 12, // 71: api.Campus.ListNews:output_type -> api.ListNewsReply + 9, // 72: api.Campus.SearchRooms:output_type -> api.SearchRoomsReply + 21, // 73: api.Campus.ListCanteenRatings:output_type -> api.ListCanteenRatingsReply + 23, // 74: api.Campus.GetDishRatings:output_type -> api.GetDishRatingsReply + 25, // 75: api.Campus.CreateCanteenRating:output_type -> api.CreateCanteenRatingReply + 27, // 76: api.Campus.CreateDishRating:output_type -> api.CreateDishRatingReply + 30, // 77: api.Campus.ListAvailableDishTags:output_type -> api.ListAvailableDishTagsReply + 32, // 78: api.Campus.ListNameTags:output_type -> api.ListNameTagsReply + 34, // 79: api.Campus.ListAvailableCanteenTags:output_type -> api.ListAvailableCanteenTagsReply + 40, // 80: api.Campus.ListCanteens:output_type -> api.ListCanteensReply + 43, // 81: api.Campus.ListDishes:output_type -> api.ListDishesReply + 45, // 82: api.Campus.ListResponsiblePerson:output_type -> api.ListResponsiblePersonReply + 49, // 83: api.Campus.ListMoreInformation:output_type -> api.ListMoreInformationReply + 51, // 84: api.Campus.ListOpeningTimes:output_type -> api.ListOpeningTimesReply + 54, // 85: api.Campus.GetUpdateNote:output_type -> api.GetUpdateNoteReply + 56, // 86: api.Campus.ListStudyRooms:output_type -> api.ListStudyRoomsReply + 60, // 87: api.Campus.ListMovies:output_type -> api.ListMoviesReply + 64, // 88: api.Campus.CreateFeedback:output_type -> api.CreateFeedbackReply + 70, // 89: api.Campus.GetUploadStatus:output_type -> api.GetUploadStatusReply + 72, // 90: api.Campus.GetNotification:output_type -> api.GetNotificationReply + 74, // 91: api.Campus.GetNotificationConfirm:output_type -> api.GetNotificationConfirmReply + 68, // 92: api.Campus.GetMember:output_type -> api.GetMemberReply + 76, // 93: api.Campus.GetCanteenHeadCount:output_type -> api.GetCanteenHeadCountReply + 7, // 94: api.Campus.IOSDeviceRequestResponse:output_type -> api.IOSDeviceRequestResponseReply + 3, // 95: api.Campus.CreateDevice:output_type -> api.CreateDeviceReply + 5, // 96: api.Campus.DeleteDevice:output_type -> api.DeleteDeviceReply + 69, // [69:97] is the sub-list for method output_type + 41, // [41:69] is the sub-list for method input_type + 41, // [41:41] is the sub-list for extension type_name + 41, // [41:41] is the sub-list for extension extendee + 0, // [0:41] is the sub-list for field type_name } func init() { file_tumdev_campus_backend_proto_init() } @@ -6303,7 +6431,7 @@ func file_tumdev_campus_backend_proto_init() { } } file_tumdev_campus_backend_proto_msgTypes[60].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CreateFeedbackReply); i { + switch v := v.(*CreateFeedbackRequest); i { case 0: return &v.state case 1: @@ -6315,7 +6443,7 @@ func file_tumdev_campus_backend_proto_init() { } } file_tumdev_campus_backend_proto_msgTypes[61].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CreateFeedbackRequest); i { + switch v := v.(*Coordinate); i { case 0: return &v.state case 1: @@ -6327,7 +6455,7 @@ func file_tumdev_campus_backend_proto_init() { } } file_tumdev_campus_backend_proto_msgTypes[62].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CreateFeedbackImageReply); i { + switch v := v.(*CreateFeedbackReply); i { case 0: return &v.state case 1: @@ -6339,7 +6467,7 @@ func file_tumdev_campus_backend_proto_init() { } } file_tumdev_campus_backend_proto_msgTypes[63].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CreateFeedbackImageRequest); i { + switch v := v.(*CreateFeedbackImageReply); i { case 0: return &v.state case 1: @@ -6351,7 +6479,7 @@ func file_tumdev_campus_backend_proto_init() { } } file_tumdev_campus_backend_proto_msgTypes[64].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetMemberRequest); i { + switch v := v.(*CreateFeedbackImageRequest); i { case 0: return &v.state case 1: @@ -6363,7 +6491,7 @@ func file_tumdev_campus_backend_proto_init() { } } file_tumdev_campus_backend_proto_msgTypes[65].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetMemberReply); i { + switch v := v.(*GetMemberRequest); i { case 0: return &v.state case 1: @@ -6375,7 +6503,7 @@ func file_tumdev_campus_backend_proto_init() { } } file_tumdev_campus_backend_proto_msgTypes[66].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetUploadStatusRequest); i { + switch v := v.(*GetMemberReply); i { case 0: return &v.state case 1: @@ -6387,7 +6515,7 @@ func file_tumdev_campus_backend_proto_init() { } } file_tumdev_campus_backend_proto_msgTypes[67].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetUploadStatusReply); i { + switch v := v.(*GetUploadStatusRequest); i { case 0: return &v.state case 1: @@ -6399,7 +6527,7 @@ func file_tumdev_campus_backend_proto_init() { } } file_tumdev_campus_backend_proto_msgTypes[68].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetNotificationRequest); i { + switch v := v.(*GetUploadStatusReply); i { case 0: return &v.state case 1: @@ -6411,7 +6539,7 @@ func file_tumdev_campus_backend_proto_init() { } } file_tumdev_campus_backend_proto_msgTypes[69].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetNotificationReply); i { + switch v := v.(*GetNotificationRequest); i { case 0: return &v.state case 1: @@ -6423,7 +6551,7 @@ func file_tumdev_campus_backend_proto_init() { } } file_tumdev_campus_backend_proto_msgTypes[70].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetNotificationConfirmRequest); i { + switch v := v.(*GetNotificationReply); i { case 0: return &v.state case 1: @@ -6435,7 +6563,7 @@ func file_tumdev_campus_backend_proto_init() { } } file_tumdev_campus_backend_proto_msgTypes[71].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetNotificationConfirmReply); i { + switch v := v.(*GetNotificationConfirmRequest); i { case 0: return &v.state case 1: @@ -6447,7 +6575,7 @@ func file_tumdev_campus_backend_proto_init() { } } file_tumdev_campus_backend_proto_msgTypes[72].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetCanteenHeadCountRequest); i { + switch v := v.(*GetNotificationConfirmReply); i { case 0: return &v.state case 1: @@ -6459,7 +6587,7 @@ func file_tumdev_campus_backend_proto_init() { } } file_tumdev_campus_backend_proto_msgTypes[73].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetCanteenHeadCountReply); i { + switch v := v.(*GetCanteenHeadCountRequest); i { case 0: return &v.state case 1: @@ -6471,6 +6599,18 @@ func file_tumdev_campus_backend_proto_init() { } } file_tumdev_campus_backend_proto_msgTypes[74].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetCanteenHeadCountReply); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tumdev_campus_backend_proto_msgTypes[75].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ListMoreInformationReply_MoreInformation); i { case 0: return &v.state @@ -6489,8 +6629,8 @@ func file_tumdev_campus_backend_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_tumdev_campus_backend_proto_rawDesc, - NumEnums: 1, - NumMessages: 75, + NumEnums: 2, + NumMessages: 76, 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 c4ebc85a..25d28d4b 100644 --- a/server/api/tumdev/campus_backend.pb.gw.go +++ b/server/api/tumdev/campus_backend.pb.gw.go @@ -86,30 +86,13 @@ func local_request_Campus_ListNewsSources_0(ctx context.Context, marshaler runti } var ( - filter_Campus_ListNews_0 = &utilities.DoubleArray{Encoding: map[string]int{"last_news_id": 0, "lastNewsId": 1}, Base: []int{1, 1, 2, 0, 0}, Check: []int{0, 1, 1, 2, 3}} + filter_Campus_ListNews_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} ) func request_Campus_ListNews_0(ctx context.Context, marshaler runtime.Marshaler, client CampusClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq ListNewsRequest var metadata runtime.ServerMetadata - var ( - val string - ok bool - err error - _ = err - ) - - val, ok = pathParams["last_news_id"] - if !ok { - return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "last_news_id") - } - - protoReq.LastNewsId, err = runtime.Int32(val) - if err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "last_news_id", err) - } - if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -126,23 +109,6 @@ func local_request_Campus_ListNews_0(ctx context.Context, marshaler runtime.Mars var protoReq ListNewsRequest var metadata runtime.ServerMetadata - var ( - val string - ok bool - err error - _ = err - ) - - val, ok = pathParams["last_news_id"] - if !ok { - return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "last_news_id") - } - - protoReq.LastNewsId, err = runtime.Int32(val) - if err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "last_news_id", err) - } - if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -661,38 +627,46 @@ func local_request_Campus_ListMovies_0(ctx context.Context, marshaler runtime.Ma } -var ( - filter_Campus_CreateFeedback_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} -) - func request_Campus_CreateFeedback_0(ctx context.Context, marshaler runtime.Marshaler, client CampusClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq CreateFeedbackRequest var metadata runtime.ServerMetadata - - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + stream, err := client.CreateFeedback(ctx) + if err != nil { + grpclog.Infof("Failed to start streaming: %v", err) + return nil, metadata, err } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Campus_CreateFeedback_0); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + dec := marshaler.NewDecoder(req.Body) + for { + var protoReq CreateFeedbackRequest + err = dec.Decode(&protoReq) + if err == io.EOF { + break + } + if err != nil { + grpclog.Infof("Failed to decode request: %v", err) + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err = stream.Send(&protoReq); err != nil { + if err == io.EOF { + break + } + grpclog.Infof("Failed to send request: %v", err) + return nil, metadata, err + } } - msg, err := client.CreateFeedback(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) - return msg, metadata, err - -} - -func local_request_Campus_CreateFeedback_0(ctx context.Context, marshaler runtime.Marshaler, server CampusServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq CreateFeedbackRequest - var metadata runtime.ServerMetadata - - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + if err := stream.CloseSend(); err != nil { + grpclog.Infof("Failed to terminate client stream: %v", err) + return nil, metadata, err } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Campus_CreateFeedback_0); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + header, err := stream.Header() + if err != nil { + grpclog.Infof("Failed to get header from client: %v", err) + return nil, metadata, err } + metadata.HeaderMD = header - msg, err := server.CreateFeedback(ctx, &protoReq) + msg, err := stream.CloseAndRecv() + metadata.TrailerMD = stream.Trailer() return msg, metadata, err } @@ -1159,7 +1133,7 @@ func RegisterCampusHandlerServer(ctx context.Context, mux *runtime.ServeMux, ser inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/api.Campus/ListNews", runtime.WithHTTPPathPattern("/news/{last_news_id}")) + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/api.Campus/ListNews", runtime.WithHTTPPathPattern("/news")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return @@ -1577,28 +1551,10 @@ func RegisterCampusHandlerServer(ctx context.Context, mux *runtime.ServeMux, ser }) mux.Handle("POST", pattern_Campus_CreateFeedback_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/CreateFeedback", runtime.WithHTTPPathPattern("/feedback")) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_Campus_CreateFeedback_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_CreateFeedback_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - + err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport") + _, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return }) mux.Handle("GET", pattern_Campus_GetUploadStatus_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { @@ -1892,7 +1848,7 @@ func RegisterCampusHandlerClient(ctx context.Context, mux *runtime.ServeMux, cli inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/api.Campus/ListNews", runtime.WithHTTPPathPattern("/news/{last_news_id}")) + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/api.Campus/ListNews", runtime.WithHTTPPathPattern("/news")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return @@ -2547,7 +2503,7 @@ var ( pattern_Campus_ListNewsSources_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"news", "sources"}, "")) - pattern_Campus_ListNews_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1}, []string{"news", "last_news_id"}, "")) + pattern_Campus_ListNews_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0}, []string{"news"}, "")) pattern_Campus_SearchRooms_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"roomfinder", "room", "search"}, "")) diff --git a/server/api/tumdev/campus_backend.proto b/server/api/tumdev/campus_backend.proto index ce2943aa..94cd6985 100644 --- a/server/api/tumdev/campus_backend.proto +++ b/server/api/tumdev/campus_backend.proto @@ -28,7 +28,7 @@ service Campus { rpc ListNews(ListNewsRequest) returns (ListNewsReply) { option (google.api.http) = { - get: "/news/{last_news_id}", + get: "/news", response_body: "news" }; } @@ -129,8 +129,11 @@ service Campus { option (google.api.http) = {get: "/movies/{last_id}"}; } - rpc CreateFeedback(CreateFeedbackRequest) returns (CreateFeedbackReply) { - option (google.api.http) = {post: "/feedback"}; + rpc CreateFeedback(stream CreateFeedbackRequest) returns (CreateFeedbackReply) { + option (google.api.http) = { + post: "/feedback", + body: "attachment", + }; } rpc GetUploadStatus(GetUploadStatusRequest) returns (GetUploadStatusReply) { @@ -234,9 +237,17 @@ message News { string title = 2; string text = 3; string link = 4; + // where a news thumbnail is stored. empty string means no image is available string image_url = 5; - string source = 6; + // the id of the news source + string source_id = 6; + // where the icon can be found + string source_icon_url = 9; + // human readable title of the news source + string source_title = 10; + // when the news item was created in OUR database google.protobuf.Timestamp created = 7; + // the date of the news item google.protobuf.Timestamp date = 8; } @@ -262,7 +273,8 @@ message ListNewsSourcesReply { message NewsSource { string source = 1; string title = 2; - string icon = 3; + // where the icon can be found + string icon_url = 3; } message ListNewsAlertsRequest { @@ -527,8 +539,8 @@ message ListMoviesReply { } message Movie { - string cover_name = 1; - string cover_path = 2; + reserved /*string cover_name = */ 1; + reserved /*string cover_path =*/ 2; int64 movie_id = 3; google.protobuf.Timestamp date = 4; google.protobuf.Timestamp created = 5; @@ -543,25 +555,45 @@ message Movie { string imdb_rating = 12; string description = 13; int64 cover_id = 14; - // was previously the trailer - reserved 15; + reserved /*string trailer*/ 15; // Where to find additional information about this movie string link = 16; + // Where to find a cover image for this movie + string cover_url = 17; } -message CreateFeedbackReply {} message CreateFeedbackRequest { - string topic = 1; - string email = 2; - string email_id = 3; - string message = 4; - int32 image_count = 5; - double latitude = 6; - double longitude = 7; - string os_version = 8; - string app_version = 9; + enum Recipient { + // Feedback for the TUM Dev Team + TUM_DEV = 0; + // Feedback for the general TUM Contact Form + TUM_CONTACT = 1; + } + + // who is the feedback for + Recipient recipient = 1; + // the email address of the user + string from_email = 2; + // The actual message + string message = 3; + // Optional location which the user can choose (data protection) to attach or not + Coordinate location = 4; + // Optional os information which the user can choose (data protection) to attach or not + string os_version = 5; + // Optional app information which the user can choose (data protection) to attach or not + string app_version = 6; + // Optional file in Base64. + // Accepted file formats: jpeg, jpg, png, webp, md, txt, pdf + // Maximum file size 4MB as by Protobuf maximum per request message size + bytes attachment = 7; +} +message Coordinate { + double latitude = 1; + double longitude = 2; } +message CreateFeedbackReply {} + message CreateFeedbackImageReply { string status = 1; } diff --git a/server/api/tumdev/campus_backend.swagger.json b/server/api/tumdev/campus_backend.swagger.json index b9e6f4c6..188fc4c5 100644 --- a/server/api/tumdev/campus_backend.swagger.json +++ b/server/api/tumdev/campus_backend.swagger.json @@ -507,45 +507,50 @@ }, "parameters": [ { - "name": "topic", - "in": "query", - "required": false, - "type": "string" + "name": "attachment", + "description": "Optional file in Base64.\nAccepted file formats: jpeg, jpg, png, webp, md, txt, pdf\nMaximum file size 4MB as by Protobuf maximum per request message size (streaming inputs)", + "in": "body", + "required": true, + "schema": { + "type": "string", + "format": "byte" + } }, { - "name": "email", + "name": "recipient", + "description": "who is the feedback for\n\n - TUM_DEV: Feedback for the TUM Dev Team\n - TUM_CONTACT: Feedback for the general TUM Contact Form", "in": "query", "required": false, - "type": "string" + "type": "string", + "enum": [ + "TUM_DEV", + "TUM_CONTACT" + ], + "default": "TUM_DEV" }, { - "name": "emailId", + "name": "fromEmail", + "description": "the email address of the user", "in": "query", "required": false, "type": "string" }, { "name": "message", + "description": "The actual message", "in": "query", "required": false, "type": "string" }, { - "name": "imageCount", - "in": "query", - "required": false, - "type": "integer", - "format": "int32" - }, - { - "name": "latitude", + "name": "location.latitude", "in": "query", "required": false, "type": "number", "format": "double" }, { - "name": "longitude", + "name": "location.longitude", "in": "query", "required": false, "type": "number", @@ -553,12 +558,14 @@ }, { "name": "osVersion", + "description": "Optional os information which the user can choose (data protection) to attach or not", "in": "query", "required": false, "type": "string" }, { "name": "appVersion", + "description": "Optional app information which the user can choose (data protection) to attach or not", "in": "query", "required": false, "type": "string" @@ -673,9 +680,9 @@ ] } }, - "/news/alerts": { + "/news": { "get": { - "operationId": "Campus_ListNewsAlerts", + "operationId": "Campus_ListNews", "responses": { "200": { "description": "", @@ -683,7 +690,7 @@ "type": "array", "items": { "type": "object", - "$ref": "#/definitions/apiNewsAlert" + "$ref": "#/definitions/apiNews" } } }, @@ -696,12 +703,28 @@ }, "parameters": [ { - "name": "lastNewsAlertId", + "name": "lastNewsId", "description": "the last id of the news item received. 0 to get all news items", "in": "query", "required": false, "type": "integer", "format": "int32" + }, + { + "name": "newsSource", + "description": "filter by news source id. 0 to get all news items", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + }, + { + "name": "oldestDateAt", + "description": "the oldest time you want to be included in the response", + "in": "query", + "required": false, + "type": "string", + "format": "date-time" } ], "tags": [ @@ -709,9 +732,9 @@ ] } }, - "/news/sources": { + "/news/alerts": { "get": { - "operationId": "Campus_ListNewsSources", + "operationId": "Campus_ListNewsAlerts", "responses": { "200": { "description": "", @@ -719,7 +742,7 @@ "type": "array", "items": { "type": "object", - "$ref": "#/definitions/apiNewsSource" + "$ref": "#/definitions/apiNewsAlert" } } }, @@ -730,14 +753,24 @@ } } }, + "parameters": [ + { + "name": "lastNewsAlertId", + "description": "the last id of the news item received. 0 to get all news items", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + } + ], "tags": [ "Campus" ] } }, - "/news/{lastNewsId}": { + "/news/sources": { "get": { - "operationId": "Campus_ListNews", + "operationId": "Campus_ListNewsSources", "responses": { "200": { "description": "", @@ -745,7 +778,7 @@ "type": "array", "items": { "type": "object", - "$ref": "#/definitions/apiNews" + "$ref": "#/definitions/apiNewsSource" } } }, @@ -756,32 +789,6 @@ } } }, - "parameters": [ - { - "name": "lastNewsId", - "description": "the last id of the news item received. 0 to get all news items", - "in": "path", - "required": true, - "type": "integer", - "format": "int32" - }, - { - "name": "newsSource", - "description": "filter by news source id. 0 to get all news items", - "in": "query", - "required": false, - "type": "integer", - "format": "int32" - }, - { - "name": "oldestDateAt", - "description": "the oldest time you want to be included in the response", - "in": "query", - "required": false, - "type": "string", - "format": "date-time" - } - ], "tags": [ "Campus" ] @@ -970,6 +977,15 @@ } }, "definitions": { + "CreateFeedbackRequestRecipient": { + "type": "string", + "enum": [ + "TUM_DEV", + "TUM_CONTACT" + ], + "default": "TUM_DEV", + "title": "- TUM_DEV: Feedback for the TUM Dev Team\n - TUM_CONTACT: Feedback for the general TUM Contact Form" + }, "ListMoreInformationReplyMoreInformation": { "type": "object", "properties": { @@ -1003,6 +1019,19 @@ } } }, + "apiCoordinate": { + "type": "object", + "properties": { + "latitude": { + "type": "number", + "format": "double" + }, + "longitude": { + "type": "number", + "format": "double" + } + } + }, "apiCreateCanteenRatingReply": { "type": "object" }, @@ -1522,12 +1551,6 @@ "apiMovie": { "type": "object", "properties": { - "coverName": { - "type": "string" - }, - "coverPath": { - "type": "string" - }, "movieId": { "type": "string", "format": "int64" @@ -1573,6 +1596,10 @@ "link": { "type": "string", "title": "Where to find additional information about this movie" + }, + "coverUrl": { + "type": "string", + "title": "Where to find a cover image for this movie" } } }, @@ -1593,18 +1620,30 @@ "type": "string" }, "imageUrl": { - "type": "string" + "type": "string", + "title": "where a news thumbnail is stored. empty string means no image is available" }, - "source": { - "type": "string" + "sourceId": { + "type": "string", + "title": "the id of the news source" + }, + "sourceIconUrl": { + "type": "string", + "title": "where the icon can be found" + }, + "sourceTitle": { + "type": "string", + "title": "human readable title of the news source" }, "created": { "type": "string", - "format": "date-time" + "format": "date-time", + "title": "when the news item was created in OUR database" }, "date": { "type": "string", - "format": "date-time" + "format": "date-time", + "title": "the date of the news item" } } }, @@ -1640,8 +1679,9 @@ "title": { "type": "string" }, - "icon": { - "type": "string" + "iconUrl": { + "type": "string", + "title": "where the icon can be found" } } }, diff --git a/server/api/tumdev/campus_backend_grpc.pb.go b/server/api/tumdev/campus_backend_grpc.pb.go index 72ed56d0..f68f154f 100644 --- a/server/api/tumdev/campus_backend_grpc.pb.go +++ b/server/api/tumdev/campus_backend_grpc.pb.go @@ -74,7 +74,7 @@ type CampusClient interface { GetUpdateNote(ctx context.Context, in *GetUpdateNoteRequest, opts ...grpc.CallOption) (*GetUpdateNoteReply, error) ListStudyRooms(ctx context.Context, in *ListStudyRoomsRequest, opts ...grpc.CallOption) (*ListStudyRoomsReply, error) ListMovies(ctx context.Context, in *ListMoviesRequest, opts ...grpc.CallOption) (*ListMoviesReply, error) - CreateFeedback(ctx context.Context, in *CreateFeedbackRequest, opts ...grpc.CallOption) (*CreateFeedbackReply, error) + CreateFeedback(ctx context.Context, opts ...grpc.CallOption) (Campus_CreateFeedbackClient, error) GetUploadStatus(ctx context.Context, in *GetUploadStatusRequest, opts ...grpc.CallOption) (*GetUploadStatusReply, error) GetNotification(ctx context.Context, in *GetNotificationRequest, opts ...grpc.CallOption) (*GetNotificationReply, error) GetNotificationConfirm(ctx context.Context, in *GetNotificationConfirmRequest, opts ...grpc.CallOption) (*GetNotificationConfirmReply, error) @@ -267,13 +267,38 @@ func (c *campusClient) ListMovies(ctx context.Context, in *ListMoviesRequest, op return out, nil } -func (c *campusClient) CreateFeedback(ctx context.Context, in *CreateFeedbackRequest, opts ...grpc.CallOption) (*CreateFeedbackReply, error) { - out := new(CreateFeedbackReply) - err := c.cc.Invoke(ctx, Campus_CreateFeedback_FullMethodName, in, out, opts...) +func (c *campusClient) CreateFeedback(ctx context.Context, opts ...grpc.CallOption) (Campus_CreateFeedbackClient, error) { + stream, err := c.cc.NewStream(ctx, &Campus_ServiceDesc.Streams[0], Campus_CreateFeedback_FullMethodName, opts...) if err != nil { return nil, err } - return out, nil + x := &campusCreateFeedbackClient{stream} + return x, nil +} + +type Campus_CreateFeedbackClient interface { + Send(*CreateFeedbackRequest) error + CloseAndRecv() (*CreateFeedbackReply, error) + grpc.ClientStream +} + +type campusCreateFeedbackClient struct { + grpc.ClientStream +} + +func (x *campusCreateFeedbackClient) Send(m *CreateFeedbackRequest) error { + return x.ClientStream.SendMsg(m) +} + +func (x *campusCreateFeedbackClient) CloseAndRecv() (*CreateFeedbackReply, error) { + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + m := new(CreateFeedbackReply) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil } func (c *campusClient) GetUploadStatus(ctx context.Context, in *GetUploadStatusRequest, opts ...grpc.CallOption) (*GetUploadStatusReply, error) { @@ -372,7 +397,7 @@ type CampusServer interface { GetUpdateNote(context.Context, *GetUpdateNoteRequest) (*GetUpdateNoteReply, error) ListStudyRooms(context.Context, *ListStudyRoomsRequest) (*ListStudyRoomsReply, error) ListMovies(context.Context, *ListMoviesRequest) (*ListMoviesReply, error) - CreateFeedback(context.Context, *CreateFeedbackRequest) (*CreateFeedbackReply, error) + CreateFeedback(Campus_CreateFeedbackServer) error GetUploadStatus(context.Context, *GetUploadStatusRequest) (*GetUploadStatusReply, error) GetNotification(context.Context, *GetNotificationRequest) (*GetNotificationReply, error) GetNotificationConfirm(context.Context, *GetNotificationConfirmRequest) (*GetNotificationConfirmReply, error) @@ -448,8 +473,8 @@ func (UnimplementedCampusServer) ListStudyRooms(context.Context, *ListStudyRooms func (UnimplementedCampusServer) ListMovies(context.Context, *ListMoviesRequest) (*ListMoviesReply, error) { return nil, status.Errorf(codes.Unimplemented, "method ListMovies not implemented") } -func (UnimplementedCampusServer) CreateFeedback(context.Context, *CreateFeedbackRequest) (*CreateFeedbackReply, error) { - return nil, status.Errorf(codes.Unimplemented, "method CreateFeedback not implemented") +func (UnimplementedCampusServer) CreateFeedback(Campus_CreateFeedbackServer) error { + return status.Errorf(codes.Unimplemented, "method CreateFeedback not implemented") } func (UnimplementedCampusServer) GetUploadStatus(context.Context, *GetUploadStatusRequest) (*GetUploadStatusReply, error) { return nil, status.Errorf(codes.Unimplemented, "method GetUploadStatus not implemented") @@ -830,22 +855,30 @@ func _Campus_ListMovies_Handler(srv interface{}, ctx context.Context, dec func(i return interceptor(ctx, in, info, handler) } -func _Campus_CreateFeedback_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(CreateFeedbackRequest) - if err := dec(in); err != nil { +func _Campus_CreateFeedback_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(CampusServer).CreateFeedback(&campusCreateFeedbackServer{stream}) +} + +type Campus_CreateFeedbackServer interface { + SendAndClose(*CreateFeedbackReply) error + Recv() (*CreateFeedbackRequest, error) + grpc.ServerStream +} + +type campusCreateFeedbackServer struct { + grpc.ServerStream +} + +func (x *campusCreateFeedbackServer) SendAndClose(m *CreateFeedbackReply) error { + return x.ServerStream.SendMsg(m) +} + +func (x *campusCreateFeedbackServer) Recv() (*CreateFeedbackRequest, error) { + m := new(CreateFeedbackRequest) + if err := x.ServerStream.RecvMsg(m); err != nil { return nil, err } - if interceptor == nil { - return srv.(CampusServer).CreateFeedback(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: Campus_CreateFeedback_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(CampusServer).CreateFeedback(ctx, req.(*CreateFeedbackRequest)) - } - return interceptor(ctx, in, info, handler) + return m, nil } func _Campus_GetUploadStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { @@ -1075,10 +1108,6 @@ var Campus_ServiceDesc = grpc.ServiceDesc{ MethodName: "ListMovies", Handler: _Campus_ListMovies_Handler, }, - { - MethodName: "CreateFeedback", - Handler: _Campus_CreateFeedback_Handler, - }, { MethodName: "GetUploadStatus", Handler: _Campus_GetUploadStatus_Handler, @@ -1112,6 +1141,12 @@ var Campus_ServiceDesc = grpc.ServiceDesc{ Handler: _Campus_DeleteDevice_Handler, }, }, - Streams: []grpc.StreamDesc{}, + Streams: []grpc.StreamDesc{ + { + StreamName: "CreateFeedback", + Handler: _Campus_CreateFeedback_Handler, + ClientStreams: true, + }, + }, Metadata: "tumdev/campus_backend.proto", } diff --git a/server/backend/cafeteria.go b/server/backend/cafeteria.go index 3fa7a355..6f3e57c7 100644 --- a/server/backend/cafeteria.go +++ b/server/backend/cafeteria.go @@ -45,15 +45,12 @@ func (s *CampusServer) GetCafeteriaRatings(ctx context.Context, input *pb.ListCa var result model.CafeteriaRatingAverage //get the average rating for this specific cafeteria tx := s.db.WithContext(ctx) cafeteriaId := getIDForCafeteriaName(input.CanteenId, tx) - res := tx.Model(&model.CafeteriaRatingAverage{}). - Where("cafeteriaId = ?", cafeteriaId). - First(&result) + res := tx.First(&result, "cafeteriaId = ?", cafeteriaId) if res.Error != nil { log.WithError(res.Error).Error("Error while querying the cafeteria with Id ", cafeteriaId) return nil, status.Error(codes.Internal, "This cafeteria has not yet been rated.") } - if res.RowsAffected > 0 { ratings := queryLastCafeteriaRatingsWithLimit(input, cafeteriaId, tx) cafeteriaTags := queryTags(cafeteriaId, -1, CAFETERIA, tx) @@ -102,17 +99,14 @@ func queryLastCafeteriaRatingsWithLimit(input *pb.ListCanteenRatingsRequest, caf } else { to = input.To.AsTime() } - err = tx.Model(&model.CafeteriaRating{}). - Where("cafeteriaID = ? AND timestamp < ? AND timestamp > ?", cafeteriaID, to, from). + err = tx. Order("timestamp desc, cafeteriaRating desc"). Limit(limit). - Find(&ratings).Error + Find(&ratings, "cafeteriaID = ? AND timestamp < ? AND timestamp > ?", cafeteriaID, to, from).Error } else { - err = tx.Model(&model.CafeteriaRating{}). - Where("cafeteriaID = ?", cafeteriaID). - Order("timestamp desc, cafeteriaRating desc"). + err = tx.Order("timestamp desc, cafeteriaRating desc"). Limit(limit). - Find(&ratings).Error + Find(&ratings, "cafeteriaID = ?", cafeteriaID).Error } if err != nil { @@ -152,17 +146,15 @@ func (s *CampusServer) GetDishRatings(ctx context.Context, input *pb.GetDishRati cafeteriaID := getIDForCafeteriaName(input.CanteenId, tx) dishID := getIDForDishName(input.Dish, cafeteriaID, tx) - err := tx.Model(&model.DishRatingAverage{}). - Where("cafeteriaID = ? AND dishID = ?", cafeteriaID, dishID). - First(&result) + res := tx.First(&result, "cafeteriaID = ? AND dishID = ?", cafeteriaID, dishID) - if err.Error != nil { + if res.Error != nil { fields := log.Fields{"dishID": dishID, "cafeteriaID": cafeteriaID} - log.WithError(err.Error).WithFields(fields).Error("Error while querying the average ratings") + log.WithError(res.Error).WithFields(fields).Error("Error while querying the average ratings") return nil, status.Error(codes.Internal, "This dish has not yet been rated.") } - if err.RowsAffected > 0 { + if res.RowsAffected > 0 { ratings := queryLastDishRatingsWithLimit(input, cafeteriaID, dishID, tx) dishTags := queryTags(cafeteriaID, dishID, DISH, tx) nameTags := queryTags(cafeteriaID, dishID, NAME, tx) @@ -212,17 +204,13 @@ func queryLastDishRatingsWithLimit(input *pb.GetDishRatingsRequest, cafeteriaID to = input.To.AsTime() } - err = tx.Model(&model.DishRating{}). - Where("cafeteriaID = ? AND dishID = ? AND timestamp < ? AND timestamp > ?", cafeteriaID, dishID, to, from). - Order("timestamp desc, dishRating desc"). + err = tx.Order("timestamp desc, dishRating desc"). Limit(limit). - Find(&ratings).Error + Find(&ratings, "cafeteriaID = ? AND dishID = ? AND timestamp < ? AND timestamp > ?", cafeteriaID, dishID, to, from).Error } else { - err = tx.Model(&model.DishRating{}). - Where("cafeteriaID = ? AND dishID = ?", cafeteriaID, dishID). - Order("timestamp desc, dishRating desc"). + err = tx.Order("timestamp desc, dishRating desc"). Limit(limit). - Find(&ratings).Error + Find(&ratings, "cafeteriaID = ? AND dishID = ?", cafeteriaID, dishID).Error } if err != nil { @@ -374,7 +362,7 @@ func (s *CampusServer) CreateCanteenRating(ctx context.Context, input *pb.Create Timestamp: time.Now(), Image: resPath, } - if err := tx.Model(&model.CafeteriaRating{}).Create(&rating).Error; err != nil { + if err := tx.Create(&rating).Error; err != nil { log.WithError(err).Error("Error occurred while creating the new cafeteria rating.") return nil, status.Error(codes.InvalidArgument, "Error while creating new cafeteria rating. Rating has not been saved.") @@ -386,15 +374,13 @@ func (s *CampusServer) CreateCanteenRating(ctx context.Context, input *pb.Create } func imageWrapper(image []byte, path string, id int64) string { - var resPath = "" - if len(image) > 0 { - var resError error - path := fmt.Sprintf("%s%s%s%d%s", "/Storage/rating/", path, "/", id, "/") - resPath, resError = storeImage(path, image) - - if resError != nil { - log.WithError(resError).Error("Error occurred while storing the image.") - } + if len(image) == 0 { + return "" + } + path = fmt.Sprintf("/Storage/rating/%s/%d/", path, id) + resPath, err := storeImage(path, image) + if err != nil { + log.WithError(err).Error("Error occurred while storing the image.") } return resPath } @@ -413,20 +399,18 @@ func storeImage(path string, i []byte) (string, error) { img, _, _ := image.Decode(bytes.NewReader(i)) resizedImage := imaging.Resize(img, 1280, 0, imaging.Lanczos) - var opts jpeg.Options + quality := 100 // if image small enough use it directly maxImageSize := 524288 // 0.55MB if len(i) > maxImageSize { - opts.Quality = (maxImageSize / len(i)) * 100 - } else { - opts.Quality = 100 // if image small enough use it directly + quality = (maxImageSize / len(i)) * 100 } var imgPath = fmt.Sprintf("%s%x.jpeg", path, md5.Sum(i)) - out, errFile := os.Create(imgPath) - if errFile != nil { - log.WithError(errFile).Error("Error while creating a new file on the path: ", path) - return imgPath, errFile + out, err := os.Create(imgPath) + if err != nil { + log.WithError(err).Error("Error while creating a new file on the path: ", path) + return imgPath, err } defer func(out *os.File) { if err := out.Close(); err != nil { @@ -434,8 +418,7 @@ func storeImage(path string, i []byte) (string, error) { } }(out) - errFile = jpeg.Encode(out, resizedImage, &opts) - return imgPath, errFile + return imgPath, jpeg.Encode(out, resizedImage, &jpeg.Options{Quality: quality}) } // CreateDishRating RPC Endpoint @@ -451,32 +434,28 @@ func (s *CampusServer) CreateDishRating(ctx context.Context, input *pb.CreateDis return nil, errorRes } - var dish *model.Dish - errDish := tx.Model(&model.Dish{}). //Dish must exist in the given mensa - Where("name LIKE ? AND cafeteriaID = ?", input.Dish, cafeteriaID). - First(&dish).Error - if errDish != nil || dish == nil { - log.WithError(errDish).Error("Error while creating a new dish rating.") + var dishInMensa *model.Dish + if err := tx.First(&dishInMensa, "name LIKE ? AND cafeteriaID = ?", input.Dish, cafeteriaID).Error; err != nil || dishInMensa == nil { + log.WithError(err).Error("Error while creating a new dishInMensa rating.") return nil, status.Error(codes.InvalidArgument, "Dish is not offered in this week in this canteen. Rating has not been saved.") } - resPath := imageWrapper(input.Image, "dishes", dish.Dish) + resPath := imageWrapper(input.Image, "dishes", dishInMensa.Dish) rating := model.DishRating{ Comment: input.Comment, CafeteriaID: cafeteriaID, - DishID: dish.Dish, + DishID: dishInMensa.Dish, Points: input.Points, Timestamp: time.Now(), Image: resPath, } - - if err := tx.Model(&model.DishRating{}).Create(&rating).Error; err != nil { - log.WithError(err).Error("while creating a new dish rating.") + if err := tx.Create(&rating).Error; err != nil { + log.WithError(err).Error("while creating a new dishInMensa rating.") return nil, status.Error(codes.Internal, "Error while creating the new rating in the database. Rating has not been saved.") } - assignDishNameTag(rating, dish.Dish, tx) + assignDishNameTag(rating, dishInMensa.Dish, tx) if err := storeRatingTags(rating.DishRating, input.RatingTags, DISH, tx); err != nil { return &pb.CreateDishRatingReply{}, err @@ -496,12 +475,11 @@ func assignDishNameTag(rating model.DishRating, dishID int64, tx *gorm.DB) { log.WithError(err).Error("while loading the dishID for the given name.") } else { for _, tagID := range result { - err := tx.Model(&model.DishNameTag{}).Create(&model.DishNameTag{ + if err := tx.Create(&model.DishNameTag{ CorrespondingRating: rating.DishRating, Points: rating.Points, TagNameID: tagID, - }).Error - if err != nil { + }).Error; err != nil { log.WithError(err).Error("while creating a new dish name rating.") } } @@ -512,7 +490,7 @@ func assignDishNameTag(rating model.DishRating, dishID int64, tx *gorm.DB) { // Additionally, queries the cafeteria ID, since it checks whether the cafeteria actually exists. func inputSanitizationForNewRatingElements(rating int32, comment string, cafeteriaName string, tx *gorm.DB) (int64, error) { if rating > 5 || rating < 0 { - return -1, status.Error(codes.InvalidArgument, "Rating must be a positive number not larger than 10. Rating has not been saved.") + return -1, status.Error(codes.InvalidArgument, "Rating must be a positive number not larger than 5. Rating has not been saved.") } if len(comment) > 256 { @@ -524,10 +502,7 @@ func inputSanitizationForNewRatingElements(rating int32, comment string, cafeter } var result *model.Cafeteria - res := tx.Model(&model.Cafeteria{}). - Where("name LIKE ?", cafeteriaName). - First(&result) - if errors.Is(res.Error, gorm.ErrRecordNotFound) || res.RowsAffected == 0 { + if res := tx.First(&result, "name LIKE ?", cafeteriaName); errors.Is(res.Error, gorm.ErrRecordNotFound) || res.RowsAffected == 0 { log.WithError(res.Error).Error("Error while querying the cafeteria id by name: ", cafeteriaName) return -1, status.Error(codes.InvalidArgument, "Cafeteria does not exist. Rating has not been saved.") } @@ -687,8 +662,7 @@ func (s *CampusServer) GetAvailableCafeteriaTags(ctx context.Context, _ *pb.List func (s *CampusServer) GetCafeterias(ctx context.Context, _ *pb.ListCanteensRequest) (*pb.ListCanteensReply, error) { var result []*pb.Canteen var requestStatus error = nil - err := s.db.WithContext(ctx).Model(&model.Cafeteria{}).Select("cafeteria as id,address,latitude,longitude").Scan(&result).Error - if err != nil { + if err := s.db.WithContext(ctx).Model(&model.Cafeteria{}).Select("cafeteria as id,address,latitude,longitude").Scan(&result).Error; err != nil { log.WithError(err).Error("while loading Cafeterias from database.") requestStatus = status.Error(codes.Internal, "Cafeterias could not be loaded from the database.") } diff --git a/server/backend/cron/average_rating_computation.go b/server/backend/cron/average_rating_computation.go index 24bd0a6c..8421e0ba 100644 --- a/server/backend/cron/average_rating_computation.go +++ b/server/backend/cron/average_rating_computation.go @@ -28,12 +28,10 @@ func computeAverageNameTags(c *CronService) { if err != nil { log.WithError(err).Error("while precomputing average name tags.") } else if len(results) > 0 { - errDelete := c.db.Where("1=1").Delete(&model.DishNameTagAverage{}).Error // Does not work with "true" - if errDelete != nil { - log.WithError(errDelete).Error("Error while deleting old averages in the table.") + if err := c.db.Where("1=1").Delete(&model.DishNameTagAverage{}).Error; err != nil { + log.WithError(err).Error("Error while deleting old averages in the table.") } - err := c.db.Model(&model.DishNameTagAverage{}).Create(&results).Error - if err != nil { + if err := c.db.Create(&results).Error; err != nil { log.WithError(err).Error("while creating a new average name tag rating in the database.") } } @@ -49,13 +47,11 @@ func computeAverageForDishesInCafeteriasTags(c *CronService) { if err != nil { log.WithError(err).Error("while precomputing average dish tags.") } else if len(results) > 0 { - errDelete := c.db.Where("1=1").Delete(&model.DishRatingTagAverage{}).Error - if errDelete != nil { - log.WithError(errDelete).Error("Error while deleting old averages in the table.") + if err := c.db.Where("1=1").Delete(&model.DishRatingTagAverage{}).Error; err != nil { + log.WithError(err).Error("Error while deleting old averages in the table.") } - err := c.db.Model(&model.DishRatingTagAverage{}).Create(&results).Error - if err != nil { + if err := c.db.Create(&results).Error; err != nil { log.WithError(err).Error("while creating a new average dish tag rating in the database.") } @@ -72,13 +68,11 @@ func computeAverageCafeteriaTags(c *CronService) { if err != nil { log.WithError(err).Error("while precomputing average cafeteria tags.") } else if len(results) > 0 { - errDelete := c.db.Where("1=1").Delete(&model.CafeteriaRatingTagsAverage{}).Error - if errDelete != nil { - log.WithError(errDelete).Error("Error while deleting old averages in the table.") + if err := c.db.Where("1=1").Delete(&model.CafeteriaRatingTagsAverage{}).Error; err != nil { + log.WithError(err).Error("Error while deleting old averages in the table.") } - err := c.db.Model(&model.CafeteriaRatingTagsAverage{}).Create(&results).Error - if err != nil { + if err := c.db.Create(&results).Error; err != nil { log.WithError(err).Error("while creating a new average cafeteria tag rating in the database.") } } @@ -93,12 +87,10 @@ func computeAverageForDishesInCafeterias(c *CronService) { if err != nil { log.WithError(err).Error("while precomputing average dish ratings.") } else if len(results) > 0 { - errDelete := c.db.Where("1=1").Delete(&model.DishRatingAverage{}).Error - if errDelete != nil { - log.WithError(errDelete).Error("Error while deleting old averages in the table.") + if err := c.db.Where("1=1").Delete(&model.DishRatingAverage{}).Error; err != nil { + log.WithError(err).Error("Error while deleting old averages in the table.") } - err := c.db.Model(&model.DishRatingAverage{}).Create(&results).Error - if err != nil { + if err := c.db.Create(&results).Error; err != nil { log.WithError(err).Error("while creating a new average dish rating in the database.") } } @@ -113,12 +105,11 @@ func computeAverageForCafeteria(c *CronService) { if err != nil { log.WithError(err).Error("while precomputing average cafeteria ratings.") } else if len(results) > 0 { - errDelete := c.db.Where("1=1").Delete(&model.CafeteriaRatingAverage{}).Error - if errDelete != nil { - log.WithError(errDelete).Error("Error while deleting old averages in the table.") + if err := c.db.Where("1=1").Delete(&model.CafeteriaRatingAverage{}).Error; err != nil { + log.WithError(err).Error("Error while deleting old averages in the table.") } - err := c.db.Model(&model.CafeteriaRatingAverage{}).Create(&results).Error + err := c.db.Create(&results).Error if err != nil { log.WithError(err).Error("while creating a new average cafeteria rating in the database.") } diff --git a/server/backend/cron/canteen_head_count.go b/server/backend/cron/canteen_head_count.go index 23892602..b14b816f 100644 --- a/server/backend/cron/canteen_head_count.go +++ b/server/backend/cron/canteen_head_count.go @@ -224,7 +224,7 @@ func (canteen CanteenApInformation) requestApData() []AccessPoint { // Parse as JSON var aps []AccessPoint if err = json.NewDecoder(resp.Body).Decode(&aps); err != nil { - log.WithError(err).Error("Canteen HeadCount parsing output as JSON failed for: ", canteen.CanteenId) + log.WithError(err).WithField("CanteenId", canteen.CanteenId).Error("Canteen HeadCount parsing failed") return []AccessPoint{} } return aps diff --git a/server/backend/cron/cronjobs.go b/server/backend/cron/cronjobs.go index 55c8bce0..95ba45a2 100644 --- a/server/backend/cron/cronjobs.go +++ b/server/backend/cron/cronjobs.go @@ -19,7 +19,9 @@ type CronService struct { APNs *apns.Service } -const StorageDir = "/Storage/" // target location of files +// StorageDir is the directory where files are stored +// this is a variable, so it can be changed during tests +var StorageDir = "/Storage/" // target location of files // names for cron jobs as specified in database const ( diff --git a/server/backend/cron/dish_name_download.go b/server/backend/cron/dish_name_download.go index b0f0e117..e02e0e28 100644 --- a/server/backend/cron/dish_name_download.go +++ b/server/backend/cron/dish_name_download.go @@ -12,31 +12,31 @@ import ( "gorm.io/gorm" ) -type cafeteriaName struct { - Name string `json:"enum_name"` - Location location `json:"location"` +type CafeteriaName struct { + Name string `json:"enum_name"` + Location CanteenLocation `json:"location"` } -type cafeteriaWithID struct { +type CafeteriaWithID struct { Name string `json:"name"` Cafeteria int64 `json:"cafeteria"` } -type location struct { +type CanteenLocation struct { Longitude float32 `json:"longitude"` Latitude float32 `json:"latitude"` Address string `json:"address"` } -type days struct { - Days []date `json:"days"` +type CanteenDays struct { + Days []CanteenDate `json:"days"` } -type date struct { - Dates []dish `json:"dishes"` +type CanteenDate struct { + Dates []CanteenDish `json:"dishes"` } -type dish struct { +type CanteenDish struct { Name string `json:"name"` DishType string `json:"dish_type"` } @@ -44,27 +44,24 @@ type dish struct { // fileDownloadCron // Downloads all files that are not marked as finished in the database. func (c *CronService) dishNameDownloadCron() error { - downloadCanteenNames(c) downloadDailyDishes(c) - return nil } func downloadDailyDishes(c *CronService) { - var result []cafeteriaWithID - errQueryCafeterias := c.db.Model(&model.Cafeteria{}).Select("name,cafeteria").Scan(&result).Error - if errQueryCafeterias != nil { - log.WithError(errQueryCafeterias).Error("Error while querying all cafeteria names from the database.") + var result []CafeteriaWithID + if err := c.db.Model(&model.Cafeteria{}).Select("name,cafeteria").Scan(&result).Error; err != nil { + log.WithError(err).Error("Error while querying all cafeteria names from the database.") } year, week := time.Now().UTC().ISOWeek() var weekliesWereAdded int64 - errExistsQuery := c.db.Model(&model.DishesOfTheWeek{}). + + if err := c.db.Model(&model.DishesOfTheWeek{}). Where("year = ? AND week = ?", year, week). - Count(&weekliesWereAdded).Error - if errExistsQuery != nil { - log.WithError(errExistsQuery).Error("Error while checking whether the meals of the current week have already been added to the weekly table.") + Count(&weekliesWereAdded).Error; err != nil { + log.WithError(err).Error("Error while checking whether the meals of the current week have already been added to the weekly table.") } for _, v := range result { @@ -75,6 +72,7 @@ func downloadDailyDishes(c *CronService) { var resp, err = http.Get(req) if err != nil { log.WithError(err).Error("Error fetching menu.") + continue } if resp.StatusCode != 200 { fields := log.Fields{ @@ -82,49 +80,45 @@ func downloadDailyDishes(c *CronService) { "StatusCode": resp.StatusCode, } log.WithError(err).WithFields(fields).Error("Menu does not exist") - } else { - var dishes days - errJson := json.NewDecoder(resp.Body).Decode(&dishes) - if errJson != nil { - log.WithError(err).Error("Error in Parsing") - } + continue + } + var dishes CanteenDays + if err := json.NewDecoder(resp.Body).Decode(&dishes); err != nil { + log.WithError(err).Error("Error in Parsing") + } - for weekDayIndex := 0; weekDayIndex < len(dishes.Days); weekDayIndex++ { - for u := 0; u < len(dishes.Days[weekDayIndex].Dates); u++ { - dish := model.Dish{ - Name: dishes.Days[weekDayIndex].Dates[u].Name, - Type: dishes.Days[weekDayIndex].Dates[u].DishType, - CafeteriaID: v.Cafeteria, - } + for weekDayIndex, day := range dishes.Days { + for _, date := range day.Dates { + dish := model.Dish{ + Name: date.Name, + Type: date.DishType, + CafeteriaID: v.Cafeteria, + } - var count int64 - var dishId int64 - errCount := c.db.Model(&model.Dish{}). - Where("name = ? AND cafeteriaID = ?", dish.Name, dish.CafeteriaID). - Select("dish").First(&dishId). - Count(&count).Error - if errCount != nil { - log.WithError(errCount).Error("Error while checking whether this is already in database") - } - if count == 0 { - errCreate := c.db.Model(&model.Dish{}).Create(&dish).Error - if errCreate != nil { - log.WithError(errCreate).Error("Error while creating new dish entry with name {}. dish won't be saved", dish.Name) - } - addDishTagsToMapping(dish.Dish, dish.Name, c.db) - dishId = dish.Dish + var count int64 + var dishId int64 + if err := c.db.Model(&model.Dish{}). + Where("name = ? AND cafeteriaID = ?", dish.Name, dish.CafeteriaID). + Select("CanteenDish").First(&dishId). + Count(&count).Error; err != nil { + log.WithError(err).Error("Error while checking whether this is already in database") + } + if count == 0 { + if err := c.db.Create(&dish).Error; err != nil { + log.WithError(err).Error("Error while creating new CanteenDish entry with name {}. CanteenDish won't be saved", dish.Name) } - if weekliesWereAdded == 0 { - errCreate := c.db.Model(&model.DishesOfTheWeek{}). - Create(&model.DishesOfTheWeek{ - DishID: dishId, - Year: int32(year), - Week: int32(week), - Day: int32(weekDayIndex), - }).Error - if errCreate != nil { - log.WithError(errCreate).Error("Error while inserting dish for this weeks weekly dishes", dish.Name) - } + addDishTagsToMapping(dish.Dish, dish.Name, c.db) + dishId = dish.Dish + } + if weekliesWereAdded == 0 { + errCreate := c.db.Create(&model.DishesOfTheWeek{ + DishID: dishId, + Year: int32(year), + Week: int32(week), + Day: int32(weekDayIndex), + }).Error + if errCreate != nil { + log.WithError(errCreate).Error("Error while inserting CanteenDish for this weeks weekly dishes", dish.Name) } } } @@ -137,70 +131,56 @@ func downloadCanteenNames(c *CronService) { if err != nil { log.WithError(err).Error("Error fetching cafeteria list from eat-api.") } - var cafeteriaNames []cafeteriaName - errjson := json.NewDecoder(resp.Body).Decode(&cafeteriaNames) - - if errjson != nil { - log.WithError(errjson).Error("Error while unmarshalling json data.") + var cafeteriaNames []CafeteriaName + if err := json.NewDecoder(resp.Body).Decode(&cafeteriaNames); err != nil { + log.WithError(err).Error("Error while unmarshalling json data.") } - for i := 0; i < len(cafeteriaNames); i++ { - + for _, cafeteriaName := range cafeteriaNames { mensa := model.Cafeteria{ - Name: cafeteriaNames[i].Name, - Address: cafeteriaNames[i].Location.Address, - Latitude: cafeteriaNames[i].Location.Latitude, - Longitude: cafeteriaNames[i].Location.Longitude, + Name: cafeteriaName.Name, + Address: cafeteriaName.Location.Address, + Latitude: cafeteriaName.Location.Latitude, + Longitude: cafeteriaName.Location.Longitude, } var cafeteriaResult model.Cafeteria - resExists := c.db.Model(&model.Cafeteria{}). - Where("name = ?", cafeteriaNames[i].Name). - First(&cafeteriaResult) - - if resExists.Error != nil { - errCreate := c.db.Model(&model.Cafeteria{}).Create(&mensa).Error - if errCreate != nil { - log.WithError(errCreate).Error("Error while creating the db entry for the cafeteria ", cafeteriaNames[i].Name) + if err := c.db.First(&cafeteriaResult, "name = ?", cafeteriaName.Name).Error; err != nil { + if err := c.db.Create(&mensa).Error; err != nil { + log.WithError(err).Error("Error while creating the db entry for the cafeteria ", cafeteriaName.Name) } } else { - errUpdate := c.db.Model(&model.Cafeteria{}). - Where("name = ?", cafeteriaNames[i].Name). - Updates(&mensa).Error - if errUpdate != nil { - log.WithError(errUpdate).Error("Error while updating the db entry for the cafeteria {}.", cafeteriaNames[i].Name) + if err := c.db.Where("name = ?", cafeteriaName.Name).Updates(&mensa).Error; err != nil { + log.WithError(err).Error("Error while updating the db entry for the cafeteria {}.", cafeteriaName.Name) } } } } // addDishTagsToMapping -// Checks whether the dish name includes one of the expressions for the excluded tags as well as the included tags. +// Checks whether the CanteenDish name includes one of the expressions for the excluded tags as well as the included tags. // The corresponding tags for all identified DishNames will be saved in the table DishNameTags. func addDishTagsToMapping(dishID int64, dishName string, db *gorm.DB) { lowercaseDish := strings.ToLower(dishName) var includedTags []int64 - errIncluded := db.Model(&model.DishNameTagOptionIncluded{}). + if err := db.Model(&model.DishNameTagOptionIncluded{}). Where("? LIKE CONCAT('%', expression ,'%')", lowercaseDish). Select("nameTagID"). - Scan(&includedTags).Error - if errIncluded != nil { - log.WithError(errIncluded).Error("Error while querying all included expressions for the dish: ", lowercaseDish) + Scan(&includedTags).Error; err != nil { + log.WithError(err).Error("Error while querying all included expressions for the CanteenDish: ", lowercaseDish) } var excludedTags []int64 - err := db.Model(&model.DishNameTagOptionExcluded{}). + if err := db.Model(&model.DishNameTagOptionExcluded{}). Where("? LIKE CONCAT('%', expression ,'%')", lowercaseDish). Select("nameTagID"). - Scan(&excludedTags).Error - if err != nil { - log.WithError(err).Error("Error while querying all excluded expressions for the dish: ", lowercaseDish) + Scan(&excludedTags).Error; err != nil { + log.WithError(err).Error("Error while querying all excluded expressions for the CanteenDish: ", lowercaseDish) } //set all entries in included to -1 if the excluded tag was recognised for this tag rating. if len(excludedTags) > 0 { for _, a := range excludedTags { - i := contains(includedTags, a) - if i != -1 { + if i := contains(includedTags, a); i != -1 { includedTags[i] = -1 } } @@ -208,11 +188,10 @@ func addDishTagsToMapping(dishID int64, dishName string, db *gorm.DB) { for _, nametagID := range includedTags { if nametagID != -1 { - err := db.Model(&model.DishToDishNameTag{}).Create(&model.DishToDishNameTag{ + if err := db.Create(&model.DishToDishNameTag{ DishID: dishID, NameTagID: nametagID, - }).Error - if err != nil { + }).Error; err != nil { fields := log.Fields{"dishID": dishID, "nametagID": nametagID} log.WithError(err).WithFields(fields).Error("creating a new entry") } diff --git a/server/backend/cron/feedback_email.go b/server/backend/cron/feedback_email.go index 01d39867..c1ffa02f 100644 --- a/server/backend/cron/feedback_email.go +++ b/server/backend/cron/feedback_email.go @@ -58,8 +58,8 @@ func messageWithHeaders(feedback *model.Feedback) *gomail.Message { // From m.SetAddressHeader("From", os.Getenv("SMTP_USERNAME"), "TUM Campus App") // To - if feedback.Receiver.Valid { - m.SetHeader("To", feedback.Receiver.String) + if feedback.Recipient.Valid { + m.SetHeader("To", feedback.Recipient.String) } else { m.SetHeader("To", "app@tum.de") } @@ -94,12 +94,12 @@ func (c *CronService) feedbackEmailCron() error { var results []model.Feedback if err := c.db.Find(&results, "processed = false").Scan(&results).Error; err != nil { - log.WithError(err).Fatal("could not get unprocessed feedback") + log.WithError(err).Error("could not get unprocessed feedback") return err } parsedHtmlBody, parsedTxtBody, err := parseTemplates() if err != nil { - log.WithError(err).Fatal("could not parse email templates") + log.WithError(err).Error("could not parse email templates") return err } @@ -124,7 +124,7 @@ func (c *CronService) feedbackEmailCron() error { log.WithError(err).Error("could not send mail") continue } - log.Tracef("sending feedback %dialer to %v successfull", i, feedback.Receiver) + log.Tracef("sending feedback %d to %v successfull", i, feedback.Recipient) // prevent the message being send the next time around if err := c.db.Find(model.Feedback{}, "id = ?", feedback.Id).Update("processed", "true").Error; err != nil { @@ -138,7 +138,7 @@ func (c *CronService) feedbackEmailCron() error { func setupSMTPDialer() (*gomail.Dialer, error) { smtpPort, err := strconv.Atoi(os.Getenv("SMTP_PORT")) if err != nil { - log.WithError(err).Fatal("SMTP_PORT is not an integer") + log.WithError(err).Error("SMTP_PORT is not an integer") return nil, err } d := gomail.NewDialer(os.Getenv("SMTP_URL"), smtpPort, os.Getenv("SMTP_USERNAME"), os.Getenv("SMTP_PASSWORD")) diff --git a/server/backend/cron/feedback_email_test.go b/server/backend/cron/feedback_email_test.go index 7c5f1a2e..90dd0d16 100644 --- a/server/backend/cron/feedback_email_test.go +++ b/server/backend/cron/feedback_email_test.go @@ -24,7 +24,7 @@ func TestIterate(t *testing.T) { func fullFeedback() *model.Feedback { return &model.Feedback{ EmailId: null.StringFrom("magic-id"), - Receiver: null.StringFrom("tca"), + Recipient: null.StringFrom("tca"), ReplyTo: null.StringFrom("test@example.de"), Feedback: null.StringFrom("This is a Test"), ImageCount: 1, @@ -39,7 +39,7 @@ func fullFeedback() *model.Feedback { func emptyFeedback() *model.Feedback { return &model.Feedback{ EmailId: null.String{}, - Receiver: null.String{}, + Recipient: null.String{}, ReplyTo: null.String{}, Feedback: null.String{}, ImageCount: 0, @@ -56,7 +56,7 @@ func TestHeaderInstantiationWithFullFeedback(t *testing.T) { fb := fullFeedback() m := messageWithHeaders(fb) assert.Equal(t, []string{`"TUM Campus App" `}, m.GetHeader("From")) - assert.Equal(t, []string{fb.Receiver.String}, m.GetHeader("To")) + assert.Equal(t, []string{fb.Recipient.String}, m.GetHeader("To")) assert.Equal(t, []string{"test@example.de"}, m.GetHeader("Reply-To")) assert.Equal(t, []string{fb.Timestamp.Time.Format(time.RFC1123Z)}, m.GetHeader("Date")) assert.Equal(t, []string{"Feedback via Tum Campus App"}, m.GetHeader("Subject")) diff --git a/server/backend/cron/movies.go b/server/backend/cron/movies.go index 9c89ced7..1d2a85e4 100644 --- a/server/backend/cron/movies.go +++ b/server/backend/cron/movies.go @@ -49,7 +49,7 @@ func (c *CronService) movieCron() error { for _, item := range channel.Items { logFields := log.Fields{"link": item.Link, "title": item.Title, "date": item.PubDate, "location": item.Location, "url": item.Enclosure.Url} var exists bool - if err := c.db.Model(model.Kino{}).Select("count(*) > 0").Where("link = ?", item.Link).Find(&exists).Error; err != nil { + if err := c.db.Model(model.Kino{}).Select("count(*) > 0").Find(&exists, "link = ?", item.Link).Error; err != nil { log.WithError(err).WithFields(logFields).Error("Cound lot check if movie already exists") continue } diff --git a/server/backend/cron/news.go b/server/backend/cron/news.go index 640754e2..8514c0b0 100644 --- a/server/backend/cron/news.go +++ b/server/backend/cron/news.go @@ -87,43 +87,47 @@ func (c *CronService) parseNewsFeed(source model.NewsSource) error { } } - if !skipNews(existingNewsLinksForSource, item.Link) { - // pick the first enclosure that is an image (if any) - var pickedEnclosure *gofeed.Enclosure - for _, enclosure := range item.Enclosures { - if strings.HasSuffix(enclosure.URL, "jpg") || - strings.HasSuffix(enclosure.URL, "jpeg") || - strings.HasSuffix(enclosure.URL, "png") || - ImageContentTypeRegex.MatchString(enclosure.Type) { - pickedEnclosure = enclosure - break - } + if skipNews(existingNewsLinksForSource, item.Link) { + continue + } + + // pick the first enclosure that is an image (if any) + var pickedEnclosure *gofeed.Enclosure + for _, enclosure := range item.Enclosures { + if strings.HasSuffix(enclosure.URL, "jpg") || + strings.HasSuffix(enclosure.URL, "jpeg") || + strings.HasSuffix(enclosure.URL, "png") || + ImageContentTypeRegex.MatchString(enclosure.Type) { + pickedEnclosure = enclosure + break } - var enclosureUrl = null.StringFrom("") - var file *model.File - if pickedEnclosure != nil { - file, err = c.saveImage(pickedEnclosure.URL) - if err != nil { - log.WithError(err).Error("can't save news image") - } - enclosureUrl = null.StringFrom(pickedEnclosure.URL) + } + var enclosureUrl = null.StringFrom("") + var file *model.File + var fileID null.Int + if pickedEnclosure != nil { + file, err = c.saveImage(pickedEnclosure.URL) + if err != nil { + log.WithError(err).WithField("url", pickedEnclosure.URL).Error("can't save news image") + } else { + fileID = null.IntFrom(file.File) } - bm := bluemonday.StrictPolicy() - sanitizedDesc := bm.Sanitize(item.Description) + enclosureUrl = null.StringFrom(pickedEnclosure.URL) + } - newsItem := model.News{ - Date: *item.PublishedParsed, - Created: time.Now(), - Title: item.Title, - Description: sanitizedDesc, - Src: source.Source, - Link: item.Link, - Image: enclosureUrl, - FileID: null.IntFrom(file.File), - File: file, - } - newNews = append(newNews, newsItem) + newsItem := model.News{ + Date: *item.PublishedParsed, + Created: time.Now(), + Title: item.Title, + Description: bluemonday.StrictPolicy().Sanitize(item.Description), + NewsSourceID: source.Source, + NewsSource: source, + Link: item.Link, + Image: enclosureUrl, + FileID: fileID, + File: file, } + newNews = append(newNews, newsItem) } if ammountOfNewNews := len(newNews); ammountOfNewNews != 0 { err = c.db.Save(&newNews).Error @@ -140,10 +144,9 @@ func (c *CronService) parseNewsFeed(source model.NewsSource) error { // saveImage saves an image to the database, so it can be downloaded by another cronjob and returns its id func (c *CronService) saveImage(url string) (*model.File, error) { targetFileName := fmt.Sprintf("%x.jpg", md5.Sum([]byte(url))) - file := model.File{ - Name: targetFileName, // path intentionally omitted - } - if err := c.db.First(&file).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + var file model.File + // path intentionally omitted in query to allow for deduplication + if err := c.db.First(&file, "name = ?", targetFileName).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { log.WithError(err).WithField("targetFileName", targetFileName).Error("Couldn't query database for file") return nil, err } else if err == nil { @@ -197,7 +200,6 @@ func (c *CronService) newspreadHook(item *gofeed.Item) { extractedImageURL = extractedImageSlice[0] } item.Enclosures = []*gofeed.Enclosure{{URL: extractedImageURL}} - item.Link = extractedImageURL item.Description = "" } diff --git a/server/backend/feedback.go b/server/backend/feedback.go new file mode 100644 index 00000000..8cc3cb22 --- /dev/null +++ b/server/backend/feedback.go @@ -0,0 +1,160 @@ +package backend + +import ( + "bytes" + "fmt" + "io" + "os" + "path" + "slices" + + pb "github.com/TUM-Dev/Campus-Backend/server/api/tumdev" + "github.com/TUM-Dev/Campus-Backend/server/backend/cron" + "github.com/TUM-Dev/Campus-Backend/server/model" + "github.com/gabriel-vasile/mimetype" + "github.com/gofrs/uuid/v5" + "github.com/guregu/null" + log "github.com/sirupsen/logrus" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "gorm.io/gorm" +) + +// CreateFeedback accepts a stream of feedback messages from the client and stores them in the database/file system. +func (s *CampusServer) CreateFeedback(stream pb.Campus_CreateFeedbackServer) error { + // receive metadata + id, err := uuid.NewGen().NewV4() + if err != nil { + log.WithError(err).Error("Error generating uuid") + return status.Error(codes.Internal, "Error starting feedback submission") + } + feedback := &model.Feedback{EmailId: null.StringFrom(id.String())} + + // download images + dbPath := path.Join("feedback", feedback.EmailId.String) + var uploadedFilenames []*string + for { + req, err := stream.Recv() + if err == io.EOF { + break + } + if err != nil { + log.WithError(err).Error("Error receiving feedback") + deleteUploaded(feedback.EmailId.String) + return status.Error(codes.Internal, "Error receiving feedback") + } + mergeFeedback(feedback, req) + + if len(req.Attachment) > 0 { + filename := handleImageUpload(req.Attachment, len(uploadedFilenames), dbPath) + if filename != nil { + uploadedFilenames = append(uploadedFilenames, filename) + } + } + } + feedback.ImageCount = int32(len(uploadedFilenames)) + // save feedback to db + if err := s.db.WithContext(stream.Context()).Transaction(func(tx *gorm.DB) error { + for _, filename := range uploadedFilenames { + if err := tx.Create(&model.File{ + Name: *filename, + Path: dbPath, + Downloads: 1, + Downloaded: null.BoolFrom(true), + }).Error; err != nil { + return err + } + } + return tx.Create(feedback).Error + }); err != nil { + log.WithError(err).Error("Error creating feedback") + deleteUploaded(feedback.EmailId.String) + return status.Error(codes.Internal, "Error creating feedback") + } + + if err := stream.SendAndClose(&pb.CreateFeedbackReply{}); err != nil { + log.WithError(err).Error("Error sending feedback-reply") + return status.Error(codes.Internal, "Error sending feedback-reply") + } + return nil +} + +// deleteUploaded deletes all uploaded images from the filesystem +func deleteUploaded(dbPath string) { + if err := os.RemoveAll(cron.StorageDir + dbPath); err != nil { + log.WithError(err).WithField("path", dbPath).Error("Error deleting uploaded images from filesystem") + } +} + +func handleImageUpload(content []byte, imageCounter int, dbPath string) *string { + filename, realFilePath := inferFileName(mimetype.Detect(content), dbPath, imageCounter) + if filename == nil { + return nil // the filetype is not accepted by us + } + + if err := os.MkdirAll(path.Dir(*realFilePath), 0755); err != nil { + log.WithError(err).WithField("dbPath", dbPath).Error("Error creating directory for feedback") + return nil + } + out, err := os.Create(*realFilePath) + if err != nil { + log.WithError(err).WithField("path", dbPath).Error("Error creating file for feedback") + return nil + } + defer func(out *os.File) { + err := out.Close() + if err != nil { + log.WithError(err).WithField("path", dbPath).Error("Error while closing file") + } + }(out) + if _, err := io.Copy(out, bytes.NewReader(content)); err != nil { + log.WithError(err).WithField("path", dbPath).Error("Error while writing file") + if err := os.Remove(*realFilePath); err != nil { + log.WithError(err).WithField("path", dbPath).Warn("Could not clean up file") + } + return nil + } + return filename +} + +func inferFileName(mime *mimetype.MIME, dbPath string, counter int) (*string, *string) { + allowedExt := []string{".jpeg", ".jpg", ".png", ".webp", ".md", ".txt", ".pdf"} + if !slices.Contains(allowedExt, mime.Extension()) { + return nil, nil + } + + filename := fmt.Sprintf("%d%s", counter, mime.Extension()) + realFilePath := path.Join(cron.StorageDir, dbPath, filename) + return &filename, &realFilePath +} + +func mergeFeedback(feedback *model.Feedback, req *pb.CreateFeedbackRequest) { + if req.Recipient.Enum() != nil { + feedback.Recipient = null.StringFrom(receiverFromTopic(req.Recipient)) + } + if req.OsVersion != "" { + feedback.OsVersion = null.StringFrom(req.OsVersion) + } + if req.AppVersion != "" { + feedback.AppVersion = null.StringFrom(req.AppVersion) + } + if req.Location != nil { + feedback.Longitude = null.FloatFrom(req.Location.Longitude) + feedback.Latitude = null.FloatFrom(req.Location.Latitude) + } + if req.Message != "" { + feedback.Feedback = null.StringFrom(req.Message) + } + if req.FromEmail != "" { + feedback.ReplyTo = null.StringFrom(req.FromEmail) + } +} + +func receiverFromTopic(topic pb.CreateFeedbackRequest_Recipient) string { + switch topic { + case pb.CreateFeedbackRequest_TUM_DEV: + return "app@tum.de" + default: + return "kontakt@tum.de" + } +} diff --git a/server/backend/feedback_test.go b/server/backend/feedback_test.go new file mode 100644 index 00000000..550143c9 --- /dev/null +++ b/server/backend/feedback_test.go @@ -0,0 +1,191 @@ +package backend + +import ( + "bytes" + "context" + "database/sql" + "image" + "image/png" + "io" + "os" + "path" + "regexp" + "testing" + "time" + + "github.com/TUM-Dev/Campus-Backend/server/backend/cron" + + "github.com/DATA-DOG/go-sqlmock" + pb "github.com/TUM-Dev/Campus-Backend/server/api/tumdev" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "google.golang.org/grpc" + "gorm.io/driver/mysql" + "gorm.io/gorm" +) + +type FeedbackSuite struct { + suite.Suite + DB *gorm.DB + mock sqlmock.Sqlmock +} + +func (s *FeedbackSuite) SetupSuite() { + var ( + db *sql.DB + err error + ) + + db, s.mock, err = sqlmock.New() + require.NoError(s.T(), err) + + dialector := mysql.New(mysql.Config{ + Conn: db, + DriverName: "mysql", + }) + s.mock.ExpectQuery("SELECT VERSION()"). + WillReturnRows(sqlmock.NewRows([]string{"VERSION()"}).AddRow("10.11.4-MariaDB")) + s.DB, err = gorm.Open(dialector, &gorm.Config{}) + require.NoError(s.T(), err) +} + +type mockedFeedbackStream struct { + grpc.ServerStream + recived []*pb.CreateFeedbackRequest + reply *pb.CreateFeedbackReply + T *testing.T +} + +func (f mockedFeedbackStream) SendAndClose(reply *pb.CreateFeedbackReply) error { + require.Equal(f.T, f.reply, reply) + return nil +} + +// because of the way the mocked stream works, we need to keep track of the index +// we however can't track this inside of mockedFeedbackStream, as we cannot mutate the struct +// => tracking this as a global variable is the only way +var index = uint(0) + +func (f mockedFeedbackStream) Recv() (*pb.CreateFeedbackRequest, error) { + if int(index) >= len(f.recived) { + return nil, io.EOF + } + index++ + return f.recived[index-1], nil +} + +func (f mockedFeedbackStream) Context() context.Context { + // reset index, as this function is called before Recv() + // This is a hacky solution, but it works + index = 0 + return context.Background() +} + +// createDummyImage creates a dummy image with the specified dimensions +func createDummyImage(t *testing.T, width, height int) []byte { + img := image.NewRGBA(image.Rect(0, 0, width, height)) + // encode img to buffer + buf := new(bytes.Buffer) + require.NoError(t, png.Encode(buf, img)) + return buf.Bytes() +} + +func (s *FeedbackSuite) Test_CreateFeedback_OneFile() { + cron.StorageDir = "test_one_image/" + defer func(path string) { + err := os.RemoveAll(path) + if err != nil { + s.T().Fatal(err) + } + }(cron.StorageDir) + + server := CampusServer{db: s.DB} + s.mock.ExpectBegin() + returnedTime := time.Now() + s.mock.ExpectQuery(regexp.QuoteMeta("INSERT INTO `files` (`name`,`path`,`downloads`,`downloaded`) VALUES (?,?,?,?) RETURNING `url`,`file`")). + WithArgs("0.txt", sqlmock.AnyArg(), 1, true). + WillReturnRows(sqlmock.NewRows([]string{"url", "file"}).AddRow(nil, 1)) + s.mock.ExpectQuery(regexp.QuoteMeta("INSERT INTO `files` (`name`,`path`,`downloads`,`downloaded`) VALUES (?,?,?,?) RETURNING `url`,`file`")). + WithArgs("1.png", sqlmock.AnyArg(), 1, true). + WillReturnRows(sqlmock.NewRows([]string{"url", "file"}).AddRow(nil, 1)) + s.mock.ExpectQuery(regexp.QuoteMeta("INSERT INTO `feedback` (`image_count`,`email_id`,`receiver`,`reply_to`,`feedback`,`latitude`,`longitude`,`os_version`,`app_version`,`processed`) VALUES (?,?,?,?,?,?,?,?,?,?) RETURNING `timestamp`,`id`")). + WithArgs(2, sqlmock.AnyArg(), "app@tum.de", "testing@example.com", "Hello World", nil, nil, nil, nil, false). + WillReturnRows(sqlmock.NewRows([]string{"timestamp", "id"}).AddRow(returnedTime, 1)) + s.mock.ExpectCommit() + + dummyImage := createDummyImage(s.T(), 10, 10) + dummyText := []byte("Dummy Text") + stream := mockedFeedbackStream{ + T: s.T(), + recived: []*pb.CreateFeedbackRequest{ + {Recipient: pb.CreateFeedbackRequest_TUM_DEV, FromEmail: "testing@example.com", Message: "Hello World", Attachment: dummyText}, + {Attachment: dummyImage}, + }, + reply: &pb.CreateFeedbackReply{}, + } + require.NoError(s.T(), server.CreateFeedback(stream)) + + // check that the correct operations happened to the file system + files := extractUploadedFiles(s.T(), cron.StorageDir, 2) + expectFileMatches(s.T(), (*files)[0], "0.txt", returnedTime, dummyText) + expectFileMatches(s.T(), (*files)[1], "1.png", returnedTime, dummyImage) +} + +func expectFileMatches(t *testing.T, file os.DirEntry, name string, returnedTime time.Time, content []byte) { + require.Equal(t, name, file.Name()) + require.True(t, file.Type().IsRegular()) + info, err := file.Info() + require.NoError(t, err) + require.LessOrEqual(t, returnedTime.Unix(), info.ModTime().Unix()) + require.Len(t, content, int(info.Size())) +} + +func (s *FeedbackSuite) Test_CreateFeedback_NoImage() { + cron.StorageDir = "test_no_image/" + + server := CampusServer{db: s.DB} + s.mock.ExpectBegin() + s.mock.ExpectQuery(regexp.QuoteMeta("INSERT INTO `feedback` (`image_count`,`email_id`,`receiver`,`reply_to`,`feedback`,`latitude`,`longitude`,`os_version`,`app_version`,`processed`) VALUES (?,?,?,?,?,?,?,?,?,?) RETURNING `timestamp`,`id`")). + WithArgs(0, sqlmock.AnyArg(), "app@tum.de", "testing@example.com", "Hello World", nil, nil, nil, nil, false). + WillReturnRows(sqlmock.NewRows([]string{"timestamp", "id"}).AddRow(time.Now(), 1)) + s.mock.ExpectCommit() + + stream := mockedFeedbackStream{ + T: s.T(), + recived: []*pb.CreateFeedbackRequest{ + {Recipient: pb.CreateFeedbackRequest_TUM_DEV, FromEmail: "testing@example.com", Message: "Hello World"}, + {}, // empty images should be ignored + }, + reply: &pb.CreateFeedbackReply{}, + } + require.NoError(s.T(), server.CreateFeedback(stream)) + + // no image should be uploaded to the file system + extractUploadedFiles(s.T(), cron.StorageDir, 0) +} + +func extractUploadedFiles(t *testing.T, storageRoot string, expected int) *[]os.DirEntry { + parentDir, err := os.ReadDir(path.Join(storageRoot, "feedback")) + if expected == 0 { + require.Error(t, err, os.ErrNotExist.Error()) + require.Empty(t, parentDir) + return nil + } + + require.NoError(t, err) + require.Len(t, parentDir, 1) + dir, err := os.ReadDir(path.Join(storageRoot, "feedback", parentDir[0].Name())) + require.NoError(t, err) + require.Len(t, dir, expected) + return &dir +} + +func (s *FeedbackSuite) AfterTest(_, _ string) { + require.NoError(s.T(), s.mock.ExpectationsWereMet()) +} + +// In order for 'go test' to run this suite, we need to create +// a normal test function and pass our suite to suite.Run +func TestExampleTestSuite(t *testing.T) { + suite.Run(t, new(FeedbackSuite)) +} diff --git a/server/backend/ios_notifications/scheduling/service.go b/server/backend/ios_notifications/scheduling/service.go index 00cc7d79..4c7aa2cb 100644 --- a/server/backend/ios_notifications/scheduling/service.go +++ b/server/backend/ios_notifications/scheduling/service.go @@ -65,15 +65,12 @@ func (service *Service) HandleScheduledCron() error { } func (service *Service) handleDevices(devices []model.IOSDeviceLastUpdated) { - routinesCount := routineCount(devices) - + routinesCount := min(len(devices), MaxRoutineCount) chunkSize := len(devices) / routinesCount - - perfectChunkable := (len(devices) % routinesCount) == 0 + perfectlyChunkable := (len(devices) % routinesCount) == 0 chunksArrSize := routinesCount - - if !perfectChunkable { + if !perfectlyChunkable { chunksArrSize = routinesCount + 1 } @@ -83,7 +80,7 @@ func (service *Service) handleDevices(devices []model.IOSDeviceLastUpdated) { chunks[i] = devices[i*chunkSize : (i+1)*chunkSize] } - if !perfectChunkable { + if !perfectlyChunkable { chunks[routinesCount] = devices[routinesCount*chunkSize:] } @@ -112,14 +109,6 @@ func (service *Service) handleDevicesChunk(devices []model.IOSDeviceLastUpdated) } } -func routineCount(devices []model.IOSDeviceLastUpdated) int { - if len(devices) < MaxRoutineCount { - return len(devices) - } - - return MaxRoutineCount -} - func (service *Service) LogScheduledUpdate(deviceID string) error { scheduleLog := model.IOSScheduledUpdateLog{ DeviceID: deviceID, diff --git a/server/backend/migration/20230826000000.go b/server/backend/migration/20230826000000.go index 2a22bccc..3b8fe0dc 100644 --- a/server/backend/migration/20230826000000.go +++ b/server/backend/migration/20230826000000.go @@ -55,7 +55,7 @@ func (m TumDBMigrator) migrate20230826000000() *gormigrate.Migration { if err := tx.Migrator().DropColumn(&Feedback{}, "AppVersion"); err != nil { return err } - if err := tx.Delete(&model.Crontab{}, "type = 'fileDownload'").Error; err != nil { + if err := tx.Delete(&model.Crontab{}, "type = 'feedbackEmail'").Error; err != nil { return err } return SafeEnumAdd(tx, &model.Crontab{}, "type", "feedbackEmail") diff --git a/server/backend/migration/20231003000000.go b/server/backend/migration/20231003000000.go index 7db90e5f..b15eea95 100644 --- a/server/backend/migration/20231003000000.go +++ b/server/backend/migration/20231003000000.go @@ -136,9 +136,8 @@ func setNameTagOptions(db *gorm.DB) { } var tagsNames multiLanguageNameTags - errjson := json.Unmarshal(file, &tagsNames) - if errjson != nil { - log.WithError(errjson).Error("Error parsing nameTagList to json.") + if err := json.Unmarshal(file, &tagsNames); err != nil { + log.WithError(err).Error("Error parsing nameTagList to json.") } for _, v := range tagsNames.MultiLanguageNameTags { parent := DishNameTagOption{ diff --git a/server/backend/movie.go b/server/backend/movie.go index 7c029804..12981ccd 100644 --- a/server/backend/movie.go +++ b/server/backend/movie.go @@ -35,8 +35,7 @@ func (s *CampusServer) ListMovies(ctx context.Context, req *pb.ListMoviesRequest Actors: movie.Actors, ImdbRating: movie.ImdbRating, Description: movie.Description, - CoverName: movie.File.Name, - CoverPath: movie.File.Path, + CoverUrl: movie.File.FullExternalUrl(), CoverId: movie.File.File, Link: movie.Link, }) diff --git a/server/backend/movie_test.go b/server/backend/movie_test.go index badcaee5..db06e585 100644 --- a/server/backend/movie_test.go +++ b/server/backend/movie_test.go @@ -53,8 +53,7 @@ var ( Actors: "Tom Cruise, Jeremy Renner, Simon Pegg, Paula Patton", ImdbRating: "7.4", Description: "The IMF is shut down when it's implicated in the bombing of the Kremlin, causing Ethan Hunt and his new team to go rogue to clear their organization's name.", - CoverName: "mission_impossible_4.jpg", - CoverPath: "movie/mission_impossible_4.jpg", + CoverUrl: "https://api.tum.app/files/movie/mission_impossible_4.jpg", CoverId: 1, Link: "https://www.imdb.com/title/tt1229238/", } @@ -70,8 +69,7 @@ var ( Actors: "Tom Cruise, Jeremy Renner, Simon Pegg, Rebecca Ferguson", ImdbRating: "7.4", Description: "Ethan and his team take on their most impossible mission yet when they have to eradicate an international rogue organization as highly skilled as they are and committed to destroying the IMF.", - CoverName: "mission_impossible_5.jpg", - CoverPath: "movie/mission_impossible_5.jpg", + CoverUrl: "https://api.tum.app/files/movie/mission_impossible_5.jpg", CoverId: 2, Link: "https://www.imdb.com/title/tt2381249/", } @@ -82,8 +80,8 @@ func (s *MovieSuite) Test_ListMoviesAll() { s.mock.ExpectQuery("SELECT `kino`.`kino`,`kino`.`date`,`kino`.`created`,`kino`.`title`,`kino`.`year`,`kino`.`runtime`,`kino`.`genre`,`kino`.`director`,`kino`.`actors`,`kino`.`rating`,`kino`.`description`,`kino`.`trailer`,`kino`.`cover`,`kino`.`link`,`File`.`file` AS `File__file`,`File`.`name` AS `File__name`,`File`.`path` AS `File__path`,`File`.`downloads` AS `File__downloads`,`File`.`url` AS `File__url`,`File`.`downloaded` AS `File__downloaded` FROM `kino` LEFT JOIN `files` `File` ON `kino`.`cover` = `File`.`file` WHERE kino > ?"). WithArgs(-1). WillReturnRows(sqlmock.NewRows([]string{"kino", "date", "created", "title", "year", "runtime", "genre", "director", "actors", "rating", "description", "trailer", "cover", "link", "File__file", "File__name", "File__path", "File__downloads", "File__url", "File__downloaded"}). - AddRow(movie2.MovieId, movie2.Date.AsTime(), movie2.Created.AsTime(), movie2.Title, movie2.ReleaseYear, movie2.Runtime, movie2.Genre, movie2.Director, movie2.Actors, movie2.ImdbRating, movie2.Description, nil, movie2.CoverId, movie2.Link, movie2.CoverId, movie2.CoverName, movie2.CoverPath, 1, "", 1). - AddRow(movie1.MovieId, movie1.Date.AsTime(), movie1.Created.AsTime(), movie1.Title, movie1.ReleaseYear, movie1.Runtime, movie1.Genre, movie1.Director, movie1.Actors, movie1.ImdbRating, movie1.Description, nil, movie1.CoverId, movie1.Link, movie1.CoverId, movie1.CoverName, movie1.CoverPath, 1, "", 1)) + AddRow(movie2.MovieId, movie2.Date.AsTime(), movie2.Created.AsTime(), movie2.Title, movie2.ReleaseYear, movie2.Runtime, movie2.Genre, movie2.Director, movie2.Actors, movie2.ImdbRating, movie2.Description, nil, movie2.CoverId, movie2.Link, movie2.CoverId, "mission_impossible_5.jpg", "movie/", 1, "", 1). + AddRow(movie1.MovieId, movie1.Date.AsTime(), movie1.Created.AsTime(), movie1.Title, movie1.ReleaseYear, movie1.Runtime, movie1.Genre, movie1.Director, movie1.Actors, movie1.ImdbRating, movie1.Description, nil, movie1.CoverId, movie1.Link, movie1.CoverId, "mission_impossible_4.jpg", "movie/", 1, "", 1)) response, err := server.ListMovies(context.Background(), &pb.ListMoviesRequest{LastId: -1}) require.NoError(s.T(), err) require.Equal(s.T(), &pb.ListMoviesReply{Movies: []*pb.Movie{&movie2, &movie1}}, response) @@ -94,7 +92,7 @@ func (s *MovieSuite) Test_ListMoviesOne() { s.mock.ExpectQuery("SELECT `kino`.`kino`,`kino`.`date`,`kino`.`created`,`kino`.`title`,`kino`.`year`,`kino`.`runtime`,`kino`.`genre`,`kino`.`director`,`kino`.`actors`,`kino`.`rating`,`kino`.`description`,`kino`.`trailer`,`kino`.`cover`,`kino`.`link`,`File`.`file` AS `File__file`,`File`.`name` AS `File__name`,`File`.`path` AS `File__path`,`File`.`downloads` AS `File__downloads`,`File`.`url` AS `File__url`,`File`.`downloaded` AS `File__downloaded` FROM `kino` LEFT JOIN `files` `File` ON `kino`.`cover` = `File`.`file` WHERE kino > ?"). WithArgs(1). WillReturnRows(sqlmock.NewRows([]string{"kino", "date", "created", "title", "year", "runtime", "genre", "director", "actors", "rating", "description", "trailer", "cover", "link", "File__file", "File__name", "File__path", "File__downloads", "File__url", "File__downloaded"}). - AddRow(movie1.MovieId, movie1.Date.AsTime(), movie1.Created.AsTime(), movie1.Title, movie1.ReleaseYear, movie1.Runtime, movie1.Genre, movie1.Director, movie1.Actors, movie1.ImdbRating, movie1.Description, nil, movie1.CoverId, movie1.Link, movie1.CoverId, movie1.CoverName, movie1.CoverPath, 1, "", 1)) + AddRow(movie1.MovieId, movie1.Date.AsTime(), movie1.Created.AsTime(), movie1.Title, movie1.ReleaseYear, movie1.Runtime, movie1.Genre, movie1.Director, movie1.Actors, movie1.ImdbRating, movie1.Description, nil, movie1.CoverId, movie1.Link, movie1.CoverId, "mission_impossible_4.jpg", "movie/", 1, "", 1)) response, err := server.ListMovies(context.Background(), &pb.ListMoviesRequest{LastId: 1}) require.NoError(s.T(), err) require.Equal(s.T(), &pb.ListMoviesReply{Movies: []*pb.Movie{&movie1}}, response) diff --git a/server/backend/news.go b/server/backend/news.go index ee8512e6..f8f29847 100644 --- a/server/backend/news.go +++ b/server/backend/news.go @@ -31,9 +31,9 @@ func (s *CampusServer) ListNewsSources(ctx context.Context, _ *pb.ListNewsSource for _, source := range sources { log.WithField("title", source.Title).Trace("sending news source") resp = append(resp, &pb.NewsSource{ - Source: fmt.Sprintf("%d", source.Source), - Title: source.Title, - Icon: source.File.URL.String, + Source: fmt.Sprintf("%d", source.Source), + Title: source.Title, + IconUrl: source.File.FullExternalUrl(), }) } return &pb.ListNewsSourcesReply{Sources: resp}, nil @@ -45,7 +45,7 @@ func (s *CampusServer) ListNews(ctx context.Context, req *pb.ListNewsRequest) (* } var newsEntries []model.News - tx := s.db.WithContext(ctx).Joins("File") + tx := s.db.WithContext(ctx).Joins("File").Joins("NewsSource").Joins("NewsSource.File") if req.NewsSource != 0 { tx = tx.Where("src = ?", req.NewsSource) } @@ -63,15 +63,21 @@ func (s *CampusServer) ListNews(ctx context.Context, req *pb.ListNewsRequest) (* resp := make([]*pb.News, len(newsEntries)) for i, item := range newsEntries { log.WithField("title", item.Title).Trace("sending news") + imgUrl := "" + if item.File != nil { + imgUrl = item.File.FullExternalUrl() + } resp[i] = &pb.News{ - Id: item.News, - Title: item.Title, - Text: item.Description, - Link: item.Link, - ImageUrl: item.Image.String, - Source: fmt.Sprintf("%d", item.Src), - Created: timestamppb.New(item.Created), - Date: timestamppb.New(item.Date), + Id: item.News, + Title: item.Title, + Text: item.Description, + Link: item.Link, + ImageUrl: imgUrl, + SourceId: fmt.Sprintf("%d", item.NewsSource.Source), + SourceTitle: item.NewsSource.Title, + SourceIconUrl: item.NewsSource.File.FullExternalUrl(), + Created: timestamppb.New(item.Created), + Date: timestamppb.New(item.Date), } } return &pb.ListNewsReply{News: resp}, nil @@ -85,7 +91,7 @@ func (s *CampusServer) ListNewsAlerts(ctx context.Context, req *pb.ListNewsAlert var res []*model.NewsAlert tx := s.db.WithContext(ctx).Joins("File").Where("news_alert.to >= NOW()") if req.LastNewsAlertId != 0 { - tx = tx.Where("news_alert.alert > ?", req.LastNewsAlertId) + tx = tx.Where("news_alert.news_alert > ?", req.LastNewsAlertId) } if err := tx.Find(&res).Error; errors.Is(err, gorm.ErrRecordNotFound) { return nil, status.Error(codes.NotFound, "no news alerts") @@ -97,7 +103,7 @@ func (s *CampusServer) ListNewsAlerts(ctx context.Context, req *pb.ListNewsAlert var alerts []*pb.NewsAlert for _, alert := range res { alerts = append(alerts, &pb.NewsAlert{ - ImageUrl: alert.File.URL.String, + ImageUrl: alert.File.FullExternalUrl(), Link: alert.Link.String, Created: timestamppb.New(alert.Created), From: timestamppb.New(alert.From), diff --git a/server/backend/news_test.go b/server/backend/news_test.go index 71e78a19..62922c7c 100644 --- a/server/backend/news_test.go +++ b/server/backend/news_test.go @@ -56,7 +56,7 @@ func newsFile(id int64) *model.File { return &model.File{ File: id, Name: fmt.Sprintf("src_%d.png", id), - Path: "news/sources", + Path: "news/sources/", Downloads: 1, URL: null.String{}, Downloaded: null.BoolFrom(true), @@ -68,8 +68,8 @@ func source1() *model.NewsSource { Source: 1, Title: "Amazing News 1", URL: null.StringFrom("https://example.com/amazing1"), - FileID: newsFile(2).File, - File: *newsFile(2), + FileID: newsFile(1).File, + File: *newsFile(1), Hook: null.StringFrom(""), } } @@ -88,10 +88,11 @@ func source2() *model.NewsSource { const ExpectedListNewsSourcesQuery = "SELECT `newsSource`.`source`,`newsSource`.`title`,`newsSource`.`url`,`newsSource`.`icon`,`newsSource`.`hook`,`File`.`file` AS `File__file`,`File`.`name` AS `File__name`,`File`.`path` AS `File__path`,`File`.`downloads` AS `File__downloads`,`File`.`url` AS `File__url`,`File`.`downloaded` AS `File__downloaded` FROM `newsSource` LEFT JOIN `files` `File` ON `newsSource`.`icon` = `File`.`file`" func (s *NewsSuite) Test_ListNewsSourcesMultiple() { + s1, s2 := source1(), source2() s.mock.ExpectQuery(regexp.QuoteMeta(ExpectedListNewsSourcesQuery)). WillReturnRows(sqlmock.NewRows([]string{"source", "title", "url", "icon", "hook", "File__file", "File__name", "File__path", "File__downloads", "File__url", "File__downloaded"}). - AddRow(source1().Source, source1().Title, source1().URL, source1().FileID, source1().Hook, source1().File.File, source1().File.Name, source1().File.Path, source1().File.Downloads, source1().File.URL, source1().File.Downloaded). - AddRow(source2().Source, source2().Title, source2().URL, source2().FileID, source2().Hook, source2().File.File, source2().File.Name, source2().File.Path, source2().File.Downloads, source2().File.URL, source2().File.Downloaded)) + AddRow(s1.Source, s1.Title, s1.URL, s1.FileID, s1.Hook, s1.File.File, s1.File.Name, s1.File.Path, s1.File.Downloads, s1.File.URL, s1.File.Downloaded). + AddRow(s2.Source, s2.Title, s2.URL, s2.FileID, s2.Hook, s2.File.File, s2.File.Name, s2.File.Path, s2.File.Downloads, s2.File.URL, s2.File.Downloaded)) meta := metadata.MD{} server := CampusServer{db: s.DB, deviceBuf: s.deviceBuf} @@ -99,8 +100,8 @@ func (s *NewsSuite) Test_ListNewsSourcesMultiple() { require.NoError(s.T(), err) expectedResp := &pb.ListNewsSourcesReply{ Sources: []*pb.NewsSource{ - {Source: fmt.Sprintf("%d", source1().Source), Title: source1().Title, Icon: source1().File.URL.String}, - {Source: fmt.Sprintf("%d", source2().Source), Title: source2().Title, Icon: source2().File.URL.String}, + {Source: fmt.Sprintf("%d", s1.Source), Title: s1.Title, IconUrl: "https://api.tum.app/files/news/sources/src_1.png"}, + {Source: fmt.Sprintf("%d", s2.Source), Title: s2.Title, IconUrl: "https://api.tum.app/files/news/sources/src_2.png"}, }, } require.Equal(s.T(), expectedResp, response) @@ -140,12 +141,12 @@ func (s *NewsSuite) Test_ListNewsSourcesNone() { require.Equal(s.T(), expectedResp, response) } -const ExpectedListNewsQuery = "SELECT `news`.`news`,`news`.`date`,`news`.`created`,`news`.`title`,`news`.`description`,`news`.`src`,`news`.`link`,`news`.`image`,`news`.`file`,`File`.`file` AS `File__file`,`File`.`name` AS `File__name`,`File`.`path` AS `File__path`,`File`.`downloads` AS `File__downloads`,`File`.`url` AS `File__url`,`File`.`downloaded` AS `File__downloaded` FROM `news` LEFT JOIN `files` `File` ON `news`.`file` = `File`.`file`" +const ExpectedListNewsQuery = "SELECT `news`.`news`,`news`.`date`,`news`.`created`,`news`.`title`,`news`.`description`,`news`.`src`,`news`.`link`,`news`.`image`,`news`.`file`,`File`.`file` AS `File__file`,`File`.`name` AS `File__name`,`File`.`path` AS `File__path`,`File`.`downloads` AS `File__downloads`,`File`.`url` AS `File__url`,`File`.`downloaded` AS `File__downloaded`,`NewsSource`.`source` AS `NewsSource__source`,`NewsSource`.`title` AS `NewsSource__title`,`NewsSource`.`url` AS `NewsSource__url`,`NewsSource`.`icon` AS `NewsSource__icon`,`NewsSource`.`hook` AS `NewsSource__hook`,`NewsSource__File`.`file` AS `NewsSource__File__file`,`NewsSource__File`.`name` AS `NewsSource__File__name`,`NewsSource__File`.`path` AS `NewsSource__File__path`,`NewsSource__File`.`downloads` AS `NewsSource__File__downloads`,`NewsSource__File`.`url` AS `NewsSource__File__url`,`NewsSource__File`.`downloaded` AS `NewsSource__File__downloaded` FROM `news` LEFT JOIN `files` `File` ON `news`.`file` = `File`.`file` LEFT JOIN `newsSource` `NewsSource` ON `news`.`src` = `NewsSource`.`source` LEFT JOIN `files` `NewsSource__File` ON `NewsSource`.`icon` = `NewsSource__File`.`file`" func (s *NewsSuite) Test_ListNewsNone_withFilters() { s.mock.ExpectQuery(regexp.QuoteMeta(ExpectedListNewsQuery+" WHERE src = ? AND news > ?")). WithArgs(1, 2). - WillReturnRows(sqlmock.NewRows([]string{"news", "date", "created", "title", "description", "src", "link", "image", "file", "File__file", "File__name", "File__path", "File__downloads", "File__url", "File__downloaded"})) + WillReturnRows(sqlmock.NewRows([]string{"news", "date", "created", "title", "description", "src", "link", "image", "file", "File__file", "File__name", "File__path", "File__downloads", "File__url", "File__downloaded", "source", "NewsSource__source", "NewsSource__title", "NewsSource__url", "NewsSource__icon", "NewsSource__hook", "NewsSource__File__file", "NewsSource__File__name", "NewsSource__File__path", "NewsSource__File__downloads", "NewsSource__File__url", "NewsSource__File__downloaded"})) meta := metadata.NewIncomingContext(context.Background(), metadata.MD{}) server := CampusServer{db: s.DB, deviceBuf: s.deviceBuf} @@ -158,7 +159,7 @@ func (s *NewsSuite) Test_ListNewsNone_withFilters() { } func (s *NewsSuite) Test_ListNewsNone() { s.mock.ExpectQuery(regexp.QuoteMeta(ExpectedListNewsQuery)). - WillReturnRows(sqlmock.NewRows([]string{"news", "date", "created", "title", "description", "src", "link", "image", "file", "File__file", "File__name", "File__path", "File__downloads", "File__url", "File__downloaded"})) + WillReturnRows(sqlmock.NewRows([]string{"news", "date", "created", "title", "description", "src", "link", "image", "file", "File__file", "File__name", "File__path", "File__downloads", "File__url", "File__downloaded", "source", "NewsSource__source", "NewsSource__title", "NewsSource__url", "NewsSource__icon", "NewsSource__hook", "NewsSource__File__file", "NewsSource__File__name", "NewsSource__File__path", "NewsSource__File__downloads", "NewsSource__File__url", "NewsSource__File__downloaded"})) meta := metadata.NewIncomingContext(context.Background(), metadata.MD{}) server := CampusServer{db: s.DB, deviceBuf: s.deviceBuf} @@ -174,8 +175,8 @@ func (s *NewsSuite) Test_ListNewsMultiple() { n2 := news2() s.mock.ExpectQuery(regexp.QuoteMeta(" ")). WillReturnRows(sqlmock.NewRows([]string{"news", "date", "created", "title", "description", "src", "link", "image", "file", "File__file", "File__name", "File__path", "File__downloads", "File__url", "File__downloaded"}). - AddRow(n1.News, n1.Date, n1.Created, n1.Title, n1.Description, n1.Src, n1.Link, n1.Image, n1.FileID, n1.File.File, n1.File.Name, n1.File.Path, n1.File.Downloads, n1.File.URL, n1.File.Downloaded). - AddRow(n2.News, n2.Date, n2.Created, n2.Title, n2.Description, n2.Src, n2.Link, n2.Image, nil, nil, nil, nil, nil, nil, nil)) + AddRow(n1.News, n1.Date, n1.Created, n1.Title, n1.Description, n1.NewsSourceID, n1.Link, n1.Image, n1.FileID, n1.File.File, n1.File.Name, n1.File.Path, n1.File.Downloads, n1.File.URL, n1.File.Downloaded). + AddRow(n2.News, n2.Date, n2.Created, n2.Title, n2.Description, n2.NewsSourceID, n2.Link, n2.Image, nil, nil, nil, nil, nil, nil, nil)) meta := metadata.NewIncomingContext(context.Background(), metadata.MD{}) server := CampusServer{db: s.DB, deviceBuf: s.deviceBuf} @@ -183,8 +184,8 @@ func (s *NewsSuite) Test_ListNewsMultiple() { require.NoError(s.T(), err) expectedResp := &pb.ListNewsReply{ News: []*pb.News{ - {Id: n1.News, Title: n1.Title, Text: n1.Description, Link: n1.Link, ImageUrl: n1.Image.String, Source: fmt.Sprintf("%d", n1.Src), Created: timestamppb.New(n1.Created), Date: timestamppb.New(n1.Date)}, - {Id: n2.News, Title: n2.Title, Text: n2.Description, Link: n2.Link, ImageUrl: n2.Image.String, Source: fmt.Sprintf("%d", n2.Src), Created: timestamppb.New(n2.Created), Date: timestamppb.New(n2.Date)}, + {Id: n1.News, Title: n1.Title, Text: n1.Description, Link: n1.Link, ImageUrl: "https://api.tum.app/files/news/sources/src_1.png", SourceId: fmt.Sprintf("%d", n1.NewsSourceID), SourceIconUrl: n1.NewsSource.File.FullExternalUrl(), SourceTitle: n1.NewsSource.Title, Created: timestamppb.New(n1.Created), Date: timestamppb.New(n1.Date)}, + {Id: n2.News, Title: n2.Title, Text: n2.Description, Link: n2.Link, ImageUrl: "", SourceId: fmt.Sprintf("%d", n2.NewsSourceID), SourceIconUrl: n2.NewsSource.File.FullExternalUrl(), SourceTitle: n2.NewsSource.Title, Created: timestamppb.New(n2.Created), Date: timestamppb.New(n2.Date)}, }, } require.Equal(s.T(), expectedResp, response) @@ -194,7 +195,7 @@ func newsAlertFile(id int64) *model.File { return &model.File{ File: id, Name: fmt.Sprintf("src_%d.png", id), - Path: "news/sources", + Path: "news/sources/", Downloads: 1, URL: null.String{}, Downloaded: null.BoolFrom(true), @@ -216,8 +217,8 @@ func alert1() *model.NewsAlert { func alert2() *model.NewsAlert { return &model.NewsAlert{ NewsAlert: 2, - FileID: newsAlertFile(1).File, - File: *newsAlertFile(1), + FileID: newsAlertFile(2).File, + File: *newsAlertFile(2), Name: null.String{}, Link: null.String{}, Created: time.Time.Add(time.Now(), time.Hour), @@ -246,7 +247,7 @@ func (s *NewsSuite) Test_ListNewsAlertsNone_noFilter() { require.Nil(s.T(), response) } func (s *NewsSuite) Test_ListNewsAlertsNone_Filter() { - s.mock.ExpectQuery(regexp.QuoteMeta(ExpectedListNewsAlertsQuery + " AND news_alert.alert > ?")).WithArgs(42).WillReturnError(gorm.ErrRecordNotFound) + s.mock.ExpectQuery(regexp.QuoteMeta(ExpectedListNewsAlertsQuery + " AND news_alert.news_alert > ?")).WithArgs(42).WillReturnError(gorm.ErrRecordNotFound) server := CampusServer{db: s.DB, deviceBuf: s.deviceBuf} response, err := server.ListNewsAlerts(metadata.NewIncomingContext(context.Background(), metadata.MD{}), &pb.ListNewsAlertsRequest{LastNewsAlertId: 42}) @@ -254,10 +255,9 @@ func (s *NewsSuite) Test_ListNewsAlertsNone_Filter() { require.Nil(s.T(), response) } func (s *NewsSuite) Test_ListNewsAlertsMultiple() { - a1 := alert1() - a2 := alert2() + a1, a2 := alert1(), alert2() s.mock.ExpectQuery(regexp.QuoteMeta(ExpectedListNewsAlertsQuery)). - WillReturnRows(sqlmock.NewRows([]string{"news_alert", "file", "name", "link", "created", "from", "to", "Files__file", "File__name", "File__path", "Files__downloads", "Files__url", "Files__downloaded"}). + WillReturnRows(sqlmock.NewRows([]string{"news_alert", "file", "name", "link", "created", "from", "to", "File__file", "File__name", "File__path", "File__downloads", "File__url", "File__downloaded"}). AddRow(a1.NewsAlert, a1.FileID, a1.Name, a1.Link, a1.Created, a1.From, a1.To, a1.File.File, a1.File.Name, a1.File.Path, a1.File.Downloads, a1.File.URL, a1.File.Downloaded). AddRow(a2.NewsAlert, a2.FileID, a2.Name, a2.Link, a2.Created, a2.From, a2.To, a2.File.File, a2.File.Name, a2.File.Path, a2.File.Downloads, a2.File.URL, a2.File.Downloaded)) @@ -266,8 +266,8 @@ func (s *NewsSuite) Test_ListNewsAlertsMultiple() { require.NoError(s.T(), err) expectedResp := &pb.ListNewsAlertsReply{ Alerts: []*pb.NewsAlert{ - {ImageUrl: a1.File.URL.String, Link: a1.Link.String, Created: timestamppb.New(a1.Created), From: timestamppb.New(a1.From), To: timestamppb.New(a1.To)}, - {ImageUrl: a2.File.URL.String, Link: a2.Link.String, Created: timestamppb.New(a2.Created), From: timestamppb.New(a2.From), To: timestamppb.New(a2.To)}, + {ImageUrl: "https://api.tum.app/files/news/sources/src_1.png", Link: a1.Link.String, Created: timestamppb.New(a1.Created), From: timestamppb.New(a1.From), To: timestamppb.New(a1.To)}, + {ImageUrl: "https://api.tum.app/files/news/sources/src_2.png", Link: a2.Link.String, Created: timestamppb.New(a2.Created), From: timestamppb.New(a2.From), To: timestamppb.New(a2.To)}, }} require.Equal(s.T(), expectedResp, response) } diff --git a/server/backend/rpcserver.go b/server/backend/rpcserver.go index a29ee275..aed6b256 100644 --- a/server/backend/rpcserver.go +++ b/server/backend/rpcserver.go @@ -3,27 +3,16 @@ package backend import ( "context" "errors" - "net" pb "github.com/TUM-Dev/Campus-Backend/server/api/tumdev" "github.com/TUM-Dev/Campus-Backend/server/backend/ios_notifications/apns" "github.com/TUM-Dev/Campus-Backend/server/model" log "github.com/sirupsen/logrus" - "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "gorm.io/gorm" ) -func (s *CampusServer) GRPCServe(l net.Listener) error { - grpcServer := grpc.NewServer() - pb.RegisterCampusServer(grpcServer, s) - if err := grpcServer.Serve(l); err != nil { - log.WithError(err).Fatal("failed to serve") - } - return grpcServer.Serve(l) -} - type CampusServer struct { pb.UnimplementedCampusServer db *gorm.DB diff --git a/server/go.mod b/server/go.mod index b404eec6..2df4c17b 100644 --- a/server/go.mod +++ b/server/go.mod @@ -21,7 +21,7 @@ require ( github.com/sirupsen/logrus v1.9.3 github.com/soheilhy/cmux v0.1.5 github.com/stretchr/testify v1.8.4 - golang.org/x/net v0.15.0 + golang.org/x/net v0.17.0 golang.org/x/sync v0.3.0 google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb google.golang.org/grpc v1.58.2 @@ -52,7 +52,7 @@ require ( github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.11.1 // indirect golang.org/x/image v0.5.0 // indirect - golang.org/x/sys v0.12.0 // indirect + golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect diff --git a/server/go.sum b/server/go.sum index cf378dba..7749353f 100644 --- a/server/go.sum +++ b/server/go.sum @@ -607,8 +607,8 @@ golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfS golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -709,8 +709,8 @@ golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= diff --git a/server/main.go b/server/main.go index a174efd3..08b15d4f 100644 --- a/server/main.go +++ b/server/main.go @@ -4,7 +4,6 @@ import ( "context" "embed" "encoding/json" - "io/fs" "net" "net/http" "net/textproto" @@ -12,6 +11,7 @@ import ( "time" "github.com/TUM-Dev/Campus-Backend/server/utils" + "google.golang.org/grpc/reflection" "github.com/prometheus/client_golang/prometheus/promhttp" @@ -69,12 +69,14 @@ func main() { }) httpMux.Handle("/metrics", promhttp.Handler()) - static, _ := fs.Sub(swagfs, "swagger") - httpMux.Handle("/", http.FileServer(http.FS(static))) + httpMux.Handle("/", http.RedirectHandler("/swagger/", http.StatusTemporaryRedirect)) + httpMux.Handle("/files/", http.StripPrefix("/files/", http.FileServer(http.Dir("/Storage")))) + httpMux.Handle("/swagger/", http.FileServer(http.FS(swagfs))) // Main GRPC Server - grpcServer := grpc.NewServer() + grpcServer := grpc.NewServer(grpc.UnaryInterceptor(UnaryRequestLogger), grpc.StreamInterceptor(StreamRequestLogger)) pb.RegisterCampusServer(grpcServer, campusService) + reflection.Register(grpcServer) // GRPC Gateway for HTTP REST -> GRPC grpcGatewayMux := runtime.NewServeMux(runtime.WithIncomingHeaderMatcher( @@ -112,6 +114,20 @@ func main() { } } +func UnaryRequestLogger(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + start := time.Now() + resp, err := handler(ctx, req) + fields := log.Fields{"elapsed": time.Since(start)} + log.WithContext(ctx).WithFields(fields).WithError(err).Info(info.FullMethod) + return resp, err +} +func StreamRequestLogger(srv any, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + start := time.Now() + err := handler(srv, stream) + log.WithField("elapsed", time.Since(start)).WithError(err).Info(info.FullMethod) + return err +} + // addMethodNameInterceptor adds the method name (e.g. "ListNewsSources") to the metadata as x-campus-method for later use (currently logging the devices api usage) func addMethodNameInterceptor(ctx context.Context, method string, req interface{}, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { ctx = metadata.AppendToOutgoingContext(ctx, "x-campus-method", method) diff --git a/server/model/feedback.go b/server/model/feedback.go index 6a0ea3d1..bbc16d1b 100644 --- a/server/model/feedback.go +++ b/server/model/feedback.go @@ -8,7 +8,7 @@ type Feedback struct { Id int64 `gorm:"column:id;primary_key;AUTO_INCREMENT;type:int;not null;"` ImageCount int32 `gorm:"column:image_count;type:int;not null;"` EmailId null.String `gorm:"column:email_id;type:text;null"` - Receiver null.String `gorm:"column:receiver;type:text;null"` + Recipient null.String `gorm:"column:receiver;type:text;null"` ReplyTo null.String `gorm:"column:reply_to;type:text;null"` Feedback null.String `gorm:"column:feedback;type:text;null"` Latitude null.Float `gorm:"column:latitude;type:float;null;"` diff --git a/server/model/file.go b/server/model/file.go index f9d27ddd..be246062 100644 --- a/server/model/file.go +++ b/server/model/file.go @@ -2,6 +2,7 @@ package model import ( "database/sql" + "fmt" "time" "github.com/gofrs/uuid/v5" @@ -24,3 +25,11 @@ type File struct { URL null.String `gorm:"column:url;default:null;" json:"url"` // URL of the files source (if any) Downloaded null.Bool `gorm:"column:downloaded;type:boolean;default:1;" json:"downloaded"` // true when file is ready to be served, false when still being downloaded } + +// FullExternalUrl is the full url of the file after being downloaded for external use +func (f *File) FullExternalUrl() string { + if !f.Downloaded.Valid || !f.Downloaded.Bool { + return "" + } + return fmt.Sprintf("https://api.tum.app/files/%s%s", f.Path, f.Name) +} diff --git a/server/model/news.go b/server/model/news.go index 4fa2158f..5de7ace7 100755 --- a/server/model/news.go +++ b/server/model/news.go @@ -15,16 +15,17 @@ var ( // News struct is a row record of the news table in the tca database type News struct { - News int64 `gorm:"primary_key;AUTO_INCREMENT;column:news;type:int;"` - Date time.Time `gorm:"column:date;type:datetime;"` - Created time.Time `gorm:"column:created;type:timestamp;default:CURRENT_TIMESTAMP;"` - Title string `gorm:"column:title;type:text;size:255;"` - Description string `gorm:"column:description;type:text;size:65535;"` - Src int64 `gorm:"column:src;type:int;"` - Link string `gorm:"column:link;type:varchar(190);"` - Image null.String `gorm:"column:image;type:text;size:65535;"` - FileID null.Int `gorm:"column:file;type:int;"` - File *File `gorm:"foreignKey:FileID;references:file;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"` + News int64 `gorm:"primary_key;AUTO_INCREMENT;column:news;type:int;"` + Date time.Time `gorm:"column:date;type:datetime;"` + Created time.Time `gorm:"column:created;type:timestamp;default:CURRENT_TIMESTAMP;"` + Title string `gorm:"column:title;type:text;size:255;"` + Description string `gorm:"column:description;type:text;size:65535;"` + NewsSourceID int64 `gorm:"column:src;type:int;"` + NewsSource NewsSource `gorm:"foreignKey:NewsSourceID;references:source;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"` + Link string `gorm:"column:link;type:varchar(190);"` + Image null.String `gorm:"column:image;type:text;size:65535;"` + FileID null.Int `gorm:"column:file;type:int;"` + File *File `gorm:"foreignKey:FileID;references:file;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"` } // TableName sets the insert table name for this struct type