feat: add quest service

master
ALI Hamza 2021-12-22 21:38:22 +07:00
parent 87a7a9a7de
commit 321da1e6c5
Signed by: hamza
GPG Key ID: 22473A32291F8CB6
32 changed files with 1254 additions and 383 deletions

2
.gitignore vendored

@ -7,4 +7,4 @@ node_modules
client.secret.json
.vim
*.sqlite
*.sqlite*

@ -5,9 +5,9 @@ import (
codequestpb "github.com/hhhapz/codequest/api/v1"
"github.com/hhhapz/codequest/models"
"github.com/hhhapz/codequest/question"
"golang.org/x/oauth2"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/reflection"
)
@ -16,32 +16,40 @@ import (
var inProd bool
type OAuthStore interface {
Create(callback string) (code string)
Validate(ctx context.Context, state, code string) (*oauth2.Token, string, error)
Create(string) string
Validate(context.Context, string, string) (*oauth2.Token, string, error)
}
type UserStore interface {
ConsumeToken(ctx context.Context, token *oauth2.Token) (*models.Token, error)
ConsumeToken(context.Context, *oauth2.Token) (*models.Token, error)
CreateToken(ctx context.Context, user *models.User) (*models.Token, error)
RevokeToken(ctx context.Context, token string) error
RevokeUserTokens(ctx context.Context, user *models.User) (int64, error)
CreateToken(context.Context, *models.User) (*models.Token, error)
RevokeToken(context.Context, string) error
RevokeUserTokens(context.Context, *models.User) (int64, error)
User(ctx context.Context, email string) (*models.User, error)
Users(ctx context.Context) ([]*models.User, error)
User(context.Context, string) (*models.User, error)
Users(context.Context) ([]*models.User, error)
UserByToken(ctx context.Context, token string) (*models.User, error)
UserByToken(context.Context, string) (*models.User, error)
UpdateUser(ctx context.Context, user *models.User) error
DeleteUser(ctx context.Context, user *models.User) error
UpdateUser(context.Context, *models.User) error
DeleteUser(context.Context, *models.User) error
}
type QuestStore interface {
Questions(context.Context, *models.User) ([]*question.Data, error)
Question(context.Context, *models.User, string) (*question.Data, error)
Submissions(context.Context, *models.User, string, question.Part) (bool, int, error)
AddSubmission(context.Context, *models.QuestionAttempt) error
}
type Server struct {
*AuthService
*UserService
*QuestService
}
func NewServer(os OAuthStore, us UserStore) (*grpc.Server, error) {
func NewServer(os OAuthStore, us UserStore, qs QuestStore) (*grpc.Server, error) {
s := &Server{
AuthService: &AuthService{
oauthStore: os,
@ -50,6 +58,10 @@ func NewServer(os OAuthStore, us UserStore) (*grpc.Server, error) {
UserService: &UserService{
userStore: us,
},
QuestService: &QuestService{
userStore: us,
questStore: qs,
},
}
srv := grpc.NewServer(
grpc.UnaryInterceptor(AuthInterceptor(s.AuthService.defaultAuthFunc)),
@ -57,16 +69,6 @@ func NewServer(os OAuthStore, us UserStore) (*grpc.Server, error) {
reflection.Register(srv)
codequestpb.RegisterAuthServiceServer(srv, s.AuthService)
codequestpb.RegisterUserServiceServer(srv, s.UserService)
codequestpb.RegisterQuestServiceServer(srv, s.QuestService)
return srv, nil
}
// List of commonly used error values
var (
ErrInvalidArgument = grpc.Errorf(
codes.InvalidArgument,
"Invalid argument",
)
)
func err() {
}

@ -53,7 +53,7 @@ func (as *AuthService) Token(ctx context.Context, req *codequestpb.TokenRequest)
return nil, status.Errorf(codes.InvalidArgument, err.Error())
}
log.Printf("could not perform token exchange: %v", err)
return nil, status.Errorf(codes.InvalidArgument, "invalid code %q: %v", req.Code, err)
return nil, status.Errorf(codes.Internal, "could not sign in")
}
tk, err := as.userStore.ConsumeToken(ctx, token)

@ -13,8 +13,8 @@ import (
)
const (
UserKey = "ctxuser"
TokenKey = "ctxtoken"
UserKey = "ctxUser"
TokenKey = "ctxToken"
)
func AuthInterceptor(authFunc grpc_auth.AuthFunc) grpc.UnaryServerInterceptor {

@ -0,0 +1,95 @@
package api
import (
"bytes"
"context"
"errors"
"log"
codequestpb "github.com/hhhapz/codequest/api/v1"
"github.com/hhhapz/codequest/models"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
)
type QuestService struct {
codequestpb.UnimplementedQuestServiceServer
questStore QuestStore
userStore UserStore
}
func (qs *QuestService) Questions(ctx context.Context, req *emptypb.Empty) (*codequestpb.QuestionsResponse, error) {
u := UserCtx(ctx)
questions, err := qs.questStore.Questions(ctx, u)
if err != nil {
if errors.As(err, &models.UserError{}) {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
return nil, status.Errorf(codes.Internal, "could not get questions: internal error")
}
res := new(codequestpb.QuestionsResponse)
for _, q := range questions {
res.Questions = append(res.Questions, &codequestpb.Question{
ID: q.QuestionID,
Title: q.Name,
Part1: &codequestpb.PartData{
Completed: q.Part1.Completed,
PointsWorth: int32(q.Part1.PointsWorth),
},
Part2: &codequestpb.PartData{
Completed: q.Part2.Completed,
PointsWorth: int32(q.Part2.PointsWorth),
},
})
}
return res, nil
}
func (qs *QuestService) QuestionByID(ctx context.Context, req *codequestpb.QuestionByIDRequest) (*codequestpb.Question, error) {
u := UserCtx(ctx)
question, err := qs.questStore.Question(ctx, u, req.ID)
if err != nil {
if errors.As(err, &models.UserError{}) {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
return nil, status.Errorf(codes.Internal, "could not get question: internal error")
}
content := new(bytes.Buffer)
err = question.Text.Execute(content, question)
if err != nil {
log.Printf("could gen write question %s template: %v", req.ID, err)
return nil, status.Errorf(codes.Internal, "could not get question: internal error")
}
q := &codequestpb.Question{
ID: question.QuestionID,
Title: question.Name,
Text: content.String(),
Part1: &codequestpb.PartData{
Completed: question.Part1.Completed,
PointsWorth: int32(question.Part1.PointsWorth),
},
Part2: &codequestpb.PartData{
Completed: question.Part2.Completed,
PointsWorth: int32(question.Part2.PointsWorth),
},
}
return q, nil
}
func (qs *QuestService) QuestionInput(ctx context.Context, req *codequestpb.QuestionInputRequest) (*codequestpb.QuestionInput, error) {
panic("not implemented") // TODO: Implement
}
func (qs *QuestService) Submit(ctx context.Context, req *codequestpb.SubmitRequest) (*codequestpb.SubmitResponse, error) {
panic("not implemented") // TODO: Implement
}

@ -2,7 +2,6 @@ package api
import (
"context"
"database/sql"
"log"
codequestpb "github.com/hhhapz/codequest/api/v1"
@ -70,11 +69,12 @@ func (us *UserService) UpdateUser(ctx context.Context, req *codequestpb.UpdateUs
return nil, status.Errorf(codes.InvalidArgument, "name must be between 3 and 20 characters")
}
gl := req.Body.GradeLevel
u.GradeLevel = sql.NullInt64{Int64: int64(gl), Valid: true}
if gl < 9 || gl > 12 {
return nil, status.Errorf(codes.InvalidArgument, "grade level must be between 9 and 12")
}
// TODO FIX THIS
// gl := req.Body.GradeLevel
// u.GradeLevel = sql.NullInt64{Int64: int64(gl), Valid: true}
// if gl < 9 || gl > 12 {
// return nil, status.Errorf(codes.InvalidArgument, "grade level must be between 9 and 12")
// }
if err := us.userStore.UpdateUser(ctx, u); err != nil {
log.Printf("could not update user: %v", err)
@ -101,11 +101,11 @@ func (us *UserService) AdminUpdateUser(ctx context.Context, req *codequestpb.Upd
return nil, status.Errorf(codes.InvalidArgument, "name must be between 3 and 20 characters")
}
gl := req.Body.GradeLevel
user.GradeLevel = sql.NullInt64{Int64: int64(gl), Valid: true}
if gl < 9 || gl > 12 {
return nil, status.Errorf(codes.InvalidArgument, "grade level must be between 9 and 12")
}
// gl := req.Body.GradeLevel
// user.GradeLevel = sql.NullInt64{Int64: int64(gl), Valid: true}
// if gl < 9 || gl > 12 {
// return nil, status.Errorf(codes.InvalidArgument, "grade level must be between 9 and 12")
// }
user.Admin = req.Body.Admin
@ -139,12 +139,11 @@ func (us *UserService) DeleteUser(ctx context.Context, req *codequestpb.DeleteUs
func convertUser(u *models.User) *codequestpb.User {
return &codequestpb.User{
Admin: u.Admin,
Email: u.Email,
GradeLevel: int32(u.GradeLevel.Int64),
ID: u.ID,
Name: u.Name,
Picture: u.Picture,
CreatedAt: timestamppb.New(u.CreatedAt.Time()),
ID: u.ID,
Name: u.Name,
Email: u.Email,
Picture: u.Picture,
Admin: u.Admin,
CreatedAt: timestamppb.New(u.CreatedAt.Time()),
}
}

@ -287,8 +287,6 @@ type User struct {
Email string `protobuf:"bytes,3,opt,name=Email,json=email,proto3" json:"email,omitempty"`
// Picture is the URL of the user's profile picture.
Picture string `protobuf:"bytes,4,opt,name=Picture,json=picture,proto3" json:"picture,omitempty"`
// GradeLevel of the user.
GradeLevel int32 `protobuf:"varint,5,opt,name=GradeLevel,json=grade_level,proto3" json:"grade_level,omitempty"`
// Admin is true if the user is an administrator.
Admin bool `protobuf:"varint,6,opt,name=Admin,json=admin,proto3" json:"admin,omitempty"`
// CreatedAt is the time the user was created.
@ -355,13 +353,6 @@ func (x *User) GetPicture() string {
return ""
}
func (x *User) GetGradeLevel() int32 {
if x != nil {
return x.GradeLevel
}
return 0
}
func (x *User) GetAdmin() bool {
if x != nil {
return x.Admin
@ -381,7 +372,6 @@ type PartData struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Part int32 `protobuf:"varint,1,opt,name=Part,json=part,proto3" json:"part,omitempty"`
Completed bool `protobuf:"varint,2,opt,name=Completed,json=completed,proto3" json:"completed,omitempty"`
PointsWorth int32 `protobuf:"varint,3,opt,name=PointsWorth,json=points_worth,proto3" json:"points_worth,omitempty"`
}
@ -418,13 +408,6 @@ func (*PartData) Descriptor() ([]byte, []int) {
return file_github_com_hhhapz_codequest_api_v1_all_proto_rawDescGZIP(), []int{6}
}
func (x *PartData) GetPart() int32 {
if x != nil {
return x.Part
}
return 0
}
func (x *PartData) GetCompleted() bool {
if x != nil {
return x.Completed
@ -444,11 +427,11 @@ type Question struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ID string `protobuf:"bytes,1,opt,name=ID,json=id,proto3" json:"id,omitempty"`
Title string `protobuf:"bytes,2,opt,name=Title,json=title,proto3" json:"title,omitempty"`
Content string `protobuf:"bytes,3,opt,name=Content,json=content,proto3" json:"content,omitempty"`
Part1 *PartData `protobuf:"bytes,4,opt,name=Part1,json=part1,proto3" json:"part1,omitempty"`
Part2 *PartData `protobuf:"bytes,5,opt,name=Part2,json=part2,proto3" json:"part2,omitempty"`
ID string `protobuf:"bytes,1,opt,name=ID,json=id,proto3" json:"id,omitempty"`
Title string `protobuf:"bytes,2,opt,name=Title,json=title,proto3" json:"title,omitempty"`
Text string `protobuf:"bytes,3,opt,name=Text,json=text,proto3" json:"text,omitempty"`
Part1 *PartData `protobuf:"bytes,4,opt,name=Part1,json=part1,proto3" json:"part1,omitempty"`
Part2 *PartData `protobuf:"bytes,5,opt,name=Part2,json=part2,proto3" json:"part2,omitempty"`
}
func (x *Question) Reset() {
@ -497,9 +480,9 @@ func (x *Question) GetTitle() string {
return ""
}
func (x *Question) GetContent() string {
func (x *Question) GetText() string {
if x != nil {
return x.Content
return x.Text
}
return ""
}
@ -1239,7 +1222,7 @@ var file_github_com_hhhapz_codequest_api_v1_all_proto_rawDesc = []byte{
0x70, 0x7a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e,
0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50,
0x00, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x3a, 0x06, 0x08, 0x00, 0x10, 0x00, 0x18, 0x00,
0x22, 0xa8, 0x02, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1a, 0x0a, 0x02, 0x49, 0x44, 0x18,
0x22, 0xfb, 0x01, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1a, 0x0a, 0x02, 0x49, 0x44, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50,
0x00, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1e, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20,
0x01, 0x28, 0x09, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50, 0x00, 0x52,
@ -1247,224 +1230,218 @@ var file_github_com_hhhapz_codequest_api_v1_all_proto_rawDesc = []byte{
0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50, 0x00,
0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x24, 0x0a, 0x07, 0x50, 0x69, 0x63, 0x74, 0x75,
0x72, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28, 0x00,
0x30, 0x00, 0x50, 0x00, 0x52, 0x07, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x12, 0x2b, 0x0a,
0x0a, 0x47, 0x72, 0x61, 0x64, 0x65, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28,
0x05, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x0b, 0x67,
0x72, 0x61, 0x64, 0x65, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x20, 0x0a, 0x05, 0x41, 0x64,
0x6d, 0x69, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28,
0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x05, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x12, 0x45, 0x0a, 0x09,
0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x0a, 0x08, 0x00, 0x18,
0x00, 0x28, 0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64,
0x5f, 0x61, 0x74, 0x3a, 0x06, 0x08, 0x00, 0x10, 0x00, 0x18, 0x00, 0x22, 0x8b, 0x01, 0x0a, 0x08,
0x50, 0x61, 0x72, 0x74, 0x44, 0x61, 0x74, 0x61, 0x12, 0x1e, 0x0a, 0x04, 0x50, 0x61, 0x72, 0x74,
0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00,
0x50, 0x00, 0x52, 0x04, 0x70, 0x61, 0x72, 0x74, 0x12, 0x28, 0x0a, 0x09, 0x43, 0x6f, 0x6d, 0x70,
0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x42, 0x0a, 0x08, 0x00, 0x18,
0x00, 0x28, 0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74,
0x65, 0x64, 0x12, 0x2d, 0x0a, 0x0b, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x57, 0x6f, 0x72, 0x74,
0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30,
0x00, 0x50, 0x00, 0x52, 0x0c, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x5f, 0x77, 0x6f, 0x72, 0x74,
0x68, 0x3a, 0x06, 0x08, 0x00, 0x10, 0x00, 0x18, 0x00, 0x22, 0xf8, 0x01, 0x0a, 0x08, 0x51, 0x75,
0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x02,
0x69, 0x64, 0x12, 0x20, 0x0a, 0x05, 0x54, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
0x09, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x05, 0x74,
0x69, 0x74, 0x6c, 0x65, 0x12, 0x24, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18,
0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50,
0x00, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x3f, 0x0a, 0x05, 0x50, 0x61,
0x72, 0x74, 0x31, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x68, 0x68, 0x61,
0x70, 0x7a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e,
0x50, 0x61, 0x72, 0x74, 0x44, 0x61, 0x74, 0x61, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28, 0x00,
0x30, 0x00, 0x50, 0x00, 0x52, 0x05, 0x70, 0x61, 0x72, 0x74, 0x31, 0x12, 0x3f, 0x0a, 0x05, 0x50,
0x61, 0x72, 0x74, 0x32, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x68, 0x68,
0x61, 0x70, 0x7a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31,
0x2e, 0x50, 0x61, 0x72, 0x74, 0x44, 0x61, 0x74, 0x61, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28,
0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x05, 0x70, 0x61, 0x72, 0x74, 0x32, 0x3a, 0x06, 0x08, 0x00,
0x10, 0x00, 0x18, 0x00, 0x22, 0x64, 0x0a, 0x11, 0x51, 0x75, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e,
0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x09, 0x51, 0x75, 0x65,
0x73, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68,
0x68, 0x68, 0x61, 0x70, 0x7a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e,
0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x0a, 0x08, 0x00, 0x18,
0x00, 0x28, 0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x09, 0x71, 0x75, 0x65, 0x73, 0x74, 0x69, 0x6f,
0x6e, 0x73, 0x3a, 0x06, 0x08, 0x00, 0x10, 0x00, 0x18, 0x00, 0x22, 0x39, 0x0a, 0x13, 0x51, 0x75,
0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x79, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x1a, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0x08,
0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x02, 0x69, 0x64, 0x3a, 0x06, 0x08,
0x00, 0x10, 0x00, 0x18, 0x00, 0x22, 0x3a, 0x0a, 0x14, 0x51, 0x75, 0x65, 0x73, 0x74, 0x69, 0x6f,
0x6e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a,
0x30, 0x00, 0x50, 0x00, 0x52, 0x07, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x12, 0x20, 0x0a,
0x05, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x42, 0x0a, 0x08, 0x00,
0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x05, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x12,
0x45, 0x0a, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x07, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x0a,
0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61,
0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x3a, 0x06, 0x08, 0x00, 0x10, 0x00, 0x18, 0x00, 0x22, 0x6b,
0x0a, 0x08, 0x50, 0x61, 0x72, 0x74, 0x44, 0x61, 0x74, 0x61, 0x12, 0x28, 0x0a, 0x09, 0x43, 0x6f,
0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x42, 0x0a, 0x08,
0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x70, 0x6c,
0x65, 0x74, 0x65, 0x64, 0x12, 0x2d, 0x0a, 0x0b, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x57, 0x6f,
0x72, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28,
0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x0c, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x5f, 0x77, 0x6f,
0x72, 0x74, 0x68, 0x3a, 0x06, 0x08, 0x00, 0x10, 0x00, 0x18, 0x00, 0x22, 0xf2, 0x01, 0x0a, 0x08,
0x51, 0x75, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50, 0x00,
0x52, 0x02, 0x69, 0x64, 0x12, 0x20, 0x0a, 0x05, 0x54, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x02, 0x20,
0x01, 0x28, 0x09, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50, 0x00, 0x52,
0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x1e, 0x0a, 0x04, 0x54, 0x65, 0x78, 0x74, 0x18, 0x03,
0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50, 0x00,
0x52, 0x04, 0x74, 0x65, 0x78, 0x74, 0x12, 0x3f, 0x0a, 0x05, 0x50, 0x61, 0x72, 0x74, 0x31, 0x18,
0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a, 0x2e, 0x63,
0x6f, 0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x72, 0x74,
0x44, 0x61, 0x74, 0x61, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50, 0x00,
0x52, 0x05, 0x70, 0x61, 0x72, 0x74, 0x31, 0x12, 0x3f, 0x0a, 0x05, 0x50, 0x61, 0x72, 0x74, 0x32,
0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a, 0x2e,
0x63, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x72,
0x74, 0x44, 0x61, 0x74, 0x61, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50,
0x00, 0x52, 0x05, 0x70, 0x61, 0x72, 0x74, 0x32, 0x3a, 0x06, 0x08, 0x00, 0x10, 0x00, 0x18, 0x00,
0x22, 0x64, 0x0a, 0x11, 0x51, 0x75, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x09, 0x51, 0x75, 0x65, 0x73, 0x74, 0x69, 0x6f,
0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x68, 0x68, 0x68, 0x61, 0x70,
0x7a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x51,
0x75, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30,
0x00, 0x50, 0x00, 0x52, 0x09, 0x71, 0x75, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x3a, 0x06,
0x08, 0x00, 0x10, 0x00, 0x18, 0x00, 0x22, 0x39, 0x0a, 0x13, 0x51, 0x75, 0x65, 0x73, 0x74, 0x69,
0x6f, 0x6e, 0x42, 0x79, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a,
0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28,
0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x02, 0x69, 0x64, 0x3a, 0x06, 0x08, 0x00, 0x10, 0x00, 0x18,
0x00, 0x22, 0x55, 0x0a, 0x0d, 0x51, 0x75, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x70,
0x75, 0x74, 0x12, 0x1a, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a,
0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x02, 0x69, 0x64, 0x12, 0x20,
0x0a, 0x05, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0x08,
0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74,
0x3a, 0x06, 0x08, 0x00, 0x10, 0x00, 0x18, 0x00, 0x22, 0x98, 0x01, 0x0a, 0x0d, 0x53, 0x75, 0x62,
0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x02, 0x49, 0x44,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00,
0x50, 0x00, 0x52, 0x02, 0x69, 0x64, 0x12, 0x23, 0x0a, 0x06, 0x41, 0x6e, 0x73, 0x77, 0x65, 0x72,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00,
0x50, 0x00, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x1e, 0x0a, 0x04, 0x50,
0x61, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28,
0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x04, 0x70, 0x61, 0x72, 0x74, 0x12, 0x1e, 0x0a, 0x04, 0x43,
0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28,
0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x3a, 0x06, 0x08, 0x00, 0x10,
0x00, 0x18, 0x00, 0x22, 0xc4, 0x01, 0x0a, 0x0e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01,
0x00, 0x22, 0x3a, 0x0a, 0x14, 0x51, 0x75, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x70,
0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x02, 0x49, 0x44, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50,
0x00, 0x52, 0x02, 0x69, 0x64, 0x3a, 0x06, 0x08, 0x00, 0x10, 0x00, 0x18, 0x00, 0x22, 0x55, 0x0a,
0x0d, 0x51, 0x75, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x1a,
0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00,
0x28, 0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x02, 0x69, 0x64, 0x12, 0x20, 0x0a, 0x05, 0x49, 0x6e,
0x70, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28,
0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x3a, 0x06, 0x08, 0x00,
0x10, 0x00, 0x18, 0x00, 0x22, 0x98, 0x01, 0x0a, 0x0d, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x02,
0x69, 0x64, 0x12, 0x1e, 0x0a, 0x04, 0x50, 0x61, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05,
0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x04, 0x70, 0x61,
0x72, 0x74, 0x12, 0x24, 0x0a, 0x07, 0x43, 0x6f, 0x72, 0x72, 0x65, 0x63, 0x74, 0x18, 0x03, 0x20,
0x69, 0x64, 0x12, 0x23, 0x0a, 0x06, 0x41, 0x6e, 0x73, 0x77, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01,
0x28, 0x09, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x07,
0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x1e, 0x0a, 0x04, 0x50, 0x61, 0x72, 0x74, 0x18,
0x03, 0x20, 0x01, 0x28, 0x08, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50,
0x00, 0x52, 0x04, 0x70, 0x61, 0x72, 0x74, 0x12, 0x1e, 0x0a, 0x04, 0x43, 0x6f, 0x64, 0x65, 0x18,
0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50,
0x00, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x3a, 0x06, 0x08, 0x00, 0x10, 0x00, 0x18, 0x00, 0x22,
0xc4, 0x01, 0x0a, 0x0e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x1a, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a,
0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1e,
0x0a, 0x04, 0x50, 0x61, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x42, 0x0a, 0x08, 0x00,
0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x04, 0x70, 0x61, 0x72, 0x74, 0x12, 0x24,
0x0a, 0x07, 0x43, 0x6f, 0x72, 0x72, 0x65, 0x63, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x42,
0x0a, 0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x07, 0x63, 0x6f, 0x72,
0x72, 0x65, 0x63, 0x74, 0x12, 0x24, 0x0a, 0x07, 0x52, 0x61, 0x6e, 0x6b, 0x69, 0x6e, 0x67, 0x18,
0x04, 0x20, 0x01, 0x28, 0x05, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50,
0x00, 0x52, 0x07, 0x72, 0x61, 0x6e, 0x6b, 0x69, 0x6e, 0x67, 0x12, 0x22, 0x0a, 0x06, 0x50, 0x6f,
0x69, 0x6e, 0x74, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00,
0x28, 0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x06, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x3a, 0x06,
0x08, 0x00, 0x10, 0x00, 0x18, 0x00, 0x22, 0x85, 0x01, 0x0a, 0x0c, 0x55, 0x70, 0x64, 0x61, 0x74,
0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x1e, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50,
0x00, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2b, 0x0a, 0x0a, 0x47, 0x72, 0x61, 0x64, 0x65,
0x4c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x42, 0x0a, 0x08, 0x00, 0x18,
0x00, 0x28, 0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x0b, 0x67, 0x72, 0x61, 0x64, 0x65, 0x5f, 0x6c,
0x65, 0x76, 0x65, 0x6c, 0x12, 0x20, 0x0a, 0x05, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x18, 0x04, 0x20,
0x01, 0x28, 0x08, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50, 0x00, 0x52,
0x07, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x63, 0x74, 0x12, 0x24, 0x0a, 0x07, 0x52, 0x61, 0x6e, 0x6b,
0x69, 0x6e, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28,
0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x07, 0x72, 0x61, 0x6e, 0x6b, 0x69, 0x6e, 0x67, 0x12, 0x22,
0x0a, 0x06, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x42, 0x0a,
0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x06, 0x70, 0x6f, 0x69, 0x6e,
0x74, 0x73, 0x3a, 0x06, 0x08, 0x00, 0x10, 0x00, 0x18, 0x00, 0x22, 0x85, 0x01, 0x0a, 0x0c, 0x55,
0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x1e, 0x0a, 0x04, 0x4e,
0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28,
0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2b, 0x0a, 0x0a, 0x47,
0x72, 0x61, 0x64, 0x65, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x42,
0x0a, 0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x0b, 0x67, 0x72, 0x61,
0x64, 0x65, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x20, 0x0a, 0x05, 0x41, 0x64, 0x6d, 0x69,
0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30,
0x00, 0x50, 0x00, 0x52, 0x05, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x3a, 0x06, 0x08, 0x00, 0x10, 0x00,
0x18, 0x00, 0x22, 0x15, 0x0a, 0x0b, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x3a, 0x06, 0x08, 0x00, 0x10, 0x00, 0x18, 0x00, 0x22, 0x19, 0x0a, 0x0f, 0x41, 0x6c, 0x6c,
0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x3a, 0x06, 0x08, 0x00,
0x10, 0x00, 0x18, 0x00, 0x22, 0x82, 0x01, 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55,
0x05, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x3a, 0x06, 0x08, 0x00, 0x10, 0x00, 0x18, 0x00, 0x22, 0x15,
0x0a, 0x0b, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x3a, 0x06, 0x08,
0x00, 0x10, 0x00, 0x18, 0x00, 0x22, 0x19, 0x0a, 0x0f, 0x41, 0x6c, 0x6c, 0x55, 0x73, 0x65, 0x72,
0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x3a, 0x06, 0x08, 0x00, 0x10, 0x00, 0x18, 0x00,
0x22, 0x82, 0x01, 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x05, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50,
0x00, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x43, 0x0a, 0x04, 0x42, 0x6f, 0x64, 0x79,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a, 0x2e,
0x63, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64,
0x61, 0x74, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28,
0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x3a, 0x06, 0x08,
0x00, 0x10, 0x00, 0x18, 0x00, 0x22, 0x3e, 0x0a, 0x12, 0x55, 0x73, 0x65, 0x72, 0x42, 0x79, 0x45,
0x6d, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x05, 0x45,
0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00,
0x28, 0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x3a, 0x06, 0x08,
0x00, 0x10, 0x00, 0x18, 0x00, 0x22, 0x3d, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55,
0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x05, 0x45, 0x6d,
0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28,
0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x43, 0x0a, 0x04,
0x42, 0x6f, 0x64, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x68, 0x68, 0x68,
0x61, 0x70, 0x7a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31,
0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x42, 0x0a, 0x08,
0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64,
0x73, 0x3a, 0x06, 0x08, 0x00, 0x10, 0x00, 0x18, 0x00, 0x22, 0x3e, 0x0a, 0x12, 0x55, 0x73, 0x65,
0x72, 0x42, 0x79, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
0x20, 0x0a, 0x05, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a,
0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69,
0x6c, 0x3a, 0x06, 0x08, 0x00, 0x10, 0x00, 0x18, 0x00, 0x22, 0x3d, 0x0a, 0x11, 0x44, 0x65, 0x6c,
0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20,
0x0a, 0x05, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0x08,
0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c,
0x3a, 0x06, 0x08, 0x00, 0x10, 0x00, 0x18, 0x00, 0x22, 0x57, 0x0a, 0x10, 0x41, 0x6c, 0x6c, 0x55,
0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x05,
0x55, 0x73, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x68, 0x68,
0x68, 0x61, 0x70, 0x7a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76,
0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00,
0x50, 0x00, 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x3a, 0x06, 0x08, 0x00, 0x10, 0x00, 0x18,
0x00, 0x32, 0xe9, 0x02, 0x0a, 0x0b, 0x41, 0x75, 0x74, 0x68, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
0x65, 0x12, 0x7b, 0x0a, 0x09, 0x4f, 0x41, 0x75, 0x74, 0x68, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x25,
0x2e, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x41, 0x75, 0x74, 0x68, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a, 0x2e, 0x63,
0x6f, 0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x41, 0x75, 0x74,
0x68, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x88,
0x02, 0x00, 0x90, 0x02, 0x00, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x12, 0x0d, 0x2f, 0x76, 0x31,
0x2f, 0x61, 0x75, 0x74, 0x68, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x28, 0x00, 0x30, 0x00, 0x12, 0x68,
0x0a, 0x05, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x21, 0x2e, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a,
0x2e, 0x63, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x6f,
0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x68, 0x68, 0x68,
0x61, 0x70, 0x7a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31,
0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x1c, 0x88, 0x02, 0x00, 0x90, 0x02, 0x00, 0x82, 0xd3,
0xe4, 0x93, 0x02, 0x10, 0x12, 0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x2f, 0x74,
0x6f, 0x6b, 0x65, 0x6e, 0x28, 0x00, 0x30, 0x00, 0x12, 0x6e, 0x0a, 0x0b, 0x44, 0x65, 0x6c, 0x65,
0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x27, 0x2e, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a,
0x2e, 0x63, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65,
0x6c, 0x65, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x1c, 0x88, 0x02, 0x00, 0x90, 0x02, 0x00,
0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x2a, 0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x75, 0x74, 0x68,
0x2f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x28, 0x00, 0x1a, 0x03, 0x88, 0x02, 0x00, 0x32, 0x84, 0x04,
0x0a, 0x0c, 0x51, 0x75, 0x65, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x6a,
0x0a, 0x09, 0x51, 0x75, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f,
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d,
0x70, 0x74, 0x79, 0x1a, 0x26, 0x2e, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a, 0x2e, 0x63, 0x6f, 0x64,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x73, 0x74, 0x69,
0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x88, 0x02, 0x00,
0x90, 0x02, 0x00, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x12, 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x71,
0x75, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x30, 0x00, 0x12, 0x7d, 0x0a, 0x0c, 0x51, 0x75,
0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x79, 0x49, 0x44, 0x12, 0x28, 0x2e, 0x68, 0x68, 0x68,
0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x3a, 0x06, 0x08, 0x00,
0x10, 0x00, 0x18, 0x00, 0x22, 0x57, 0x0a, 0x10, 0x41, 0x6c, 0x6c, 0x55, 0x73, 0x65, 0x72, 0x73,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x05, 0x55, 0x73, 0x65, 0x72,
0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a,
0x2e, 0x63, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73,
0x65, 0x72, 0x42, 0x0a, 0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x30, 0x00, 0x50, 0x00, 0x52, 0x05,
0x75, 0x73, 0x65, 0x72, 0x73, 0x3a, 0x06, 0x08, 0x00, 0x10, 0x00, 0x18, 0x00, 0x32, 0xe9, 0x02,
0x0a, 0x0b, 0x41, 0x75, 0x74, 0x68, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x7b, 0x0a,
0x09, 0x4f, 0x41, 0x75, 0x74, 0x68, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x25, 0x2e, 0x68, 0x68, 0x68,
0x61, 0x70, 0x7a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31,
0x2e, 0x51, 0x75, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x79, 0x49, 0x44, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a, 0x2e, 0x63, 0x6f,
0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x73, 0x74,
0x69, 0x6f, 0x6e, 0x22, 0x20, 0x88, 0x02, 0x00, 0x90, 0x02, 0x00, 0x82, 0xd3, 0xe4, 0x93, 0x02,
0x14, 0x12, 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x71, 0x75, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x73,
0x2f, 0x7b, 0x49, 0x44, 0x7d, 0x28, 0x00, 0x30, 0x00, 0x12, 0x8a, 0x01, 0x0a, 0x0d, 0x51, 0x75,
0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x29, 0x2e, 0x68, 0x68,
0x68, 0x61, 0x70, 0x7a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76,
0x31, 0x2e, 0x51, 0x75, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a, 0x2e,
0x2e, 0x4f, 0x41, 0x75, 0x74, 0x68, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x26, 0x2e, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x41, 0x75, 0x74, 0x68, 0x43, 0x6f, 0x64,
0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x88, 0x02, 0x00, 0x90, 0x02,
0x00, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x12, 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x75, 0x74,
0x68, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x28, 0x00, 0x30, 0x00, 0x12, 0x68, 0x0a, 0x05, 0x54, 0x6f,
0x6b, 0x65, 0x6e, 0x12, 0x21, 0x2e, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a, 0x2e, 0x63, 0x6f, 0x64,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a, 0x2e,
0x63, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x6f, 0x6b,
0x65, 0x6e, 0x22, 0x1c, 0x88, 0x02, 0x00, 0x90, 0x02, 0x00, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10,
0x12, 0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x2f, 0x74, 0x6f, 0x6b, 0x65, 0x6e,
0x28, 0x00, 0x30, 0x00, 0x12, 0x6e, 0x0a, 0x0b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x6f,
0x6b, 0x65, 0x6e, 0x12, 0x27, 0x2e, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a, 0x2e, 0x63, 0x6f, 0x64,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,
0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67,
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45,
0x6d, 0x70, 0x74, 0x79, 0x22, 0x1c, 0x88, 0x02, 0x00, 0x90, 0x02, 0x00, 0x82, 0xd3, 0xe4, 0x93,
0x02, 0x10, 0x2a, 0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x2f, 0x74, 0x6f, 0x6b,
0x65, 0x6e, 0x28, 0x00, 0x1a, 0x03, 0x88, 0x02, 0x00, 0x32, 0x84, 0x04, 0x0a, 0x0c, 0x51, 0x75,
0x65, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x6a, 0x0a, 0x09, 0x51, 0x75,
0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a,
0x26, 0x2e, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x88, 0x02, 0x00, 0x90, 0x02, 0x00, 0x82,
0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x12, 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x71, 0x75, 0x65, 0x73, 0x74,
0x69, 0x6f, 0x6e, 0x73, 0x30, 0x00, 0x12, 0x7d, 0x0a, 0x0c, 0x51, 0x75, 0x65, 0x73, 0x74, 0x69,
0x6f, 0x6e, 0x42, 0x79, 0x49, 0x44, 0x12, 0x28, 0x2e, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a, 0x2e,
0x63, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65,
0x73, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x22, 0x26, 0x88, 0x02, 0x00, 0x90,
0x02, 0x00, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x12, 0x18, 0x2f, 0x76, 0x31, 0x2f, 0x71, 0x75,
0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x7b, 0x49, 0x44, 0x7d, 0x2f, 0x69, 0x6e, 0x70,
0x75, 0x74, 0x28, 0x00, 0x30, 0x00, 0x12, 0x77, 0x0a, 0x06, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74,
0x12, 0x22, 0x2e, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a, 0x2e, 0x63, 0x6f,
0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69,
0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x88, 0x02, 0x00, 0x90, 0x02,
0x00, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x22, 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x71, 0x75, 0x65,
0x73, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x7b, 0x49, 0x44, 0x7d, 0x28, 0x00, 0x30, 0x00, 0x1a,
0x03, 0x88, 0x02, 0x00, 0x32, 0xec, 0x05, 0x0a, 0x0b, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x72,
0x76, 0x69, 0x63, 0x65, 0x12, 0x63, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 0x12, 0x20, 0x2e, 0x68,
0x73, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x79, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x1d, 0x2e, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x22,
0x20, 0x88, 0x02, 0x00, 0x90, 0x02, 0x00, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x12, 0x12, 0x2f,
0x76, 0x31, 0x2f, 0x71, 0x75, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x7b, 0x49, 0x44,
0x7d, 0x28, 0x00, 0x30, 0x00, 0x12, 0x8a, 0x01, 0x0a, 0x0d, 0x51, 0x75, 0x65, 0x73, 0x74, 0x69,
0x6f, 0x6e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x29, 0x2e, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a,
0x2e, 0x63, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75,
0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a, 0x2e, 0x63, 0x6f, 0x64, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x73, 0x74, 0x69, 0x6f,
0x6e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x22, 0x26, 0x88, 0x02, 0x00, 0x90, 0x02, 0x00, 0x82, 0xd3,
0xe4, 0x93, 0x02, 0x1a, 0x12, 0x18, 0x2f, 0x76, 0x31, 0x2f, 0x71, 0x75, 0x65, 0x73, 0x74, 0x69,
0x6f, 0x6e, 0x73, 0x2f, 0x7b, 0x49, 0x44, 0x7d, 0x2f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x28, 0x00,
0x30, 0x00, 0x12, 0x77, 0x0a, 0x06, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x12, 0x22, 0x2e, 0x68,
0x68, 0x68, 0x61, 0x70, 0x7a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e,
0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19,
0x2e, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x22, 0x1a, 0x88, 0x02, 0x00, 0x90, 0x02,
0x00, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0e, 0x12, 0x0c, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65,
0x72, 0x73, 0x2f, 0x6d, 0x65, 0x28, 0x00, 0x30, 0x00, 0x12, 0x7c, 0x0a, 0x0b, 0x55, 0x73, 0x65,
0x72, 0x42, 0x79, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x27, 0x2e, 0x68, 0x68, 0x68, 0x61, 0x70,
0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x23, 0x2e, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x88, 0x02, 0x00, 0x90, 0x02, 0x00, 0x82, 0xd3, 0xe4,
0x93, 0x02, 0x14, 0x22, 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x71, 0x75, 0x65, 0x73, 0x74, 0x69, 0x6f,
0x6e, 0x73, 0x2f, 0x7b, 0x49, 0x44, 0x7d, 0x28, 0x00, 0x30, 0x00, 0x1a, 0x03, 0x88, 0x02, 0x00,
0x32, 0xec, 0x05, 0x0a, 0x0b, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
0x12, 0x63, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 0x12, 0x20, 0x2e, 0x68, 0x68, 0x68, 0x61, 0x70,
0x7a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55,
0x73, 0x65, 0x72, 0x42, 0x79, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x19, 0x2e, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x22, 0x25, 0x88, 0x02,
0x00, 0x90, 0x02, 0x00, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x12, 0x17, 0x2f, 0x76, 0x31, 0x2f,
0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x45, 0x6d, 0x61,
0x69, 0x6c, 0x7d, 0x28, 0x00, 0x30, 0x00, 0x12, 0x7a, 0x0a, 0x08, 0x41, 0x6c, 0x6c, 0x55, 0x73,
0x65, 0x72, 0x73, 0x12, 0x24, 0x2e, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a, 0x2e, 0x63, 0x6f, 0x64,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x6c, 0x6c, 0x55, 0x73, 0x65,
0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x68, 0x68, 0x61,
0x70, 0x7a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e,
0x41, 0x6c, 0x6c, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x22, 0x1d, 0x88, 0x02, 0x00, 0x90, 0x02, 0x00, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x11, 0x12, 0x0f,
0x2f, 0x76, 0x31, 0x2f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x28,
0x00, 0x30, 0x00, 0x12, 0x75, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65,
0x72, 0x12, 0x26, 0x2e, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73,
0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x68, 0x68, 0x68, 0x61,
0x70, 0x7a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e,
0x55, 0x73, 0x65, 0x72, 0x22, 0x20, 0x88, 0x02, 0x00, 0x90, 0x02, 0x00, 0x82, 0xd3, 0xe4, 0x93,
0x02, 0x14, 0x3a, 0x04, 0x42, 0x6f, 0x64, 0x79, 0x1a, 0x0c, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73,
0x65, 0x72, 0x73, 0x2f, 0x6d, 0x65, 0x28, 0x00, 0x30, 0x00, 0x12, 0x85, 0x01, 0x0a, 0x0f, 0x41,
0x64, 0x6d, 0x69, 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x12, 0x26,
0x2e, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a, 0x2e,
0x63, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65,
0x72, 0x22, 0x2b, 0x88, 0x02, 0x00, 0x90, 0x02, 0x00, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x3a,
0x04, 0x42, 0x6f, 0x64, 0x79, 0x1a, 0x17, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x64, 0x6d, 0x69, 0x6e,
0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x7d, 0x28, 0x00,
0x30, 0x00, 0x12, 0x7a, 0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72,
0x12, 0x26, 0x2e, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65,
0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x68, 0x68, 0x68, 0x61, 0x70,
0x7a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55,
0x73, 0x65, 0x72, 0x22, 0x25, 0x88, 0x02, 0x00, 0x90, 0x02, 0x00, 0x82, 0xd3, 0xe4, 0x93, 0x02,
0x19, 0x2a, 0x17, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2f, 0x75, 0x73, 0x65,
0x72, 0x73, 0x2f, 0x7b, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x7d, 0x28, 0x00, 0x30, 0x00, 0x1a, 0x03,
0x88, 0x02, 0x00, 0x42, 0x47, 0x48, 0x01, 0x50, 0x00, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75,
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a, 0x2f, 0x63, 0x6f, 0x64,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x3b, 0x63, 0x6f,
0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x80, 0x01, 0x00, 0x88, 0x01, 0x00, 0x90, 0x01, 0x00,
0xb8, 0x01, 0x00, 0xd8, 0x01, 0x00, 0xf8, 0x01, 0x01, 0xd0, 0x02, 0x00, 0x62, 0x06, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x33,
0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x68, 0x68, 0x68,
0x61, 0x70, 0x7a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31,
0x2e, 0x55, 0x73, 0x65, 0x72, 0x22, 0x1a, 0x88, 0x02, 0x00, 0x90, 0x02, 0x00, 0x82, 0xd3, 0xe4,
0x93, 0x02, 0x0e, 0x12, 0x0c, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2f, 0x6d,
0x65, 0x28, 0x00, 0x30, 0x00, 0x12, 0x7c, 0x0a, 0x0b, 0x55, 0x73, 0x65, 0x72, 0x42, 0x79, 0x45,
0x6d, 0x61, 0x69, 0x6c, 0x12, 0x27, 0x2e, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a, 0x2e, 0x63, 0x6f,
0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x42,
0x79, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e,
0x68, 0x68, 0x68, 0x61, 0x70, 0x7a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x22, 0x25, 0x88, 0x02, 0x00, 0x90, 0x02, 0x00,
0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x12, 0x17, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x64, 0x6d, 0x69,
0x6e, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x7d, 0x28,
0x00, 0x30, 0x00, 0x12, 0x7a, 0x0a, 0x08, 0x41, 0x6c, 0x6c, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12,
0x24, 0x2e, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x6c, 0x6c, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a, 0x2e, 0x63,
0x6f, 0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x6c, 0x6c, 0x55,
0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x88, 0x02,
0x00, 0x90, 0x02, 0x00, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x11, 0x12, 0x0f, 0x2f, 0x76, 0x31, 0x2f,
0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x28, 0x00, 0x30, 0x00, 0x12,
0x75, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x12, 0x26, 0x2e,
0x68, 0x68, 0x68, 0x61, 0x70, 0x7a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a, 0x2e, 0x63,
0x6f, 0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72,
0x22, 0x20, 0x88, 0x02, 0x00, 0x90, 0x02, 0x00, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x3a, 0x04,
0x42, 0x6f, 0x64, 0x79, 0x1a, 0x0c, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2f,
0x6d, 0x65, 0x28, 0x00, 0x30, 0x00, 0x12, 0x85, 0x01, 0x0a, 0x0f, 0x41, 0x64, 0x6d, 0x69, 0x6e,
0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x12, 0x26, 0x2e, 0x68, 0x68, 0x68,
0x61, 0x70, 0x7a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31,
0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x19, 0x2e, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a, 0x2e, 0x63, 0x6f, 0x64, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x22, 0x2b, 0x88,
0x02, 0x00, 0x90, 0x02, 0x00, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x3a, 0x04, 0x42, 0x6f, 0x64,
0x79, 0x1a, 0x17, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2f, 0x75, 0x73, 0x65,
0x72, 0x73, 0x2f, 0x7b, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x7d, 0x28, 0x00, 0x30, 0x00, 0x12, 0x7a,
0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x12, 0x26, 0x2e, 0x68,
0x68, 0x68, 0x61, 0x70, 0x7a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e,
0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a, 0x2e, 0x63, 0x6f,
0x64, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x22,
0x25, 0x88, 0x02, 0x00, 0x90, 0x02, 0x00, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x2a, 0x17, 0x2f,
0x76, 0x31, 0x2f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2f, 0x7b,
0x45, 0x6d, 0x61, 0x69, 0x6c, 0x7d, 0x28, 0x00, 0x30, 0x00, 0x1a, 0x03, 0x88, 0x02, 0x00, 0x42,
0x47, 0x48, 0x01, 0x50, 0x00, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
0x6d, 0x2f, 0x68, 0x68, 0x68, 0x61, 0x70, 0x7a, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x3b, 0x63, 0x6f, 0x64, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x80, 0x01, 0x00, 0x88, 0x01, 0x00, 0x90, 0x01, 0x00, 0xb8, 0x01, 0x00, 0xd8,
0x01, 0x00, 0xf8, 0x01, 0x01, 0xd0, 0x02, 0x00, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (

@ -18,8 +18,6 @@ type User struct {
Email string `pb:"3" json:"email"`
// Picture is the URL of the user's profile picture.
Picture string `pb:"4" json:"picture"`
// GradeLevel of the user.
GradeLevel int `pb:"5" json:"grade_level"`
// Admin is true if the user is an administrator.
Admin bool `pb:"6" json:"admin"`
// CreatedAt is the time the user was created.

@ -474,10 +474,6 @@
"v1PartData": {
"type": "object",
"properties": {
"part": {
"type": "integer",
"format": "int32"
},
"completed": {
"type": "boolean"
},
@ -496,7 +492,7 @@
"title": {
"type": "string"
},
"content": {
"text": {
"type": "string"
},
"part1": {
@ -598,11 +594,6 @@
"type": "string",
"description": "Picture is the URL of the user's profile picture."
},
"grade_level": {
"type": "integer",
"format": "int32",
"description": "GradeLevel of the user."
},
"admin": {
"type": "boolean",
"description": "Admin is true if the user is an administrator."

@ -34,13 +34,11 @@ export type User = {
name?: string
email?: string
picture?: string
gradeLevel?: number
admin?: boolean
createdAt?: GoogleProtobufTimestamp.Timestamp
}
export type PartData = {
part?: number
completed?: boolean
pointsWorth?: number
}
@ -48,7 +46,7 @@ export type PartData = {
export type Question = {
id?: string
title?: string
content?: string
text?: string
part1?: PartData
part2?: PartData
}

@ -131,9 +131,6 @@ export class User extends jspb.Message {
getPicture(): string;
setPicture(value: string): void;
getGradelevel(): number;
setGradelevel(value: number): void;
getAdmin(): boolean;
setAdmin(value: boolean): void;
@ -158,16 +155,12 @@ export namespace User {
name: string,
email: string,
picture: string,
gradelevel: number,
admin: boolean,
createdat?: google_protobuf_timestamp_pb.Timestamp.AsObject,
}
}
export class PartData extends jspb.Message {
getPart(): number;
setPart(value: number): void;
getCompleted(): boolean;
setCompleted(value: boolean): void;
@ -186,7 +179,6 @@ export class PartData extends jspb.Message {
export namespace PartData {
export type AsObject = {
part: number,
completed: boolean,
pointsworth: number,
}
@ -199,8 +191,8 @@ export class Question extends jspb.Message {
getTitle(): string;
setTitle(value: string): void;
getContent(): string;
setContent(value: string): void;
getText(): string;
setText(value: string): void;
hasPart1(): boolean;
clearPart1(): void;
@ -226,7 +218,7 @@ export namespace Question {
export type AsObject = {
id: string,
title: string,
content: string,
text: string,
part1?: PartData.AsObject,
part2?: PartData.AsObject,
}

@ -34,17 +34,16 @@ type QuestService interface {
}
type PartData struct {
Part int `pb:"1" json:"part"`
Completed bool `pb:"2" json:"completed"`
PointsWorth int `pb:"3" json:"points_worth"`
}
type Question struct {
ID string `pb:"1" json:"id"`
Title string `pb:"2" json:"title"`
Content string `pb:"3" json:"content"`
Part1 PartData `pb:"4" json:"part1"`
Part2 PartData `pb:"5" json:"part2"`
ID string `pb:"1" json:"id"`
Title string `pb:"2" json:"title"`
Text string `pb:"3" json:"text"`
Part1 PartData `pb:"4" json:"part1"`
Part2 PartData `pb:"5" json:"part2"`
}
type QuestionsResponse struct {

@ -4,13 +4,19 @@ import (
"flag"
"fmt"
"io"
"log"
"net"
"os"
"github.com/hhhapz/codequest/api"
"github.com/hhhapz/codequest/db"
"github.com/hhhapz/codequest/models"
"github.com/peterbourgon/ff/v3"
"google.golang.org/grpc/grpclog"
// questions
_ "github.com/hhhapz/codequest/question/q01"
_ "github.com/hhhapz/codequest/question/q02"
)
func run() error {
@ -30,13 +36,14 @@ func run() error {
if err != nil {
return fmt.Errorf("could not open db: %v", err)
}
models.SetLogger(log.Printf)
oaStore, err := db.NewOAuthState(*secretFile)
if err != nil {
return fmt.Errorf("could not create oauth config: %w", err)
}
server, err := api.NewServer(oaStore, database)
server, err := api.NewServer(oaStore, database, database)
log := grpclog.NewLoggerV2(os.Stderr, io.Discard, os.Stderr)
grpclog.SetLoggerV2(log)

@ -0,0 +1,119 @@
package db
import (
"context"
"database/sql"
"errors"
"fmt"
"github.com/hhhapz/codequest/models"
"github.com/hhhapz/codequest/question"
)
func (db *DB) Questions(ctx context.Context, u *models.User) ([]*question.Data, error) {
ui, err := models.UserInfoByUserID(ctx, db.DB, u.ID)
if errors.Is(err, sql.ErrNoRows) {
return nil, models.NewUserError("you must fill in your user details first")
}
if err != nil {
return nil, err
}
var questions []*question.Question
for _, q := range question.Questions {
if ui.SkillLevel != int(q.Level) {
continue
}
questions = append(questions, q)
}
parts, err := models.AllQuestionPartsData(ctx, db.DB, u.ID)
if err != nil {
return nil, err
}
var data []*question.Data
for _, q := range questions {
var p1, p2 question.PartData
for _, p := range parts {
if p.QuestionID == q.ID {
p1, p2 = question.PartData{
Completed: p.P1Awarded.Valid,
PointsWorth: int(p.P1Awarded.Int64),
}, question.PartData{
Completed: p.P2Awarded.Valid,
PointsWorth: int(p.P2Awarded.Int64),
}
}
break
}
data = append(data, &question.Data{
QuestionID: q.ID,
UserID: u.ID,
Name: q.Name,
Part1: p1,
Part2: p2,
})
}
return data, nil
}
func (db *DB) Question(ctx context.Context, u *models.User, id string) (*question.Data, error) {
ui, err := models.UserInfoByUserID(ctx, db.DB, u.ID)
if errors.Is(err, sql.ErrNoRows) {
return nil, models.NewUserError("you must fill in your user details first")
}
if err != nil {
return nil, err
}
var q *question.Question
for _, qq := range question.Questions {
if qq.ID != id {
continue
}
if ui.SkillLevel != int(qq.Level) {
return nil, models.NewUserError("this question is not available for your skill level")
}
q = qq
break
}
if q == nil {
return nil, models.NewUserError("question with the id %q not found", id)
}
partData, err := models.QuestionPartsData(ctx, db.DB, q.ID, u.ID)
if err != nil {
return nil, fmt.Errorf("could not retrieve question data: %w", err)
}
return &question.Data{
QuestionID: q.ID,
UserID: u.ID,
Name: q.Name,
Text: q.Text,
Level: q.Level,
Part1: question.PartData{
Completed: partData.P1Awarded.Valid,
PointsWorth: int(partData.P1Awarded.Int64),
},
Part2: question.PartData{
Completed: partData.P2Awarded.Valid,
PointsWorth: int(partData.P2Awarded.Int64),
},
}, nil
}
func (db *DB) Submissions(ctx context.Context, u *models.User, questionID string, part question.Part) (bool, int, error) {
s, err := models.QuestionSubmissions(ctx, db.DB, u.ID, questionID, int(part))
if err != nil {
return false, 0, err
}
return s.Correct, s.ByUser, nil
}
func (db *DB) AddSubmission(ctx context.Context, submission *models.QuestionAttempt) error {
return submission.Insert(ctx, db.DB)
}

@ -57,3 +57,70 @@ DELETE FROM tokens
WHERE user_id = %%user_id string%%
ENDSQL
FIELDS='QuestionID string,P1Awarded sql.NullInt64,P2Awarded sql.NullInt64'
xo query $DB -M -B -2 -Z "$FIELDS" -T AllPartsData -F AllQuestionPartsData -o $DEST << ENDSQL
WITH attempts AS (
SELECT *
FROM question_attempt
WHERE correct = true
)
SELECT DISTINCT
a.question_id as question_id,
MAX(p1.points_awarded) AS p1_awarded,
MAX(p2.points_awarded) AS p2_awarded
FROM attempts a
LEFT JOIN attempts AS p1
ON p1.id = a.id
AND p1.question_part = 1
LEFT JOIN attempts AS p2
ON p2.id = a.id
AND p2.question_part = 2
WHERE a.user_id = %%user_id string%%
GROUP BY a.user_id, a.question_id
ENDSQL
xo query $DB -M -B -2 -1 -T PartsData -F QuestionPartsData -o $DEST << ENDSQL
WITH attempts AS (
SELECT *
FROM question_attempt
WHERE question_id = %%question_id string%%
AND correct = true
)
SELECT
p1.points_awarded AS p1_awarded,
p2.points_awarded AS p2_awarded
FROM users u
LEFT JOIN attempts AS p1
ON p1.user_id = u.id
AND p1.question_part = 1
LEFT JOIN attempts AS p2
ON p2.user_id = u.id
AND p2.question_part = 2
WHERE
u.id = %%user_id string%%
ENDSQL
FIELDS='ByUser int,Correct bool'
xo query $DB -M -B -2 -1 -Z "$FIELDS" -T Submissions -F QuestionSubmissions -o $DEST << ENDSQL
WITH attempts AS (
SELECT * FROM question_attempt
WHERE user_id = %%user_id string%%
AND question_id = %%question_id string%%
AND question_part = %%question_part int%%
)
SELECT
c.count AS by_user,
COALESCE(a.correct, FALSE) AS correct
FROM (
SELECT COUNT(*) as count from attempts
) c
LEFT JOIN attempts a ON a.correct;
ENDSQL

@ -3,8 +3,11 @@ module github.com/hhhapz/codequest
go 1.17
require (
github.com/google/uuid v1.1.2
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.6.0
github.com/k0kubun/pp/v3 v3.0.7
github.com/kenshaw/redoc v0.1.3
github.com/mattn/go-sqlite3 v1.14.8
github.com/peterbourgon/ff/v3 v3.1.0
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8
@ -24,10 +27,10 @@ require (
github.com/gunk/opt v0.2.0 // indirect
github.com/kenshaw/diskcache v0.5.1 // indirect
github.com/kenshaw/httplog v0.4.0 // indirect
github.com/kenshaw/redoc v0.1.3 // indirect
github.com/kenshaw/webfonts v0.2.0 // indirect
github.com/mattn/go-colorable v0.1.7 // indirect
github.com/mattn/go-isatty v0.0.12 // indirect
github.com/spf13/afero v1.6.0 // indirect
github.com/stretchr/testify v1.7.0 // indirect
github.com/tdewolff/minify/v2 v2.9.22 // indirect
github.com/tdewolff/parse/v2 v2.5.22 // indirect
github.com/vanng822/css v1.0.1 // indirect

@ -23,7 +23,6 @@ cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSU
cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
cloud.google.com/go v0.94.1 h1:DwuSvDZ1pTYGbXo8yOJevCTr3BoBlE+OVkHAKiYQUXc=
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
cloud.google.com/go v0.97.0 h1:3DXvAyifywvq64LfkKaMOmkWPS1CikIQdMe2lY9vxU8=
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
@ -81,6 +80,7 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m
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/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
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=
@ -162,10 +162,10 @@ github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.1.0 h1:6DWmvNpomjL1+3liNSZbVns3zsYzzCjm6pRBO1tLeso=
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
github.com/googleapis/gax-go/v2 v2.1.1 h1:dp3bWCh+PPO1zjRRiCSczJav13sBvG4UhNyVTa1KqdU=
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
@ -185,8 +185,13 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/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/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
github.com/k0kubun/pp/v3 v3.0.7 h1:Qj4zVxA0ceXq0mfNbHwFPye58UyabBWi3emM2SwBT5Y=
github.com/k0kubun/pp/v3 v3.0.7/go.mod h1:2ol0zQBSPTermAo8igHVJ4d5vTiNmBkCrUdu7wZp4aI=
github.com/kenshaw/diskcache v0.5.0/go.mod h1:niKrpaT3MnShxQ4OojUoT8VNr9e5r+SQf8nrzVWjIrQ=
github.com/kenshaw/diskcache v0.5.1 h1:mdgyJGiGOn+1ZW6WOa7u+HwsJoExiRzEBymjjTBbOJc=
github.com/kenshaw/diskcache v0.5.1/go.mod h1:2g5XF+9Dgd1iBFi0anYYw3beLaLnTgJSSzsKIuk+Z5E=
github.com/kenshaw/httplog v0.3.2/go.mod h1:1mIAV0a4k32ePm32Ve3oVzzCfPje66oWCLfUOA4l8Pw=
github.com/kenshaw/httplog v0.4.0 h1:6gevB91JwSsEKB+Q10zxv392t4bLcab/HxfVYBJ0ohs=
github.com/kenshaw/httplog v0.4.0/go.mod h1:O0bRNzPagLH+kWMB9f+rwFwmjT4MfKcuTy4D6q4/2rU=
github.com/kenshaw/redoc v0.1.3 h1:GP5yi6+Z8Eyoa3ZwYzOFUnxkBZk/wwTAhZq9VdTj11M=
@ -202,6 +207,10 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
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/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw=
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU=
github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
@ -228,11 +237,14 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tdewolff/minify/v2 v2.9.21/go.mod h1:PoDBts2L7sCwUT28vTAlozGeD6qxjrrihtin4bR/RMM=
github.com/tdewolff/minify/v2 v2.9.22 h1:PlmaAakaJHdMMdTTwjjsuSwIxKqWPTlvjTj6a/g/ILU=
github.com/tdewolff/minify/v2 v2.9.22/go.mod h1:dNlaFdXaIxgSXh3UFASqjTY0/xjpDkkCsYHA1NCGnmQ=
github.com/tdewolff/parse/v2 v2.5.19/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
github.com/tdewolff/parse/v2 v2.5.21/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
github.com/tdewolff/parse/v2 v2.5.22 h1:KXMHTyx4VTL6Zu9a94SULQalDMvtP5FQq10mnSfaoGs=
github.com/tdewolff/parse/v2 v2.5.22/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4=
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/vanng822/css v1.0.1 h1:10yiXc4e8NI8ldU6mSrWmSWMuyWgPr9DZ63RSlsgDw8=
github.com/vanng822/css v1.0.1/go.mod h1:tcnB1voG49QhCrwq1W0w5hhGasvOg+VQp9i9H1rCM1w=
@ -331,8 +343,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211014222326-fd004c51d1d6/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211116231205-47ca1ff31462 h1:2vmJlzGKvQ7e/X9XT0XydeWDxmqx8DnegiIMRT+5ssI=
golang.org/x/net v0.0.0-20211116231205-47ca1ff31462/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -351,8 +362,6 @@ golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211028175245-ba495a64dcb5 h1:v79phzBz03tsVCUTbvTBmmC3CUXF5mKYt7DA4ZVldpM=
golang.org/x/oauth2 v0.0.0-20211028175245-ba495a64dcb5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -377,9 +386,11 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w
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-20191005200804-aed5e4c7ecf9/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-20200116001909-b77594299b42/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=
@ -412,6 +423,8 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c h1:DHcbWVXeY+0Y8HHKR+rbLwnoh2F4tNCY7rTiHJ30RmA=
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -515,8 +528,8 @@ google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
google.golang.org/api v0.57.0 h1:4t9zuDlHLcIx0ZEhmXEeFVCRsiOgpgn2QOH9N0MNjPI=
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
google.golang.org/api v0.58.0/go.mod h1:cAbP2FsxoGVNwtgNAmmn3y5G1TWAiVYRmg4yku3lv+E=
google.golang.org/api v0.60.0 h1:eq/zs5WPH4J9undYM9IP1O7dSr7Yh8Y0GtSCpzGzIUk=
google.golang.org/api v0.60.0/go.mod h1:d7rl65NZAkEQ90JFzqBjcRq1TVeG5ZoGV3sSpEnnVb4=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
@ -583,7 +596,9 @@ google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEc
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211013025323-ce878158c4d4/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211021150943-2b146023228c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211116182654-e63d96a377c4 h1:nPiLDJ9/wsay2NDshdJ1B24frx+butTxmaVaCxDBChY=
google.golang.org/genproto v0.0.0-20211116182654-e63d96a377c4/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
@ -611,8 +626,8 @@ google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQ
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
google.golang.org/grpc v1.42.0 h1:XT2/MFpuPFsEX2fWh3YQtHkZ+WYZFQRfaUgLZYj/p6A=
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=

@ -0,0 +1,63 @@
package models
// Code generated by xo. DO NOT EDIT.
import (
"context"
"database/sql"
)
// AllPartsData represents a row from 'all_parts_data'.
type AllPartsData struct {
QuestionID string `json:"question_id"` // question_id
P1Awarded sql.NullInt64 `json:"p1awarded"` // p1awarded
P2Awarded sql.NullInt64 `json:"p2awarded"` // p2awarded
}
// AllQuestionPartsData runs a custom query, returning results as AllPartsData.
func AllQuestionPartsData(ctx context.Context, db DB, user_id string) ([]*AllPartsData, error) {
// query
const sqlstr = `WITH attempts AS ( ` +
`SELECT * ` +
`FROM question_attempt ` +
`WHERE correct = true ` +
`) ` +
`SELECT DISTINCT ` +
`a.question_id as question_id, ` +
`MAX(p1.points_awarded) AS p1_awarded, ` +
`MAX(p2.points_awarded) AS p2_awarded ` +
`FROM attempts a ` +
` ` +
`LEFT JOIN attempts AS p1 ` +
`ON p1.id = a.id ` +
`AND p1.question_part = 1 ` +
` ` +
`LEFT JOIN attempts AS p2 ` +
`ON p2.id = a.id ` +
`AND p2.question_part = 2 ` +
` ` +
`WHERE a.user_id = $1 ` +
` ` +
`GROUP BY a.user_id, a.question_id`
// run
logf(sqlstr, user_id)
rows, err := db.QueryContext(ctx, sqlstr, user_id)
if err != nil {
return nil, logerror(err)
}
defer rows.Close()
// load results
var res []*AllPartsData
for rows.Next() {
var apd AllPartsData
// scan
if err := rows.Scan(&apd.QuestionID, &apd.P1Awarded, &apd.P2Awarded); err != nil {
return nil, logerror(err)
}
res = append(res, &apd)
}
if err := rows.Err(); err != nil {
return nil, logerror(err)
}
return res, nil
}

@ -0,0 +1,47 @@
package models
// Code generated by xo. DO NOT EDIT.
import (
"context"
"database/sql"
)
// PartsData represents a row from 'parts_data'.
type PartsData struct {
P1Awarded sql.NullInt64 `json:"p1_awarded"` // p1_awarded
P2Awarded sql.NullInt64 `json:"p2_awarded"` // p2_awarded
}
// QuestionPartsData runs a custom query, returning results as PartsData.
func QuestionPartsData(ctx context.Context, db DB, question_id, user_id string) (*PartsData, error) {
// query
const sqlstr = `WITH attempts AS ( ` +
`SELECT * ` +
`FROM question_attempt ` +
`WHERE question_id = $1 ` +
`AND correct = true ` +
`) ` +
`SELECT ` +
`p1.points_awarded AS p1_awarded, ` +
`p2.points_awarded AS p2_awarded ` +
`FROM users u ` +
` ` +
`LEFT JOIN attempts AS p1 ` +
`ON p1.user_id = u.id ` +
`AND p1.question_part = 1 ` +
` ` +
`LEFT JOIN attempts AS p2 ` +
`ON p2.user_id = u.id ` +
`AND p2.question_part = 2 ` +
` ` +
`WHERE ` +
`u.id = $2`
// run
logf(sqlstr, question_id, user_id)
var pd PartsData
if err := db.QueryRowContext(ctx, sqlstr, question_id, user_id).Scan(&pd.P1Awarded, &pd.P2Awarded); err != nil {
return nil, logerror(err)
}
return &pd, nil
}

@ -0,0 +1,165 @@
package models
// Code generated by xo. DO NOT EDIT.
import (
"context"
)
// QuestionAttempt represents a row from 'question_attempt'.
type QuestionAttempt struct {
ID int `json:"id"` // id
UserID string `json:"user_id"` // user_id
QuestionID string `json:"question_id"` // question_id
QuestionPart int `json:"question_part"` // question_part
Correct bool `json:"correct"` // correct
PointsAwarded int `json:"points_awarded"` // points_awarded
Input string `json:"input"` // input
Code string `json:"code"` // code
SubmittedAt Time `json:"submitted_at"` // submitted_at
// xo fields
_exists, _deleted bool
}
// Exists returns true when the QuestionAttempt exists in the database.
func (qa *QuestionAttempt) Exists() bool {
return qa._exists
}
// Deleted returns true when the QuestionAttempt has been marked for deletion from
// the database.
func (qa *QuestionAttempt) Deleted() bool {
return qa._deleted
}
// Insert inserts the QuestionAttempt to the database.
func (qa *QuestionAttempt) Insert(ctx context.Context, db DB) error {
switch {
case qa._exists: // already exists
return logerror(&ErrInsertFailed{ErrAlreadyExists})
case qa._deleted: // deleted
return logerror(&ErrInsertFailed{ErrMarkedForDeletion})
}
// insert (primary key generated and returned by database)
const sqlstr = `INSERT INTO question_attempt (` +
`user_id, question_id, question_part, correct, points_awarded, input, code, submitted_at` +
`) VALUES (` +
`$1, $2, $3, $4, $5, $6, $7, $8` +
`)`
// run
logf(sqlstr, qa.UserID, qa.QuestionID, qa.QuestionPart, qa.Correct, qa.PointsAwarded, qa.Input, qa.Code, qa.SubmittedAt)
res, err := db.ExecContext(ctx, sqlstr, qa.UserID, qa.QuestionID, qa.QuestionPart, qa.Correct, qa.PointsAwarded, qa.Input, qa.Code, qa.SubmittedAt)
if err != nil {
return logerror(err)
}
// retrieve id
id, err := res.LastInsertId()
if err != nil {
return logerror(err)
} // set primary key
qa.ID = int(id)
// set exists
qa._exists = true
return nil
}
// Update updates a QuestionAttempt in the database.
func (qa *QuestionAttempt) Update(ctx context.Context, db DB) error {
switch {
case !qa._exists: // doesn't exist
return logerror(&ErrUpdateFailed{ErrDoesNotExist})
case qa._deleted: // deleted
return logerror(&ErrUpdateFailed{ErrMarkedForDeletion})
}
// update with primary key
const sqlstr = `UPDATE question_attempt SET ` +
`user_id = $1, question_id = $2, question_part = $3, correct = $4, points_awarded = $5, input = $6, code = $7, submitted_at = $8 ` +
`WHERE id = $9`
// run
logf(sqlstr, qa.UserID, qa.QuestionID, qa.QuestionPart, qa.Correct, qa.PointsAwarded, qa.Input, qa.Code, qa.SubmittedAt, qa.ID)
if _, err := db.ExecContext(ctx, sqlstr, qa.UserID, qa.QuestionID, qa.QuestionPart, qa.Correct, qa.PointsAwarded, qa.Input, qa.Code, qa.SubmittedAt, qa.ID); err != nil {
return logerror(err)
}
return nil
}
// Save saves the QuestionAttempt to the database.
func (qa *QuestionAttempt) Save(ctx context.Context, db DB) error {
if qa.Exists() {
return qa.Update(ctx, db)
}
return qa.Insert(ctx, db)
}
// Upsert performs an upsert for QuestionAttempt.
func (qa *QuestionAttempt) Upsert(ctx context.Context, db DB) error {
switch {
case qa._deleted: // deleted
return logerror(&ErrUpsertFailed{ErrMarkedForDeletion})
}
// upsert
const sqlstr = `INSERT INTO question_attempt (` +
`id, user_id, question_id, question_part, correct, points_awarded, input, code, submitted_at` +
`) VALUES (` +
`$1, $2, $3, $4, $5, $6, $7, $8, $9` +
`)` +
` ON CONFLICT (id) DO ` +
`UPDATE SET ` +
`user_id = EXCLUDED.user_id, question_id = EXCLUDED.question_id, question_part = EXCLUDED.question_part, correct = EXCLUDED.correct, points_awarded = EXCLUDED.points_awarded, input = EXCLUDED.input, code = EXCLUDED.code, submitted_at = EXCLUDED.submitted_at `
// run
logf(sqlstr, qa.ID, qa.UserID, qa.QuestionID, qa.QuestionPart, qa.Correct, qa.PointsAwarded, qa.Input, qa.Code, qa.SubmittedAt)
if _, err := db.ExecContext(ctx, sqlstr, qa.ID, qa.UserID, qa.QuestionID, qa.QuestionPart, qa.Correct, qa.PointsAwarded, qa.Input, qa.Code, qa.SubmittedAt); err != nil {
return logerror(err)
}
// set exists
qa._exists = true
return nil
}
// Delete deletes the QuestionAttempt from the database.
func (qa *QuestionAttempt) Delete(ctx context.Context, db DB) error {
switch {
case !qa._exists: // doesn't exist
return nil
case qa._deleted: // deleted
return nil
}
// delete with single primary key
const sqlstr = `DELETE FROM question_attempt ` +
`WHERE id = $1`
// run
logf(sqlstr, qa.ID)
if _, err := db.ExecContext(ctx, sqlstr, qa.ID); err != nil {
return logerror(err)
}
// set deleted
qa._deleted = true
return nil
}
// QuestionAttemptByID retrieves a row from 'question_attempt' as a QuestionAttempt.
//
// Generated from index 'question_attempt_id_pkey'.
func QuestionAttemptByID(ctx context.Context, db DB, id int) (*QuestionAttempt, error) {
// query
const sqlstr = `SELECT ` +
`id, user_id, question_id, question_part, correct, points_awarded, input, code, submitted_at ` +
`FROM question_attempt ` +
`WHERE id = $1`
// run
logf(sqlstr, id)
qa := QuestionAttempt{
_exists: true,
}
if err := db.QueryRowContext(ctx, sqlstr, id).Scan(&qa.ID, &qa.UserID, &qa.QuestionID, &qa.QuestionPart, &qa.Correct, &qa.PointsAwarded, &qa.Input, &qa.Code, &qa.SubmittedAt); err != nil {
return nil, logerror(err)
}
return &qa, nil
}
// User returns the User associated with the QuestionAttempt's (UserID).
//
// Generated from foreign key 'question_attempt_user_id_fkey'.
func (qa *QuestionAttempt) User(ctx context.Context, db DB) (*User, error) {
return UserByID(ctx, db, qa.UserID)
}

@ -0,0 +1,38 @@
package models
// Code generated by xo. DO NOT EDIT.
import (
"context"
)
// Submissions represents a row from 'submissions'.
type Submissions struct {
ByUser int `json:"by_user"` // by_user
Correct bool `json:"correct"` // correct
}
// QuestionSubmissions runs a custom query, returning results as Submissions.
func QuestionSubmissions(ctx context.Context, db DB, user_id, question_id string, question_part int) (*Submissions, error) {
// query
const sqlstr = `WITH attempts AS ( ` +
`SELECT * FROM question_attempt ` +
`WHERE user_id = $1 ` +
`AND question_id = $2 ` +
`AND question_part = $3 ` +
`) ` +
`SELECT ` +
`c.count AS by_user, ` +
`COALESCE(a.correct, FALSE) AS correct ` +
`FROM ( ` +
`SELECT COUNT(*) as count from attempts ` +
`) c ` +
`LEFT JOIN attempts a ON a.correct;`
// run
logf(sqlstr, user_id, question_id, question_part)
var s Submissions
if err := db.QueryRowContext(ctx, sqlstr, user_id, question_id, question_part).Scan(&s.ByUser, &s.Correct); err != nil {
return nil, logerror(err)
}
return &s, nil
}

@ -4,19 +4,16 @@ package models
import (
"context"
"database/sql"
)
// User represents a row from 'users'.
type User struct {
ID string `json:"id"` // id
Name string `json:"name"` // name
Email string `json:"email"` // email
Picture string `json:"picture"` // picture
GradeLevel sql.NullInt64 `json:"grade_level"` // grade_level
Teacher bool `json:"teacher"` // teacher
Admin bool `json:"admin"` // admin
CreatedAt Time `json:"created_at"` // created_at
ID string `json:"id"` // id
Name string `json:"name"` // name
Email string `json:"email"` // email
Picture string `json:"picture"` // picture
Admin bool `json:"admin"` // admin
CreatedAt Time `json:"created_at"` // created_at
// xo fields
_exists, _deleted bool
}
@ -42,13 +39,13 @@ func (u *User) Insert(ctx context.Context, db DB) error {
}
// insert (manual)
const sqlstr = `INSERT INTO users (` +
`id, name, email, picture, grade_level, teacher, admin, created_at` +
`id, name, email, picture, admin, created_at` +
`) VALUES (` +
`$1, $2, $3, $4, $5, $6, $7, $8` +
`$1, $2, $3, $4, $5, $6` +
`)`
// run
logf(sqlstr, u.ID, u.Name, u.Email, u.Picture, u.GradeLevel, u.Teacher, u.Admin, u.CreatedAt)
if _, err := db.ExecContext(ctx, sqlstr, u.ID, u.Name, u.Email, u.Picture, u.GradeLevel, u.Teacher, u.Admin, u.CreatedAt); err != nil {
logf(sqlstr, u.ID, u.Name, u.Email, u.Picture, u.Admin, u.CreatedAt)
if _, err := db.ExecContext(ctx, sqlstr, u.ID, u.Name, u.Email, u.Picture, u.Admin, u.CreatedAt); err != nil {
return logerror(err)
}
// set exists
@ -66,11 +63,11 @@ func (u *User) Update(ctx context.Context, db DB) error {
}
// update with primary key
const sqlstr = `UPDATE users SET ` +
`name = $1, email = $2, picture = $3, grade_level = $4, teacher = $5, admin = $6, created_at = $7 ` +
`WHERE id = $8`
`name = $1, email = $2, picture = $3, admin = $4, created_at = $5 ` +
`WHERE id = $6`
// run
logf(sqlstr, u.Name, u.Email, u.Picture, u.GradeLevel, u.Teacher, u.Admin, u.CreatedAt, u.ID)
if _, err := db.ExecContext(ctx, sqlstr, u.Name, u.Email, u.Picture, u.GradeLevel, u.Teacher, u.Admin, u.CreatedAt, u.ID); err != nil {
logf(sqlstr, u.Name, u.Email, u.Picture, u.Admin, u.CreatedAt, u.ID)
if _, err := db.ExecContext(ctx, sqlstr, u.Name, u.Email, u.Picture, u.Admin, u.CreatedAt, u.ID); err != nil {
return logerror(err)
}
return nil
@ -92,16 +89,16 @@ func (u *User) Upsert(ctx context.Context, db DB) error {
}
// upsert
const sqlstr = `INSERT INTO users (` +
`id, name, email, picture, grade_level, teacher, admin, created_at` +
`id, name, email, picture, admin, created_at` +
`) VALUES (` +
`$1, $2, $3, $4, $5, $6, $7, $8` +
`$1, $2, $3, $4, $5, $6` +
`)` +
` ON CONFLICT (id) DO ` +
`UPDATE SET ` +
`name = EXCLUDED.name, email = EXCLUDED.email, picture = EXCLUDED.picture, grade_level = EXCLUDED.grade_level, teacher = EXCLUDED.teacher, admin = EXCLUDED.admin, created_at = EXCLUDED.created_at `
`name = EXCLUDED.name, email = EXCLUDED.email, picture = EXCLUDED.picture, admin = EXCLUDED.admin, created_at = EXCLUDED.created_at `
// run
logf(sqlstr, u.ID, u.Name, u.Email, u.Picture, u.GradeLevel, u.Teacher, u.Admin, u.CreatedAt)
if _, err := db.ExecContext(ctx, sqlstr, u.ID, u.Name, u.Email, u.Picture, u.GradeLevel, u.Teacher, u.Admin, u.CreatedAt); err != nil {
logf(sqlstr, u.ID, u.Name, u.Email, u.Picture, u.Admin, u.CreatedAt)
if _, err := db.ExecContext(ctx, sqlstr, u.ID, u.Name, u.Email, u.Picture, u.Admin, u.CreatedAt); err != nil {
return logerror(err)
}
// set exists
@ -136,7 +133,7 @@ func (u *User) Delete(ctx context.Context, db DB) error {
func UserByID(ctx context.Context, db DB, id string) (*User, error) {
// query
const sqlstr = `SELECT ` +
`id, name, email, picture, grade_level, teacher, admin, created_at ` +
`id, name, email, picture, admin, created_at ` +
`FROM users ` +
`WHERE id = $1`
// run
@ -144,7 +141,7 @@ func UserByID(ctx context.Context, db DB, id string) (*User, error) {
u := User{
_exists: true,
}
if err := db.QueryRowContext(ctx, sqlstr, id).Scan(&u.ID, &u.Name, &u.Email, &u.Picture, &u.GradeLevel, &u.Teacher, &u.Admin, &u.CreatedAt); err != nil {
if err := db.QueryRowContext(ctx, sqlstr, id).Scan(&u.ID, &u.Name, &u.Email, &u.Picture, &u.Admin, &u.CreatedAt); err != nil {
return nil, logerror(err)
}
return &u, nil
@ -156,7 +153,7 @@ func UserByID(ctx context.Context, db DB, id string) (*User, error) {
func UserByEmail(ctx context.Context, db DB, email string) (*User, error) {
// query
const sqlstr = `SELECT ` +
`id, name, email, picture, grade_level, teacher, admin, created_at ` +
`id, name, email, picture, admin, created_at ` +
`FROM users ` +
`WHERE email = $1`
// run
@ -164,7 +161,7 @@ func UserByEmail(ctx context.Context, db DB, email string) (*User, error) {
u := User{
_exists: true,
}
if err := db.QueryRowContext(ctx, sqlstr, email).Scan(&u.ID, &u.Name, &u.Email, &u.Picture, &u.GradeLevel, &u.Teacher, &u.Admin, &u.CreatedAt); err != nil {
if err := db.QueryRowContext(ctx, sqlstr, email).Scan(&u.ID, &u.Name, &u.Email, &u.Picture, &u.Admin, &u.CreatedAt); err != nil {
return nil, logerror(err)
}
return &u, nil
@ -186,7 +183,7 @@ func Users(ctx context.Context, db DB) ([]*User, error) {
for rows.Next() {
var u User
// scan
if err := rows.Scan(&u.ID, &u.Name, &u.Email, &u.Picture, &u.GradeLevel, &u.Teacher, &u.Admin, &u.CreatedAt); err != nil {
if err := rows.Scan(&u.ID, &u.Name, &u.Email, &u.Picture, &u.Admin, &u.CreatedAt); err != nil {
return nil, logerror(err)
}
res = append(res, &u)
@ -208,7 +205,7 @@ func UserByToken(ctx context.Context, db DB, token string) (*User, error) {
// run
logf(sqlstr, token)
var u User
if err := db.QueryRowContext(ctx, sqlstr, token).Scan(&u.ID, &u.Name, &u.Email, &u.Picture, &u.GradeLevel, &u.Teacher, &u.Admin, &u.CreatedAt); err != nil {
if err := db.QueryRowContext(ctx, sqlstr, token).Scan(&u.ID, &u.Name, &u.Email, &u.Picture, &u.Admin, &u.CreatedAt); err != nil {
return nil, logerror(err)
}
return &u, nil

@ -0,0 +1,152 @@
package models
// Code generated by xo. DO NOT EDIT.
import (
"context"
)
// UserInfo represents a row from 'user_info'.
type UserInfo struct {
UserID string `json:"user_id"` // user_id
GradeLevel int `json:"grade_level"` // grade_level
SkillLevel int `json:"skill_level"` // skill_level
// xo fields
_exists, _deleted bool
}
// Exists returns true when the UserInfo exists in the database.
func (ui *UserInfo) Exists() bool {
return ui._exists
}
// Deleted returns true when the UserInfo has been marked for deletion from
// the database.
func (ui *UserInfo) Deleted() bool {
return ui._deleted
}
// Insert inserts the UserInfo to the database.
func (ui *UserInfo) Insert(ctx context.Context, db DB) error {
switch {
case ui._exists: // already exists
return logerror(&ErrInsertFailed{ErrAlreadyExists})
case ui._deleted: // deleted
return logerror(&ErrInsertFailed{ErrMarkedForDeletion})
}
// insert (manual)
const sqlstr = `INSERT INTO user_info (` +
`user_id, grade_level, skill_level` +
`) VALUES (` +
`$1, $2, $3` +
`)`
// run
logf(sqlstr, ui.UserID, ui.GradeLevel, ui.SkillLevel)
if _, err := db.ExecContext(ctx, sqlstr, ui.UserID, ui.GradeLevel, ui.SkillLevel); err != nil {
return logerror(err)
}
// set exists
ui._exists = true
return nil
}
// Update updates a UserInfo in the database.
func (ui *UserInfo) Update(ctx context.Context, db DB) error {
switch {
case !ui._exists: // doesn't exist
return logerror(&ErrUpdateFailed{ErrDoesNotExist})
case ui._deleted: // deleted
return logerror(&ErrUpdateFailed{ErrMarkedForDeletion})
}
// update with primary key
const sqlstr = `UPDATE user_info SET ` +
`grade_level = $1, skill_level = $2 ` +
`WHERE user_id = $3`
// run
logf(sqlstr, ui.GradeLevel, ui.SkillLevel, ui.UserID)
if _, err := db.ExecContext(ctx, sqlstr, ui.GradeLevel, ui.SkillLevel, ui.UserID); err != nil {
return logerror(err)
}
return nil
}
// Save saves the UserInfo to the database.
func (ui *UserInfo) Save(ctx context.Context, db DB) error {
if ui.Exists() {
return ui.Update(ctx, db)
}
return ui.Insert(ctx, db)
}
// Upsert performs an upsert for UserInfo.
func (ui *UserInfo) Upsert(ctx context.Context, db DB) error {
switch {
case ui._deleted: // deleted
return logerror(&ErrUpsertFailed{ErrMarkedForDeletion})
}
// upsert
const sqlstr = `INSERT INTO user_info (` +
`user_id, grade_level, skill_level` +
`) VALUES (` +
`$1, $2, $3` +
`)` +
` ON CONFLICT (user_id) DO ` +
`UPDATE SET ` +
`grade_level = EXCLUDED.grade_level, skill_level = EXCLUDED.skill_level `
// run
logf(sqlstr, ui.UserID, ui.GradeLevel, ui.SkillLevel)
if _, err := db.ExecContext(ctx, sqlstr, ui.UserID, ui.GradeLevel, ui.SkillLevel); err != nil {
return logerror(err)
}
// set exists
ui._exists = true
return nil
}
// Delete deletes the UserInfo from the database.
func (ui *UserInfo) Delete(ctx context.Context, db DB) error {
switch {
case !ui._exists: // doesn't exist
return nil
case ui._deleted: // deleted
return nil
}
// delete with single primary key
const sqlstr = `DELETE FROM user_info ` +
`WHERE user_id = $1`
// run
logf(sqlstr, ui.UserID)
if _, err := db.ExecContext(ctx, sqlstr, ui.UserID); err != nil {
return logerror(err)
}
// set deleted
ui._deleted = true
return nil
}
// UserInfoByUserID retrieves a row from 'user_info' as a UserInfo.
//
// Generated from index 'sqlite_autoindex_user_info_1'.
func UserInfoByUserID(ctx context.Context, db DB, userID string) (*UserInfo, error) {
// query
const sqlstr = `SELECT ` +
`user_id, grade_level, skill_level ` +
`FROM user_info ` +
`WHERE user_id = $1`
// run
logf(sqlstr, userID)
ui := UserInfo{
_exists: true,
}
if err := db.QueryRowContext(ctx, sqlstr, userID).Scan(&ui.UserID, &ui.GradeLevel, &ui.SkillLevel); err != nil {
return nil, logerror(err)
}
return &ui, nil
}
// User returns the User associated with the UserInfo's (UserID).
//
// Generated from foreign key 'user_info_user_id_fkey'.
func (ui *UserInfo) User(ctx context.Context, db DB) (*User, error) {
return UserByID(ctx, db, ui.UserID)
}

@ -4,6 +4,66 @@ schemas:
- type: sqlite3
name: db.sqlite
tables:
- type: table
name: question_attempt
columns:
- name: id
datatype:
type: integer
is_primary: true
is_sequence: true
- name: user_id
datatype:
type: text
- name: question_id
datatype:
type: text
- name: question_part
datatype:
type: integer
- name: correct
datatype:
type: boolean
- name: points_awarded
datatype:
type: integer
- name: input
datatype:
type: text
- name: code
datatype:
type: text
- name: submitted_at
datatype:
type: datetime
primary_keys:
- name: id
datatype:
type: integer
is_primary: true
is_sequence: true
indexes:
- name: question_attempt_id_pkey
fields:
- name: id
datatype:
type: integer
is_primary: true
is_sequence: true
is_unique: true
is_primary: true
foreign_keys:
- name: question_attempt_user_id_fkey
column:
- name: user_id
datatype:
type: text
ref_table: users
ref_column:
- name: id
datatype:
type: text
is_primary: true
- type: table
name: tokens
columns:
@ -60,6 +120,47 @@ schemas:
datatype:
type: text
is_primary: true
- type: table
name: user_info
columns:
- name: user_id
datatype:
type: text
is_primary: true
- name: grade_level
datatype:
type: integer
- name: skill_level
datatype:
type: integer
primary_keys:
- name: user_id
datatype:
type: text
is_primary: true
indexes:
- name: sqlite_autoindex_user_info_1
fields:
- name: user_id
datatype:
type: text
is_primary: true
is_unique: true
is_primary: true
foreign_keys:
- name: user_info_user_id_fkey
column:
- name: user_id
datatype:
type: text
is_primary: true
ref_table: users
ref_column:
- name: id
datatype:
type: text
is_primary: true
manual: true
- type: table
name: users
columns:
@ -76,13 +177,6 @@ schemas:
- name: picture
datatype:
type: text
- name: grade_level
datatype:
type: integer
nullable: true
- name: teacher
datatype:
type: boolean
- name: admin
datatype:
type: boolean

@ -0,0 +1,19 @@
package question
import "text/template"
type PartData struct {
Completed bool
PointsWorth int
Solution string
}
type Data struct {
QuestionID string
UserID string
Name string
Text *template.Template
Level Level
Part1 PartData
Part2 PartData
}

@ -5,17 +5,24 @@ import (
"fmt"
"strconv"
"strings"
"text/template"
"github.com/hhhapz/codequest/models"
"github.com/hhhapz/codequest/question"
)
func init() {
t := template.New("directions")
var err error
t, err = t.Parse(q01Text)
if err != nil {
panic(err)
}
question.Register(
&question.Question{
ID: "directions",
Name: "No Time for Directions!",
Text: q01Text,
Text: t,
Level: question.Level1,
Generate: func(u *models.User) string {
return strings.Join(generate(u), "\n")

@ -59,9 +59,9 @@ always be between 1 and 9, inclusive, steps.
**How many steps away** is the key?
{{ if eq .Part 2 }}
{{ if .Part1.Completed -}}
**Congratulations! You got Part 1 correct. Your answer was `{{ .Answer1 }}`.**
**Congratulations! You got Part 1 correct. Your answer was `{{ .Part1.Solution }}`.**
## Part 2
@ -88,4 +88,9 @@ The first location visit twice is `7` blocks away South.
With these new instructions, to find the key, **how many steps away is the first location you visit
twice?**
{{ end }}
{{ if .Part2.Completed -}}
**Congratulations! You have completed both parts! The answer was `{{ .Part2.Solution }}`.**
{{- end }}
{{- end }}

@ -6,17 +6,24 @@ import (
"math/big"
"strconv"
"strings"
"text/template"
"github.com/hhhapz/codequest/models"
"github.com/hhhapz/codequest/question"
)
func init() {
t := template.New("saturnalia")
var err error
t, err = t.Parse(q02Text)
if err != nil {
panic(err)
}
question.Register(
&question.Question{
ID: "saturnalia",
Name: "Saturnalia's Problem",
Text: q02Text,
Text: t,
Level: question.Level1,
Generate: func(u *models.User) string {
inp := generate(u)

@ -60,9 +60,9 @@ returns back at the same time after the leave?
Hint: The numbers might get a bit large here!
{{ if eq .Part 2 }}
{{ if .Part1.Completed -}}
**Congratulations! You got Part 1 correct. Your answer was `{{ .Answer1 }}`.**
**Congratulations! You got Part 1 correct. Your answer was `{{ .Part1.Solution }}`.**
## Part 2
@ -85,4 +85,9 @@ trips, and the last ship will complete `5` trips, for a total of `29` trips.
With these new instructions, **how many steps total trips will all the ships complete?**
{{ end }}
{{ if .Part2.Completed -}}
**Congratulations! You have completed both parts! The answer was `{{ .Part2.Solution }}`.**
{{- end }}
{{- end }}

@ -1 +0,0 @@
package question

@ -3,25 +3,19 @@ package question
import (
"math/rand"
"strconv"
"text/template"
"github.com/hhhapz/codequest/models"
)
var bank Bank
// Bank is a map of different questions registered.
// The key of the map is the ID of the question.
//
// A custom type was created for convenience for picking random questions based
// on difficulties, and for registration.
type Bank []*Question
var Questions []*Question
func Register(q *Question) {
bank = append(bank, q)
Questions = append(Questions, q)
}
func QuestionByID(id string) *Question {
for _, q := range bank {
for _, q := range Questions {
if q.ID == id {
return q
}
@ -29,15 +23,10 @@ func QuestionByID(id string) *Question {
return nil
}
func Questions(user *models.User, level Level) []*Question {
// TODO: player skill level should be used to determine which problems to return
return bank
}
type Question struct {
ID string
Name string
Text string
Text *template.Template
Level Level
Generate func(user *models.User) string

@ -1,13 +1,23 @@
PRAGMA journal_mode = WAL;
PRAGMA busy_timeout = 5000;
PRAGMA foreign_keys = ON;
CREATE TABLE users (
id TEXT PRIMARY KEY NOT NULL, -- supplied from google - not auto increment
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
picture TEXT NOT NULL,
grade_level INTEGER,
admin BOOLEAN NOT NULL,
created_at DATETIME NOT NULL
);
CREATE TABLE user_info (
user_id TEXT PRIMARY KEY NOT NULL REFERENCES users (id),
grade_level INTEGER NOT NULL,
skill_level INTEGER NOT NULL
);
CREATE TABLE tokens (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
token TEXT NOT NULL UNIQUE,
@ -16,3 +26,15 @@ CREATE TABLE tokens (
);
CREATE INDEX user_tokens ON tokens(user_id);
CREATE TABLE question_attempt (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
user_id TEXT NOT NULL REFERENCES users (id),
question_id TEXT NOT NULL,
question_part INTEGER NOT NULL,
correct BOOLEAN NOT NULL,
points_awarded INTEGER NOT NULL,
input TEXT NOT NULL,
code TEXT NOT NULL,
submitted_at DATETIME NOT NULL
);