diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..48b8bf90 --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +vendor/ diff --git a/.gitkeep b/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/1 b/1 deleted file mode 100644 index d00491fd..00000000 --- a/1 +++ /dev/null @@ -1 +0,0 @@ -1 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..f7e5fc93 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,43 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM golang:1.20.4-alpine@sha256:0a03b591c358a0bb02e39b93c30e955358dadd18dc507087a3b7f3912c17fe13 as builder +RUN apk add --no-cache ca-certificates git +RUN apk add build-base +WORKDIR /src + +# restore dependencies +COPY go.mod go.sum ./ +RUN go mod download +COPY . . + +# Skaffold passes in debug-oriented compiler flags +ARG SKAFFOLD_GO_GCFLAGS +RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /go/bin/frontend . + +FROM alpine:3.18.0@sha256:02bb6f428431fbc2809c5d1b41eab5a68350194fb508869a33cb1af4444c9b11 as release +RUN apk add --no-cache ca-certificates \ + busybox-extras net-tools bind-tools +WORKDIR /src +COPY --from=builder /go/bin/frontend /src/server +COPY ./templates ./templates +COPY ./static ./static + +# Definition of this variable is used by 'skaffold debug' to identify a golang binary. +# Default behavior - a failure prints a stack trace for the current goroutine. +# See https://golang.org/pkg/runtime/ +ENV GOTRACEBACK=single + +EXPOSE 8080 +ENTRYPOINT ["/src/server"] diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 00000000..9f6b6fdb --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,25 @@ +pipeline { + agent any + + stages { + stage('Build & Tag Docker Image') { + steps { + script { + withDockerRegistry(credentialsId: 'docker-cred', toolName: 'docker') { + sh "docker build -t abhishek0768/frontend:latest ." + } + } + } + } + + stage('Push Docker Image') { + steps { + script { + withDockerRegistry(credentialsId: 'docker-cred', toolName: 'docker') { + sh "docker push abhishek0768/frontend:latest" + } + } + } + } + } +} diff --git a/README.md b/README.md new file mode 100644 index 00000000..4e8f7259 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# frontend + +Run the following command to restore dependencies to `vendor/` directory: + + dep ensure --vendor-only diff --git a/deployment_details.go b/deployment_details.go new file mode 100644 index 00000000..02cabbda --- /dev/null +++ b/deployment_details.go @@ -0,0 +1,64 @@ +package main + +import ( + "net/http" + "os" + "time" + + "cloud.google.com/go/compute/metadata" + "github.com/sirupsen/logrus" +) + +var deploymentDetailsMap map[string]string +var log *logrus.Logger + +func init() { + initializeLogger() + // Use a goroutine to ensure loadDeploymentDetails()'s GCP API + // calls don't block non-GCP deployments. See issue #685. + go loadDeploymentDetails() +} + +func initializeLogger() { + log = logrus.New() + log.Level = logrus.DebugLevel + log.Formatter = &logrus.JSONFormatter{ + FieldMap: logrus.FieldMap{ + logrus.FieldKeyTime: "timestamp", + logrus.FieldKeyLevel: "severity", + logrus.FieldKeyMsg: "message", + }, + TimestampFormat: time.RFC3339Nano, + } + log.Out = os.Stdout +} + +func loadDeploymentDetails() { + deploymentDetailsMap = make(map[string]string) + var metaServerClient = metadata.NewClient(&http.Client{}) + + podHostname, err := os.Hostname() + if err != nil { + log.Error("Failed to fetch the hostname for the Pod", err) + } + + podCluster, err := metaServerClient.InstanceAttributeValue("cluster-name") + if err != nil { + log.Error("Failed to fetch the name of the cluster in which the pod is running", err) + } + + podZone, err := metaServerClient.Zone() + if err != nil { + log.Error("Failed to fetch the Zone of the node where the pod is scheduled", err) + } + + deploymentDetailsMap["HOSTNAME"] = podHostname + deploymentDetailsMap["CLUSTERNAME"] = podCluster + deploymentDetailsMap["ZONE"] = podZone + + log.WithFields(logrus.Fields{ + "cluster": podCluster, + "zone": podZone, + "hostname": podHostname, + }).Debug("Loaded deployment details") +} diff --git a/genproto.sh b/genproto.sh new file mode 100644 index 00000000..6e532fd0 --- /dev/null +++ b/genproto.sh @@ -0,0 +1,24 @@ +#!/bin/bash -eu +# +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START gke_frontend_genproto] + +PATH=$PATH:$GOPATH/bin +protodir=../../protos + +protoc --go_out=plugins=grpc:genproto -I $protodir $protodir/demo.proto + +# [END gke_frontend_genproto] \ No newline at end of file diff --git a/genproto/demo.pb.go b/genproto/demo.pb.go new file mode 100644 index 00000000..05ee3ab7 --- /dev/null +++ b/genproto/demo.pb.go @@ -0,0 +1,2478 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: demo.proto + +package hipstershop + +import ( + fmt "fmt" + proto "github.com/golang/protobuf/proto" + math "math" +) + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type CartItem struct { + ProductId string `protobuf:"bytes,1,opt,name=product_id,json=productId,proto3" json:"product_id,omitempty"` + Quantity int32 `protobuf:"varint,2,opt,name=quantity,proto3" json:"quantity,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *CartItem) Reset() { *m = CartItem{} } +func (m *CartItem) String() string { return proto.CompactTextString(m) } +func (*CartItem) ProtoMessage() {} +func (*CartItem) Descriptor() ([]byte, []int) { + return fileDescriptor_ca53982754088a9d, []int{0} +} + +func (m *CartItem) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CartItem.Unmarshal(m, b) +} +func (m *CartItem) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CartItem.Marshal(b, m, deterministic) +} +func (m *CartItem) XXX_Merge(src proto.Message) { + xxx_messageInfo_CartItem.Merge(m, src) +} +func (m *CartItem) XXX_Size() int { + return xxx_messageInfo_CartItem.Size(m) +} +func (m *CartItem) XXX_DiscardUnknown() { + xxx_messageInfo_CartItem.DiscardUnknown(m) +} + +var xxx_messageInfo_CartItem proto.InternalMessageInfo + +func (m *CartItem) GetProductId() string { + if m != nil { + return m.ProductId + } + return "" +} + +func (m *CartItem) GetQuantity() int32 { + if m != nil { + return m.Quantity + } + return 0 +} + +type AddItemRequest struct { + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Item *CartItem `protobuf:"bytes,2,opt,name=item,proto3" json:"item,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *AddItemRequest) Reset() { *m = AddItemRequest{} } +func (m *AddItemRequest) String() string { return proto.CompactTextString(m) } +func (*AddItemRequest) ProtoMessage() {} +func (*AddItemRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_ca53982754088a9d, []int{1} +} + +func (m *AddItemRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_AddItemRequest.Unmarshal(m, b) +} +func (m *AddItemRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_AddItemRequest.Marshal(b, m, deterministic) +} +func (m *AddItemRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_AddItemRequest.Merge(m, src) +} +func (m *AddItemRequest) XXX_Size() int { + return xxx_messageInfo_AddItemRequest.Size(m) +} +func (m *AddItemRequest) XXX_DiscardUnknown() { + xxx_messageInfo_AddItemRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_AddItemRequest proto.InternalMessageInfo + +func (m *AddItemRequest) GetUserId() string { + if m != nil { + return m.UserId + } + return "" +} + +func (m *AddItemRequest) GetItem() *CartItem { + if m != nil { + return m.Item + } + return nil +} + +type EmptyCartRequest struct { + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *EmptyCartRequest) Reset() { *m = EmptyCartRequest{} } +func (m *EmptyCartRequest) String() string { return proto.CompactTextString(m) } +func (*EmptyCartRequest) ProtoMessage() {} +func (*EmptyCartRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_ca53982754088a9d, []int{2} +} + +func (m *EmptyCartRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_EmptyCartRequest.Unmarshal(m, b) +} +func (m *EmptyCartRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_EmptyCartRequest.Marshal(b, m, deterministic) +} +func (m *EmptyCartRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_EmptyCartRequest.Merge(m, src) +} +func (m *EmptyCartRequest) XXX_Size() int { + return xxx_messageInfo_EmptyCartRequest.Size(m) +} +func (m *EmptyCartRequest) XXX_DiscardUnknown() { + xxx_messageInfo_EmptyCartRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_EmptyCartRequest proto.InternalMessageInfo + +func (m *EmptyCartRequest) GetUserId() string { + if m != nil { + return m.UserId + } + return "" +} + +type GetCartRequest struct { + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetCartRequest) Reset() { *m = GetCartRequest{} } +func (m *GetCartRequest) String() string { return proto.CompactTextString(m) } +func (*GetCartRequest) ProtoMessage() {} +func (*GetCartRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_ca53982754088a9d, []int{3} +} + +func (m *GetCartRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetCartRequest.Unmarshal(m, b) +} +func (m *GetCartRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetCartRequest.Marshal(b, m, deterministic) +} +func (m *GetCartRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetCartRequest.Merge(m, src) +} +func (m *GetCartRequest) XXX_Size() int { + return xxx_messageInfo_GetCartRequest.Size(m) +} +func (m *GetCartRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetCartRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetCartRequest proto.InternalMessageInfo + +func (m *GetCartRequest) GetUserId() string { + if m != nil { + return m.UserId + } + return "" +} + +type Cart struct { + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Items []*CartItem `protobuf:"bytes,2,rep,name=items,proto3" json:"items,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Cart) Reset() { *m = Cart{} } +func (m *Cart) String() string { return proto.CompactTextString(m) } +func (*Cart) ProtoMessage() {} +func (*Cart) Descriptor() ([]byte, []int) { + return fileDescriptor_ca53982754088a9d, []int{4} +} + +func (m *Cart) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Cart.Unmarshal(m, b) +} +func (m *Cart) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Cart.Marshal(b, m, deterministic) +} +func (m *Cart) XXX_Merge(src proto.Message) { + xxx_messageInfo_Cart.Merge(m, src) +} +func (m *Cart) XXX_Size() int { + return xxx_messageInfo_Cart.Size(m) +} +func (m *Cart) XXX_DiscardUnknown() { + xxx_messageInfo_Cart.DiscardUnknown(m) +} + +var xxx_messageInfo_Cart proto.InternalMessageInfo + +func (m *Cart) GetUserId() string { + if m != nil { + return m.UserId + } + return "" +} + +func (m *Cart) GetItems() []*CartItem { + if m != nil { + return m.Items + } + return nil +} + +type Empty struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Empty) Reset() { *m = Empty{} } +func (m *Empty) String() string { return proto.CompactTextString(m) } +func (*Empty) ProtoMessage() {} +func (*Empty) Descriptor() ([]byte, []int) { + return fileDescriptor_ca53982754088a9d, []int{5} +} + +func (m *Empty) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Empty.Unmarshal(m, b) +} +func (m *Empty) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Empty.Marshal(b, m, deterministic) +} +func (m *Empty) XXX_Merge(src proto.Message) { + xxx_messageInfo_Empty.Merge(m, src) +} +func (m *Empty) XXX_Size() int { + return xxx_messageInfo_Empty.Size(m) +} +func (m *Empty) XXX_DiscardUnknown() { + xxx_messageInfo_Empty.DiscardUnknown(m) +} + +var xxx_messageInfo_Empty proto.InternalMessageInfo + +type ListRecommendationsRequest struct { + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + ProductIds []string `protobuf:"bytes,2,rep,name=product_ids,json=productIds,proto3" json:"product_ids,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ListRecommendationsRequest) Reset() { *m = ListRecommendationsRequest{} } +func (m *ListRecommendationsRequest) String() string { return proto.CompactTextString(m) } +func (*ListRecommendationsRequest) ProtoMessage() {} +func (*ListRecommendationsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_ca53982754088a9d, []int{6} +} + +func (m *ListRecommendationsRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ListRecommendationsRequest.Unmarshal(m, b) +} +func (m *ListRecommendationsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ListRecommendationsRequest.Marshal(b, m, deterministic) +} +func (m *ListRecommendationsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_ListRecommendationsRequest.Merge(m, src) +} +func (m *ListRecommendationsRequest) XXX_Size() int { + return xxx_messageInfo_ListRecommendationsRequest.Size(m) +} +func (m *ListRecommendationsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_ListRecommendationsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_ListRecommendationsRequest proto.InternalMessageInfo + +func (m *ListRecommendationsRequest) GetUserId() string { + if m != nil { + return m.UserId + } + return "" +} + +func (m *ListRecommendationsRequest) GetProductIds() []string { + if m != nil { + return m.ProductIds + } + return nil +} + +type ListRecommendationsResponse struct { + ProductIds []string `protobuf:"bytes,1,rep,name=product_ids,json=productIds,proto3" json:"product_ids,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ListRecommendationsResponse) Reset() { *m = ListRecommendationsResponse{} } +func (m *ListRecommendationsResponse) String() string { return proto.CompactTextString(m) } +func (*ListRecommendationsResponse) ProtoMessage() {} +func (*ListRecommendationsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_ca53982754088a9d, []int{7} +} + +func (m *ListRecommendationsResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ListRecommendationsResponse.Unmarshal(m, b) +} +func (m *ListRecommendationsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ListRecommendationsResponse.Marshal(b, m, deterministic) +} +func (m *ListRecommendationsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_ListRecommendationsResponse.Merge(m, src) +} +func (m *ListRecommendationsResponse) XXX_Size() int { + return xxx_messageInfo_ListRecommendationsResponse.Size(m) +} +func (m *ListRecommendationsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_ListRecommendationsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_ListRecommendationsResponse proto.InternalMessageInfo + +func (m *ListRecommendationsResponse) GetProductIds() []string { + if m != nil { + return m.ProductIds + } + return nil +} + +type Product struct { + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` + Picture string `protobuf:"bytes,4,opt,name=picture,proto3" json:"picture,omitempty"` + PriceUsd *Money `protobuf:"bytes,5,opt,name=price_usd,json=priceUsd,proto3" json:"price_usd,omitempty"` + // Categories such as "clothing" or "kitchen" that can be used to look up + // other related products. + Categories []string `protobuf:"bytes,6,rep,name=categories,proto3" json:"categories,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Product) Reset() { *m = Product{} } +func (m *Product) String() string { return proto.CompactTextString(m) } +func (*Product) ProtoMessage() {} +func (*Product) Descriptor() ([]byte, []int) { + return fileDescriptor_ca53982754088a9d, []int{8} +} + +func (m *Product) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Product.Unmarshal(m, b) +} +func (m *Product) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Product.Marshal(b, m, deterministic) +} +func (m *Product) XXX_Merge(src proto.Message) { + xxx_messageInfo_Product.Merge(m, src) +} +func (m *Product) XXX_Size() int { + return xxx_messageInfo_Product.Size(m) +} +func (m *Product) XXX_DiscardUnknown() { + xxx_messageInfo_Product.DiscardUnknown(m) +} + +var xxx_messageInfo_Product proto.InternalMessageInfo + +func (m *Product) GetId() string { + if m != nil { + return m.Id + } + return "" +} + +func (m *Product) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *Product) GetDescription() string { + if m != nil { + return m.Description + } + return "" +} + +func (m *Product) GetPicture() string { + if m != nil { + return m.Picture + } + return "" +} + +func (m *Product) GetPriceUsd() *Money { + if m != nil { + return m.PriceUsd + } + return nil +} + +func (m *Product) GetCategories() []string { + if m != nil { + return m.Categories + } + return nil +} + +type ListProductsResponse struct { + Products []*Product `protobuf:"bytes,1,rep,name=products,proto3" json:"products,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ListProductsResponse) Reset() { *m = ListProductsResponse{} } +func (m *ListProductsResponse) String() string { return proto.CompactTextString(m) } +func (*ListProductsResponse) ProtoMessage() {} +func (*ListProductsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_ca53982754088a9d, []int{9} +} + +func (m *ListProductsResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ListProductsResponse.Unmarshal(m, b) +} +func (m *ListProductsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ListProductsResponse.Marshal(b, m, deterministic) +} +func (m *ListProductsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_ListProductsResponse.Merge(m, src) +} +func (m *ListProductsResponse) XXX_Size() int { + return xxx_messageInfo_ListProductsResponse.Size(m) +} +func (m *ListProductsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_ListProductsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_ListProductsResponse proto.InternalMessageInfo + +func (m *ListProductsResponse) GetProducts() []*Product { + if m != nil { + return m.Products + } + return nil +} + +type GetProductRequest struct { + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetProductRequest) Reset() { *m = GetProductRequest{} } +func (m *GetProductRequest) String() string { return proto.CompactTextString(m) } +func (*GetProductRequest) ProtoMessage() {} +func (*GetProductRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_ca53982754088a9d, []int{10} +} + +func (m *GetProductRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetProductRequest.Unmarshal(m, b) +} +func (m *GetProductRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetProductRequest.Marshal(b, m, deterministic) +} +func (m *GetProductRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetProductRequest.Merge(m, src) +} +func (m *GetProductRequest) XXX_Size() int { + return xxx_messageInfo_GetProductRequest.Size(m) +} +func (m *GetProductRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetProductRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetProductRequest proto.InternalMessageInfo + +func (m *GetProductRequest) GetId() string { + if m != nil { + return m.Id + } + return "" +} + +type SearchProductsRequest struct { + Query string `protobuf:"bytes,1,opt,name=query,proto3" json:"query,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *SearchProductsRequest) Reset() { *m = SearchProductsRequest{} } +func (m *SearchProductsRequest) String() string { return proto.CompactTextString(m) } +func (*SearchProductsRequest) ProtoMessage() {} +func (*SearchProductsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_ca53982754088a9d, []int{11} +} + +func (m *SearchProductsRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SearchProductsRequest.Unmarshal(m, b) +} +func (m *SearchProductsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SearchProductsRequest.Marshal(b, m, deterministic) +} +func (m *SearchProductsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_SearchProductsRequest.Merge(m, src) +} +func (m *SearchProductsRequest) XXX_Size() int { + return xxx_messageInfo_SearchProductsRequest.Size(m) +} +func (m *SearchProductsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_SearchProductsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_SearchProductsRequest proto.InternalMessageInfo + +func (m *SearchProductsRequest) GetQuery() string { + if m != nil { + return m.Query + } + return "" +} + +type SearchProductsResponse struct { + Results []*Product `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *SearchProductsResponse) Reset() { *m = SearchProductsResponse{} } +func (m *SearchProductsResponse) String() string { return proto.CompactTextString(m) } +func (*SearchProductsResponse) ProtoMessage() {} +func (*SearchProductsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_ca53982754088a9d, []int{12} +} + +func (m *SearchProductsResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SearchProductsResponse.Unmarshal(m, b) +} +func (m *SearchProductsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SearchProductsResponse.Marshal(b, m, deterministic) +} +func (m *SearchProductsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_SearchProductsResponse.Merge(m, src) +} +func (m *SearchProductsResponse) XXX_Size() int { + return xxx_messageInfo_SearchProductsResponse.Size(m) +} +func (m *SearchProductsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_SearchProductsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_SearchProductsResponse proto.InternalMessageInfo + +func (m *SearchProductsResponse) GetResults() []*Product { + if m != nil { + return m.Results + } + return nil +} + +type GetQuoteRequest struct { + Address *Address `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + Items []*CartItem `protobuf:"bytes,2,rep,name=items,proto3" json:"items,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetQuoteRequest) Reset() { *m = GetQuoteRequest{} } +func (m *GetQuoteRequest) String() string { return proto.CompactTextString(m) } +func (*GetQuoteRequest) ProtoMessage() {} +func (*GetQuoteRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_ca53982754088a9d, []int{13} +} + +func (m *GetQuoteRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetQuoteRequest.Unmarshal(m, b) +} +func (m *GetQuoteRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetQuoteRequest.Marshal(b, m, deterministic) +} +func (m *GetQuoteRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetQuoteRequest.Merge(m, src) +} +func (m *GetQuoteRequest) XXX_Size() int { + return xxx_messageInfo_GetQuoteRequest.Size(m) +} +func (m *GetQuoteRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetQuoteRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetQuoteRequest proto.InternalMessageInfo + +func (m *GetQuoteRequest) GetAddress() *Address { + if m != nil { + return m.Address + } + return nil +} + +func (m *GetQuoteRequest) GetItems() []*CartItem { + if m != nil { + return m.Items + } + return nil +} + +type GetQuoteResponse struct { + CostUsd *Money `protobuf:"bytes,1,opt,name=cost_usd,json=costUsd,proto3" json:"cost_usd,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetQuoteResponse) Reset() { *m = GetQuoteResponse{} } +func (m *GetQuoteResponse) String() string { return proto.CompactTextString(m) } +func (*GetQuoteResponse) ProtoMessage() {} +func (*GetQuoteResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_ca53982754088a9d, []int{14} +} + +func (m *GetQuoteResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetQuoteResponse.Unmarshal(m, b) +} +func (m *GetQuoteResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetQuoteResponse.Marshal(b, m, deterministic) +} +func (m *GetQuoteResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetQuoteResponse.Merge(m, src) +} +func (m *GetQuoteResponse) XXX_Size() int { + return xxx_messageInfo_GetQuoteResponse.Size(m) +} +func (m *GetQuoteResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetQuoteResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetQuoteResponse proto.InternalMessageInfo + +func (m *GetQuoteResponse) GetCostUsd() *Money { + if m != nil { + return m.CostUsd + } + return nil +} + +type ShipOrderRequest struct { + Address *Address `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + Items []*CartItem `protobuf:"bytes,2,rep,name=items,proto3" json:"items,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ShipOrderRequest) Reset() { *m = ShipOrderRequest{} } +func (m *ShipOrderRequest) String() string { return proto.CompactTextString(m) } +func (*ShipOrderRequest) ProtoMessage() {} +func (*ShipOrderRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_ca53982754088a9d, []int{15} +} + +func (m *ShipOrderRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ShipOrderRequest.Unmarshal(m, b) +} +func (m *ShipOrderRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ShipOrderRequest.Marshal(b, m, deterministic) +} +func (m *ShipOrderRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_ShipOrderRequest.Merge(m, src) +} +func (m *ShipOrderRequest) XXX_Size() int { + return xxx_messageInfo_ShipOrderRequest.Size(m) +} +func (m *ShipOrderRequest) XXX_DiscardUnknown() { + xxx_messageInfo_ShipOrderRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_ShipOrderRequest proto.InternalMessageInfo + +func (m *ShipOrderRequest) GetAddress() *Address { + if m != nil { + return m.Address + } + return nil +} + +func (m *ShipOrderRequest) GetItems() []*CartItem { + if m != nil { + return m.Items + } + return nil +} + +type ShipOrderResponse struct { + TrackingId string `protobuf:"bytes,1,opt,name=tracking_id,json=trackingId,proto3" json:"tracking_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ShipOrderResponse) Reset() { *m = ShipOrderResponse{} } +func (m *ShipOrderResponse) String() string { return proto.CompactTextString(m) } +func (*ShipOrderResponse) ProtoMessage() {} +func (*ShipOrderResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_ca53982754088a9d, []int{16} +} + +func (m *ShipOrderResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ShipOrderResponse.Unmarshal(m, b) +} +func (m *ShipOrderResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ShipOrderResponse.Marshal(b, m, deterministic) +} +func (m *ShipOrderResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_ShipOrderResponse.Merge(m, src) +} +func (m *ShipOrderResponse) XXX_Size() int { + return xxx_messageInfo_ShipOrderResponse.Size(m) +} +func (m *ShipOrderResponse) XXX_DiscardUnknown() { + xxx_messageInfo_ShipOrderResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_ShipOrderResponse proto.InternalMessageInfo + +func (m *ShipOrderResponse) GetTrackingId() string { + if m != nil { + return m.TrackingId + } + return "" +} + +type Address struct { + StreetAddress string `protobuf:"bytes,1,opt,name=street_address,json=streetAddress,proto3" json:"street_address,omitempty"` + City string `protobuf:"bytes,2,opt,name=city,proto3" json:"city,omitempty"` + State string `protobuf:"bytes,3,opt,name=state,proto3" json:"state,omitempty"` + Country string `protobuf:"bytes,4,opt,name=country,proto3" json:"country,omitempty"` + ZipCode int32 `protobuf:"varint,5,opt,name=zip_code,json=zipCode,proto3" json:"zip_code,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Address) Reset() { *m = Address{} } +func (m *Address) String() string { return proto.CompactTextString(m) } +func (*Address) ProtoMessage() {} +func (*Address) Descriptor() ([]byte, []int) { + return fileDescriptor_ca53982754088a9d, []int{17} +} + +func (m *Address) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Address.Unmarshal(m, b) +} +func (m *Address) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Address.Marshal(b, m, deterministic) +} +func (m *Address) XXX_Merge(src proto.Message) { + xxx_messageInfo_Address.Merge(m, src) +} +func (m *Address) XXX_Size() int { + return xxx_messageInfo_Address.Size(m) +} +func (m *Address) XXX_DiscardUnknown() { + xxx_messageInfo_Address.DiscardUnknown(m) +} + +var xxx_messageInfo_Address proto.InternalMessageInfo + +func (m *Address) GetStreetAddress() string { + if m != nil { + return m.StreetAddress + } + return "" +} + +func (m *Address) GetCity() string { + if m != nil { + return m.City + } + return "" +} + +func (m *Address) GetState() string { + if m != nil { + return m.State + } + return "" +} + +func (m *Address) GetCountry() string { + if m != nil { + return m.Country + } + return "" +} + +func (m *Address) GetZipCode() int32 { + if m != nil { + return m.ZipCode + } + return 0 +} + +// Represents an amount of money with its currency type. +type Money struct { + // The 3-letter currency code defined in ISO 4217. + CurrencyCode string `protobuf:"bytes,1,opt,name=currency_code,json=currencyCode,proto3" json:"currency_code,omitempty"` + // The whole units of the amount. + // For example if `currencyCode` is `"USD"`, then 1 unit is one US dollar. + Units int64 `protobuf:"varint,2,opt,name=units,proto3" json:"units,omitempty"` + // Number of nano (10^-9) units of the amount. + // The value must be between -999,999,999 and +999,999,999 inclusive. + // If `units` is positive, `nanos` must be positive or zero. + // If `units` is zero, `nanos` can be positive, zero, or negative. + // If `units` is negative, `nanos` must be negative or zero. + // For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000. + Nanos int32 `protobuf:"varint,3,opt,name=nanos,proto3" json:"nanos,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Money) Reset() { *m = Money{} } +func (m *Money) String() string { return proto.CompactTextString(m) } +func (*Money) ProtoMessage() {} +func (*Money) Descriptor() ([]byte, []int) { + return fileDescriptor_ca53982754088a9d, []int{18} +} + +func (m *Money) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Money.Unmarshal(m, b) +} +func (m *Money) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Money.Marshal(b, m, deterministic) +} +func (m *Money) XXX_Merge(src proto.Message) { + xxx_messageInfo_Money.Merge(m, src) +} +func (m *Money) XXX_Size() int { + return xxx_messageInfo_Money.Size(m) +} +func (m *Money) XXX_DiscardUnknown() { + xxx_messageInfo_Money.DiscardUnknown(m) +} + +var xxx_messageInfo_Money proto.InternalMessageInfo + +func (m *Money) GetCurrencyCode() string { + if m != nil { + return m.CurrencyCode + } + return "" +} + +func (m *Money) GetUnits() int64 { + if m != nil { + return m.Units + } + return 0 +} + +func (m *Money) GetNanos() int32 { + if m != nil { + return m.Nanos + } + return 0 +} + +type GetSupportedCurrenciesResponse struct { + // The 3-letter currency code defined in ISO 4217. + CurrencyCodes []string `protobuf:"bytes,1,rep,name=currency_codes,json=currencyCodes,proto3" json:"currency_codes,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetSupportedCurrenciesResponse) Reset() { *m = GetSupportedCurrenciesResponse{} } +func (m *GetSupportedCurrenciesResponse) String() string { return proto.CompactTextString(m) } +func (*GetSupportedCurrenciesResponse) ProtoMessage() {} +func (*GetSupportedCurrenciesResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_ca53982754088a9d, []int{19} +} + +func (m *GetSupportedCurrenciesResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetSupportedCurrenciesResponse.Unmarshal(m, b) +} +func (m *GetSupportedCurrenciesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetSupportedCurrenciesResponse.Marshal(b, m, deterministic) +} +func (m *GetSupportedCurrenciesResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetSupportedCurrenciesResponse.Merge(m, src) +} +func (m *GetSupportedCurrenciesResponse) XXX_Size() int { + return xxx_messageInfo_GetSupportedCurrenciesResponse.Size(m) +} +func (m *GetSupportedCurrenciesResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetSupportedCurrenciesResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetSupportedCurrenciesResponse proto.InternalMessageInfo + +func (m *GetSupportedCurrenciesResponse) GetCurrencyCodes() []string { + if m != nil { + return m.CurrencyCodes + } + return nil +} + +type CurrencyConversionRequest struct { + From *Money `protobuf:"bytes,1,opt,name=from,proto3" json:"from,omitempty"` + // The 3-letter currency code defined in ISO 4217. + ToCode string `protobuf:"bytes,2,opt,name=to_code,json=toCode,proto3" json:"to_code,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *CurrencyConversionRequest) Reset() { *m = CurrencyConversionRequest{} } +func (m *CurrencyConversionRequest) String() string { return proto.CompactTextString(m) } +func (*CurrencyConversionRequest) ProtoMessage() {} +func (*CurrencyConversionRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_ca53982754088a9d, []int{20} +} + +func (m *CurrencyConversionRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CurrencyConversionRequest.Unmarshal(m, b) +} +func (m *CurrencyConversionRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CurrencyConversionRequest.Marshal(b, m, deterministic) +} +func (m *CurrencyConversionRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_CurrencyConversionRequest.Merge(m, src) +} +func (m *CurrencyConversionRequest) XXX_Size() int { + return xxx_messageInfo_CurrencyConversionRequest.Size(m) +} +func (m *CurrencyConversionRequest) XXX_DiscardUnknown() { + xxx_messageInfo_CurrencyConversionRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_CurrencyConversionRequest proto.InternalMessageInfo + +func (m *CurrencyConversionRequest) GetFrom() *Money { + if m != nil { + return m.From + } + return nil +} + +func (m *CurrencyConversionRequest) GetToCode() string { + if m != nil { + return m.ToCode + } + return "" +} + +type CreditCardInfo struct { + CreditCardNumber string `protobuf:"bytes,1,opt,name=credit_card_number,json=creditCardNumber,proto3" json:"credit_card_number,omitempty"` + CreditCardCvv int32 `protobuf:"varint,2,opt,name=credit_card_cvv,json=creditCardCvv,proto3" json:"credit_card_cvv,omitempty"` + CreditCardExpirationYear int32 `protobuf:"varint,3,opt,name=credit_card_expiration_year,json=creditCardExpirationYear,proto3" json:"credit_card_expiration_year,omitempty"` + CreditCardExpirationMonth int32 `protobuf:"varint,4,opt,name=credit_card_expiration_month,json=creditCardExpirationMonth,proto3" json:"credit_card_expiration_month,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *CreditCardInfo) Reset() { *m = CreditCardInfo{} } +func (m *CreditCardInfo) String() string { return proto.CompactTextString(m) } +func (*CreditCardInfo) ProtoMessage() {} +func (*CreditCardInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_ca53982754088a9d, []int{21} +} + +func (m *CreditCardInfo) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CreditCardInfo.Unmarshal(m, b) +} +func (m *CreditCardInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CreditCardInfo.Marshal(b, m, deterministic) +} +func (m *CreditCardInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_CreditCardInfo.Merge(m, src) +} +func (m *CreditCardInfo) XXX_Size() int { + return xxx_messageInfo_CreditCardInfo.Size(m) +} +func (m *CreditCardInfo) XXX_DiscardUnknown() { + xxx_messageInfo_CreditCardInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_CreditCardInfo proto.InternalMessageInfo + +func (m *CreditCardInfo) GetCreditCardNumber() string { + if m != nil { + return m.CreditCardNumber + } + return "" +} + +func (m *CreditCardInfo) GetCreditCardCvv() int32 { + if m != nil { + return m.CreditCardCvv + } + return 0 +} + +func (m *CreditCardInfo) GetCreditCardExpirationYear() int32 { + if m != nil { + return m.CreditCardExpirationYear + } + return 0 +} + +func (m *CreditCardInfo) GetCreditCardExpirationMonth() int32 { + if m != nil { + return m.CreditCardExpirationMonth + } + return 0 +} + +type ChargeRequest struct { + Amount *Money `protobuf:"bytes,1,opt,name=amount,proto3" json:"amount,omitempty"` + CreditCard *CreditCardInfo `protobuf:"bytes,2,opt,name=credit_card,json=creditCard,proto3" json:"credit_card,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ChargeRequest) Reset() { *m = ChargeRequest{} } +func (m *ChargeRequest) String() string { return proto.CompactTextString(m) } +func (*ChargeRequest) ProtoMessage() {} +func (*ChargeRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_ca53982754088a9d, []int{22} +} + +func (m *ChargeRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ChargeRequest.Unmarshal(m, b) +} +func (m *ChargeRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ChargeRequest.Marshal(b, m, deterministic) +} +func (m *ChargeRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_ChargeRequest.Merge(m, src) +} +func (m *ChargeRequest) XXX_Size() int { + return xxx_messageInfo_ChargeRequest.Size(m) +} +func (m *ChargeRequest) XXX_DiscardUnknown() { + xxx_messageInfo_ChargeRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_ChargeRequest proto.InternalMessageInfo + +func (m *ChargeRequest) GetAmount() *Money { + if m != nil { + return m.Amount + } + return nil +} + +func (m *ChargeRequest) GetCreditCard() *CreditCardInfo { + if m != nil { + return m.CreditCard + } + return nil +} + +type ChargeResponse struct { + TransactionId string `protobuf:"bytes,1,opt,name=transaction_id,json=transactionId,proto3" json:"transaction_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ChargeResponse) Reset() { *m = ChargeResponse{} } +func (m *ChargeResponse) String() string { return proto.CompactTextString(m) } +func (*ChargeResponse) ProtoMessage() {} +func (*ChargeResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_ca53982754088a9d, []int{23} +} + +func (m *ChargeResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ChargeResponse.Unmarshal(m, b) +} +func (m *ChargeResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ChargeResponse.Marshal(b, m, deterministic) +} +func (m *ChargeResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_ChargeResponse.Merge(m, src) +} +func (m *ChargeResponse) XXX_Size() int { + return xxx_messageInfo_ChargeResponse.Size(m) +} +func (m *ChargeResponse) XXX_DiscardUnknown() { + xxx_messageInfo_ChargeResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_ChargeResponse proto.InternalMessageInfo + +func (m *ChargeResponse) GetTransactionId() string { + if m != nil { + return m.TransactionId + } + return "" +} + +type OrderItem struct { + Item *CartItem `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` + Cost *Money `protobuf:"bytes,2,opt,name=cost,proto3" json:"cost,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *OrderItem) Reset() { *m = OrderItem{} } +func (m *OrderItem) String() string { return proto.CompactTextString(m) } +func (*OrderItem) ProtoMessage() {} +func (*OrderItem) Descriptor() ([]byte, []int) { + return fileDescriptor_ca53982754088a9d, []int{24} +} + +func (m *OrderItem) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_OrderItem.Unmarshal(m, b) +} +func (m *OrderItem) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_OrderItem.Marshal(b, m, deterministic) +} +func (m *OrderItem) XXX_Merge(src proto.Message) { + xxx_messageInfo_OrderItem.Merge(m, src) +} +func (m *OrderItem) XXX_Size() int { + return xxx_messageInfo_OrderItem.Size(m) +} +func (m *OrderItem) XXX_DiscardUnknown() { + xxx_messageInfo_OrderItem.DiscardUnknown(m) +} + +var xxx_messageInfo_OrderItem proto.InternalMessageInfo + +func (m *OrderItem) GetItem() *CartItem { + if m != nil { + return m.Item + } + return nil +} + +func (m *OrderItem) GetCost() *Money { + if m != nil { + return m.Cost + } + return nil +} + +type OrderResult struct { + OrderId string `protobuf:"bytes,1,opt,name=order_id,json=orderId,proto3" json:"order_id,omitempty"` + ShippingTrackingId string `protobuf:"bytes,2,opt,name=shipping_tracking_id,json=shippingTrackingId,proto3" json:"shipping_tracking_id,omitempty"` + ShippingCost *Money `protobuf:"bytes,3,opt,name=shipping_cost,json=shippingCost,proto3" json:"shipping_cost,omitempty"` + ShippingAddress *Address `protobuf:"bytes,4,opt,name=shipping_address,json=shippingAddress,proto3" json:"shipping_address,omitempty"` + Items []*OrderItem `protobuf:"bytes,5,rep,name=items,proto3" json:"items,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *OrderResult) Reset() { *m = OrderResult{} } +func (m *OrderResult) String() string { return proto.CompactTextString(m) } +func (*OrderResult) ProtoMessage() {} +func (*OrderResult) Descriptor() ([]byte, []int) { + return fileDescriptor_ca53982754088a9d, []int{25} +} + +func (m *OrderResult) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_OrderResult.Unmarshal(m, b) +} +func (m *OrderResult) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_OrderResult.Marshal(b, m, deterministic) +} +func (m *OrderResult) XXX_Merge(src proto.Message) { + xxx_messageInfo_OrderResult.Merge(m, src) +} +func (m *OrderResult) XXX_Size() int { + return xxx_messageInfo_OrderResult.Size(m) +} +func (m *OrderResult) XXX_DiscardUnknown() { + xxx_messageInfo_OrderResult.DiscardUnknown(m) +} + +var xxx_messageInfo_OrderResult proto.InternalMessageInfo + +func (m *OrderResult) GetOrderId() string { + if m != nil { + return m.OrderId + } + return "" +} + +func (m *OrderResult) GetShippingTrackingId() string { + if m != nil { + return m.ShippingTrackingId + } + return "" +} + +func (m *OrderResult) GetShippingCost() *Money { + if m != nil { + return m.ShippingCost + } + return nil +} + +func (m *OrderResult) GetShippingAddress() *Address { + if m != nil { + return m.ShippingAddress + } + return nil +} + +func (m *OrderResult) GetItems() []*OrderItem { + if m != nil { + return m.Items + } + return nil +} + +type SendOrderConfirmationRequest struct { + Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` + Order *OrderResult `protobuf:"bytes,2,opt,name=order,proto3" json:"order,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *SendOrderConfirmationRequest) Reset() { *m = SendOrderConfirmationRequest{} } +func (m *SendOrderConfirmationRequest) String() string { return proto.CompactTextString(m) } +func (*SendOrderConfirmationRequest) ProtoMessage() {} +func (*SendOrderConfirmationRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_ca53982754088a9d, []int{26} +} + +func (m *SendOrderConfirmationRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SendOrderConfirmationRequest.Unmarshal(m, b) +} +func (m *SendOrderConfirmationRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SendOrderConfirmationRequest.Marshal(b, m, deterministic) +} +func (m *SendOrderConfirmationRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_SendOrderConfirmationRequest.Merge(m, src) +} +func (m *SendOrderConfirmationRequest) XXX_Size() int { + return xxx_messageInfo_SendOrderConfirmationRequest.Size(m) +} +func (m *SendOrderConfirmationRequest) XXX_DiscardUnknown() { + xxx_messageInfo_SendOrderConfirmationRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_SendOrderConfirmationRequest proto.InternalMessageInfo + +func (m *SendOrderConfirmationRequest) GetEmail() string { + if m != nil { + return m.Email + } + return "" +} + +func (m *SendOrderConfirmationRequest) GetOrder() *OrderResult { + if m != nil { + return m.Order + } + return nil +} + +type PlaceOrderRequest struct { + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + UserCurrency string `protobuf:"bytes,2,opt,name=user_currency,json=userCurrency,proto3" json:"user_currency,omitempty"` + Address *Address `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty"` + Email string `protobuf:"bytes,5,opt,name=email,proto3" json:"email,omitempty"` + CreditCard *CreditCardInfo `protobuf:"bytes,6,opt,name=credit_card,json=creditCard,proto3" json:"credit_card,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *PlaceOrderRequest) Reset() { *m = PlaceOrderRequest{} } +func (m *PlaceOrderRequest) String() string { return proto.CompactTextString(m) } +func (*PlaceOrderRequest) ProtoMessage() {} +func (*PlaceOrderRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_ca53982754088a9d, []int{27} +} + +func (m *PlaceOrderRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_PlaceOrderRequest.Unmarshal(m, b) +} +func (m *PlaceOrderRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_PlaceOrderRequest.Marshal(b, m, deterministic) +} +func (m *PlaceOrderRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_PlaceOrderRequest.Merge(m, src) +} +func (m *PlaceOrderRequest) XXX_Size() int { + return xxx_messageInfo_PlaceOrderRequest.Size(m) +} +func (m *PlaceOrderRequest) XXX_DiscardUnknown() { + xxx_messageInfo_PlaceOrderRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_PlaceOrderRequest proto.InternalMessageInfo + +func (m *PlaceOrderRequest) GetUserId() string { + if m != nil { + return m.UserId + } + return "" +} + +func (m *PlaceOrderRequest) GetUserCurrency() string { + if m != nil { + return m.UserCurrency + } + return "" +} + +func (m *PlaceOrderRequest) GetAddress() *Address { + if m != nil { + return m.Address + } + return nil +} + +func (m *PlaceOrderRequest) GetEmail() string { + if m != nil { + return m.Email + } + return "" +} + +func (m *PlaceOrderRequest) GetCreditCard() *CreditCardInfo { + if m != nil { + return m.CreditCard + } + return nil +} + +type PlaceOrderResponse struct { + Order *OrderResult `protobuf:"bytes,1,opt,name=order,proto3" json:"order,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *PlaceOrderResponse) Reset() { *m = PlaceOrderResponse{} } +func (m *PlaceOrderResponse) String() string { return proto.CompactTextString(m) } +func (*PlaceOrderResponse) ProtoMessage() {} +func (*PlaceOrderResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_ca53982754088a9d, []int{28} +} + +func (m *PlaceOrderResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_PlaceOrderResponse.Unmarshal(m, b) +} +func (m *PlaceOrderResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_PlaceOrderResponse.Marshal(b, m, deterministic) +} +func (m *PlaceOrderResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_PlaceOrderResponse.Merge(m, src) +} +func (m *PlaceOrderResponse) XXX_Size() int { + return xxx_messageInfo_PlaceOrderResponse.Size(m) +} +func (m *PlaceOrderResponse) XXX_DiscardUnknown() { + xxx_messageInfo_PlaceOrderResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_PlaceOrderResponse proto.InternalMessageInfo + +func (m *PlaceOrderResponse) GetOrder() *OrderResult { + if m != nil { + return m.Order + } + return nil +} + +type AdRequest struct { + // List of important key words from the current page describing the context. + ContextKeys []string `protobuf:"bytes,1,rep,name=context_keys,json=contextKeys,proto3" json:"context_keys,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *AdRequest) Reset() { *m = AdRequest{} } +func (m *AdRequest) String() string { return proto.CompactTextString(m) } +func (*AdRequest) ProtoMessage() {} +func (*AdRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_ca53982754088a9d, []int{29} +} + +func (m *AdRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_AdRequest.Unmarshal(m, b) +} +func (m *AdRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_AdRequest.Marshal(b, m, deterministic) +} +func (m *AdRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_AdRequest.Merge(m, src) +} +func (m *AdRequest) XXX_Size() int { + return xxx_messageInfo_AdRequest.Size(m) +} +func (m *AdRequest) XXX_DiscardUnknown() { + xxx_messageInfo_AdRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_AdRequest proto.InternalMessageInfo + +func (m *AdRequest) GetContextKeys() []string { + if m != nil { + return m.ContextKeys + } + return nil +} + +type AdResponse struct { + Ads []*Ad `protobuf:"bytes,1,rep,name=ads,proto3" json:"ads,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *AdResponse) Reset() { *m = AdResponse{} } +func (m *AdResponse) String() string { return proto.CompactTextString(m) } +func (*AdResponse) ProtoMessage() {} +func (*AdResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_ca53982754088a9d, []int{30} +} + +func (m *AdResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_AdResponse.Unmarshal(m, b) +} +func (m *AdResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_AdResponse.Marshal(b, m, deterministic) +} +func (m *AdResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_AdResponse.Merge(m, src) +} +func (m *AdResponse) XXX_Size() int { + return xxx_messageInfo_AdResponse.Size(m) +} +func (m *AdResponse) XXX_DiscardUnknown() { + xxx_messageInfo_AdResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_AdResponse proto.InternalMessageInfo + +func (m *AdResponse) GetAds() []*Ad { + if m != nil { + return m.Ads + } + return nil +} + +type Ad struct { + // url to redirect to when an ad is clicked. + RedirectUrl string `protobuf:"bytes,1,opt,name=redirect_url,json=redirectUrl,proto3" json:"redirect_url,omitempty"` + // short advertisement text to display. + Text string `protobuf:"bytes,2,opt,name=text,proto3" json:"text,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Ad) Reset() { *m = Ad{} } +func (m *Ad) String() string { return proto.CompactTextString(m) } +func (*Ad) ProtoMessage() {} +func (*Ad) Descriptor() ([]byte, []int) { + return fileDescriptor_ca53982754088a9d, []int{31} +} + +func (m *Ad) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Ad.Unmarshal(m, b) +} +func (m *Ad) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Ad.Marshal(b, m, deterministic) +} +func (m *Ad) XXX_Merge(src proto.Message) { + xxx_messageInfo_Ad.Merge(m, src) +} +func (m *Ad) XXX_Size() int { + return xxx_messageInfo_Ad.Size(m) +} +func (m *Ad) XXX_DiscardUnknown() { + xxx_messageInfo_Ad.DiscardUnknown(m) +} + +var xxx_messageInfo_Ad proto.InternalMessageInfo + +func (m *Ad) GetRedirectUrl() string { + if m != nil { + return m.RedirectUrl + } + return "" +} + +func (m *Ad) GetText() string { + if m != nil { + return m.Text + } + return "" +} + +func init() { + proto.RegisterType((*CartItem)(nil), "hipstershop.CartItem") + proto.RegisterType((*AddItemRequest)(nil), "hipstershop.AddItemRequest") + proto.RegisterType((*EmptyCartRequest)(nil), "hipstershop.EmptyCartRequest") + proto.RegisterType((*GetCartRequest)(nil), "hipstershop.GetCartRequest") + proto.RegisterType((*Cart)(nil), "hipstershop.Cart") + proto.RegisterType((*Empty)(nil), "hipstershop.Empty") + proto.RegisterType((*ListRecommendationsRequest)(nil), "hipstershop.ListRecommendationsRequest") + proto.RegisterType((*ListRecommendationsResponse)(nil), "hipstershop.ListRecommendationsResponse") + proto.RegisterType((*Product)(nil), "hipstershop.Product") + proto.RegisterType((*ListProductsResponse)(nil), "hipstershop.ListProductsResponse") + proto.RegisterType((*GetProductRequest)(nil), "hipstershop.GetProductRequest") + proto.RegisterType((*SearchProductsRequest)(nil), "hipstershop.SearchProductsRequest") + proto.RegisterType((*SearchProductsResponse)(nil), "hipstershop.SearchProductsResponse") + proto.RegisterType((*GetQuoteRequest)(nil), "hipstershop.GetQuoteRequest") + proto.RegisterType((*GetQuoteResponse)(nil), "hipstershop.GetQuoteResponse") + proto.RegisterType((*ShipOrderRequest)(nil), "hipstershop.ShipOrderRequest") + proto.RegisterType((*ShipOrderResponse)(nil), "hipstershop.ShipOrderResponse") + proto.RegisterType((*Address)(nil), "hipstershop.Address") + proto.RegisterType((*Money)(nil), "hipstershop.Money") + proto.RegisterType((*GetSupportedCurrenciesResponse)(nil), "hipstershop.GetSupportedCurrenciesResponse") + proto.RegisterType((*CurrencyConversionRequest)(nil), "hipstershop.CurrencyConversionRequest") + proto.RegisterType((*CreditCardInfo)(nil), "hipstershop.CreditCardInfo") + proto.RegisterType((*ChargeRequest)(nil), "hipstershop.ChargeRequest") + proto.RegisterType((*ChargeResponse)(nil), "hipstershop.ChargeResponse") + proto.RegisterType((*OrderItem)(nil), "hipstershop.OrderItem") + proto.RegisterType((*OrderResult)(nil), "hipstershop.OrderResult") + proto.RegisterType((*SendOrderConfirmationRequest)(nil), "hipstershop.SendOrderConfirmationRequest") + proto.RegisterType((*PlaceOrderRequest)(nil), "hipstershop.PlaceOrderRequest") + proto.RegisterType((*PlaceOrderResponse)(nil), "hipstershop.PlaceOrderResponse") + proto.RegisterType((*AdRequest)(nil), "hipstershop.AdRequest") + proto.RegisterType((*AdResponse)(nil), "hipstershop.AdResponse") + proto.RegisterType((*Ad)(nil), "hipstershop.Ad") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// CartServiceClient is the client API for CartService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type CartServiceClient interface { + AddItem(ctx context.Context, in *AddItemRequest, opts ...grpc.CallOption) (*Empty, error) + GetCart(ctx context.Context, in *GetCartRequest, opts ...grpc.CallOption) (*Cart, error) + EmptyCart(ctx context.Context, in *EmptyCartRequest, opts ...grpc.CallOption) (*Empty, error) +} + +type cartServiceClient struct { + cc *grpc.ClientConn +} + +func NewCartServiceClient(cc *grpc.ClientConn) CartServiceClient { + return &cartServiceClient{cc} +} + +func (c *cartServiceClient) AddItem(ctx context.Context, in *AddItemRequest, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/hipstershop.CartService/AddItem", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *cartServiceClient) GetCart(ctx context.Context, in *GetCartRequest, opts ...grpc.CallOption) (*Cart, error) { + out := new(Cart) + err := c.cc.Invoke(ctx, "/hipstershop.CartService/GetCart", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *cartServiceClient) EmptyCart(ctx context.Context, in *EmptyCartRequest, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/hipstershop.CartService/EmptyCart", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// CartServiceServer is the server API for CartService service. +type CartServiceServer interface { + AddItem(context.Context, *AddItemRequest) (*Empty, error) + GetCart(context.Context, *GetCartRequest) (*Cart, error) + EmptyCart(context.Context, *EmptyCartRequest) (*Empty, error) +} + +func RegisterCartServiceServer(s *grpc.Server, srv CartServiceServer) { + s.RegisterService(&_CartService_serviceDesc, srv) +} + +func _CartService_AddItem_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AddItemRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CartServiceServer).AddItem(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/hipstershop.CartService/AddItem", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CartServiceServer).AddItem(ctx, req.(*AddItemRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CartService_GetCart_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetCartRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CartServiceServer).GetCart(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/hipstershop.CartService/GetCart", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CartServiceServer).GetCart(ctx, req.(*GetCartRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CartService_EmptyCart_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(EmptyCartRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CartServiceServer).EmptyCart(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/hipstershop.CartService/EmptyCart", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CartServiceServer).EmptyCart(ctx, req.(*EmptyCartRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _CartService_serviceDesc = grpc.ServiceDesc{ + ServiceName: "hipstershop.CartService", + HandlerType: (*CartServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "AddItem", + Handler: _CartService_AddItem_Handler, + }, + { + MethodName: "GetCart", + Handler: _CartService_GetCart_Handler, + }, + { + MethodName: "EmptyCart", + Handler: _CartService_EmptyCart_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "demo.proto", +} + +// RecommendationServiceClient is the client API for RecommendationService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type RecommendationServiceClient interface { + ListRecommendations(ctx context.Context, in *ListRecommendationsRequest, opts ...grpc.CallOption) (*ListRecommendationsResponse, error) +} + +type recommendationServiceClient struct { + cc *grpc.ClientConn +} + +func NewRecommendationServiceClient(cc *grpc.ClientConn) RecommendationServiceClient { + return &recommendationServiceClient{cc} +} + +func (c *recommendationServiceClient) ListRecommendations(ctx context.Context, in *ListRecommendationsRequest, opts ...grpc.CallOption) (*ListRecommendationsResponse, error) { + out := new(ListRecommendationsResponse) + err := c.cc.Invoke(ctx, "/hipstershop.RecommendationService/ListRecommendations", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// RecommendationServiceServer is the server API for RecommendationService service. +type RecommendationServiceServer interface { + ListRecommendations(context.Context, *ListRecommendationsRequest) (*ListRecommendationsResponse, error) +} + +func RegisterRecommendationServiceServer(s *grpc.Server, srv RecommendationServiceServer) { + s.RegisterService(&_RecommendationService_serviceDesc, srv) +} + +func _RecommendationService_ListRecommendations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListRecommendationsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RecommendationServiceServer).ListRecommendations(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/hipstershop.RecommendationService/ListRecommendations", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RecommendationServiceServer).ListRecommendations(ctx, req.(*ListRecommendationsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _RecommendationService_serviceDesc = grpc.ServiceDesc{ + ServiceName: "hipstershop.RecommendationService", + HandlerType: (*RecommendationServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ListRecommendations", + Handler: _RecommendationService_ListRecommendations_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "demo.proto", +} + +// ProductCatalogServiceClient is the client API for ProductCatalogService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type ProductCatalogServiceClient interface { + ListProducts(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ListProductsResponse, error) + GetProduct(ctx context.Context, in *GetProductRequest, opts ...grpc.CallOption) (*Product, error) + SearchProducts(ctx context.Context, in *SearchProductsRequest, opts ...grpc.CallOption) (*SearchProductsResponse, error) +} + +type productCatalogServiceClient struct { + cc *grpc.ClientConn +} + +func NewProductCatalogServiceClient(cc *grpc.ClientConn) ProductCatalogServiceClient { + return &productCatalogServiceClient{cc} +} + +func (c *productCatalogServiceClient) ListProducts(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ListProductsResponse, error) { + out := new(ListProductsResponse) + err := c.cc.Invoke(ctx, "/hipstershop.ProductCatalogService/ListProducts", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *productCatalogServiceClient) GetProduct(ctx context.Context, in *GetProductRequest, opts ...grpc.CallOption) (*Product, error) { + out := new(Product) + err := c.cc.Invoke(ctx, "/hipstershop.ProductCatalogService/GetProduct", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *productCatalogServiceClient) SearchProducts(ctx context.Context, in *SearchProductsRequest, opts ...grpc.CallOption) (*SearchProductsResponse, error) { + out := new(SearchProductsResponse) + err := c.cc.Invoke(ctx, "/hipstershop.ProductCatalogService/SearchProducts", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ProductCatalogServiceServer is the server API for ProductCatalogService service. +type ProductCatalogServiceServer interface { + ListProducts(context.Context, *Empty) (*ListProductsResponse, error) + GetProduct(context.Context, *GetProductRequest) (*Product, error) + SearchProducts(context.Context, *SearchProductsRequest) (*SearchProductsResponse, error) +} + +func RegisterProductCatalogServiceServer(s *grpc.Server, srv ProductCatalogServiceServer) { + s.RegisterService(&_ProductCatalogService_serviceDesc, srv) +} + +func _ProductCatalogService_ListProducts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProductCatalogServiceServer).ListProducts(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/hipstershop.ProductCatalogService/ListProducts", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProductCatalogServiceServer).ListProducts(ctx, req.(*Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _ProductCatalogService_GetProduct_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetProductRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProductCatalogServiceServer).GetProduct(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/hipstershop.ProductCatalogService/GetProduct", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProductCatalogServiceServer).GetProduct(ctx, req.(*GetProductRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ProductCatalogService_SearchProducts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SearchProductsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProductCatalogServiceServer).SearchProducts(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/hipstershop.ProductCatalogService/SearchProducts", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProductCatalogServiceServer).SearchProducts(ctx, req.(*SearchProductsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _ProductCatalogService_serviceDesc = grpc.ServiceDesc{ + ServiceName: "hipstershop.ProductCatalogService", + HandlerType: (*ProductCatalogServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ListProducts", + Handler: _ProductCatalogService_ListProducts_Handler, + }, + { + MethodName: "GetProduct", + Handler: _ProductCatalogService_GetProduct_Handler, + }, + { + MethodName: "SearchProducts", + Handler: _ProductCatalogService_SearchProducts_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "demo.proto", +} + +// ShippingServiceClient is the client API for ShippingService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type ShippingServiceClient interface { + GetQuote(ctx context.Context, in *GetQuoteRequest, opts ...grpc.CallOption) (*GetQuoteResponse, error) + ShipOrder(ctx context.Context, in *ShipOrderRequest, opts ...grpc.CallOption) (*ShipOrderResponse, error) +} + +type shippingServiceClient struct { + cc *grpc.ClientConn +} + +func NewShippingServiceClient(cc *grpc.ClientConn) ShippingServiceClient { + return &shippingServiceClient{cc} +} + +func (c *shippingServiceClient) GetQuote(ctx context.Context, in *GetQuoteRequest, opts ...grpc.CallOption) (*GetQuoteResponse, error) { + out := new(GetQuoteResponse) + err := c.cc.Invoke(ctx, "/hipstershop.ShippingService/GetQuote", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *shippingServiceClient) ShipOrder(ctx context.Context, in *ShipOrderRequest, opts ...grpc.CallOption) (*ShipOrderResponse, error) { + out := new(ShipOrderResponse) + err := c.cc.Invoke(ctx, "/hipstershop.ShippingService/ShipOrder", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ShippingServiceServer is the server API for ShippingService service. +type ShippingServiceServer interface { + GetQuote(context.Context, *GetQuoteRequest) (*GetQuoteResponse, error) + ShipOrder(context.Context, *ShipOrderRequest) (*ShipOrderResponse, error) +} + +func RegisterShippingServiceServer(s *grpc.Server, srv ShippingServiceServer) { + s.RegisterService(&_ShippingService_serviceDesc, srv) +} + +func _ShippingService_GetQuote_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetQuoteRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ShippingServiceServer).GetQuote(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/hipstershop.ShippingService/GetQuote", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ShippingServiceServer).GetQuote(ctx, req.(*GetQuoteRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ShippingService_ShipOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ShipOrderRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ShippingServiceServer).ShipOrder(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/hipstershop.ShippingService/ShipOrder", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ShippingServiceServer).ShipOrder(ctx, req.(*ShipOrderRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _ShippingService_serviceDesc = grpc.ServiceDesc{ + ServiceName: "hipstershop.ShippingService", + HandlerType: (*ShippingServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetQuote", + Handler: _ShippingService_GetQuote_Handler, + }, + { + MethodName: "ShipOrder", + Handler: _ShippingService_ShipOrder_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "demo.proto", +} + +// CurrencyServiceClient is the client API for CurrencyService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type CurrencyServiceClient interface { + GetSupportedCurrencies(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*GetSupportedCurrenciesResponse, error) + Convert(ctx context.Context, in *CurrencyConversionRequest, opts ...grpc.CallOption) (*Money, error) +} + +type currencyServiceClient struct { + cc *grpc.ClientConn +} + +func NewCurrencyServiceClient(cc *grpc.ClientConn) CurrencyServiceClient { + return ¤cyServiceClient{cc} +} + +func (c *currencyServiceClient) GetSupportedCurrencies(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*GetSupportedCurrenciesResponse, error) { + out := new(GetSupportedCurrenciesResponse) + err := c.cc.Invoke(ctx, "/hipstershop.CurrencyService/GetSupportedCurrencies", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *currencyServiceClient) Convert(ctx context.Context, in *CurrencyConversionRequest, opts ...grpc.CallOption) (*Money, error) { + out := new(Money) + err := c.cc.Invoke(ctx, "/hipstershop.CurrencyService/Convert", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// CurrencyServiceServer is the server API for CurrencyService service. +type CurrencyServiceServer interface { + GetSupportedCurrencies(context.Context, *Empty) (*GetSupportedCurrenciesResponse, error) + Convert(context.Context, *CurrencyConversionRequest) (*Money, error) +} + +func RegisterCurrencyServiceServer(s *grpc.Server, srv CurrencyServiceServer) { + s.RegisterService(&_CurrencyService_serviceDesc, srv) +} + +func _CurrencyService_GetSupportedCurrencies_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CurrencyServiceServer).GetSupportedCurrencies(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/hipstershop.CurrencyService/GetSupportedCurrencies", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CurrencyServiceServer).GetSupportedCurrencies(ctx, req.(*Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _CurrencyService_Convert_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CurrencyConversionRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CurrencyServiceServer).Convert(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/hipstershop.CurrencyService/Convert", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CurrencyServiceServer).Convert(ctx, req.(*CurrencyConversionRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _CurrencyService_serviceDesc = grpc.ServiceDesc{ + ServiceName: "hipstershop.CurrencyService", + HandlerType: (*CurrencyServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetSupportedCurrencies", + Handler: _CurrencyService_GetSupportedCurrencies_Handler, + }, + { + MethodName: "Convert", + Handler: _CurrencyService_Convert_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "demo.proto", +} + +// PaymentServiceClient is the client API for PaymentService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type PaymentServiceClient interface { + Charge(ctx context.Context, in *ChargeRequest, opts ...grpc.CallOption) (*ChargeResponse, error) +} + +type paymentServiceClient struct { + cc *grpc.ClientConn +} + +func NewPaymentServiceClient(cc *grpc.ClientConn) PaymentServiceClient { + return &paymentServiceClient{cc} +} + +func (c *paymentServiceClient) Charge(ctx context.Context, in *ChargeRequest, opts ...grpc.CallOption) (*ChargeResponse, error) { + out := new(ChargeResponse) + err := c.cc.Invoke(ctx, "/hipstershop.PaymentService/Charge", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// PaymentServiceServer is the server API for PaymentService service. +type PaymentServiceServer interface { + Charge(context.Context, *ChargeRequest) (*ChargeResponse, error) +} + +func RegisterPaymentServiceServer(s *grpc.Server, srv PaymentServiceServer) { + s.RegisterService(&_PaymentService_serviceDesc, srv) +} + +func _PaymentService_Charge_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ChargeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PaymentServiceServer).Charge(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/hipstershop.PaymentService/Charge", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PaymentServiceServer).Charge(ctx, req.(*ChargeRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _PaymentService_serviceDesc = grpc.ServiceDesc{ + ServiceName: "hipstershop.PaymentService", + HandlerType: (*PaymentServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Charge", + Handler: _PaymentService_Charge_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "demo.proto", +} + +// EmailServiceClient is the client API for EmailService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type EmailServiceClient interface { + SendOrderConfirmation(ctx context.Context, in *SendOrderConfirmationRequest, opts ...grpc.CallOption) (*Empty, error) +} + +type emailServiceClient struct { + cc *grpc.ClientConn +} + +func NewEmailServiceClient(cc *grpc.ClientConn) EmailServiceClient { + return &emailServiceClient{cc} +} + +func (c *emailServiceClient) SendOrderConfirmation(ctx context.Context, in *SendOrderConfirmationRequest, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/hipstershop.EmailService/SendOrderConfirmation", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// EmailServiceServer is the server API for EmailService service. +type EmailServiceServer interface { + SendOrderConfirmation(context.Context, *SendOrderConfirmationRequest) (*Empty, error) +} + +func RegisterEmailServiceServer(s *grpc.Server, srv EmailServiceServer) { + s.RegisterService(&_EmailService_serviceDesc, srv) +} + +func _EmailService_SendOrderConfirmation_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SendOrderConfirmationRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(EmailServiceServer).SendOrderConfirmation(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/hipstershop.EmailService/SendOrderConfirmation", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(EmailServiceServer).SendOrderConfirmation(ctx, req.(*SendOrderConfirmationRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _EmailService_serviceDesc = grpc.ServiceDesc{ + ServiceName: "hipstershop.EmailService", + HandlerType: (*EmailServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "SendOrderConfirmation", + Handler: _EmailService_SendOrderConfirmation_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "demo.proto", +} + +// CheckoutServiceClient is the client API for CheckoutService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type CheckoutServiceClient interface { + PlaceOrder(ctx context.Context, in *PlaceOrderRequest, opts ...grpc.CallOption) (*PlaceOrderResponse, error) +} + +type checkoutServiceClient struct { + cc *grpc.ClientConn +} + +func NewCheckoutServiceClient(cc *grpc.ClientConn) CheckoutServiceClient { + return &checkoutServiceClient{cc} +} + +func (c *checkoutServiceClient) PlaceOrder(ctx context.Context, in *PlaceOrderRequest, opts ...grpc.CallOption) (*PlaceOrderResponse, error) { + out := new(PlaceOrderResponse) + err := c.cc.Invoke(ctx, "/hipstershop.CheckoutService/PlaceOrder", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// CheckoutServiceServer is the server API for CheckoutService service. +type CheckoutServiceServer interface { + PlaceOrder(context.Context, *PlaceOrderRequest) (*PlaceOrderResponse, error) +} + +func RegisterCheckoutServiceServer(s *grpc.Server, srv CheckoutServiceServer) { + s.RegisterService(&_CheckoutService_serviceDesc, srv) +} + +func _CheckoutService_PlaceOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PlaceOrderRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CheckoutServiceServer).PlaceOrder(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/hipstershop.CheckoutService/PlaceOrder", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CheckoutServiceServer).PlaceOrder(ctx, req.(*PlaceOrderRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _CheckoutService_serviceDesc = grpc.ServiceDesc{ + ServiceName: "hipstershop.CheckoutService", + HandlerType: (*CheckoutServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "PlaceOrder", + Handler: _CheckoutService_PlaceOrder_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "demo.proto", +} + +// AdServiceClient is the client API for AdService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type AdServiceClient interface { + GetAds(ctx context.Context, in *AdRequest, opts ...grpc.CallOption) (*AdResponse, error) +} + +type adServiceClient struct { + cc *grpc.ClientConn +} + +func NewAdServiceClient(cc *grpc.ClientConn) AdServiceClient { + return &adServiceClient{cc} +} + +func (c *adServiceClient) GetAds(ctx context.Context, in *AdRequest, opts ...grpc.CallOption) (*AdResponse, error) { + out := new(AdResponse) + err := c.cc.Invoke(ctx, "/hipstershop.AdService/GetAds", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// AdServiceServer is the server API for AdService service. +type AdServiceServer interface { + GetAds(context.Context, *AdRequest) (*AdResponse, error) +} + +func RegisterAdServiceServer(s *grpc.Server, srv AdServiceServer) { + s.RegisterService(&_AdService_serviceDesc, srv) +} + +func _AdService_GetAds_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AdRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AdServiceServer).GetAds(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/hipstershop.AdService/GetAds", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AdServiceServer).GetAds(ctx, req.(*AdRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _AdService_serviceDesc = grpc.ServiceDesc{ + ServiceName: "hipstershop.AdService", + HandlerType: (*AdServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetAds", + Handler: _AdService_GetAds_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "demo.proto", +} + +func init() { proto.RegisterFile("demo.proto", fileDescriptor_ca53982754088a9d) } + +var fileDescriptor_ca53982754088a9d = []byte{ + // 1500 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x58, 0xef, 0x72, 0x13, 0xb7, + 0x16, 0xcf, 0x26, 0xb1, 0x1d, 0x1f, 0xc7, 0x4e, 0xa2, 0x9b, 0x04, 0xb3, 0x81, 0x10, 0x94, 0x81, + 0x0b, 0x17, 0x08, 0x4c, 0xee, 0x9d, 0xe1, 0x03, 0xdc, 0xd2, 0x8c, 0xc9, 0x18, 0x4f, 0xa1, 0xd0, + 0x0d, 0xe9, 0xd0, 0xa1, 0x53, 0xcf, 0xb2, 0x12, 0xf1, 0x96, 0xec, 0x6a, 0x91, 0xb4, 0x19, 0xcc, + 0xc7, 0xf6, 0x01, 0xfa, 0x1e, 0x7d, 0x81, 0xce, 0xf4, 0x11, 0xfa, 0xbd, 0xaf, 0xd0, 0xe7, 0xe8, + 0x48, 0xbb, 0xda, 0x7f, 0xb1, 0x13, 0xf8, 0xd2, 0x6f, 0xab, 0xa3, 0x9f, 0xce, 0xf9, 0xe9, 0xe8, + 0xfc, 0xb3, 0x01, 0x08, 0x0d, 0xd8, 0x4e, 0xc4, 0x99, 0x64, 0xa8, 0x35, 0xf2, 0x23, 0x21, 0x29, + 0x17, 0x23, 0x16, 0xe1, 0x7d, 0x58, 0xe8, 0xb9, 0x5c, 0x0e, 0x24, 0x0d, 0xd0, 0x65, 0x80, 0x88, + 0x33, 0x12, 0x7b, 0x72, 0xe8, 0x93, 0xae, 0xb5, 0x65, 0xdd, 0x68, 0x3a, 0xcd, 0x54, 0x32, 0x20, + 0xc8, 0x86, 0x85, 0xf7, 0xb1, 0x1b, 0x4a, 0x5f, 0x8e, 0xbb, 0xb3, 0x5b, 0xd6, 0x8d, 0x9a, 0x93, + 0xad, 0xf1, 0x4b, 0xe8, 0xec, 0x11, 0xa2, 0xb4, 0x38, 0xf4, 0x7d, 0x4c, 0x85, 0x44, 0x17, 0xa0, + 0x11, 0x0b, 0xca, 0x73, 0x4d, 0x75, 0xb5, 0x1c, 0x10, 0x74, 0x13, 0xe6, 0x7d, 0x49, 0x03, 0xad, + 0xa2, 0xb5, 0xbb, 0xb6, 0x53, 0x60, 0xb3, 0x63, 0xa8, 0x38, 0x1a, 0x82, 0x6f, 0xc1, 0xf2, 0x7e, + 0x10, 0xc9, 0xb1, 0x12, 0x9f, 0xa7, 0x17, 0xdf, 0x84, 0x4e, 0x9f, 0xca, 0x4f, 0x82, 0x3e, 0x85, + 0x79, 0x85, 0x9b, 0xce, 0xf1, 0x16, 0xd4, 0x14, 0x01, 0xd1, 0x9d, 0xdd, 0x9a, 0x9b, 0x4e, 0x32, + 0xc1, 0xe0, 0x06, 0xd4, 0x34, 0x4b, 0xfc, 0x2d, 0xd8, 0x4f, 0x7d, 0x21, 0x1d, 0xea, 0xb1, 0x20, + 0xa0, 0x21, 0x71, 0xa5, 0xcf, 0x42, 0x71, 0xae, 0x43, 0xae, 0x40, 0x2b, 0x77, 0x7b, 0x62, 0xb2, + 0xe9, 0x40, 0xe6, 0x77, 0x81, 0xbf, 0x80, 0x8d, 0x89, 0x7a, 0x45, 0xc4, 0x42, 0x41, 0xab, 0xe7, + 0xad, 0x53, 0xe7, 0x7f, 0xb7, 0xa0, 0xf1, 0x22, 0x59, 0xa2, 0x0e, 0xcc, 0x66, 0x04, 0x66, 0x7d, + 0x82, 0x10, 0xcc, 0x87, 0x6e, 0x40, 0xf5, 0x6b, 0x34, 0x1d, 0xfd, 0x8d, 0xb6, 0xa0, 0x45, 0xa8, + 0xf0, 0xb8, 0x1f, 0x29, 0x43, 0xdd, 0x39, 0xbd, 0x55, 0x14, 0xa1, 0x2e, 0x34, 0x22, 0xdf, 0x93, + 0x31, 0xa7, 0xdd, 0x79, 0xbd, 0x6b, 0x96, 0xe8, 0x2e, 0x34, 0x23, 0xee, 0x7b, 0x74, 0x18, 0x0b, + 0xd2, 0xad, 0xe9, 0x27, 0x46, 0x25, 0xef, 0x3d, 0x63, 0x21, 0x1d, 0x3b, 0x0b, 0x1a, 0x74, 0x28, + 0x08, 0xda, 0x04, 0xf0, 0x5c, 0x49, 0x8f, 0x18, 0xf7, 0xa9, 0xe8, 0xd6, 0x13, 0xf2, 0xb9, 0x04, + 0x3f, 0x81, 0x55, 0x75, 0xf9, 0x94, 0x7f, 0x7e, 0xeb, 0x7b, 0xb0, 0x90, 0x5e, 0x31, 0xb9, 0x72, + 0x6b, 0x77, 0xb5, 0x64, 0x27, 0x3d, 0xe0, 0x64, 0x28, 0xbc, 0x0d, 0x2b, 0x7d, 0x6a, 0x14, 0x99, + 0x57, 0xa9, 0xf8, 0x03, 0xdf, 0x81, 0xb5, 0x03, 0xea, 0x72, 0x6f, 0x94, 0x1b, 0x4c, 0x80, 0xab, + 0x50, 0x7b, 0x1f, 0x53, 0x3e, 0x4e, 0xb1, 0xc9, 0x02, 0x3f, 0x81, 0xf5, 0x2a, 0x3c, 0xe5, 0xb7, + 0x03, 0x0d, 0x4e, 0x45, 0x7c, 0x7c, 0x0e, 0x3d, 0x03, 0xc2, 0x21, 0x2c, 0xf5, 0xa9, 0xfc, 0x26, + 0x66, 0x92, 0x1a, 0x93, 0x3b, 0xd0, 0x70, 0x09, 0xe1, 0x54, 0x08, 0x6d, 0xb4, 0xaa, 0x62, 0x2f, + 0xd9, 0x73, 0x0c, 0xe8, 0xf3, 0xa2, 0x76, 0x0f, 0x96, 0x73, 0x7b, 0x29, 0xe7, 0x3b, 0xb0, 0xe0, + 0x31, 0x21, 0xf5, 0xdb, 0x59, 0x53, 0xdf, 0xae, 0xa1, 0x30, 0x87, 0x82, 0x60, 0x06, 0xcb, 0x07, + 0x23, 0x3f, 0x7a, 0xce, 0x09, 0xe5, 0xff, 0x08, 0xe7, 0xff, 0xc1, 0x4a, 0xc1, 0x60, 0x1e, 0xfe, + 0x92, 0xbb, 0xde, 0x3b, 0x3f, 0x3c, 0xca, 0x73, 0x0b, 0x8c, 0x68, 0x40, 0xf0, 0x2f, 0x16, 0x34, + 0x52, 0xbb, 0xe8, 0x1a, 0x74, 0x84, 0xe4, 0x94, 0xca, 0x61, 0x91, 0x65, 0xd3, 0x69, 0x27, 0x52, + 0x03, 0x43, 0x30, 0xef, 0x99, 0x32, 0xd7, 0x74, 0xf4, 0xb7, 0x0a, 0x00, 0x21, 0x5d, 0x49, 0xd3, + 0x7c, 0x48, 0x16, 0x2a, 0x13, 0x3c, 0x16, 0x87, 0x92, 0x8f, 0x4d, 0x26, 0xa4, 0x4b, 0x74, 0x11, + 0x16, 0x3e, 0xfa, 0xd1, 0xd0, 0x63, 0x84, 0xea, 0x44, 0xa8, 0x39, 0x8d, 0x8f, 0x7e, 0xd4, 0x63, + 0x84, 0xe2, 0x57, 0x50, 0xd3, 0xae, 0x44, 0xdb, 0xd0, 0xf6, 0x62, 0xce, 0x69, 0xe8, 0x8d, 0x13, + 0x60, 0xc2, 0x66, 0xd1, 0x08, 0x15, 0x5a, 0x19, 0x8e, 0x43, 0x5f, 0x0a, 0xcd, 0x66, 0xce, 0x49, + 0x16, 0x4a, 0x1a, 0xba, 0x21, 0x13, 0x9a, 0x4e, 0xcd, 0x49, 0x16, 0xb8, 0x0f, 0x9b, 0x7d, 0x2a, + 0x0f, 0xe2, 0x28, 0x62, 0x5c, 0x52, 0xd2, 0x4b, 0xf4, 0xf8, 0x34, 0x8f, 0xcb, 0x6b, 0xd0, 0x29, + 0x99, 0x34, 0x05, 0xa3, 0x5d, 0xb4, 0x29, 0xf0, 0xf7, 0x70, 0xb1, 0x97, 0x09, 0xc2, 0x13, 0xca, + 0x85, 0xcf, 0x42, 0xf3, 0xc8, 0xd7, 0x61, 0xfe, 0x2d, 0x67, 0xc1, 0x19, 0x31, 0xa2, 0xf7, 0x55, + 0xc9, 0x93, 0x2c, 0xb9, 0x58, 0xe2, 0xc9, 0xba, 0x64, 0xda, 0x01, 0x7f, 0x59, 0xd0, 0xe9, 0x71, + 0x4a, 0x7c, 0x55, 0xaf, 0xc9, 0x20, 0x7c, 0xcb, 0xd0, 0x6d, 0x40, 0x9e, 0x96, 0x0c, 0x3d, 0x97, + 0x93, 0x61, 0x18, 0x07, 0x6f, 0x28, 0x4f, 0xfd, 0xb1, 0xec, 0x65, 0xd8, 0xaf, 0xb5, 0x1c, 0x5d, + 0x87, 0xa5, 0x22, 0xda, 0x3b, 0x39, 0x49, 0x5b, 0x52, 0x3b, 0x87, 0xf6, 0x4e, 0x4e, 0xd0, 0xff, + 0x61, 0xa3, 0x88, 0xa3, 0x1f, 0x22, 0x9f, 0xeb, 0xf2, 0x39, 0x1c, 0x53, 0x97, 0xa7, 0xbe, 0xeb, + 0xe6, 0x67, 0xf6, 0x33, 0xc0, 0x77, 0xd4, 0xe5, 0xe8, 0x11, 0x5c, 0x9a, 0x72, 0x3c, 0x60, 0xa1, + 0x1c, 0xe9, 0x27, 0xaf, 0x39, 0x17, 0x27, 0x9d, 0x7f, 0xa6, 0x00, 0x78, 0x0c, 0xed, 0xde, 0xc8, + 0xe5, 0x47, 0x59, 0x4e, 0xff, 0x07, 0xea, 0x6e, 0xa0, 0x22, 0xe4, 0x0c, 0xe7, 0xa5, 0x08, 0xf4, + 0x10, 0x5a, 0x05, 0xeb, 0x69, 0xc3, 0xdc, 0x28, 0x67, 0x48, 0xc9, 0x89, 0x0e, 0xe4, 0x4c, 0xf0, + 0x7d, 0xe8, 0x18, 0xd3, 0xf9, 0xd3, 0x4b, 0xee, 0x86, 0xc2, 0xf5, 0xf4, 0x15, 0xb2, 0x64, 0x69, + 0x17, 0xa4, 0x03, 0x82, 0x7f, 0x80, 0xa6, 0xce, 0x30, 0x3d, 0x13, 0x98, 0x6e, 0x6d, 0x9d, 0xdb, + 0xad, 0x55, 0x54, 0xa8, 0xca, 0x90, 0xf2, 0x9c, 0x18, 0x15, 0x6a, 0x1f, 0xff, 0x34, 0x0b, 0x2d, + 0x93, 0xc2, 0xf1, 0xb1, 0x54, 0x89, 0xc2, 0xd4, 0x32, 0x27, 0xd4, 0xd0, 0xeb, 0x01, 0x41, 0xf7, + 0x60, 0x55, 0x8c, 0xfc, 0x28, 0x52, 0xb9, 0x5d, 0x4c, 0xf2, 0x24, 0x9a, 0x90, 0xd9, 0x7b, 0x99, + 0x25, 0x3b, 0xba, 0x0f, 0xed, 0xec, 0x84, 0x66, 0x33, 0x37, 0x95, 0xcd, 0xa2, 0x01, 0xf6, 0x98, + 0x90, 0xe8, 0x11, 0x2c, 0x67, 0x07, 0x4d, 0x6d, 0x98, 0x3f, 0xa3, 0x82, 0x2d, 0x19, 0xb4, 0xa9, + 0x19, 0xb7, 0x4d, 0x25, 0xab, 0xe9, 0x4a, 0xb6, 0x5e, 0x3a, 0x95, 0x39, 0xd4, 0x94, 0x32, 0x02, + 0x97, 0x0e, 0x68, 0x48, 0xb4, 0xbc, 0xc7, 0xc2, 0xb7, 0x3e, 0x0f, 0x74, 0xd8, 0x14, 0xda, 0x0d, + 0x0d, 0x5c, 0xff, 0xd8, 0xb4, 0x1b, 0xbd, 0x40, 0x3b, 0x50, 0xd3, 0xae, 0x49, 0x7d, 0xdc, 0x3d, + 0x6d, 0x23, 0xf1, 0xa9, 0x93, 0xc0, 0xf0, 0x9f, 0x16, 0xac, 0xbc, 0x38, 0x76, 0x3d, 0x5a, 0xaa, + 0xd1, 0x53, 0x27, 0x91, 0x6d, 0x68, 0xeb, 0x0d, 0x53, 0x0a, 0x52, 0x3f, 0x2f, 0x2a, 0xa1, 0xa9, + 0x06, 0xc5, 0x0a, 0x3f, 0xf7, 0x29, 0x15, 0x3e, 0xbb, 0x49, 0xad, 0x78, 0x93, 0x4a, 0x6c, 0xd7, + 0x3f, 0x2f, 0xb6, 0x1f, 0x03, 0x2a, 0x5e, 0x2b, 0x6b, 0xb9, 0xa9, 0x77, 0xac, 0x4f, 0xf3, 0xce, + 0x0e, 0x34, 0xf7, 0x88, 0x71, 0xca, 0x55, 0x58, 0xf4, 0x58, 0x28, 0xe9, 0x07, 0x39, 0x7c, 0x47, + 0xc7, 0xa6, 0x2a, 0xb6, 0x52, 0xd9, 0x57, 0x74, 0x2c, 0xf0, 0x5d, 0x00, 0x85, 0x4f, 0xad, 0x5d, + 0x85, 0x39, 0x97, 0x98, 0xe6, 0xbe, 0x54, 0xf1, 0x81, 0xa3, 0xf6, 0xf0, 0x03, 0x98, 0xdd, 0x23, + 0x4a, 0xb3, 0x62, 0xce, 0xa9, 0x27, 0x87, 0x31, 0x37, 0x2f, 0xda, 0x32, 0xb2, 0x43, 0x7e, 0xac, + 0xfa, 0x8d, 0xb2, 0x62, 0xfa, 0x8d, 0xfa, 0xde, 0xfd, 0xc3, 0x82, 0x96, 0xca, 0xb0, 0x03, 0xca, + 0x4f, 0x7c, 0x8f, 0xa2, 0x87, 0xba, 0x8b, 0xe9, 0xa4, 0xdc, 0xa8, 0x7a, 0xbc, 0x30, 0x78, 0xdb, + 0xe5, 0x50, 0x4f, 0x26, 0xd3, 0x19, 0xf4, 0x00, 0x1a, 0xe9, 0x74, 0x5c, 0x39, 0x5d, 0x9e, 0x99, + 0xed, 0x95, 0x53, 0x19, 0x8e, 0x67, 0xd0, 0x97, 0xd0, 0xcc, 0xe6, 0x70, 0x74, 0xf9, 0xb4, 0xfe, + 0xa2, 0x82, 0x89, 0xe6, 0x77, 0x7f, 0xb6, 0x60, 0xad, 0x3c, 0xbf, 0x9a, 0x6b, 0xfd, 0x08, 0xff, + 0x9a, 0x30, 0xdc, 0xa2, 0x7f, 0x97, 0xd4, 0x4c, 0x1f, 0xab, 0xed, 0x1b, 0xe7, 0x03, 0x93, 0x07, + 0x53, 0x2c, 0x66, 0x61, 0x2d, 0x1d, 0xbc, 0x7a, 0xae, 0x74, 0x8f, 0xd9, 0x91, 0x61, 0xd1, 0x87, + 0xc5, 0xe2, 0x94, 0x89, 0x26, 0xdc, 0xc2, 0xbe, 0x7a, 0xca, 0x52, 0x75, 0xe8, 0xc3, 0x33, 0xe8, + 0x31, 0x40, 0x3e, 0x64, 0xa2, 0xcd, 0xaa, 0xab, 0xcb, 0xd3, 0xa7, 0x3d, 0x71, 0x26, 0xc4, 0x33, + 0xe8, 0x35, 0x74, 0xca, 0x63, 0x25, 0xc2, 0x25, 0xe4, 0xc4, 0x11, 0xd5, 0xde, 0x3e, 0x13, 0x93, + 0x79, 0xe1, 0x57, 0x0b, 0x96, 0x0e, 0xd2, 0xe2, 0x65, 0xee, 0x3f, 0x80, 0x05, 0x33, 0x0d, 0xa2, + 0x4b, 0x55, 0xd2, 0xc5, 0xa1, 0xd4, 0xbe, 0x3c, 0x65, 0x37, 0xf3, 0xc0, 0x53, 0x68, 0x66, 0x43, + 0x5a, 0x25, 0x58, 0xaa, 0xd3, 0xa2, 0xbd, 0x39, 0x6d, 0x3b, 0x23, 0xfb, 0x9b, 0x05, 0x4b, 0xa6, + 0xf4, 0x18, 0xb2, 0xaf, 0x61, 0x7d, 0xf2, 0x90, 0x33, 0xf1, 0xd9, 0x6e, 0x55, 0x09, 0x9f, 0x31, + 0x1d, 0xe1, 0x19, 0xd4, 0x87, 0x46, 0x32, 0xf0, 0x48, 0x74, 0xbd, 0x9c, 0x0b, 0xd3, 0xc6, 0x21, + 0x7b, 0x42, 0x73, 0xc1, 0x33, 0xbb, 0x87, 0xd0, 0x79, 0xe1, 0x8e, 0x03, 0x1a, 0x66, 0x19, 0xdc, + 0x83, 0x7a, 0xd2, 0x91, 0x91, 0x5d, 0xd6, 0x5c, 0x9c, 0x10, 0xec, 0x8d, 0x89, 0x7b, 0x99, 0x43, + 0x46, 0xb0, 0xb8, 0xaf, 0x2a, 0xa8, 0x51, 0xfa, 0x4a, 0xfd, 0x60, 0x99, 0xd0, 0x48, 0xd0, 0xcd, + 0x4a, 0x34, 0x4c, 0x6f, 0x36, 0x53, 0x72, 0xf6, 0x0d, 0x2c, 0xf5, 0x46, 0xd4, 0x7b, 0xc7, 0xe2, + 0xec, 0x06, 0xcf, 0x01, 0xf2, 0xba, 0x5b, 0x89, 0xee, 0x53, 0x7d, 0xc6, 0xbe, 0x32, 0x75, 0x3f, + 0xbb, 0xcd, 0x13, 0x55, 0x82, 0x8d, 0xf6, 0x07, 0x50, 0xef, 0xab, 0x19, 0x5c, 0xa0, 0xf5, 0x6a, + 0x39, 0x4d, 0x35, 0x5e, 0x38, 0x25, 0x37, 0x9a, 0xde, 0xd4, 0xf5, 0x9f, 0x1b, 0xff, 0xfd, 0x3b, + 0x00, 0x00, 0xff, 0xff, 0xb2, 0xa0, 0x6e, 0x6c, 0xea, 0x10, 0x00, 0x00, +} diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..638e4d03 --- /dev/null +++ b/go.mod @@ -0,0 +1,48 @@ +module github.com/GoogleCloudPlatform/microservices-demo/src/frontend + +go 1.19 + +require ( + cloud.google.com/go/compute/metadata v0.2.3 + cloud.google.com/go/profiler v0.3.1 + github.com/golang/protobuf v1.5.3 + github.com/google/uuid v1.3.0 + github.com/gorilla/mux v1.8.0 + github.com/pkg/errors v0.9.1 + github.com/sirupsen/logrus v1.9.1 + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.41.1 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.41.1 + go.opentelemetry.io/otel v1.15.1 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.15.1 + go.opentelemetry.io/otel/sdk v1.15.1 + golang.org/x/net v0.10.0 + google.golang.org/grpc v1.54.0 +) + +require ( + cloud.google.com/go v0.107.0 // indirect + cloud.google.com/go/compute v1.15.1 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/felixge/httpsnoop v1.0.3 // indirect + github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/google/pprof v0.0.0-20221103000818-d260c55eee4c // indirect + github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect + github.com/googleapis/gax-go/v2 v2.7.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect + go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.15.1 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.15.1 // indirect + go.opentelemetry.io/otel/metric v0.38.1 // indirect + go.opentelemetry.io/otel/trace v1.15.1 // indirect + go.opentelemetry.io/proto/otlp v0.19.0 // indirect + golang.org/x/oauth2 v0.4.0 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/text v0.9.0 // indirect + google.golang.org/api v0.103.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect + google.golang.org/protobuf v1.30.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..8781c55f --- /dev/null +++ b/go.sum @@ -0,0 +1,501 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.107.0 h1:qkj22L7bgkl6vIeZDlOY2po43Mx/TIa2Wsa7VR+PEww= +cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute v1.15.1 h1:7UGq3QknM33pw5xATlpzeoomNxsacIVvTqTTvbfajmE= +cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/iam v0.8.0 h1:E2osAkZzxI/+8pZcxVLcDtAQx/u+hZXVryUaYQ5O0Kk= +cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs= +cloud.google.com/go/profiler v0.3.1 h1:b5got9Be9Ia0HVvyt7PavWxXEht15B9lWnigdvHtxOc= +cloud.google.com/go/profiler v0.3.1/go.mod h1:GsG14VnmcMFQ9b+kq71wh3EKMZr3WRMgLzNiFRpW7tE= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.27.0 h1:YOO045NZI9RKfCj1c5A/ZtuuENUc8OAW+gHdGnDgyMQ= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= +github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20221103000818-d260c55eee4c h1:lvddKcYTQ545ADhBujtIJmqQrZBDsGo7XIMbAQe/sNY= +github.com/google/pprof v0.0.0-20221103000818-d260c55eee4c/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.2.0 h1:y8Yozv7SZtlU//QXbezB6QkpuE6jMD2/gfzk4AftXjs= +github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ= +github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/sirupsen/logrus v1.9.1 h1:Ou41VVR3nMWWmTiEUnj0OlsgOSCUFgsPAOl6jRIcVtQ= +github.com/sirupsen/logrus v1.9.1/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.41.1 h1:Ei1FUQ5CbSNkl2o/XAiksXSyQNAeJBX3ivqJpJ254Ak= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.41.1/go.mod h1:f7TOPTlEcliCBlOYPuNnZTuND71MVTAoINWIt1SmP/c= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.41.1 h1:pX+lppB8PArapyhS6nBStyQmkaDUPWdQf0UmEGRCQ54= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.41.1/go.mod h1:2FmkXne0k9nkp27LD/m+uoh8dNlstsiCJ7PLc/S72aI= +go.opentelemetry.io/otel v1.15.1 h1:3Iwq3lfRByPaws0f6bU3naAqOR1n5IeDWd9390kWHa8= +go.opentelemetry.io/otel v1.15.1/go.mod h1:mHHGEHVDLal6YrKMmk9LqC4a3sF5g+fHfrttQIB1NTc= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.15.1 h1:XYDQtNzdb2T4uM1pku2m76eSMDJgqhJ+6KzkqgQBALc= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.15.1/go.mod h1:uOTV75+LOzV+ODmL8ahRLWkFA3eQcSC2aAsbxIu4duk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.15.1 h1:tyoeaUh8REKay72DVYsSEBYV18+fGONe+YYPaOxgLoE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.15.1/go.mod h1:HUSnrjQQ19KX9ECjpQxufsF+3ioD3zISPMlauTPZu2g= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.15.1 h1:pIfoG5IAZFzp9EUlJzdSkpUwpaUAAnD+Ru1nBLTACIQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.15.1/go.mod h1:poNKBqF5+nR/6ke2oGTDjHfksrsHDOHXAl2g4+9ONsY= +go.opentelemetry.io/otel/metric v0.38.1 h1:2MM7m6wPw9B8Qv8iHygoAgkbejed59uUR6ezR5T3X2s= +go.opentelemetry.io/otel/metric v0.38.1/go.mod h1:FwqNHD3I/5iX9pfrRGZIlYICrJv0rHEUl2Ln5vdIVnQ= +go.opentelemetry.io/otel/sdk v1.15.1 h1:5FKR+skgpzvhPQHIEfcwMYjCBr14LWzs3uSqKiQzETI= +go.opentelemetry.io/otel/sdk v1.15.1/go.mod h1:8rVtxQfrbmbHKfqzpQkT5EzZMcbMBwTzNAggbEAM0KA= +go.opentelemetry.io/otel/trace v1.15.1 h1:uXLo6iHJEzDfrNC0L0mNjItIp06SyaBQxu5t3xMlngY= +go.opentelemetry.io/otel/trace v1.15.1/go.mod h1:IWdQG/5N1x7f6YUlmdLeJvH9yxtuJAfc4VW5Agv9r/8= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= +go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M= +golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.103.0 h1:9yuVqlu2JCvcLg9p8S3fcFLZij8EPSyvODIY1rkMizQ= +google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= +google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/handlers.go b/handlers.go new file mode 100644 index 00000000..abed0627 --- /dev/null +++ b/handlers.go @@ -0,0 +1,525 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "context" + "fmt" + "html/template" + "math/rand" + "net" + "net/http" + "os" + "strconv" + "strings" + "time" + + "github.com/gorilla/mux" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + + pb "github.com/GoogleCloudPlatform/microservices-demo/src/frontend/genproto" + "github.com/GoogleCloudPlatform/microservices-demo/src/frontend/money" +) + +type platformDetails struct { + css string + provider string +} + +var ( + frontendMessage = strings.TrimSpace(os.Getenv("FRONTEND_MESSAGE")) + isCymbalBrand = "true" == strings.ToLower(os.Getenv("CYMBAL_BRANDING")) + templates = template.Must(template.New(""). + Funcs(template.FuncMap{ + "renderMoney": renderMoney, + "renderCurrencyLogo": renderCurrencyLogo, + }).ParseGlob("templates/*.html")) + plat platformDetails +) + +var validEnvs = []string{"local", "gcp", "azure", "aws", "onprem", "alibaba"} + +func (fe *frontendServer) homeHandler(w http.ResponseWriter, r *http.Request) { + log := r.Context().Value(ctxKeyLog{}).(logrus.FieldLogger) + log.WithField("currency", currentCurrency(r)).Info("home") + currencies, err := fe.getCurrencies(r.Context()) + if err != nil { + renderHTTPError(log, r, w, errors.Wrap(err, "could not retrieve currencies"), http.StatusInternalServerError) + return + } + products, err := fe.getProducts(r.Context()) + if err != nil { + renderHTTPError(log, r, w, errors.Wrap(err, "could not retrieve products"), http.StatusInternalServerError) + return + } + cart, err := fe.getCart(r.Context(), sessionID(r)) + if err != nil { + renderHTTPError(log, r, w, errors.Wrap(err, "could not retrieve cart"), http.StatusInternalServerError) + return + } + + type productView struct { + Item *pb.Product + Price *pb.Money + } + ps := make([]productView, len(products)) + for i, p := range products { + price, err := fe.convertCurrency(r.Context(), p.GetPriceUsd(), currentCurrency(r)) + if err != nil { + renderHTTPError(log, r, w, errors.Wrapf(err, "failed to do currency conversion for product %s", p.GetId()), http.StatusInternalServerError) + return + } + ps[i] = productView{p, price} + } + + // Set ENV_PLATFORM (default to local if not set; use env var if set; otherwise detect GCP, which overrides env)_ + var env = os.Getenv("ENV_PLATFORM") + // Only override from env variable if set + valid env + if env == "" || stringinSlice(validEnvs, env) == false { + fmt.Println("env platform is either empty or invalid") + env = "local" + } + // Autodetect GCP + addrs, err := net.LookupHost("metadata.google.internal.") + if err == nil && len(addrs) >= 0 { + log.Debugf("Detected Google metadata server: %v, setting ENV_PLATFORM to GCP.", addrs) + env = "gcp" + } + + log.Debugf("ENV_PLATFORM is: %s", env) + plat = platformDetails{} + plat.setPlatformDetails(strings.ToLower(env)) + + if err := templates.ExecuteTemplate(w, "home", map[string]interface{}{ + "session_id": sessionID(r), + "request_id": r.Context().Value(ctxKeyRequestID{}), + "user_currency": currentCurrency(r), + "show_currency": true, + "currencies": currencies, + "products": ps, + "cart_size": cartSize(cart), + "banner_color": os.Getenv("BANNER_COLOR"), // illustrates canary deployments + "ad": fe.chooseAd(r.Context(), []string{}, log), + "platform_css": plat.css, + "platform_name": plat.provider, + "is_cymbal_brand": isCymbalBrand, + "deploymentDetails": deploymentDetailsMap, + "frontendMessage": frontendMessage, + }); err != nil { + log.Error(err) + } +} + +func (plat *platformDetails) setPlatformDetails(env string) { + if env == "aws" { + plat.provider = "AWS" + plat.css = "aws-platform" + } else if env == "onprem" { + plat.provider = "On-Premises" + plat.css = "onprem-platform" + } else if env == "azure" { + plat.provider = "Azure" + plat.css = "azure-platform" + } else if env == "gcp" { + plat.provider = "Google Cloud" + plat.css = "gcp-platform" + } else if env == "alibaba" { + plat.provider = "Alibaba Cloud" + plat.css = "alibaba-platform" + } else { + plat.provider = "local" + plat.css = "local" + } +} + +func (fe *frontendServer) productHandler(w http.ResponseWriter, r *http.Request) { + log := r.Context().Value(ctxKeyLog{}).(logrus.FieldLogger) + id := mux.Vars(r)["id"] + if id == "" { + renderHTTPError(log, r, w, errors.New("product id not specified"), http.StatusBadRequest) + return + } + log.WithField("id", id).WithField("currency", currentCurrency(r)). + Debug("serving product page") + + p, err := fe.getProduct(r.Context(), id) + if err != nil { + renderHTTPError(log, r, w, errors.Wrap(err, "could not retrieve product"), http.StatusInternalServerError) + return + } + currencies, err := fe.getCurrencies(r.Context()) + if err != nil { + renderHTTPError(log, r, w, errors.Wrap(err, "could not retrieve currencies"), http.StatusInternalServerError) + return + } + + cart, err := fe.getCart(r.Context(), sessionID(r)) + if err != nil { + renderHTTPError(log, r, w, errors.Wrap(err, "could not retrieve cart"), http.StatusInternalServerError) + return + } + + price, err := fe.convertCurrency(r.Context(), p.GetPriceUsd(), currentCurrency(r)) + if err != nil { + renderHTTPError(log, r, w, errors.Wrap(err, "failed to convert currency"), http.StatusInternalServerError) + return + } + + // ignores the error retrieving recommendations since it is not critical + recommendations, err := fe.getRecommendations(r.Context(), sessionID(r), []string{id}) + if err != nil { + log.WithField("error", err).Warn("failed to get product recommendations") + } + + product := struct { + Item *pb.Product + Price *pb.Money + }{p, price} + + if err := templates.ExecuteTemplate(w, "product", map[string]interface{}{ + "session_id": sessionID(r), + "request_id": r.Context().Value(ctxKeyRequestID{}), + "ad": fe.chooseAd(r.Context(), p.Categories, log), + "user_currency": currentCurrency(r), + "show_currency": true, + "currencies": currencies, + "product": product, + "recommendations": recommendations, + "cart_size": cartSize(cart), + "platform_css": plat.css, + "platform_name": plat.provider, + "is_cymbal_brand": isCymbalBrand, + "deploymentDetails": deploymentDetailsMap, + "frontendMessage": frontendMessage, + }); err != nil { + log.Println(err) + } +} + +func (fe *frontendServer) addToCartHandler(w http.ResponseWriter, r *http.Request) { + log := r.Context().Value(ctxKeyLog{}).(logrus.FieldLogger) + quantity, _ := strconv.ParseUint(r.FormValue("quantity"), 10, 32) + productID := r.FormValue("product_id") + if productID == "" || quantity == 0 { + renderHTTPError(log, r, w, errors.New("invalid form input"), http.StatusBadRequest) + return + } + log.WithField("product", productID).WithField("quantity", quantity).Debug("adding to cart") + + p, err := fe.getProduct(r.Context(), productID) + if err != nil { + renderHTTPError(log, r, w, errors.Wrap(err, "could not retrieve product"), http.StatusInternalServerError) + return + } + + if err := fe.insertCart(r.Context(), sessionID(r), p.GetId(), int32(quantity)); err != nil { + renderHTTPError(log, r, w, errors.Wrap(err, "failed to add to cart"), http.StatusInternalServerError) + return + } + w.Header().Set("location", "/cart") + w.WriteHeader(http.StatusFound) +} + +func (fe *frontendServer) emptyCartHandler(w http.ResponseWriter, r *http.Request) { + log := r.Context().Value(ctxKeyLog{}).(logrus.FieldLogger) + log.Debug("emptying cart") + + if err := fe.emptyCart(r.Context(), sessionID(r)); err != nil { + renderHTTPError(log, r, w, errors.Wrap(err, "failed to empty cart"), http.StatusInternalServerError) + return + } + w.Header().Set("location", "/") + w.WriteHeader(http.StatusFound) +} + +func (fe *frontendServer) viewCartHandler(w http.ResponseWriter, r *http.Request) { + log := r.Context().Value(ctxKeyLog{}).(logrus.FieldLogger) + log.Debug("view user cart") + currencies, err := fe.getCurrencies(r.Context()) + if err != nil { + renderHTTPError(log, r, w, errors.Wrap(err, "could not retrieve currencies"), http.StatusInternalServerError) + return + } + cart, err := fe.getCart(r.Context(), sessionID(r)) + if err != nil { + renderHTTPError(log, r, w, errors.Wrap(err, "could not retrieve cart"), http.StatusInternalServerError) + return + } + + // ignores the error retrieving recommendations since it is not critical + recommendations, err := fe.getRecommendations(r.Context(), sessionID(r), cartIDs(cart)) + if err != nil { + log.WithField("error", err).Warn("failed to get product recommendations") + } + + shippingCost, err := fe.getShippingQuote(r.Context(), cart, currentCurrency(r)) + if err != nil { + renderHTTPError(log, r, w, errors.Wrap(err, "failed to get shipping quote"), http.StatusInternalServerError) + return + } + + type cartItemView struct { + Item *pb.Product + Quantity int32 + Price *pb.Money + } + items := make([]cartItemView, len(cart)) + totalPrice := pb.Money{CurrencyCode: currentCurrency(r)} + for i, item := range cart { + p, err := fe.getProduct(r.Context(), item.GetProductId()) + if err != nil { + renderHTTPError(log, r, w, errors.Wrapf(err, "could not retrieve product #%s", item.GetProductId()), http.StatusInternalServerError) + return + } + price, err := fe.convertCurrency(r.Context(), p.GetPriceUsd(), currentCurrency(r)) + if err != nil { + renderHTTPError(log, r, w, errors.Wrapf(err, "could not convert currency for product #%s", item.GetProductId()), http.StatusInternalServerError) + return + } + + multPrice := money.MultiplySlow(*price, uint32(item.GetQuantity())) + items[i] = cartItemView{ + Item: p, + Quantity: item.GetQuantity(), + Price: &multPrice} + totalPrice = money.Must(money.Sum(totalPrice, multPrice)) + } + totalPrice = money.Must(money.Sum(totalPrice, *shippingCost)) + year := time.Now().Year() + + if err := templates.ExecuteTemplate(w, "cart", map[string]interface{}{ + "session_id": sessionID(r), + "request_id": r.Context().Value(ctxKeyRequestID{}), + "user_currency": currentCurrency(r), + "currencies": currencies, + "recommendations": recommendations, + "cart_size": cartSize(cart), + "shipping_cost": shippingCost, + "show_currency": true, + "total_cost": totalPrice, + "items": items, + "expiration_years": []int{year, year + 1, year + 2, year + 3, year + 4}, + "platform_css": plat.css, + "platform_name": plat.provider, + "is_cymbal_brand": isCymbalBrand, + "deploymentDetails": deploymentDetailsMap, + "frontendMessage": frontendMessage, + }); err != nil { + log.Println(err) + } +} + +func (fe *frontendServer) placeOrderHandler(w http.ResponseWriter, r *http.Request) { + log := r.Context().Value(ctxKeyLog{}).(logrus.FieldLogger) + log.Debug("placing order") + + var ( + email = r.FormValue("email") + streetAddress = r.FormValue("street_address") + zipCode, _ = strconv.ParseInt(r.FormValue("zip_code"), 10, 32) + city = r.FormValue("city") + state = r.FormValue("state") + country = r.FormValue("country") + ccNumber = r.FormValue("credit_card_number") + ccMonth, _ = strconv.ParseInt(r.FormValue("credit_card_expiration_month"), 10, 32) + ccYear, _ = strconv.ParseInt(r.FormValue("credit_card_expiration_year"), 10, 32) + ccCVV, _ = strconv.ParseInt(r.FormValue("credit_card_cvv"), 10, 32) + ) + + order, err := pb.NewCheckoutServiceClient(fe.checkoutSvcConn). + PlaceOrder(r.Context(), &pb.PlaceOrderRequest{ + Email: email, + CreditCard: &pb.CreditCardInfo{ + CreditCardNumber: ccNumber, + CreditCardExpirationMonth: int32(ccMonth), + CreditCardExpirationYear: int32(ccYear), + CreditCardCvv: int32(ccCVV)}, + UserId: sessionID(r), + UserCurrency: currentCurrency(r), + Address: &pb.Address{ + StreetAddress: streetAddress, + City: city, + State: state, + ZipCode: int32(zipCode), + Country: country}, + }) + if err != nil { + renderHTTPError(log, r, w, errors.Wrap(err, "failed to complete the order"), http.StatusInternalServerError) + return + } + log.WithField("order", order.GetOrder().GetOrderId()).Info("order placed") + + order.GetOrder().GetItems() + recommendations, _ := fe.getRecommendations(r.Context(), sessionID(r), nil) + + totalPaid := *order.GetOrder().GetShippingCost() + for _, v := range order.GetOrder().GetItems() { + multPrice := money.MultiplySlow(*v.GetCost(), uint32(v.GetItem().GetQuantity())) + totalPaid = money.Must(money.Sum(totalPaid, multPrice)) + } + + currencies, err := fe.getCurrencies(r.Context()) + if err != nil { + renderHTTPError(log, r, w, errors.Wrap(err, "could not retrieve currencies"), http.StatusInternalServerError) + return + } + + if err := templates.ExecuteTemplate(w, "order", map[string]interface{}{ + "session_id": sessionID(r), + "request_id": r.Context().Value(ctxKeyRequestID{}), + "user_currency": currentCurrency(r), + "show_currency": false, + "currencies": currencies, + "order": order.GetOrder(), + "total_paid": &totalPaid, + "recommendations": recommendations, + "platform_css": plat.css, + "platform_name": plat.provider, + "is_cymbal_brand": isCymbalBrand, + "deploymentDetails": deploymentDetailsMap, + "frontendMessage": frontendMessage, + }); err != nil { + log.Println(err) + } +} + +func (fe *frontendServer) logoutHandler(w http.ResponseWriter, r *http.Request) { + log := r.Context().Value(ctxKeyLog{}).(logrus.FieldLogger) + log.Debug("logging out") + for _, c := range r.Cookies() { + c.Expires = time.Now().Add(-time.Hour * 24 * 365) + c.MaxAge = -1 + http.SetCookie(w, c) + } + w.Header().Set("Location", "/") + w.WriteHeader(http.StatusFound) +} + +func (fe *frontendServer) setCurrencyHandler(w http.ResponseWriter, r *http.Request) { + log := r.Context().Value(ctxKeyLog{}).(logrus.FieldLogger) + cur := r.FormValue("currency_code") + log.WithField("curr.new", cur).WithField("curr.old", currentCurrency(r)). + Debug("setting currency") + + if cur != "" { + http.SetCookie(w, &http.Cookie{ + Name: cookieCurrency, + Value: cur, + MaxAge: cookieMaxAge, + }) + } + referer := r.Header.Get("referer") + if referer == "" { + referer = "/" + } + w.Header().Set("Location", referer) + w.WriteHeader(http.StatusFound) +} + +// chooseAd queries for advertisements available and randomly chooses one, if +// available. It ignores the error retrieving the ad since it is not critical. +func (fe *frontendServer) chooseAd(ctx context.Context, ctxKeys []string, log logrus.FieldLogger) *pb.Ad { + ads, err := fe.getAd(ctx, ctxKeys) + if err != nil { + log.WithField("error", err).Warn("failed to retrieve ads") + return nil + } + return ads[rand.Intn(len(ads))] +} + +func renderHTTPError(log logrus.FieldLogger, r *http.Request, w http.ResponseWriter, err error, code int) { + log.WithField("error", err).Error("request error") + errMsg := fmt.Sprintf("%+v", err) + + w.WriteHeader(code) + + if templateErr := templates.ExecuteTemplate(w, "error", map[string]interface{}{ + "session_id": sessionID(r), + "request_id": r.Context().Value(ctxKeyRequestID{}), + "error": errMsg, + "status_code": code, + "status": http.StatusText(code), + "is_cymbal_brand": isCymbalBrand, + "deploymentDetails": deploymentDetailsMap, + "frontendMessage": frontendMessage, + }); templateErr != nil { + log.Println(templateErr) + } +} + +func currentCurrency(r *http.Request) string { + c, _ := r.Cookie(cookieCurrency) + if c != nil { + return c.Value + } + return defaultCurrency +} + +func sessionID(r *http.Request) string { + v := r.Context().Value(ctxKeySessionID{}) + if v != nil { + return v.(string) + } + return "" +} + +func cartIDs(c []*pb.CartItem) []string { + out := make([]string, len(c)) + for i, v := range c { + out[i] = v.GetProductId() + } + return out +} + +// get total # of items in cart +func cartSize(c []*pb.CartItem) int { + cartSize := 0 + for _, item := range c { + cartSize += int(item.GetQuantity()) + } + return cartSize +} + +func renderMoney(money pb.Money) string { + currencyLogo := renderCurrencyLogo(money.GetCurrencyCode()) + return fmt.Sprintf("%s%d.%02d", currencyLogo, money.GetUnits(), money.GetNanos()/10000000) +} + +func renderCurrencyLogo(currencyCode string) string { + logos := map[string]string{ + "USD": "$", + "CAD": "$", + "JPY": "¥", + "EUR": "€", + "TRY": "₺", + "GBP": "£", + } + + logo := "$" //default + if val, ok := logos[currencyCode]; ok { + logo = val + } + return logo +} + +func stringinSlice(slice []string, val string) bool { + for _, item := range slice { + if item == val { + return true + } + } + return false +} diff --git a/main.go b/main.go new file mode 100644 index 00000000..068a467a --- /dev/null +++ b/main.go @@ -0,0 +1,224 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "context" + "fmt" + "net/http" + "os" + "time" + + "cloud.google.com/go/profiler" + "github.com/gorilla/mux" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" + "go.opentelemetry.io/otel/propagation" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + "google.golang.org/grpc" +) + +const ( + port = "8080" + defaultCurrency = "USD" + cookieMaxAge = 60 * 60 * 48 + + cookiePrefix = "shop_" + cookieSessionID = cookiePrefix + "session-id" + cookieCurrency = cookiePrefix + "currency" +) + +var ( + whitelistedCurrencies = map[string]bool{ + "USD": true, + "EUR": true, + "CAD": true, + "JPY": true, + "GBP": true, + "TRY": true} +) + +type ctxKeySessionID struct{} + +type frontendServer struct { + productCatalogSvcAddr string + productCatalogSvcConn *grpc.ClientConn + + currencySvcAddr string + currencySvcConn *grpc.ClientConn + + cartSvcAddr string + cartSvcConn *grpc.ClientConn + + recommendationSvcAddr string + recommendationSvcConn *grpc.ClientConn + + checkoutSvcAddr string + checkoutSvcConn *grpc.ClientConn + + shippingSvcAddr string + shippingSvcConn *grpc.ClientConn + + adSvcAddr string + adSvcConn *grpc.ClientConn + + collectorAddr string + collectorConn *grpc.ClientConn +} + +func main() { + ctx := context.Background() + log := logrus.New() + log.Level = logrus.DebugLevel + log.Formatter = &logrus.JSONFormatter{ + FieldMap: logrus.FieldMap{ + logrus.FieldKeyTime: "timestamp", + logrus.FieldKeyLevel: "severity", + logrus.FieldKeyMsg: "message", + }, + TimestampFormat: time.RFC3339Nano, + } + log.Out = os.Stdout + + svc := new(frontendServer) + + otel.SetTextMapPropagator( + propagation.NewCompositeTextMapPropagator( + propagation.TraceContext{}, propagation.Baggage{})) + + if os.Getenv("ENABLE_TRACING") == "1" { + log.Info("Tracing enabled.") + initTracing(log, ctx, svc) + } else { + log.Info("Tracing disabled.") + } + + if os.Getenv("ENABLE_PROFILER") == "1" { + log.Info("Profiling enabled.") + go initProfiling(log, "frontend", "1.0.0") + } else { + log.Info("Profiling disabled.") + } + + srvPort := port + if os.Getenv("PORT") != "" { + srvPort = os.Getenv("PORT") + } + addr := os.Getenv("LISTEN_ADDR") + mustMapEnv(&svc.productCatalogSvcAddr, "PRODUCT_CATALOG_SERVICE_ADDR") + mustMapEnv(&svc.currencySvcAddr, "CURRENCY_SERVICE_ADDR") + mustMapEnv(&svc.cartSvcAddr, "CART_SERVICE_ADDR") + mustMapEnv(&svc.recommendationSvcAddr, "RECOMMENDATION_SERVICE_ADDR") + mustMapEnv(&svc.checkoutSvcAddr, "CHECKOUT_SERVICE_ADDR") + mustMapEnv(&svc.shippingSvcAddr, "SHIPPING_SERVICE_ADDR") + mustMapEnv(&svc.adSvcAddr, "AD_SERVICE_ADDR") + + mustConnGRPC(ctx, &svc.currencySvcConn, svc.currencySvcAddr) + mustConnGRPC(ctx, &svc.productCatalogSvcConn, svc.productCatalogSvcAddr) + mustConnGRPC(ctx, &svc.cartSvcConn, svc.cartSvcAddr) + mustConnGRPC(ctx, &svc.recommendationSvcConn, svc.recommendationSvcAddr) + mustConnGRPC(ctx, &svc.shippingSvcConn, svc.shippingSvcAddr) + mustConnGRPC(ctx, &svc.checkoutSvcConn, svc.checkoutSvcAddr) + mustConnGRPC(ctx, &svc.adSvcConn, svc.adSvcAddr) + + r := mux.NewRouter() + r.HandleFunc("/", svc.homeHandler).Methods(http.MethodGet, http.MethodHead) + r.HandleFunc("/product/{id}", svc.productHandler).Methods(http.MethodGet, http.MethodHead) + r.HandleFunc("/cart", svc.viewCartHandler).Methods(http.MethodGet, http.MethodHead) + r.HandleFunc("/cart", svc.addToCartHandler).Methods(http.MethodPost) + r.HandleFunc("/cart/empty", svc.emptyCartHandler).Methods(http.MethodPost) + r.HandleFunc("/setCurrency", svc.setCurrencyHandler).Methods(http.MethodPost) + r.HandleFunc("/logout", svc.logoutHandler).Methods(http.MethodGet) + r.HandleFunc("/cart/checkout", svc.placeOrderHandler).Methods(http.MethodPost) + r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./static/")))) + r.HandleFunc("/robots.txt", func(w http.ResponseWriter, _ *http.Request) { fmt.Fprint(w, "User-agent: *\nDisallow: /") }) + r.HandleFunc("/_healthz", func(w http.ResponseWriter, _ *http.Request) { fmt.Fprint(w, "ok") }) + + var handler http.Handler = r + handler = &logHandler{log: log, next: handler} // add logging + handler = ensureSessionID(handler) // add session ID + handler = otelhttp.NewHandler(handler, "frontend") // add OTel tracing + + log.Infof("starting server on " + addr + ":" + srvPort) + log.Fatal(http.ListenAndServe(addr+":"+srvPort, handler)) +} +func initStats(log logrus.FieldLogger) { + // TODO(arbrown) Implement OpenTelemtry stats +} + +func initTracing(log logrus.FieldLogger, ctx context.Context, svc *frontendServer) (*sdktrace.TracerProvider, error) { + mustMapEnv(&svc.collectorAddr, "COLLECTOR_SERVICE_ADDR") + mustConnGRPC(ctx, &svc.collectorConn, svc.collectorAddr) + exporter, err := otlptracegrpc.New( + ctx, + otlptracegrpc.WithGRPCConn(svc.collectorConn)) + if err != nil { + log.Warnf("warn: Failed to create trace exporter: %v", err) + } + tp := sdktrace.NewTracerProvider( + sdktrace.WithBatcher(exporter), + sdktrace.WithSampler(sdktrace.AlwaysSample())) + otel.SetTracerProvider(tp) + + return tp, err +} + +func initProfiling(log logrus.FieldLogger, service, version string) { + // TODO(ahmetb) this method is duplicated in other microservices using Go + // since they are not sharing packages. + for i := 1; i <= 3; i++ { + log = log.WithField("retry", i) + if err := profiler.Start(profiler.Config{ + Service: service, + ServiceVersion: version, + // ProjectID must be set if not running on GCP. + // ProjectID: "my-project", + }); err != nil { + log.Warnf("warn: failed to start profiler: %+v", err) + } else { + log.Info("started Stackdriver profiler") + return + } + d := time.Second * 10 * time.Duration(i) + log.Debugf("sleeping %v to retry initializing Stackdriver profiler", d) + time.Sleep(d) + } + log.Warn("warning: could not initialize Stackdriver profiler after retrying, giving up") +} + +func mustMapEnv(target *string, envKey string) { + v := os.Getenv(envKey) + if v == "" { + panic(fmt.Sprintf("environment variable %q not set", envKey)) + } + *target = v +} + +func mustConnGRPC(ctx context.Context, conn **grpc.ClientConn, addr string) { + var err error + ctx, cancel := context.WithTimeout(ctx, time.Second*3) + defer cancel() + *conn, err = grpc.DialContext(ctx, addr, + grpc.WithInsecure(), + grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()), + grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor())) + if err != nil { + panic(errors.Wrapf(err, "grpc: failed to connect %s", addr)) + } +} diff --git a/middleware.go b/middleware.go new file mode 100644 index 00000000..e6b237b6 --- /dev/null +++ b/middleware.go @@ -0,0 +1,111 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "context" + "net/http" + "time" + "os" + + "github.com/google/uuid" + "github.com/sirupsen/logrus" +) + +type ctxKeyLog struct{} +type ctxKeyRequestID struct{} + +type logHandler struct { + log *logrus.Logger + next http.Handler +} + +type responseRecorder struct { + b int + status int + w http.ResponseWriter +} + +func (r *responseRecorder) Header() http.Header { return r.w.Header() } + +func (r *responseRecorder) Write(p []byte) (int, error) { + if r.status == 0 { + r.status = http.StatusOK + } + n, err := r.w.Write(p) + r.b += n + return n, err +} + +func (r *responseRecorder) WriteHeader(statusCode int) { + r.status = statusCode + r.w.WriteHeader(statusCode) +} + +func (lh *logHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + requestID, _ := uuid.NewRandom() + ctx = context.WithValue(ctx, ctxKeyRequestID{}, requestID.String()) + + start := time.Now() + rr := &responseRecorder{w: w} + log := lh.log.WithFields(logrus.Fields{ + "http.req.path": r.URL.Path, + "http.req.method": r.Method, + "http.req.id": requestID.String(), + }) + if v, ok := r.Context().Value(ctxKeySessionID{}).(string); ok { + log = log.WithField("session", v) + } + log.Debug("request started") + defer func() { + log.WithFields(logrus.Fields{ + "http.resp.took_ms": int64(time.Since(start) / time.Millisecond), + "http.resp.status": rr.status, + "http.resp.bytes": rr.b}).Debugf("request complete") + }() + + ctx = context.WithValue(ctx, ctxKeyLog{}, log) + r = r.WithContext(ctx) + lh.next.ServeHTTP(rr, r) +} + +func ensureSessionID(next http.Handler) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var sessionID string + c, err := r.Cookie(cookieSessionID) + if err == http.ErrNoCookie { + if os.Getenv("ENABLE_SINGLE_SHARED_SESSION") == "true" { + // Hard coded user id, shared across sessions + sessionID = "12345678-1234-1234-1234-123456789123" + } else { + u, _ := uuid.NewRandom() + sessionID = u.String() + } + http.SetCookie(w, &http.Cookie{ + Name: cookieSessionID, + Value: sessionID, + MaxAge: cookieMaxAge, + }) + } else if err != nil { + return + } else { + sessionID = c.Value + } + ctx := context.WithValue(r.Context(), ctxKeySessionID{}, sessionID) + r = r.WithContext(ctx) + next.ServeHTTP(w, r) + } +} diff --git a/money/money.go b/money/money.go new file mode 100644 index 00000000..f1bcc2fe --- /dev/null +++ b/money/money.go @@ -0,0 +1,132 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package money + +import ( + "errors" + + pb "github.com/GoogleCloudPlatform/microservices-demo/src/frontend/genproto" +) + +const ( + nanosMin = -999999999 + nanosMax = +999999999 + nanosMod = 1000000000 +) + +var ( + ErrInvalidValue = errors.New("one of the specified money values is invalid") + ErrMismatchingCurrency = errors.New("mismatching currency codes") +) + +// IsValid checks if specified value has a valid units/nanos signs and ranges. +func IsValid(m pb.Money) bool { + return signMatches(m) && validNanos(m.GetNanos()) +} + +func signMatches(m pb.Money) bool { + return m.GetNanos() == 0 || m.GetUnits() == 0 || (m.GetNanos() < 0) == (m.GetUnits() < 0) +} + +func validNanos(nanos int32) bool { return nanosMin <= nanos && nanos <= nanosMax } + +// IsZero returns true if the specified money value is equal to zero. +func IsZero(m pb.Money) bool { return m.GetUnits() == 0 && m.GetNanos() == 0 } + +// IsPositive returns true if the specified money value is valid and is +// positive. +func IsPositive(m pb.Money) bool { + return IsValid(m) && m.GetUnits() > 0 || (m.GetUnits() == 0 && m.GetNanos() > 0) +} + +// IsNegative returns true if the specified money value is valid and is +// negative. +func IsNegative(m pb.Money) bool { + return IsValid(m) && m.GetUnits() < 0 || (m.GetUnits() == 0 && m.GetNanos() < 0) +} + +// AreSameCurrency returns true if values l and r have a currency code and +// they are the same values. +func AreSameCurrency(l, r pb.Money) bool { + return l.GetCurrencyCode() == r.GetCurrencyCode() && l.GetCurrencyCode() != "" +} + +// AreEquals returns true if values l and r are the equal, including the +// currency. This does not check validity of the provided values. +func AreEquals(l, r pb.Money) bool { + return l.GetCurrencyCode() == r.GetCurrencyCode() && + l.GetUnits() == r.GetUnits() && l.GetNanos() == r.GetNanos() +} + +// Negate returns the same amount with the sign negated. +func Negate(m pb.Money) pb.Money { + return pb.Money{ + Units: -m.GetUnits(), + Nanos: -m.GetNanos(), + CurrencyCode: m.GetCurrencyCode()} +} + +// Must panics if the given error is not nil. This can be used with other +// functions like: "m := Must(Sum(a,b))". +func Must(v pb.Money, err error) pb.Money { + if err != nil { + panic(err) + } + return v +} + +// Sum adds two values. Returns an error if one of the values are invalid or +// currency codes are not matching (unless currency code is unspecified for +// both). +func Sum(l, r pb.Money) (pb.Money, error) { + if !IsValid(l) || !IsValid(r) { + return pb.Money{}, ErrInvalidValue + } else if l.GetCurrencyCode() != r.GetCurrencyCode() { + return pb.Money{}, ErrMismatchingCurrency + } + units := l.GetUnits() + r.GetUnits() + nanos := l.GetNanos() + r.GetNanos() + + if (units == 0 && nanos == 0) || (units > 0 && nanos >= 0) || (units < 0 && nanos <= 0) { + // same sign + units += int64(nanos / nanosMod) + nanos = nanos % nanosMod + } else { + // different sign. nanos guaranteed to not to go over the limit + if units > 0 { + units-- + nanos += nanosMod + } else { + units++ + nanos -= nanosMod + } + } + + return pb.Money{ + Units: units, + Nanos: nanos, + CurrencyCode: l.GetCurrencyCode()}, nil +} + +// MultiplySlow is a slow multiplication operation done through adding the value +// to itself n-1 times. +func MultiplySlow(m pb.Money, n uint32) pb.Money { + out := m + for n > 1 { + out = Must(Sum(out, m)) + n-- + } + return out +} diff --git a/money/money_test.go b/money/money_test.go new file mode 100644 index 00000000..643e38ff --- /dev/null +++ b/money/money_test.go @@ -0,0 +1,245 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package money + +import ( + "fmt" + "reflect" + "testing" + + pb "github.com/GoogleCloudPlatform/microservices-demo/src/frontend/genproto" +) + +func mmc(u int64, n int32, c string) pb.Money { return pb.Money{Units: u, Nanos: n, CurrencyCode: c} } +func mm(u int64, n int32) pb.Money { return mmc(u, n, "") } + +func TestIsValid(t *testing.T) { + tests := []struct { + name string + in pb.Money + want bool + }{ + {"valid -/-", mm(-981273891273, -999999999), true}, + {"invalid -/+", mm(-981273891273, +999999999), false}, + {"valid +/+", mm(981273891273, 999999999), true}, + {"invalid +/-", mm(981273891273, -999999999), false}, + {"invalid +/+overflow", mm(3, 1000000000), false}, + {"invalid +/-overflow", mm(3, -1000000000), false}, + {"invalid -/+overflow", mm(-3, 1000000000), false}, + {"invalid -/-overflow", mm(-3, -1000000000), false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := IsValid(tt.in); got != tt.want { + t.Errorf("IsValid(%v) = %v, want %v", tt.in, got, tt.want) + } + }) + } +} + +func TestIsZero(t *testing.T) { + tests := []struct { + name string + in pb.Money + want bool + }{ + {"zero", mm(0, 0), true}, + {"not-zero (-/+)", mm(-1, +1), false}, + {"not-zero (-/-)", mm(-1, -1), false}, + {"not-zero (+/+)", mm(+1, +1), false}, + {"not-zero (+/-)", mm(+1, -1), false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := IsZero(tt.in); got != tt.want { + t.Errorf("IsZero(%v) = %v, want %v", tt.in, got, tt.want) + } + }) + } +} + +func TestIsPositive(t *testing.T) { + tests := []struct { + name string + in pb.Money + want bool + }{ + {"zero", mm(0, 0), false}, + {"positive (+/+)", mm(+1, +1), true}, + {"invalid (-/+)", mm(-1, +1), false}, + {"negative (-/-)", mm(-1, -1), false}, + {"invalid (+/-)", mm(+1, -1), false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := IsPositive(tt.in); got != tt.want { + t.Errorf("IsPositive(%v) = %v, want %v", tt.in, got, tt.want) + } + }) + } +} + +func TestIsNegative(t *testing.T) { + tests := []struct { + name string + in pb.Money + want bool + }{ + {"zero", mm(0, 0), false}, + {"positive (+/+)", mm(+1, +1), false}, + {"invalid (-/+)", mm(-1, +1), false}, + {"negative (-/-)", mm(-1, -1), true}, + {"invalid (+/-)", mm(+1, -1), false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := IsNegative(tt.in); got != tt.want { + t.Errorf("IsNegative(%v) = %v, want %v", tt.in, got, tt.want) + } + }) + } +} + +func TestAreSameCurrency(t *testing.T) { + type args struct { + l pb.Money + r pb.Money + } + tests := []struct { + name string + args args + want bool + }{ + {"both empty currency", args{mmc(1, 0, ""), mmc(2, 0, "")}, false}, + {"left empty currency", args{mmc(1, 0, ""), mmc(2, 0, "USD")}, false}, + {"right empty currency", args{mmc(1, 0, "USD"), mmc(2, 0, "")}, false}, + {"mismatching", args{mmc(1, 0, "USD"), mmc(2, 0, "CAD")}, false}, + {"matching", args{mmc(1, 0, "USD"), mmc(2, 0, "USD")}, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := AreSameCurrency(tt.args.l, tt.args.r); got != tt.want { + t.Errorf("AreSameCurrency([%v],[%v]) = %v, want %v", tt.args.l, tt.args.r, got, tt.want) + } + }) + } +} + +func TestAreEquals(t *testing.T) { + type args struct { + l pb.Money + r pb.Money + } + tests := []struct { + name string + args args + want bool + }{ + {"equals", args{mmc(1, 2, "USD"), mmc(1, 2, "USD")}, true}, + {"mismatching currency", args{mmc(1, 2, "USD"), mmc(1, 2, "CAD")}, false}, + {"mismatching units", args{mmc(10, 20, "USD"), mmc(1, 20, "USD")}, false}, + {"mismatching nanos", args{mmc(1, 2, "USD"), mmc(1, 20, "USD")}, false}, + {"negated", args{mmc(1, 2, "USD"), mmc(-1, -2, "USD")}, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := AreEquals(tt.args.l, tt.args.r); got != tt.want { + t.Errorf("AreEquals([%v],[%v]) = %v, want %v", tt.args.l, tt.args.r, got, tt.want) + } + }) + } +} + +func TestNegate(t *testing.T) { + tests := []struct { + name string + in pb.Money + want pb.Money + }{ + {"zero", mm(0, 0), mm(0, 0)}, + {"negative", mm(-1, -200), mm(1, 200)}, + {"positive", mm(1, 200), mm(-1, -200)}, + {"carries currency code", mmc(0, 0, "XXX"), mmc(0, 0, "XXX")}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := Negate(tt.in); !AreEquals(got, tt.want) { + t.Errorf("Negate([%v]) = %v, want %v", tt.in, got, tt.want) + } + }) + } +} + +func TestMust_pass(t *testing.T) { + v := Must(mm(2, 3), nil) + if !AreEquals(v, mm(2, 3)) { + t.Errorf("returned the wrong value: %v", v) + } +} + +func TestMust_panic(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Logf("panic captured: %v", r) + } + }() + Must(mm(2, 3), fmt.Errorf("some error")) + t.Fatal("this should not have executed due to the panic above") +} + +func TestSum(t *testing.T) { + type args struct { + l pb.Money + r pb.Money + } + tests := []struct { + name string + args args + want pb.Money + wantErr error + }{ + {"0+0=0", args{mm(0, 0), mm(0, 0)}, mm(0, 0), nil}, + {"Error: currency code on left", args{mmc(0, 0, "XXX"), mm(0, 0)}, mm(0, 0), ErrMismatchingCurrency}, + {"Error: currency code on right", args{mm(0, 0), mmc(0, 0, "YYY")}, mm(0, 0), ErrMismatchingCurrency}, + {"Error: currency code mismatch", args{mmc(0, 0, "AAA"), mmc(0, 0, "BBB")}, mm(0, 0), ErrMismatchingCurrency}, + {"Error: invalid +/-", args{mm(+1, -1), mm(0, 0)}, mm(0, 0), ErrInvalidValue}, + {"Error: invalid -/+", args{mm(0, 0), mm(-1, +2)}, mm(0, 0), ErrInvalidValue}, + {"Error: invalid nanos", args{mm(0, 1000000000), mm(1, 0)}, mm(0, 0), ErrInvalidValue}, + {"both positive (no carry)", args{mm(2, 200000000), mm(2, 200000000)}, mm(4, 400000000), nil}, + {"both positive (nanos=max)", args{mm(2, 111111111), mm(2, 888888888)}, mm(4, 999999999), nil}, + {"both positive (carry)", args{mm(2, 200000000), mm(2, 900000000)}, mm(5, 100000000), nil}, + {"both negative (no carry)", args{mm(-2, -200000000), mm(-2, -200000000)}, mm(-4, -400000000), nil}, + {"both negative (carry)", args{mm(-2, -200000000), mm(-2, -900000000)}, mm(-5, -100000000), nil}, + {"mixed (larger positive, just decimals)", args{mm(11, 0), mm(-2, 0)}, mm(9, 0), nil}, + {"mixed (larger negative, just decimals)", args{mm(-11, 0), mm(2, 0)}, mm(-9, 0), nil}, + {"mixed (larger positive, no borrow)", args{mm(11, 100000000), mm(-2, -100000000)}, mm(9, 0), nil}, + {"mixed (larger positive, with borrow)", args{mm(11, 100000000), mm(-2, -9000000 /*.09*/)}, mm(9, 91000000 /*.091*/), nil}, + {"mixed (larger negative, no borrow)", args{mm(-11, -100000000), mm(2, 100000000)}, mm(-9, 0), nil}, + {"mixed (larger negative, with borrow)", args{mm(-11, -100000000), mm(2, 9000000 /*.09*/)}, mm(-9, -91000000 /*.091*/), nil}, + {"0+negative", args{mm(0, 0), mm(-2, -100000000)}, mm(-2, -100000000), nil}, + {"negative+0", args{mm(-2, -100000000), mm(0, 0)}, mm(-2, -100000000), nil}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := Sum(tt.args.l, tt.args.r) + if err != tt.wantErr { + t.Errorf("Sum([%v],[%v]): expected err=\"%v\" got=\"%v\"", tt.args.l, tt.args.r, tt.wantErr, err) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Sum([%v],[%v]) = %v, want %v", tt.args.l, tt.args.r, got, tt.want) + } + }) + } +} diff --git a/rpc.go b/rpc.go new file mode 100644 index 00000000..a1dd3138 --- /dev/null +++ b/rpc.go @@ -0,0 +1,127 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "context" + "time" + + pb "github.com/GoogleCloudPlatform/microservices-demo/src/frontend/genproto" + + "github.com/pkg/errors" +) + +const ( + avoidNoopCurrencyConversionRPC = false +) + +func (fe *frontendServer) getCurrencies(ctx context.Context) ([]string, error) { + currs, err := pb.NewCurrencyServiceClient(fe.currencySvcConn). + GetSupportedCurrencies(ctx, &pb.Empty{}) + if err != nil { + return nil, err + } + var out []string + for _, c := range currs.CurrencyCodes { + if _, ok := whitelistedCurrencies[c]; ok { + out = append(out, c) + } + } + return out, nil +} + +func (fe *frontendServer) getProducts(ctx context.Context) ([]*pb.Product, error) { + resp, err := pb.NewProductCatalogServiceClient(fe.productCatalogSvcConn). + ListProducts(ctx, &pb.Empty{}) + return resp.GetProducts(), err +} + +func (fe *frontendServer) getProduct(ctx context.Context, id string) (*pb.Product, error) { + resp, err := pb.NewProductCatalogServiceClient(fe.productCatalogSvcConn). + GetProduct(ctx, &pb.GetProductRequest{Id: id}) + return resp, err +} + +func (fe *frontendServer) getCart(ctx context.Context, userID string) ([]*pb.CartItem, error) { + resp, err := pb.NewCartServiceClient(fe.cartSvcConn).GetCart(ctx, &pb.GetCartRequest{UserId: userID}) + return resp.GetItems(), err +} + +func (fe *frontendServer) emptyCart(ctx context.Context, userID string) error { + _, err := pb.NewCartServiceClient(fe.cartSvcConn).EmptyCart(ctx, &pb.EmptyCartRequest{UserId: userID}) + return err +} + +func (fe *frontendServer) insertCart(ctx context.Context, userID, productID string, quantity int32) error { + _, err := pb.NewCartServiceClient(fe.cartSvcConn).AddItem(ctx, &pb.AddItemRequest{ + UserId: userID, + Item: &pb.CartItem{ + ProductId: productID, + Quantity: quantity}, + }) + return err +} + +func (fe *frontendServer) convertCurrency(ctx context.Context, money *pb.Money, currency string) (*pb.Money, error) { + if avoidNoopCurrencyConversionRPC && money.GetCurrencyCode() == currency { + return money, nil + } + return pb.NewCurrencyServiceClient(fe.currencySvcConn). + Convert(ctx, &pb.CurrencyConversionRequest{ + From: money, + ToCode: currency}) +} + +func (fe *frontendServer) getShippingQuote(ctx context.Context, items []*pb.CartItem, currency string) (*pb.Money, error) { + quote, err := pb.NewShippingServiceClient(fe.shippingSvcConn).GetQuote(ctx, + &pb.GetQuoteRequest{ + Address: nil, + Items: items}) + if err != nil { + return nil, err + } + localized, err := fe.convertCurrency(ctx, quote.GetCostUsd(), currency) + return localized, errors.Wrap(err, "failed to convert currency for shipping cost") +} + +func (fe *frontendServer) getRecommendations(ctx context.Context, userID string, productIDs []string) ([]*pb.Product, error) { + resp, err := pb.NewRecommendationServiceClient(fe.recommendationSvcConn).ListRecommendations(ctx, + &pb.ListRecommendationsRequest{UserId: userID, ProductIds: productIDs}) + if err != nil { + return nil, err + } + out := make([]*pb.Product, len(resp.GetProductIds())) + for i, v := range resp.GetProductIds() { + p, err := fe.getProduct(ctx, v) + if err != nil { + return nil, errors.Wrapf(err, "failed to get recommended product info (#%s)", v) + } + out[i] = p + } + if len(out) > 4 { + out = out[:4] // take only first four to fit the UI + } + return out, err +} + +func (fe *frontendServer) getAd(ctx context.Context, ctxKeys []string) ([]*pb.Ad, error) { + ctx, cancel := context.WithTimeout(ctx, time.Millisecond*100) + defer cancel() + + resp, err := pb.NewAdServiceClient(fe.adSvcConn).GetAds(ctx, &pb.AdRequest{ + ContextKeys: ctxKeys, + }) + return resp.GetAds(), errors.Wrap(err, "failed to get ads") +} diff --git a/static/favicon-cymbal.ico b/static/favicon-cymbal.ico new file mode 100644 index 00000000..6f8ea031 Binary files /dev/null and b/static/favicon-cymbal.ico differ diff --git a/static/favicon.ico b/static/favicon.ico new file mode 100644 index 00000000..f17be683 Binary files /dev/null and b/static/favicon.ico differ diff --git a/static/icons/Cymbal_NavLogo.svg b/static/icons/Cymbal_NavLogo.svg new file mode 100644 index 00000000..7edd507c --- /dev/null +++ b/static/icons/Cymbal_NavLogo.svg @@ -0,0 +1,170 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/icons/Hipster_Advert2.svg b/static/icons/Hipster_Advert2.svg new file mode 100644 index 00000000..94f56630 --- /dev/null +++ b/static/icons/Hipster_Advert2.svg @@ -0,0 +1 @@ +Hipster \ No newline at end of file diff --git a/static/icons/Hipster_CartIcon.svg b/static/icons/Hipster_CartIcon.svg new file mode 100644 index 00000000..9c9cb1fa --- /dev/null +++ b/static/icons/Hipster_CartIcon.svg @@ -0,0 +1,69 @@ + + + + + + image/svg+xml + + Hipster + + + + + + + + Hipster + + + + diff --git a/static/icons/Hipster_CheckOutIcon.svg b/static/icons/Hipster_CheckOutIcon.svg new file mode 100644 index 00000000..cd0faa1d --- /dev/null +++ b/static/icons/Hipster_CheckOutIcon.svg @@ -0,0 +1 @@ +Hipster \ No newline at end of file diff --git a/static/icons/Hipster_CurrencyIcon.svg b/static/icons/Hipster_CurrencyIcon.svg new file mode 100644 index 00000000..05197177 --- /dev/null +++ b/static/icons/Hipster_CurrencyIcon.svg @@ -0,0 +1 @@ +Hipster \ No newline at end of file diff --git a/static/icons/Hipster_DownArrow.svg b/static/icons/Hipster_DownArrow.svg new file mode 100644 index 00000000..39730089 --- /dev/null +++ b/static/icons/Hipster_DownArrow.svg @@ -0,0 +1,63 @@ + + + + + + image/svg+xml + + + + + + + + + Hipster + + + + diff --git a/static/icons/Hipster_FacebookIcon.svg b/static/icons/Hipster_FacebookIcon.svg new file mode 100644 index 00000000..41093ad6 --- /dev/null +++ b/static/icons/Hipster_FacebookIcon.svg @@ -0,0 +1 @@ +Hipster \ No newline at end of file diff --git a/static/icons/Hipster_GooglePlayIcon.svg b/static/icons/Hipster_GooglePlayIcon.svg new file mode 100644 index 00000000..128e761a --- /dev/null +++ b/static/icons/Hipster_GooglePlayIcon.svg @@ -0,0 +1 @@ +Hipster \ No newline at end of file diff --git a/static/icons/Hipster_HelpIcon.svg b/static/icons/Hipster_HelpIcon.svg new file mode 100644 index 00000000..3d50868c --- /dev/null +++ b/static/icons/Hipster_HelpIcon.svg @@ -0,0 +1 @@ +Hipster \ No newline at end of file diff --git a/static/icons/Hipster_HeroLogo.svg b/static/icons/Hipster_HeroLogo.svg new file mode 100644 index 00000000..203d4c1d --- /dev/null +++ b/static/icons/Hipster_HeroLogo.svg @@ -0,0 +1 @@ +Hipster \ No newline at end of file diff --git a/static/icons/Hipster_HeroLogoMaroon.svg b/static/icons/Hipster_HeroLogoMaroon.svg new file mode 100644 index 00000000..e031bcc7 --- /dev/null +++ b/static/icons/Hipster_HeroLogoMaroon.svg @@ -0,0 +1,235 @@ + + + + + + + Hipster + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Hipster + + + + diff --git a/static/icons/Hipster_InstagramIcon.svg b/static/icons/Hipster_InstagramIcon.svg new file mode 100644 index 00000000..1927fb49 --- /dev/null +++ b/static/icons/Hipster_InstagramIcon.svg @@ -0,0 +1 @@ +Hipster \ No newline at end of file diff --git a/static/icons/Hipster_KitchenwareOffer.svg b/static/icons/Hipster_KitchenwareOffer.svg new file mode 100644 index 00000000..4f5a3dba --- /dev/null +++ b/static/icons/Hipster_KitchenwareOffer.svg @@ -0,0 +1 @@ +Hipster \ No newline at end of file diff --git a/static/icons/Hipster_NavLogo.svg b/static/icons/Hipster_NavLogo.svg new file mode 100644 index 00000000..d2ad82cf --- /dev/null +++ b/static/icons/Hipster_NavLogo.svg @@ -0,0 +1,149 @@ + + + + + + image/svg+xml + + Hipster + + + + + + + + Hipster + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/icons/Hipster_PinterestIcon.svg b/static/icons/Hipster_PinterestIcon.svg new file mode 100644 index 00000000..e24bfd7f --- /dev/null +++ b/static/icons/Hipster_PinterestIcon.svg @@ -0,0 +1 @@ +Hipster \ No newline at end of file diff --git a/static/icons/Hipster_ProfileIcon.svg b/static/icons/Hipster_ProfileIcon.svg new file mode 100644 index 00000000..7d043a76 --- /dev/null +++ b/static/icons/Hipster_ProfileIcon.svg @@ -0,0 +1 @@ +Hipster \ No newline at end of file diff --git a/static/icons/Hipster_SearchIcon.svg b/static/icons/Hipster_SearchIcon.svg new file mode 100644 index 00000000..36f894dd --- /dev/null +++ b/static/icons/Hipster_SearchIcon.svg @@ -0,0 +1 @@ +Hipster \ No newline at end of file diff --git a/static/icons/Hipster_TwitterIcon.svg b/static/icons/Hipster_TwitterIcon.svg new file mode 100644 index 00000000..276b3c66 --- /dev/null +++ b/static/icons/Hipster_TwitterIcon.svg @@ -0,0 +1 @@ +Hipster \ No newline at end of file diff --git a/static/icons/Hipster_UpDownControl.svg b/static/icons/Hipster_UpDownControl.svg new file mode 100644 index 00000000..be33647e --- /dev/null +++ b/static/icons/Hipster_UpDownControl.svg @@ -0,0 +1,7 @@ + + Hipster + + + + + diff --git a/static/icons/Hipster_YoutubeIcon.svg b/static/icons/Hipster_YoutubeIcon.svg new file mode 100644 index 00000000..3d018e3c --- /dev/null +++ b/static/icons/Hipster_YoutubeIcon.svg @@ -0,0 +1 @@ +Hipster \ No newline at end of file diff --git a/static/images/Advert2BannerImage.png b/static/images/Advert2BannerImage.png new file mode 100644 index 00000000..ae012759 Binary files /dev/null and b/static/images/Advert2BannerImage.png differ diff --git a/static/images/AdvertBannerImage.png b/static/images/AdvertBannerImage.png new file mode 100644 index 00000000..c0b5b3ad Binary files /dev/null and b/static/images/AdvertBannerImage.png differ diff --git a/static/images/HeroBannerImage.png b/static/images/HeroBannerImage.png new file mode 100644 index 00000000..69e9a341 Binary files /dev/null and b/static/images/HeroBannerImage.png differ diff --git a/static/images/HeroBannerImage2.png b/static/images/HeroBannerImage2.png new file mode 100644 index 00000000..26c4cef0 Binary files /dev/null and b/static/images/HeroBannerImage2.png differ diff --git a/static/images/VRHeadsets.png b/static/images/VRHeadsets.png new file mode 100644 index 00000000..dc9a9f3e Binary files /dev/null and b/static/images/VRHeadsets.png differ diff --git a/static/images/credits.txt b/static/images/credits.txt new file mode 100644 index 00000000..0ca9cead --- /dev/null +++ b/static/images/credits.txt @@ -0,0 +1,2 @@ +folded-clothes-on-white-chair.jpg,,https://unsplash.com/photos/fr0J5-GIVyg +folded-clothes-on-white-chair-wide.jpg,,https://unsplash.com/photos/fr0J5-GIVyg diff --git a/static/images/folded-clothes-on-white-chair-wide.jpg b/static/images/folded-clothes-on-white-chair-wide.jpg new file mode 100644 index 00000000..c675194e Binary files /dev/null and b/static/images/folded-clothes-on-white-chair-wide.jpg differ diff --git a/static/images/folded-clothes-on-white-chair.jpg b/static/images/folded-clothes-on-white-chair.jpg new file mode 100644 index 00000000..23948b71 Binary files /dev/null and b/static/images/folded-clothes-on-white-chair.jpg differ diff --git a/static/img/products/bamboo-glass-jar.jpg b/static/img/products/bamboo-glass-jar.jpg new file mode 100644 index 00000000..a897f19e Binary files /dev/null and b/static/img/products/bamboo-glass-jar.jpg differ diff --git a/static/img/products/candle-holder.jpg b/static/img/products/candle-holder.jpg new file mode 100644 index 00000000..e3e2789c Binary files /dev/null and b/static/img/products/candle-holder.jpg differ diff --git a/static/img/products/hairdryer.jpg b/static/img/products/hairdryer.jpg new file mode 100644 index 00000000..5b4db418 Binary files /dev/null and b/static/img/products/hairdryer.jpg differ diff --git a/static/img/products/loafers.jpg b/static/img/products/loafers.jpg new file mode 100644 index 00000000..f14c1966 Binary files /dev/null and b/static/img/products/loafers.jpg differ diff --git a/static/img/products/mug.jpg b/static/img/products/mug.jpg new file mode 100644 index 00000000..36420367 Binary files /dev/null and b/static/img/products/mug.jpg differ diff --git a/static/img/products/salt-and-pepper-shakers.jpg b/static/img/products/salt-and-pepper-shakers.jpg new file mode 100644 index 00000000..b81264e8 Binary files /dev/null and b/static/img/products/salt-and-pepper-shakers.jpg differ diff --git a/static/img/products/sunglasses.jpg b/static/img/products/sunglasses.jpg new file mode 100644 index 00000000..f31b153f Binary files /dev/null and b/static/img/products/sunglasses.jpg differ diff --git a/static/img/products/tank-top.jpg b/static/img/products/tank-top.jpg new file mode 100644 index 00000000..2e3baa09 Binary files /dev/null and b/static/img/products/tank-top.jpg differ diff --git a/static/img/products/watch.jpg b/static/img/products/watch.jpg new file mode 100644 index 00000000..71f0c11a Binary files /dev/null and b/static/img/products/watch.jpg differ diff --git a/static/styles/cart.css b/static/styles/cart.css new file mode 100644 index 00000000..cb506cfd --- /dev/null +++ b/static/styles/cart.css @@ -0,0 +1,110 @@ +/** + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.cart-sections { + padding-bottom: 120px; + padding-top: 56px; + background-color: #F9F9F9; +} + +.cart-sections h3 { + font-size: 36px; + font-weight: normal; +} + +.cart-sections a.cymbal-button-primary:hover { + text-decoration: none; + color: white; +} + +/* Empty Cart Section */ + +.empty-cart-section { + max-width: 458px; + margin: auto; + text-align: center; +} + +.empty-cart-section a { + display: inline-block; /* So margin-top works. */ + margin-top: 32px; +} + +.empty-cart-section a:hover { + color: white; + text-decoration: none; +} + +/* Cart Summary Section */ + +.cart-summary-empty-cart-button { + margin-right: 10px; +} + +.cart-summary-item-row, +.cart-summary-shipping-row, +.cart-summary-total-row { + padding-bottom: 24px; + padding-top: 24px; + border-top: solid 1px rgba(154, 160, 166, 0.5); +} + +.cart-summary-item-row img { + border-radius: 20% 0 20% 20%; +} + +.cart-summary-item-row-item-id-row { + font-size: 12px; + color: #5C6063; +} + +.cart-summary-item-row h4 { + font-size: 18px; + font-weight: normal; +} + +/* Stick item quantity and cost to the bottom (for wider screens). */ +@media (min-width: 768px) { + .cart-summary-item-row .row:last-child { + position: absolute; + bottom: 0px; + width: 100%; + } +} + +/* Item cost (price). */ +.cart-summary-item-row .row:last-child strong { + font-weight: 500; +} + +.cart-summary-total-row { + font-size: 28px; +} + +/* Cart Checkout Form */ + +.cart-checkout-form h3 { + margin-bottom: 0; +} + +.payment-method-heading { + margin-top: 36px; +} + +/* "Place Order" button */ +.cart-checkout-form .cymbal-button-primary { + margin-top: 36px; +} \ No newline at end of file diff --git a/static/styles/order.css b/static/styles/order.css new file mode 100644 index 00000000..5b1d3e9d --- /dev/null +++ b/static/styles/order.css @@ -0,0 +1,53 @@ +/** + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.order { + background: #F9F9F9; +} + +.order-complete-section { + max-width: 487px; + padding-top: 56px; + padding-bottom: 120px; +} + +.order-complete-section h3 { + margin: 0; + font-size: 36px; + font-weight: normal; +} + +.order-complete-section p { + margin-top: 8px; +} + +.order-complete-section .padding-y-24 { + padding-bottom: 24px; + padding-top: 24px; +} + +.order-complete-section .border-bottom-solid { + border-bottom: 1px solid rgba(154, 160, 166, 0.5); +} + +.order-complete-section .cymbal-button-primary { + margin-top: 24px; +} + +.order-complete-section a.cymbal-button-primary:hover { + text-decoration: none; + color: white; +} diff --git a/static/styles/styles.css b/static/styles/styles.css new file mode 100644 index 00000000..4d66b98c --- /dev/null +++ b/static/styles/styles.css @@ -0,0 +1,657 @@ +/** + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* General */ + +html, body { + height: 100%; +} + +body { + color: #111111; + font-family: 'DM Sans', sans-serif; + display: flex; + flex-direction: column; +} + +/* Header */ + +header { + background-color: #853B5C; + color: white; +} + +/* +This allows the sub-navbar (white strip containing logo) +to be as wide as the browser window. +*/ +header > div:nth-child(2).navbar.sub-navbar { + padding-left: 0; + padding-right: 0; +} +header > div:nth-child(2) > .container { + max-width: none; +} + +header .cart-link { + position: relative; + display: block; + margin-left: 25px; + display: flex; + flex-flow: column; + align-items: center; + justify-content: center; +} + +header .cart-size-circle { + display: flex; + align-items: center; + justify-content: center; + position: absolute; + top: 24px; + left: 11px; + width: 16px; + height: 16px; + font-size: 11px; + border-radius: 4px 4px 0 4px; + color: white; + background-color: #853B5C; +} + +header .navbar { + padding-top: 5px; + padding-bottom: 5px; +} + +header .h-free-shipping { + font-size: 14px; +} + +header .h-controls { + display: flex; + justify-content: flex-end; +} + +header .h-control { + display: flex; + align-items: center; + font-size: 12px; + position: relative; + margin-left: 40px; + color: #605f64; +} + +header .h-control:first-child { + margin-left: 0; +} + +header .h-control input { + border: none; + padding: 0 31px 0 31px; + width: 250px; + height: 24px; + flex-shrink: 0; + background-color: #f2f2f2; + display: flex; + align-items: center; +} + +header .h-control input:focus { + outline: 0; + border: 0; + box-shadow: 0; +} + +header .icon { + width: 20px; + height: 20px; +} + +header .icon.search-icon { + width: 12px; + height: 13px; + position: absolute; + left: 10px; +} + +/* The currency drop-down. */ + +header img.currency-icon, header span.currency-icon { + position: relative; + left: 35px; + top: -1px; + width: 20px; + display: inline-block; + height: 20px; +} + +header span.currency-icon { + font-size: 16px; + text-align: center; +} + +header .h-control select { + display: flex; + align-items: center; + background: transparent; + border-radius: 0; + border: 1px solid #acacac; + width: 130px; + height: 40px; + flex-shrink: 0; + padding: 1px 0 0 45px; + font-size: 16px; + border-radius: 8px; +} + +header .icon.arrow { + position: absolute; + right: 25px; + width: 10px; + height: 5px; +} + +header .h-control::-webkit-input-placeholder { + /* Chrome/Opera/Safari */ + font-size: 12px; + color: #605f64; +} + +header .h-control::-moz-placeholder { + /* Firefox 19+ */ + font-size: 12px; + color: #605f64; +} + +header .h-control :-ms-input-placeholder { + /* IE 10+ */ + font-size: 12px; + color: #605f64; +} + +header .h-control :-moz-placeholder { + /* Firefox 18- */ + font-size: 12px; + color: #605f64; +} + +header .navbar.sub-navbar { + height: 60px; + background-color: white; + font-size: 15px; + color: #b4b2bb; + padding-top: 0; + padding-bottom: 0; + box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.25); + z-index: 1; /* Need this to see the box-shadow on the home page. */ +} + +header .navbar.sub-navbar > .container { + padding-left: 26px; + padding-right: 26px; +} + +header .top-left-logo { + height: 40px; +} + +header .top-left-logo-cymbal { + height: 30px; +} + +header .navbar.sub-navbar .navbar-brand { + padding: 0; +} + +header .navbar.sub-navbar a { + color: #b4b2bb; +} + +header .navbar.sub-navbar nav a { + margin: 0 10px; +} + +header .navbar.sub-navbar .controls { + display: flex; + height: 60px; +} + +header .navbar.sub-navbar .controls a img { + width: 20px; + height: 20px; + margin-bottom: 3px; +} + +/* Footer */ + +footer.py-5 { + flex-shrink: 0; + padding: 0 !important; +} + +footer .footer-top { + padding: 60px 0px; + background-color: #570D2E; + color: white; +} + +footer .footer-top a { + color: white; + text-decoration: underline; +} + +/* The

containing the session-id. */ +footer .footer-top p:nth-child(3) { + margin-top: 56px; +} + +footer .footer-top .footer-social, +footer .footer-top .footer-app, +footer .footer-links, +footer .footer-top .social, +footer .footer-top .app { + display: block; + align-items: center; +} + +footer .footer-top .footer-social { + padding: 31px; +} + +footer .footer-top .footer-social h4 { + margin-bottom: 0; +} + +footer .footer-top .footer-social div { + width: 50%; +} + +/* Home */ + +main { + flex: 1 0 auto; + background-color: #F9F9F9; +} + +@media (min-width: 992px) { + .home .container-fluid { + height: calc(100vh - 91px); /* 91px is the height of the top/header bars. */ + } + .home .container-fluid > .row > .col-4 { + height: calc(100vh - 91px); + } + .home .container-fluid > .row > .col-lg-8 { + height: calc(100vh - 91px); + overflow-y: scroll; + } +} + +.home-mobile-hero-banner { + height: 200px; + background: url(/static/images/folded-clothes-on-white-chair-wide.jpg) no-repeat top center; + background-size: cover; +} + +.home-desktop-left-image { + background: url(/static/images/folded-clothes-on-white-chair.jpg) no-repeat center; + background-size: cover; +} + +.hot-products-row h3 { + margin-bottom: 32px; + margin-top: 56px; + font-size: 36px; + font-weight: normal; +} + +.hot-products-row { + padding-bottom: 70px; + padding-left: 10%; + padding-right: 10%; +} + +.hot-product-card { + margin-bottom: 52px; + padding-left: 16px; + padding-right: 16px; +} + +.hot-product-card img { + width: 100%; + height: auto; + border-radius: 20% 0 20% 20%; +} + +.hot-product-card-name { + margin-top: 8px; + font-size: 18px; +} + +.hot-product-card-price { + font-size: 14px; +} + +.hot-product-card > a:first-child { + position: relative; + display: block; +} + +.hot-product-card-img-overlay { + position: absolute; + height: 100%; + width: 100%; + top: 0; + left: 0; + border-radius: 20% 0 20% 20%; + background-color: transparent; +} + +.hot-product-card:hover .hot-product-card-img-overlay { + background-color: rgba(71, 0, 29, 0.2); +} + +/* +This chunk ensures the left/right padding of the footer is +similar to that of the hot-products-row. +*/ +.home-desktop-footer-row { + padding-left: 9%; + padding-right: 9%; + background-color: #570D2E; +} + +/* Ad */ + +.ad { + position: relative; + background-color: #FF9A9B; + font-size: 24px; + text-align: center; +} + +/* "Ad" text. */ +.ad strong { + position: absolute; + top: 6px; + left: 12px; + font-size: 14px; + font-weight: normal; +} + +.ad a { + color: black; +} + +/* Product */ + +.h-product { + margin-top: 56px; + margin-bottom: 112px; + max-width: 1200px; + background-color: #F9F9F9; +} + +.h-product > .row { + align-items: flex-end; +} + +.h-product .product-image { + width: 100%; + border-radius: 20% 20% 0 20%; +} + +.h-product .product-price { + font-size: 28px; +} + +.h-product .product-info .product-wrapper { + margin-left: 15px; +} + +.h-product .product-info h2 { + margin-bottom: 16px; + margin-top: 16px; + font-size: 56px; + line-height: 1.14; + font-weight: normal; + color: #111111; +} + +.h-product .input-group-text, +.h-product .btn.btn-info { + font-size: 18px; + line-height: 1.89; + letter-spacing: 3.6px; + text-align: center; + color: #111111; + border-radius: 0; +} + +.product-quantity-dropdown { + position: relative; + width: 100px; +} + +.product-quantity-dropdown select { + width: 100%; + height: 45px; + border: 1px solid #acacac; + padding: 10px 16px; + border-radius: 8px; +} + +.product-quantity-dropdown img { + position: absolute; + right: 25px; + top: 20px; + width: 10px; + height: 5px; +} + +.h-product .cymbal-button-primary { + margin-top: 16px; +} + +/* Platform Banner */ + +.local, +.aws-platform, +.onprem-platform, +.azure-platform, +.alibaba-platform, +.gcp-platform { + position: fixed; + top: 0; + left: 0; + width: 10px; + height: 100vh; + color: white; + font-size: 24px; + z-index: 999; +} + +.aws-platform, +.aws-platform .platform-flag { + background-color: #ff9900; +} + +.onprem-platform, +.onprem-platform .platform-flag { + background-color: #34A853; +} + +.gcp-platform, +.gcp-platform .platform-flag { + background-color: #4285f4; +} + + +.azure-platform, +.azure-platform .platform-flag { + background-color: #f35426; +} + +.alibaba-platform, +.alibaba-platform .platform-flag { + background-color: #ffC300; +} + +.local, +.local .platform-flag { + background-color: #2c0678; +} + +.platform-flag { + position: absolute; + top: 98px; + left: 0; + width: 190px; + height: 50px; + display: flex; + justify-content: center; + align-items: center; +} + +/* Recommendation */ + +.recommendations { + background: #F9F9F9; + padding-bottom: 55px; +} + +.recommendations .container { + max-width: 1174px; +} + +@media (max-width: 992px) { + .recommendations .container { + max-width: none; + } +} + +.recommendations h2 { + border-top: solid 1px; + padding: 40px 0; + font-weight: normal; + text-align: center; +} + +.recommendations h5 { + margin-top: 8px; + font-weight: normal; + font-size: 18px; +} + +.recommendations img { + height: 100%; + width: 100%; + border-radius: 20% 0 20% 20%; +} + +select { + -webkit-appearance: none; + -webkit-border-radius: 0px; +} + +/* Cymbal */ + +/* +If we ever decide to create a separate Cymbal CSS library for Cymbal components, +the rules below could be extracted. +*/ + +.cymbal-button-primary, .cymbal-button-secondary { + display: inline-block; + border: solid 1px #CE0631; + padding: 8px 16px; + outline: none; + font-size: 14px; + border-radius: 22px; + cursor: pointer; +} + +.cymbal-button-primary:focus, .cymbal-button-secondary:focus { + outline: none; /* To override browser (Chrome) default blue outline. */ +} + +.cymbal-button-primary { + background-color: #CE0631; + color: white; +} + +.cymbal-button-primary:active, +.cymbal-button-primary:focus, +.cymbal-button-primary:hover { + border: solid 1px #7b031d; + background-color: #7b031d; + box-shadow: 0px 2px 2px 0px rgb(0 0 0 / 30%); +} + +.cymbal-button-primary:active { + box-shadow: 0px 3px 6px 0px rgb(0 0 0 / 30%); +} + +.cymbal-button-secondary { + background: none; + color: #CE0631; +} + +.cymbal-button-secondary:active, +.cymbal-button-secondary:focus, +.cymbal-button-secondary:hover { + color: #7b031d; + border: solid 1px #7b031d; +} + +.cymbal-button-secondary:active { + background-color: #f5ccd5; +} + +.cymbal-form-field { + position: relative; + margin-top: 24px; +} + +.cymbal-form-field label { + width: 100%; + margin: 0; + padding: 8px 16px 0 16px; + font-size: 12px; + line-height: 1.8em; /* Without this, there might be a 1px gap underneath. */ + font-weight: normal; + border-radius: 4px 4px 0px 0px; + color: #5C6063; + background-color: white; +} + +.cymbal-form-field input[type='email'], +.cymbal-form-field input[type='password'], +.cymbal-form-field select, +.cymbal-form-field input[type='text'] { + width: 100%; + border: none; + border-bottom: 1px solid #9AA0A6; + padding: 0 16px 8px 16px; + outline: none; + color: #1E2021; +} + +.cymbal-form-field .cymbal-dropdown-chevron { + position: absolute; + right: 25px; + width: 10px; + height: 5px; +} diff --git a/templates/ad.html b/templates/ad.html new file mode 100644 index 00000000..13a4af05 --- /dev/null +++ b/templates/ad.html @@ -0,0 +1,26 @@ + + +{{ define "text_ad" }} +

+
+ Ad + + {{.Text}} + +
+
+{{ end }} \ No newline at end of file diff --git a/templates/cart.html b/templates/cart.html new file mode 100644 index 00000000..331bd31c --- /dev/null +++ b/templates/cart.html @@ -0,0 +1,233 @@ + + +{{ define "cart" }} + {{ template "header" . }} + +
+ + {{$.platform_name}} + +
+ +
+ + {{ if eq (len $.items) 0 }} +
+

Your shopping cart is empty!

+

Items you add to your shopping cart will appear here.

+ Continue Shopping +
+ {{ else }} +
+
+ +
+ +
+
+

Cart ({{ $.cart_size }})

+
+
+
+ + + Continue Shopping + +
+
+
+ + {{ range $.items }} +
+
+ + + +
+
+
+
+

{{ .Item.Name }}

+
+
+
+
+ SKU #{{ .Item.Id }} +
+
+
+
+ Quantity: {{ .Quantity }} +
+
+ + {{ renderMoney .Price }} + +
+
+
+
+ {{ end }} + +
+
Shipping
+
{{ renderMoney .shipping_cost }}
+
+ +
+
Total
+
{{ renderMoney .total_cost }}
+
+ +
+ +
+ +
+ +
+
+

Shipping Address

+
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+

Payment Method

+
+
+ +
+
+ + +
+
+ +
+
+ + + +
+
+ + + +
+
+ + +
+
+ +
+
+ +
+
+ +
+ +
+ +
+
+ {{ end }} + +
+ + {{ if $.recommendations }} + {{ template "recommendations" $.recommendations }} + {{ end }} + + {{ template "footer" . }} + {{ end }} diff --git a/templates/error.html b/templates/error.html new file mode 100644 index 00000000..6cf38aab --- /dev/null +++ b/templates/error.html @@ -0,0 +1,40 @@ + + +{{ define "error" }} + {{ template "header" . }} +
+ + {{$.platform_name}} + +
+
+
+
+

Uh, oh!

+

Something has failed. Below are some details for debugging.

+ +

HTTP Status: {{.status_code}} {{.status}}

+
+                    {{- .error -}}
+                
+
+
+
+ + {{ template "footer" . }} + {{ end }} \ No newline at end of file diff --git a/templates/footer.html b/templates/footer.html new file mode 100644 index 00000000..928fb0a2 --- /dev/null +++ b/templates/footer.html @@ -0,0 +1,56 @@ + + +{{ define "footer" }} + + + + + + +{{ end }} diff --git a/templates/header.html b/templates/header.html new file mode 100644 index 00000000..6f42db70 --- /dev/null +++ b/templates/header.html @@ -0,0 +1,94 @@ + + +{{ define "header" }} + + + + + + + + + {{ if $.is_cymbal_brand }} + Cymbal Shops + {{ else }} + Online Boutique + {{ end }} + + + + + + + + + {{ if $.is_cymbal_brand }} + + {{ else }} + + {{ end }} + + + +
+ {{ if $.frontendMessage }} + + {{ end }} + + +
+ {{end}} diff --git a/templates/home.html b/templates/home.html new file mode 100644 index 00000000..8fd0f74e --- /dev/null +++ b/templates/home.html @@ -0,0 +1,78 @@ + + +{{ define "home" }} + +{{ template "header" . }} +
+ + {{$.platform_name}} + +
+
+ + +
+ +
+
+ + +
+ +
+ +
+ +
+

Hot Products

+
+ + {{ range $.products }} +
+ + +
+
+
+
{{ .Item.Name }}
+
{{ renderMoney .Price }}
+
+
+ {{ end }} + +
+ + + + +
+ +
+
+ +
+ + +
+ {{ template "footer" . }} +
+ +{{ end }} \ No newline at end of file diff --git a/templates/order.html b/templates/order.html new file mode 100644 index 00000000..c66a25cb --- /dev/null +++ b/templates/order.html @@ -0,0 +1,80 @@ + + +{{ define "order" }} + + {{ template "header" . }} + +
+ + {{$.platform_name}} + +
+ +
+ +
+
+
+

+ Your order is complete! +

+
+
+

We've sent you a confirmation email.

+
+
+
+
+ Confirmation # +
+
+ {{.order.OrderId}} +
+
+
+
+ Tracking # +
+
+ {{.order.ShippingTrackingId}} +
+
+
+
+ Total Paid +
+
+ {{renderMoney .total_paid}} +
+
+ +
+ + {{ if $.recommendations }} + {{ template "recommendations" $.recommendations }} + {{ end }} + +
+ + {{ template "footer" . }} + {{ end }} diff --git a/templates/product.html b/templates/product.html new file mode 100644 index 00000000..f206bbd5 --- /dev/null +++ b/templates/product.html @@ -0,0 +1,67 @@ + + +{{ define "product" }} +{{ template "header" . }} +
+ + {{$.platform_name}} + +
+ +
+
+
+
+ +
+
+
+ +

{{ $.product.Item.Name }}

+

{{ renderMoney $.product.Price }}

+

{{ $.product.Item.Description }}

+ +
+ +
+ + +
+ +
+
+
+
+
+
+ {{ if $.recommendations}} + {{ template "recommendations" $.recommendations }} + {{ end }} +
+
+ {{ with $.ad }}{{ template "text_ad" . }}{{ end }} +
+
+{{ template "footer" . }} +{{ end }} \ No newline at end of file diff --git a/templates/recommendations.html b/templates/recommendations.html new file mode 100644 index 00000000..2d493272 --- /dev/null +++ b/templates/recommendations.html @@ -0,0 +1,43 @@ + + +{{ define "recommendations" }} +
+
+
+
+

You May Also Like

+
+ {{range . }} +
+
+ + + +
+
+ {{ .Name }} +
+
+
+
+ {{ end }} +
+
+
+
+
+{{ end }} \ No newline at end of file