master
ALI Hamza 2022-04-29 06:31:09 +07:00
parent 4569fbcf76
commit 8128450d54
Signed by: hamza
GPG Key ID: 22473A32291F8CB6
89 changed files with 6824 additions and 6431 deletions

@ -0,0 +1,16 @@
FROM golang:1.18-alpine AS build_base
WORKDIR /app
RUN apk add git
RUN apk add build-base
COPY go.mod .
COPY go.sum .
RUN go mod download
COPY . .
RUN CGO_ENABLED=1 GOOS=linux go build -o gw ./cmd/gw
FROM alpine:3.9
COPY --from=build_base /app/gw /app/gw
CMD ["/app/gw"]

@ -0,0 +1,17 @@
FROM golang:1.18-alpine AS build_base
WORKDIR /app
RUN apk add git
RUN apk add build-base
COPY go.mod .
COPY go.sum .
RUN go mod download
COPY . .
RUN CGO_ENABLED=1 GOOS=linux go build -o srv ./cmd/srv
FROM alpine:3.9
COPY --from=build_base /app/srv /app/srv
COPY client.secret.json /app
CMD ["/app/srv", "-secret", "/app/client.secret.json"]

@ -0,0 +1,13 @@
FROM node:17-alpine
RUN npm install -g pnpm
COPY . /cq
WORKDIR /cq
RUN pnpm install
RUN pnpm build
CMD ["sh", "-c", "node build"]

@ -2,6 +2,7 @@ package api
import (
"context"
"time"
codequestpb "github.com/hhhapz/codequest/api/v1"
"github.com/hhhapz/codequest/models"
@ -28,6 +29,8 @@ type UserStore interface {
RevokeUserTokens(context.Context, *models.User) (int64, error)
User(context.Context, string) (*models.User, error)
UserPoints(context.Context, *models.User) (int, error)
Users(context.Context) ([]*models.User, error)
UserByToken(context.Context, string) (*models.User, error)
@ -41,6 +44,12 @@ type QuestStore interface {
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
Leaderboard(context.Context) ([]*models.LeaderboardEntry, error)
}
type CooldownStore interface {
NewAttempt(userID string, questionID string, attempts int)
Check(userID string, questionID string) time.Duration
}
type Server struct {
@ -49,7 +58,7 @@ type Server struct {
*QuestService
}
func NewServer(os OAuthStore, us UserStore, qs QuestStore) (*grpc.Server, error) {
func NewServer(os OAuthStore, us UserStore, qs QuestStore, cs CooldownStore) (*grpc.Server, error) {
s := &Server{
AuthService: &AuthService{
oauthStore: os,
@ -61,6 +70,7 @@ func NewServer(os OAuthStore, us UserStore, qs QuestStore) (*grpc.Server, error)
QuestService: &QuestService{
userStore: us,
questStore: qs,
cooldownStore: cs,
},
}
srv := grpc.NewServer(

@ -9,11 +9,10 @@ import (
"github.com/hhhapz/codequest/models"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
)
type AuthService struct {
codequestpb.UnimplementedAuthServiceServer
codequestpb.UnsafeAuthServiceServer
oauthStore OAuthStore
userStore UserStore
@ -21,7 +20,7 @@ type AuthService struct {
const (
forwardedHostKey = "x-forwarded-host"
uiHost = "codequest.teamortix.com"
uiHost = "playcode.quest"
)
func (as *AuthService) AuthFuncOverride(ctx context.Context, method string) (context.Context, error) {
@ -67,22 +66,21 @@ func (as *AuthService) Token(ctx context.Context, req *codequestpb.TokenRequest)
return &codequestpb.Token{
Token: tk.Token,
Expires: nil,
}, nil
}
func (as *AuthService) DeleteToken(ctx context.Context, req *codequestpb.DeleteTokenRequest) (*emptypb.Empty, error) {
u := UserCtx(ctx)
func (as *AuthService) DeleteToken(ctx context.Context, req *codequestpb.DeleteTokenRequest) (*codequestpb.DeleteTokenResponse, error) {
u := User(ctx)
var err error
if req.All {
_, err = as.userStore.RevokeUserTokens(ctx, u)
} else {
err = as.userStore.RevokeToken(ctx, TokenCtx(ctx))
err = as.userStore.RevokeToken(ctx, Token(ctx))
}
if err != nil {
log.Printf("could not revoke token: %v", err)
return nil, status.Errorf(codes.Internal, "could not revoke token")
}
return &emptypb.Empty{}, nil
return &codequestpb.DeleteTokenResponse{}, nil
}

@ -12,25 +12,26 @@ import (
"google.golang.org/grpc/status"
)
type ContextKey string
const (
UserKey = "ctxUser"
TokenKey = "ctxToken"
UserKey ContextKey = "ctxUser"
TokenKey ContextKey = "ctxToken"
)
func AuthInterceptor(authFunc grpc_auth.AuthFunc) grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
var newCtx context.Context
var err error
if overrideSrv, ok := info.Server.(grpc_auth.ServiceAuthFuncOverride); ok {
method := strings.LastIndex(info.FullMethod, "/")
newCtx, err = overrideSrv.AuthFuncOverride(ctx, info.FullMethod[method+1:])
ctx, err = overrideSrv.AuthFuncOverride(ctx, info.FullMethod[method+1:])
} else {
newCtx, err = authFunc(ctx)
ctx, err = authFunc(ctx)
}
if err != nil {
return nil, err
}
return handler(newCtx, req)
return handler(ctx, req)
}
}
@ -59,7 +60,7 @@ func BearerAuth(ctx context.Context, us UserStore) (context.Context, error) {
}
func AdminOnly(ctx context.Context) (context.Context, error) {
u := UserCtx(ctx)
u := User(ctx)
if u == nil {
return nil, status.Errorf(codes.Unauthenticated, "user not found")
}
@ -70,12 +71,12 @@ func AdminOnly(ctx context.Context) (context.Context, error) {
return ctx, nil
}
func UserCtx(ctx context.Context) *models.User {
func User(ctx context.Context) *models.User {
u, _ := ctx.Value(UserKey).(*models.User)
return u
}
func TokenCtx(ctx context.Context) string {
func Token(ctx context.Context) string {
t, _ := ctx.Value(TokenKey).(string)
return t
}

@ -5,6 +5,7 @@ import (
"context"
"errors"
"log"
"sort"
"time"
codequestpb "github.com/hhhapz/codequest/api/v1"
@ -12,33 +13,48 @@ import (
"github.com/hhhapz/codequest/question"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
)
var (
Start time.Time
End time.Time
)
func active() bool {
now := time.Now()
return Start.Before(now) && End.After(now)
}
type QuestService struct {
codequestpb.UnimplementedQuestServiceServer
codequestpb.UnsafeQuestServiceServer
questStore QuestStore
userStore UserStore
cooldownStore CooldownStore
}
func (qs *QuestService) Questions(ctx context.Context, req *emptypb.Empty) (*codequestpb.QuestionsResponse, error) {
u := UserCtx(ctx)
func (qs *QuestService) Questions(ctx context.Context, req *codequestpb.QuestionsRequest) (*codequestpb.QuestionsResponse, error) {
u := User(ctx)
questions, err := qs.questStore.Questions(ctx, u)
if err != nil {
if errors.As(err, &models.UserError{}) {
switch {
case errors.As(err, &models.UserError{}):
return nil, status.Error(codes.InvalidArgument, err.Error())
}
case err != nil:
return nil, status.Errorf(codes.Internal, "could not get questions: internal error")
}
if !active() {
questions = nil
}
res := new(codequestpb.QuestionsResponse)
for _, q := range questions {
res.Questions = append(res.Questions, &codequestpb.Question{
ID: q.Question.ID,
Title: q.Name,
Difficulty: codequestpb.Difficulty(q.Level),
Part1: &codequestpb.PartData{
Completed: q.Part1.Completed,
PointsWorth: int32(q.Part1.PointsWorth),
@ -50,18 +66,59 @@ func (qs *QuestService) Questions(ctx context.Context, req *emptypb.Empty) (*cod
})
}
q := res.Questions
sort.Slice(q, func(i, j int) bool {
var iSolved, jSolved int
switch {
case q[i].Part2.Completed:
iSolved = 2
case q[i].Part1.Completed:
iSolved = 1
}
switch {
case q[j].Part2.Completed:
jSolved = 2
case q[j].Part1.Completed:
jSolved = 1
}
switch {
// if progress on questions is the same, sort alphabetically
case iSolved == jSolved:
return q[i].Text < q[j].Text
// if both parts completed, always put at the end
case iSolved == 2:
return false
case jSolved == 2:
return true
// show partway completed ones before those not started
case iSolved == 1:
return true
case jSolved == 1:
return false
}
return false
})
return res, nil
}
func (qs *QuestService) QuestionByID(ctx context.Context, req *codequestpb.QuestionByIDRequest) (*codequestpb.Question, error) {
u := UserCtx(ctx)
u := User(ctx)
if !active() {
return nil, status.Errorf(codes.NotFound, "the contest is not active")
}
question, err := qs.questStore.Question(ctx, u, req.ID)
if err != nil {
if errors.As(err, &models.UserError{}) {
switch {
case 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")
case err != nil:
log.Printf("could not fetch question(%q): %v", req.ID, err)
return nil, status.Errorf(codes.Internal, "could not get questions: internal error")
}
content := new(bytes.Buffer)
@ -89,19 +146,22 @@ func (qs *QuestService) QuestionByID(ctx context.Context, req *codequestpb.Quest
}
func (qs *QuestService) QuestionInput(ctx context.Context, req *codequestpb.QuestionInputRequest) (*codequestpb.QuestionInput, error) {
u := UserCtx(ctx)
u := User(ctx)
if !active() {
return nil, status.Errorf(codes.NotFound, "the contest is not active")
}
question, err := qs.questStore.Question(ctx, u, req.ID)
if err != nil {
if errors.As(err, &models.UserError{}) {
switch {
case errors.As(err, &models.UserError{}):
return nil, status.Error(codes.InvalidArgument, err.Error())
}
case err != nil:
log.Printf("could not get question %s: %v", req.ID, err)
return nil, status.Errorf(codes.Internal, "could not get question: internal error")
}
inp := question.Question.Generate(u)
return &codequestpb.QuestionInput{
ID: question.Question.ID,
Input: inp,
@ -109,52 +169,66 @@ func (qs *QuestService) QuestionInput(ctx context.Context, req *codequestpb.Ques
}
func (qs *QuestService) Submit(ctx context.Context, req *codequestpb.SubmitRequest) (*codequestpb.SubmitResponse, error) {
u := UserCtx(ctx)
u := User(ctx)
q, 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")
if !active() {
return nil, status.Errorf(codes.NotFound, "the contest is not active")
}
data := req.Body
if req.Part != 1 && req.Part != 2 {
return nil, status.Errorf(codes.NotFound, "invalid part number")
}
completed, attempts, err := qs.questStore.Submissions(ctx, u, req.ID, question.Part(data.Part))
if err != nil {
if errors.As(err, &models.UserError{}) {
q, err := qs.questStore.Question(ctx, u, req.ID)
switch {
case errors.As(err, &models.UserError{}):
return nil, status.Error(codes.InvalidArgument, err.Error())
case err != nil:
return nil, status.Errorf(codes.Internal, "could not get questions: internal error")
}
completed, attempts, err := qs.questStore.Submissions(ctx, u, req.ID, question.Part(req.Part))
switch {
case errors.As(err, &models.UserError{}):
return nil, status.Errorf(codes.InvalidArgument, "%v", err)
case err != nil:
log.Printf("could not get submissions: %v", err)
return nil, status.Errorf(codes.Internal, "could not get submissions: internal error")
}
if completed {
return nil, status.Errorf(codes.AlreadyExists, "question %s (part %d) already completed", req.ID, data.Part)
return nil, status.Errorf(codes.AlreadyExists, "question %s (part %d) already completed", req.ID, req.Part)
}
// TODO: cooldowns
t := qs.cooldownStore.Check(u.ID, q.Question.ID)
if t != 0 {
return nil, status.Errorf(
codes.FailedPrecondition,
"you must wait %s before trying this question again", t.Truncate(time.Second),
)
}
correct := q.Question.Validate(u, question.Part(data.Part), data.Answer)
ok := q.Question.Validate(u, question.Part(req.Part), req.Body.Answer)
var points int
if correct {
if ok {
points = 5000 - (attempts * 100)
if points < 2000 {
points = 2000
}
} else {
qs.cooldownStore.NewAttempt(u.ID, q.Question.ID, attempts)
}
qa := &models.QuestionAttempt{
UserID: u.ID,
QuestionID: q.Question.ID,
QuestionPart: int(data.Part),
Correct: correct,
QuestionPart: int(req.Part),
Correct: ok,
PointsAwarded: points,
Answer: data.Answer,
Code: data.Code,
Answer: req.Body.Answer,
Code: req.Body.Code,
SubmittedAt: models.NewTime(time.Now()),
}
qs.questStore.AddSubmission(ctx, qa)
@ -165,3 +239,19 @@ func (qs *QuestService) Submit(ctx context.Context, req *codequestpb.SubmitReque
}
return res, nil
}
func (qs *QuestService) Leaderboard(ctx context.Context, req *codequestpb.LeaderboardRequest) (*codequestpb.LeaderboardResponse, error) {
entries, err := qs.questStore.Leaderboard(ctx)
if err != nil {
log.Printf("could not get leaderboard: %v", err)
return nil, status.Errorf(codes.Internal, "could not get leaderboard: internal error")
}
var lbr codequestpb.LeaderboardResponse
for _, le := range entries {
lbr.Leaderboard = append(lbr.Leaderboard, &codequestpb.LeaderboardEntry{
Username: le.Name,
Points: int32(le.Points),
})
}
return &lbr, nil
}

@ -3,31 +3,38 @@ package api
import (
"context"
"log"
"time"
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/timestamppb"
)
type UserService struct {
codequestpb.UnimplementedUserServiceServer
codequestpb.UnsafeUserServiceServer
userStore UserStore
}
func (us *UserService) User(ctx context.Context, req *codequestpb.UserRequest) (*codequestpb.User, error) {
u := UserCtx(ctx)
return convertUser(u), nil
func (us *UserService) Info(ctx context.Context, req *codequestpb.InfoRequest) (*codequestpb.Info, error) {
u := User(ctx)
points, err := us.userStore.UserPoints(ctx, u)
if err != nil {
log.Printf("Could not retrieve user points(%q): %v", u.ID, err)
// no return
}
return &codequestpb.Info{
CurrentUser: convertUser(u),
Active: active(),
Points: int32(points),
StartTime: Start.UTC().Format(time.RFC3339),
EndTime: End.UTC().Format(time.RFC3339),
}, nil
}
func (us *UserService) UserByEmail(ctx context.Context, req *codequestpb.UserByEmailRequest) (*codequestpb.User, error) {
u := UserCtx(ctx)
if u.Email == req.Email {
return convertUser(u), nil
}
var err error
if ctx, err = AdminOnly(ctx); err != nil {
return nil, err
@ -62,7 +69,7 @@ func (us *UserService) AllUsers(ctx context.Context, req *codequestpb.AllUsersRe
}
func (us *UserService) UpdateUser(ctx context.Context, req *codequestpb.UpdateUserRequest) (*codequestpb.User, error) {
u := UserCtx(ctx)
u := User(ctx)
u.Name = req.Body.Name
if len(u.Name) < 3 || len(u.Name) > 20 {
@ -84,16 +91,16 @@ func (us *UserService) UpdateUser(ctx context.Context, req *codequestpb.UpdateUs
return convertUser(u), nil
}
func (us *UserService) AdminUpdateUser(ctx context.Context, req *codequestpb.UpdateUserRequest) (*codequestpb.User, error) {
func (us *UserService) AdminUpdateUser(ctx context.Context, req *codequestpb.AdminUpdateUserRequest) (*codequestpb.User, error) {
var err error
if ctx, err = AdminOnly(ctx); err != nil {
return nil, err
}
user, err := us.userStore.User(ctx, req.Email)
user, err := us.userStore.User(ctx, req.Body.Email)
if err != nil {
log.Printf("Could not fetch user(%q): %v", req.Email, err)
return nil, status.Errorf(codes.NotFound, "email not found: %q", req.Email)
log.Printf("Could not fetch user(%q): %v", req.Body.Email, err)
return nil, status.Errorf(codes.NotFound, "email not found: %q", req.Body.Email)
}
user.Name = req.Body.Name
@ -144,6 +151,6 @@ func convertUser(u *models.User) *codequestpb.User {
Email: u.Email,
Picture: u.Picture,
Admin: u.Admin,
CreatedAt: timestamppb.New(u.CreatedAt.Time()),
CreatedAt: u.CreatedAt.Time().UTC().Format(time.RFC3339),
}
}

@ -14,3 +14,6 @@
[generate grpc-gateway-ts] ; typescript
out=../../web_src/lib/pb
ts_import_roots=../../web_src/lib/pb
fetch_module_directory=../../web_src/lib/pb
ts_import_root_aliases=.

File diff suppressed because it is too large Load Diff

@ -21,16 +21,18 @@ import (
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/emptypb"
)
// Suppress "imported and not used" errors
var _ codes.Code
var _ io.Reader
var _ status.Status
var _ = runtime.String
var _ = utilities.NewDoubleArray
var _ = metadata.Join
var (
_ io.Reader
_ status.Status
_ = runtime.String
_ = utilities.NewDoubleArray
_ = metadata.Join
)
func request_AuthService_OAuthCode_0(ctx context.Context, marshaler runtime.Marshaler, client AuthServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq OAuthCodeRequest
@ -38,7 +40,6 @@ func request_AuthService_OAuthCode_0(ctx context.Context, marshaler runtime.Mars
msg, err := client.OAuthCode(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_AuthService_OAuthCode_0(ctx context.Context, marshaler runtime.Marshaler, server AuthServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
@ -47,12 +48,9 @@ func local_request_AuthService_OAuthCode_0(ctx context.Context, marshaler runtim
msg, err := server.OAuthCode(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_AuthService_Token_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
var filter_AuthService_Token_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
func request_AuthService_Token_0(ctx context.Context, marshaler runtime.Marshaler, client AuthServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq TokenRequest
@ -67,7 +65,6 @@ func request_AuthService_Token_0(ctx context.Context, marshaler runtime.Marshale
msg, err := client.Token(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_AuthService_Token_0(ctx context.Context, marshaler runtime.Marshaler, server AuthServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
@ -83,12 +80,9 @@ func local_request_AuthService_Token_0(ctx context.Context, marshaler runtime.Ma
msg, err := server.Token(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_AuthService_DeleteToken_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
var filter_AuthService_DeleteToken_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
func request_AuthService_DeleteToken_0(ctx context.Context, marshaler runtime.Marshaler, client AuthServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq DeleteTokenRequest
@ -103,7 +97,6 @@ func request_AuthService_DeleteToken_0(ctx context.Context, marshaler runtime.Ma
msg, err := client.DeleteToken(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_AuthService_DeleteToken_0(ctx context.Context, marshaler runtime.Marshaler, server AuthServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
@ -119,25 +112,22 @@ func local_request_AuthService_DeleteToken_0(ctx context.Context, marshaler runt
msg, err := server.DeleteToken(ctx, &protoReq)
return msg, metadata, err
}
func request_QuestService_Questions_0(ctx context.Context, marshaler runtime.Marshaler, client QuestServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq emptypb.Empty
var protoReq QuestionsRequest
var metadata runtime.ServerMetadata
msg, err := client.Questions(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_QuestService_Questions_0(ctx context.Context, marshaler runtime.Marshaler, server QuestServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq emptypb.Empty
var protoReq QuestionsRequest
var metadata runtime.ServerMetadata
msg, err := server.Questions(ctx, &protoReq)
return msg, metadata, err
}
func request_QuestService_QuestionByID_0(ctx context.Context, marshaler runtime.Marshaler, client QuestServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
@ -163,7 +153,6 @@ func request_QuestService_QuestionByID_0(ctx context.Context, marshaler runtime.
msg, err := client.QuestionByID(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_QuestService_QuestionByID_0(ctx context.Context, marshaler runtime.Marshaler, server QuestServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
@ -189,7 +178,6 @@ func local_request_QuestService_QuestionByID_0(ctx context.Context, marshaler ru
msg, err := server.QuestionByID(ctx, &protoReq)
return msg, metadata, err
}
func request_QuestService_QuestionInput_0(ctx context.Context, marshaler runtime.Marshaler, client QuestServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
@ -215,7 +203,6 @@ func request_QuestService_QuestionInput_0(ctx context.Context, marshaler runtime
msg, err := client.QuestionInput(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_QuestService_QuestionInput_0(ctx context.Context, marshaler runtime.Marshaler, server QuestServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
@ -241,7 +228,6 @@ func local_request_QuestService_QuestionInput_0(ctx context.Context, marshaler r
msg, err := server.QuestionInput(ctx, &protoReq)
return msg, metadata, err
}
func request_QuestService_Submit_0(ctx context.Context, marshaler runtime.Marshaler, client QuestServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
@ -273,9 +259,18 @@ func request_QuestService_Submit_0(ctx context.Context, marshaler runtime.Marsha
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "ID", err)
}
val, ok = pathParams["Part"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "Part")
}
protoReq.Part, err = runtime.Int32(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "Part", err)
}
msg, err := client.Submit(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_QuestService_Submit_0(ctx context.Context, marshaler runtime.Marshaler, server QuestServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
@ -307,27 +302,50 @@ func local_request_QuestService_Submit_0(ctx context.Context, marshaler runtime.
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "ID", err)
}
val, ok = pathParams["Part"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "Part")
}
protoReq.Part, err = runtime.Int32(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "Part", err)
}
msg, err := server.Submit(ctx, &protoReq)
return msg, metadata, err
}
func request_UserService_User_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq UserRequest
func request_QuestService_Leaderboard_0(ctx context.Context, marshaler runtime.Marshaler, client QuestServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq LeaderboardRequest
var metadata runtime.ServerMetadata
msg, err := client.User(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
msg, err := client.Leaderboard(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_QuestService_Leaderboard_0(ctx context.Context, marshaler runtime.Marshaler, server QuestServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq LeaderboardRequest
var metadata runtime.ServerMetadata
msg, err := server.Leaderboard(ctx, &protoReq)
return msg, metadata, err
}
func local_request_UserService_User_0(ctx context.Context, marshaler runtime.Marshaler, server UserServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq UserRequest
func request_UserService_Info_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq InfoRequest
var metadata runtime.ServerMetadata
msg, err := server.User(ctx, &protoReq)
msg, err := client.Info(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_UserService_Info_0(ctx context.Context, marshaler runtime.Marshaler, server UserServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq InfoRequest
var metadata runtime.ServerMetadata
msg, err := server.Info(ctx, &protoReq)
return msg, metadata, err
}
func request_UserService_UserByEmail_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
@ -353,7 +371,6 @@ func request_UserService_UserByEmail_0(ctx context.Context, marshaler runtime.Ma
msg, err := client.UserByEmail(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_UserService_UserByEmail_0(ctx context.Context, marshaler runtime.Marshaler, server UserServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
@ -379,7 +396,6 @@ func local_request_UserService_UserByEmail_0(ctx context.Context, marshaler runt
msg, err := server.UserByEmail(ctx, &protoReq)
return msg, metadata, err
}
func request_UserService_AllUsers_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
@ -388,7 +404,6 @@ func request_UserService_AllUsers_0(ctx context.Context, marshaler runtime.Marsh
msg, err := client.AllUsers(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_UserService_AllUsers_0(ctx context.Context, marshaler runtime.Marshaler, server UserServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
@ -397,13 +412,8 @@ func local_request_UserService_AllUsers_0(ctx context.Context, marshaler runtime
msg, err := server.AllUsers(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_UserService_UpdateUser_0 = &utilities.DoubleArray{Encoding: map[string]int{"Body": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}}
)
func request_UserService_UpdateUser_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq UpdateUserRequest
var metadata runtime.ServerMetadata
@ -416,16 +426,8 @@ func request_UserService_UpdateUser_0(ctx context.Context, marshaler runtime.Mar
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_UserService_UpdateUser_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.UpdateUser(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_UserService_UpdateUser_0(ctx context.Context, marshaler runtime.Marshaler, server UserServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
@ -440,20 +442,12 @@ func local_request_UserService_UpdateUser_0(ctx context.Context, marshaler runti
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_UserService_UpdateUser_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.UpdateUser(ctx, &protoReq)
return msg, metadata, err
}
func request_UserService_AdminUpdateUser_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq UpdateUserRequest
var protoReq AdminUpdateUserRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
@ -471,23 +465,22 @@ func request_UserService_AdminUpdateUser_0(ctx context.Context, marshaler runtim
_ = err
)
val, ok = pathParams["Email"]
val, ok = pathParams["Body.Email"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "Email")
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "Body.Email")
}
protoReq.Email, err = runtime.String(val)
err = runtime.PopulateFieldFromPath(&protoReq, "Body.Email", val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "Email", err)
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "Body.Email", err)
}
msg, err := client.AdminUpdateUser(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_UserService_AdminUpdateUser_0(ctx context.Context, marshaler runtime.Marshaler, server UserServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq UpdateUserRequest
var protoReq AdminUpdateUserRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
@ -505,19 +498,18 @@ func local_request_UserService_AdminUpdateUser_0(ctx context.Context, marshaler
_ = err
)
val, ok = pathParams["Email"]
val, ok = pathParams["Body.Email"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "Email")
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "Body.Email")
}
protoReq.Email, err = runtime.String(val)
err = runtime.PopulateFieldFromPath(&protoReq, "Body.Email", val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "Email", err)
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "Body.Email", err)
}
msg, err := server.AdminUpdateUser(ctx, &protoReq)
return msg, metadata, err
}
func request_UserService_DeleteUser_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
@ -543,7 +535,6 @@ func request_UserService_DeleteUser_0(ctx context.Context, marshaler runtime.Mar
msg, err := client.DeleteUser(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_UserService_DeleteUser_0(ctx context.Context, marshaler runtime.Marshaler, server UserServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
@ -569,7 +560,6 @@ func local_request_UserService_DeleteUser_0(ctx context.Context, marshaler runti
msg, err := server.DeleteUser(ctx, &protoReq)
return msg, metadata, err
}
// RegisterAuthServiceHandlerServer registers the http handlers for service AuthService to "mux".
@ -577,7 +567,6 @@ func local_request_UserService_DeleteUser_0(ctx context.Context, marshaler runti
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterAuthServiceHandlerFromEndpoint instead.
func RegisterAuthServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server AuthServiceServer) error {
mux.Handle("GET", pattern_AuthService_OAuthCode_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@ -598,7 +587,6 @@ func RegisterAuthServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
}
forward_AuthService_OAuthCode_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_AuthService_Token_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
@ -621,7 +609,6 @@ func RegisterAuthServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
}
forward_AuthService_Token_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("DELETE", pattern_AuthService_DeleteToken_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
@ -644,7 +631,6 @@ func RegisterAuthServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
}
forward_AuthService_DeleteToken_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
@ -655,7 +641,6 @@ func RegisterAuthServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterQuestServiceHandlerFromEndpoint instead.
func RegisterQuestServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QuestServiceServer) error {
mux.Handle("GET", pattern_QuestService_Questions_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@ -676,7 +661,6 @@ func RegisterQuestServiceHandlerServer(ctx context.Context, mux *runtime.ServeMu
}
forward_QuestService_Questions_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_QuestService_QuestionByID_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
@ -699,7 +683,6 @@ func RegisterQuestServiceHandlerServer(ctx context.Context, mux *runtime.ServeMu
}
forward_QuestService_QuestionByID_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_QuestService_QuestionInput_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
@ -722,7 +705,6 @@ func RegisterQuestServiceHandlerServer(ctx context.Context, mux *runtime.ServeMu
}
forward_QuestService_QuestionInput_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_QuestService_Submit_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
@ -731,7 +713,7 @@ func RegisterQuestServiceHandlerServer(ctx context.Context, mux *runtime.ServeMu
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/hhhapz.codequest.v1.QuestService/Submit", runtime.WithHTTPPathPattern("/v1/questions/{ID}"))
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/hhhapz.codequest.v1.QuestService/Submit", runtime.WithHTTPPathPattern("/v1/questions/{ID}/{Part}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
@ -745,7 +727,28 @@ func RegisterQuestServiceHandlerServer(ctx context.Context, mux *runtime.ServeMu
}
forward_QuestService_Submit_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_QuestService_Leaderboard_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/hhhapz.codequest.v1.QuestService/Leaderboard", runtime.WithHTTPPathPattern("/v1/questions/leaderboard"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_QuestService_Leaderboard_0(rctx, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_QuestService_Leaderboard_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
@ -756,19 +759,18 @@ func RegisterQuestServiceHandlerServer(ctx context.Context, mux *runtime.ServeMu
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterUserServiceHandlerFromEndpoint instead.
func RegisterUserServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server UserServiceServer) error {
mux.Handle("GET", pattern_UserService_User_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
mux.Handle("GET", pattern_UserService_Info_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/hhhapz.codequest.v1.UserService/User", runtime.WithHTTPPathPattern("/v1/users/me"))
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/hhhapz.codequest.v1.UserService/Info", runtime.WithHTTPPathPattern("/v1/users/me"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_UserService_User_0(rctx, inboundMarshaler, server, req, pathParams)
resp, md, err := local_request_UserService_Info_0(rctx, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
@ -776,8 +778,7 @@ func RegisterUserServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
return
}
forward_UserService_User_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
forward_UserService_Info_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_UserService_UserByEmail_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
@ -800,7 +801,6 @@ func RegisterUserServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
}
forward_UserService_UserByEmail_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_UserService_AllUsers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
@ -823,10 +823,9 @@ func RegisterUserServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
}
forward_UserService_AllUsers_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("PUT", pattern_UserService_UpdateUser_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
mux.Handle("PATCH", pattern_UserService_UpdateUser_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
@ -846,16 +845,15 @@ func RegisterUserServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
}
forward_UserService_UpdateUser_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("PUT", pattern_UserService_AdminUpdateUser_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
mux.Handle("PATCH", pattern_UserService_AdminUpdateUser_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/hhhapz.codequest.v1.UserService/AdminUpdateUser", runtime.WithHTTPPathPattern("/v1/admin/users/{Email}"))
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/hhhapz.codequest.v1.UserService/AdminUpdateUser", runtime.WithHTTPPathPattern("/v1/admin/users/{Body.Email}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
@ -869,7 +867,6 @@ func RegisterUserServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
}
forward_UserService_AdminUpdateUser_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("DELETE", pattern_UserService_DeleteUser_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
@ -892,7 +889,6 @@ func RegisterUserServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
}
forward_UserService_DeleteUser_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
@ -925,7 +921,7 @@ func RegisterAuthServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.Se
// RegisterAuthServiceHandler registers the http handlers for service AuthService to "mux".
// The handlers forward requests to the grpc endpoint over "conn".
func RegisterAuthServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn grpc.ClientConnInterface) error {
func RegisterAuthServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
return RegisterAuthServiceHandlerClient(ctx, mux, NewAuthServiceClient(conn))
}
@ -935,7 +931,6 @@ func RegisterAuthServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
// "AuthServiceClient" to call the correct interceptors.
func RegisterAuthServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client AuthServiceClient) error {
mux.Handle("GET", pattern_AuthService_OAuthCode_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@ -953,7 +948,6 @@ func RegisterAuthServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux
}
forward_AuthService_OAuthCode_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_AuthService_Token_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
@ -973,7 +967,6 @@ func RegisterAuthServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux
}
forward_AuthService_Token_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("DELETE", pattern_AuthService_DeleteToken_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
@ -993,7 +986,6 @@ func RegisterAuthServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux
}
forward_AuthService_DeleteToken_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
@ -1042,7 +1034,7 @@ func RegisterQuestServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.S
// RegisterQuestServiceHandler registers the http handlers for service QuestService to "mux".
// The handlers forward requests to the grpc endpoint over "conn".
func RegisterQuestServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn grpc.ClientConnInterface) error {
func RegisterQuestServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
return RegisterQuestServiceHandlerClient(ctx, mux, NewQuestServiceClient(conn))
}
@ -1052,7 +1044,6 @@ func RegisterQuestServiceHandler(ctx context.Context, mux *runtime.ServeMux, con
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
// "QuestServiceClient" to call the correct interceptors.
func RegisterQuestServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client QuestServiceClient) error {
mux.Handle("GET", pattern_QuestService_Questions_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@ -1070,7 +1061,6 @@ func RegisterQuestServiceHandlerClient(ctx context.Context, mux *runtime.ServeMu
}
forward_QuestService_Questions_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_QuestService_QuestionByID_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
@ -1090,7 +1080,6 @@ func RegisterQuestServiceHandlerClient(ctx context.Context, mux *runtime.ServeMu
}
forward_QuestService_QuestionByID_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_QuestService_QuestionInput_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
@ -1110,14 +1099,13 @@ func RegisterQuestServiceHandlerClient(ctx context.Context, mux *runtime.ServeMu
}
forward_QuestService_QuestionInput_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_QuestService_Submit_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/hhhapz.codequest.v1.QuestService/Submit", runtime.WithHTTPPathPattern("/v1/questions/{ID}"))
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/hhhapz.codequest.v1.QuestService/Submit", runtime.WithHTTPPathPattern("/v1/questions/{ID}/{Part}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
@ -1130,7 +1118,25 @@ func RegisterQuestServiceHandlerClient(ctx context.Context, mux *runtime.ServeMu
}
forward_QuestService_Submit_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_QuestService_Leaderboard_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/hhhapz.codequest.v1.QuestService/Leaderboard", runtime.WithHTTPPathPattern("/v1/questions/leaderboard"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_QuestService_Leaderboard_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_QuestService_Leaderboard_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
@ -1143,7 +1149,9 @@ var (
pattern_QuestService_QuestionInput_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3}, []string{"v1", "questions", "ID", "input"}, ""))
pattern_QuestService_Submit_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "questions", "ID"}, ""))
pattern_QuestService_Submit_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 1, 0, 4, 1, 5, 3}, []string{"v1", "questions", "ID", "Part"}, ""))
pattern_QuestService_Leaderboard_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "questions", "leaderboard"}, ""))
)
var (
@ -1154,6 +1162,8 @@ var (
forward_QuestService_QuestionInput_0 = runtime.ForwardResponseMessage
forward_QuestService_Submit_0 = runtime.ForwardResponseMessage
forward_QuestService_Leaderboard_0 = runtime.ForwardResponseMessage
)
// RegisterUserServiceHandlerFromEndpoint is same as RegisterUserServiceHandler but
@ -1183,7 +1193,7 @@ func RegisterUserServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.Se
// RegisterUserServiceHandler registers the http handlers for service UserService to "mux".
// The handlers forward requests to the grpc endpoint over "conn".
func RegisterUserServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn grpc.ClientConnInterface) error {
func RegisterUserServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
return RegisterUserServiceHandlerClient(ctx, mux, NewUserServiceClient(conn))
}
@ -1193,25 +1203,23 @@ func RegisterUserServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
// "UserServiceClient" to call the correct interceptors.
func RegisterUserServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client UserServiceClient) error {
mux.Handle("GET", pattern_UserService_User_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
mux.Handle("GET", pattern_UserService_Info_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/hhhapz.codequest.v1.UserService/User", runtime.WithHTTPPathPattern("/v1/users/me"))
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/hhhapz.codequest.v1.UserService/Info", runtime.WithHTTPPathPattern("/v1/users/me"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_UserService_User_0(rctx, inboundMarshaler, client, req, pathParams)
resp, md, err := request_UserService_Info_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_UserService_User_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
forward_UserService_Info_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_UserService_UserByEmail_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
@ -1231,7 +1239,6 @@ func RegisterUserServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux
}
forward_UserService_UserByEmail_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_UserService_AllUsers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
@ -1251,10 +1258,9 @@ func RegisterUserServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux
}
forward_UserService_AllUsers_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("PUT", pattern_UserService_UpdateUser_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
mux.Handle("PATCH", pattern_UserService_UpdateUser_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
@ -1271,14 +1277,13 @@ func RegisterUserServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux
}
forward_UserService_UpdateUser_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("PUT", pattern_UserService_AdminUpdateUser_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
mux.Handle("PATCH", pattern_UserService_AdminUpdateUser_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/hhhapz.codequest.v1.UserService/AdminUpdateUser", runtime.WithHTTPPathPattern("/v1/admin/users/{Email}"))
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/hhhapz.codequest.v1.UserService/AdminUpdateUser", runtime.WithHTTPPathPattern("/v1/admin/users/{Body.Email}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
@ -1291,7 +1296,6 @@ func RegisterUserServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux
}
forward_UserService_AdminUpdateUser_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("DELETE", pattern_UserService_DeleteUser_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
@ -1311,14 +1315,13 @@ func RegisterUserServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux
}
forward_UserService_DeleteUser_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
var (
pattern_UserService_User_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "users", "me"}, ""))
pattern_UserService_Info_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "users", "me"}, ""))
pattern_UserService_UserByEmail_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"v1", "admin", "users", "Email"}, ""))
@ -1326,13 +1329,13 @@ var (
pattern_UserService_UpdateUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "users", "me"}, ""))
pattern_UserService_AdminUpdateUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"v1", "admin", "users", "Email"}, ""))
pattern_UserService_AdminUpdateUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"v1", "admin", "users", "Body.Email"}, ""))
pattern_UserService_DeleteUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"v1", "admin", "users", "Email"}, ""))
)
var (
forward_UserService_User_0 = runtime.ForwardResponseMessage
forward_UserService_Info_0 = runtime.ForwardResponseMessage
forward_UserService_UserByEmail_0 = runtime.ForwardResponseMessage

@ -1,4 +1,8 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.2.0
// - protoc (unknown)
// source: github.com/hhhapz/codequest/api/v1/all.proto
package codequest
@ -7,7 +11,6 @@ import (
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
emptypb "google.golang.org/protobuf/types/known/emptypb"
)
// This is a compile-time assertion to ensure that this generated file
@ -21,7 +24,7 @@ const _ = grpc.SupportPackageIsVersion7
type AuthServiceClient interface {
OAuthCode(ctx context.Context, in *OAuthCodeRequest, opts ...grpc.CallOption) (*OAuthCodeResponse, error)
Token(ctx context.Context, in *TokenRequest, opts ...grpc.CallOption) (*Token, error)
DeleteToken(ctx context.Context, in *DeleteTokenRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
DeleteToken(ctx context.Context, in *DeleteTokenRequest, opts ...grpc.CallOption) (*DeleteTokenResponse, error)
}
type authServiceClient struct {
@ -50,8 +53,8 @@ func (c *authServiceClient) Token(ctx context.Context, in *TokenRequest, opts ..
return out, nil
}
func (c *authServiceClient) DeleteToken(ctx context.Context, in *DeleteTokenRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
out := new(emptypb.Empty)
func (c *authServiceClient) DeleteToken(ctx context.Context, in *DeleteTokenRequest, opts ...grpc.CallOption) (*DeleteTokenResponse, error) {
out := new(DeleteTokenResponse)
err := c.cc.Invoke(ctx, "/hhhapz.codequest.v1.AuthService/DeleteToken", in, out, opts...)
if err != nil {
return nil, err
@ -65,7 +68,7 @@ func (c *authServiceClient) DeleteToken(ctx context.Context, in *DeleteTokenRequ
type AuthServiceServer interface {
OAuthCode(context.Context, *OAuthCodeRequest) (*OAuthCodeResponse, error)
Token(context.Context, *TokenRequest) (*Token, error)
DeleteToken(context.Context, *DeleteTokenRequest) (*emptypb.Empty, error)
DeleteToken(context.Context, *DeleteTokenRequest) (*DeleteTokenResponse, error)
mustEmbedUnimplementedAuthServiceServer()
}
@ -79,7 +82,7 @@ func (UnimplementedAuthServiceServer) OAuthCode(context.Context, *OAuthCodeReque
func (UnimplementedAuthServiceServer) Token(context.Context, *TokenRequest) (*Token, error) {
return nil, status.Errorf(codes.Unimplemented, "method Token not implemented")
}
func (UnimplementedAuthServiceServer) DeleteToken(context.Context, *DeleteTokenRequest) (*emptypb.Empty, error) {
func (UnimplementedAuthServiceServer) DeleteToken(context.Context, *DeleteTokenRequest) (*DeleteTokenResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteToken not implemented")
}
func (UnimplementedAuthServiceServer) mustEmbedUnimplementedAuthServiceServer() {}
@ -177,10 +180,22 @@ var AuthService_ServiceDesc = grpc.ServiceDesc{
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type QuestServiceClient interface {
Questions(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*QuestionsResponse, error)
// Questions returns the list of available questions.
// Each question is also listed with whether it has been answered, and
// the points awarded for answering it correctly.
//
// The Text field will not be populated. To get the text, use QuestionByID.
Questions(ctx context.Context, in *QuestionsRequest, opts ...grpc.CallOption) (*QuestionsResponse, error)
// QuestionByID returns the question with the given ID.
QuestionByID(ctx context.Context, in *QuestionByIDRequest, opts ...grpc.CallOption) (*Question, error)
// QuestionInput returns the question input for the given question.
// The input is the same for part 1 and part 2.
QuestionInput(ctx context.Context, in *QuestionInputRequest, opts ...grpc.CallOption) (*QuestionInput, error)
// Submit submits the answer to the given question and part.
Submit(ctx context.Context, in *SubmitRequest, opts ...grpc.CallOption) (*SubmitResponse, error)
// Leaderboard returns the global ranking of all participatants.
// The leaderboard is sorted by score, descending.
Leaderboard(ctx context.Context, in *LeaderboardRequest, opts ...grpc.CallOption) (*LeaderboardResponse, error)
}
type questServiceClient struct {
@ -191,7 +206,7 @@ func NewQuestServiceClient(cc grpc.ClientConnInterface) QuestServiceClient {
return &questServiceClient{cc}
}
func (c *questServiceClient) Questions(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*QuestionsResponse, error) {
func (c *questServiceClient) Questions(ctx context.Context, in *QuestionsRequest, opts ...grpc.CallOption) (*QuestionsResponse, error) {
out := new(QuestionsResponse)
err := c.cc.Invoke(ctx, "/hhhapz.codequest.v1.QuestService/Questions", in, out, opts...)
if err != nil {
@ -227,14 +242,35 @@ func (c *questServiceClient) Submit(ctx context.Context, in *SubmitRequest, opts
return out, nil
}
func (c *questServiceClient) Leaderboard(ctx context.Context, in *LeaderboardRequest, opts ...grpc.CallOption) (*LeaderboardResponse, error) {
out := new(LeaderboardResponse)
err := c.cc.Invoke(ctx, "/hhhapz.codequest.v1.QuestService/Leaderboard", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// QuestServiceServer is the server API for QuestService service.
// All implementations must embed UnimplementedQuestServiceServer
// for forward compatibility
type QuestServiceServer interface {
Questions(context.Context, *emptypb.Empty) (*QuestionsResponse, error)
// Questions returns the list of available questions.
// Each question is also listed with whether it has been answered, and
// the points awarded for answering it correctly.
//
// The Text field will not be populated. To get the text, use QuestionByID.
Questions(context.Context, *QuestionsRequest) (*QuestionsResponse, error)
// QuestionByID returns the question with the given ID.
QuestionByID(context.Context, *QuestionByIDRequest) (*Question, error)
// QuestionInput returns the question input for the given question.
// The input is the same for part 1 and part 2.
QuestionInput(context.Context, *QuestionInputRequest) (*QuestionInput, error)
// Submit submits the answer to the given question and part.
Submit(context.Context, *SubmitRequest) (*SubmitResponse, error)
// Leaderboard returns the global ranking of all participatants.
// The leaderboard is sorted by score, descending.
Leaderboard(context.Context, *LeaderboardRequest) (*LeaderboardResponse, error)
mustEmbedUnimplementedQuestServiceServer()
}
@ -242,7 +278,7 @@ type QuestServiceServer interface {
type UnimplementedQuestServiceServer struct {
}
func (UnimplementedQuestServiceServer) Questions(context.Context, *emptypb.Empty) (*QuestionsResponse, error) {
func (UnimplementedQuestServiceServer) Questions(context.Context, *QuestionsRequest) (*QuestionsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Questions not implemented")
}
func (UnimplementedQuestServiceServer) QuestionByID(context.Context, *QuestionByIDRequest) (*Question, error) {
@ -254,6 +290,9 @@ func (UnimplementedQuestServiceServer) QuestionInput(context.Context, *QuestionI
func (UnimplementedQuestServiceServer) Submit(context.Context, *SubmitRequest) (*SubmitResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Submit not implemented")
}
func (UnimplementedQuestServiceServer) Leaderboard(context.Context, *LeaderboardRequest) (*LeaderboardResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Leaderboard not implemented")
}
func (UnimplementedQuestServiceServer) mustEmbedUnimplementedQuestServiceServer() {}
// UnsafeQuestServiceServer may be embedded to opt out of forward compatibility for this service.
@ -268,7 +307,7 @@ func RegisterQuestServiceServer(s grpc.ServiceRegistrar, srv QuestServiceServer)
}
func _QuestService_Questions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(emptypb.Empty)
in := new(QuestionsRequest)
if err := dec(in); err != nil {
return nil, err
}
@ -280,7 +319,7 @@ func _QuestService_Questions_Handler(srv interface{}, ctx context.Context, dec f
FullMethod: "/hhhapz.codequest.v1.QuestService/Questions",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(QuestServiceServer).Questions(ctx, req.(*emptypb.Empty))
return srv.(QuestServiceServer).Questions(ctx, req.(*QuestionsRequest))
}
return interceptor(ctx, in, info, handler)
}
@ -339,6 +378,24 @@ func _QuestService_Submit_Handler(srv interface{}, ctx context.Context, dec func
return interceptor(ctx, in, info, handler)
}
func _QuestService_Leaderboard_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(LeaderboardRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(QuestServiceServer).Leaderboard(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/hhhapz.codequest.v1.QuestService/Leaderboard",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(QuestServiceServer).Leaderboard(ctx, req.(*LeaderboardRequest))
}
return interceptor(ctx, in, info, handler)
}
// QuestService_ServiceDesc is the grpc.ServiceDesc for QuestService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
@ -362,6 +419,10 @@ var QuestService_ServiceDesc = grpc.ServiceDesc{
MethodName: "Submit",
Handler: _QuestService_Submit_Handler,
},
{
MethodName: "Leaderboard",
Handler: _QuestService_Leaderboard_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "github.com/hhhapz/codequest/api/v1/all.proto",
@ -371,11 +432,11 @@ var QuestService_ServiceDesc = grpc.ServiceDesc{
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type UserServiceClient interface {
User(ctx context.Context, in *UserRequest, opts ...grpc.CallOption) (*User, error)
Info(ctx context.Context, in *InfoRequest, opts ...grpc.CallOption) (*Info, error)
UserByEmail(ctx context.Context, in *UserByEmailRequest, opts ...grpc.CallOption) (*User, error)
AllUsers(ctx context.Context, in *AllUsersRequest, opts ...grpc.CallOption) (*AllUsersResponse, error)
UpdateUser(ctx context.Context, in *UpdateUserRequest, opts ...grpc.CallOption) (*User, error)
AdminUpdateUser(ctx context.Context, in *UpdateUserRequest, opts ...grpc.CallOption) (*User, error)
AdminUpdateUser(ctx context.Context, in *AdminUpdateUserRequest, opts ...grpc.CallOption) (*User, error)
DeleteUser(ctx context.Context, in *DeleteUserRequest, opts ...grpc.CallOption) (*User, error)
}
@ -387,9 +448,9 @@ func NewUserServiceClient(cc grpc.ClientConnInterface) UserServiceClient {
return &userServiceClient{cc}
}
func (c *userServiceClient) User(ctx context.Context, in *UserRequest, opts ...grpc.CallOption) (*User, error) {
out := new(User)
err := c.cc.Invoke(ctx, "/hhhapz.codequest.v1.UserService/User", in, out, opts...)
func (c *userServiceClient) Info(ctx context.Context, in *InfoRequest, opts ...grpc.CallOption) (*Info, error) {
out := new(Info)
err := c.cc.Invoke(ctx, "/hhhapz.codequest.v1.UserService/Info", in, out, opts...)
if err != nil {
return nil, err
}
@ -423,7 +484,7 @@ func (c *userServiceClient) UpdateUser(ctx context.Context, in *UpdateUserReques
return out, nil
}
func (c *userServiceClient) AdminUpdateUser(ctx context.Context, in *UpdateUserRequest, opts ...grpc.CallOption) (*User, error) {
func (c *userServiceClient) AdminUpdateUser(ctx context.Context, in *AdminUpdateUserRequest, opts ...grpc.CallOption) (*User, error) {
out := new(User)
err := c.cc.Invoke(ctx, "/hhhapz.codequest.v1.UserService/AdminUpdateUser", in, out, opts...)
if err != nil {
@ -445,11 +506,11 @@ func (c *userServiceClient) DeleteUser(ctx context.Context, in *DeleteUserReques
// All implementations must embed UnimplementedUserServiceServer
// for forward compatibility
type UserServiceServer interface {
User(context.Context, *UserRequest) (*User, error)
Info(context.Context, *InfoRequest) (*Info, error)
UserByEmail(context.Context, *UserByEmailRequest) (*User, error)
AllUsers(context.Context, *AllUsersRequest) (*AllUsersResponse, error)
UpdateUser(context.Context, *UpdateUserRequest) (*User, error)
AdminUpdateUser(context.Context, *UpdateUserRequest) (*User, error)
AdminUpdateUser(context.Context, *AdminUpdateUserRequest) (*User, error)
DeleteUser(context.Context, *DeleteUserRequest) (*User, error)
mustEmbedUnimplementedUserServiceServer()
}
@ -458,8 +519,8 @@ type UserServiceServer interface {
type UnimplementedUserServiceServer struct {
}
func (UnimplementedUserServiceServer) User(context.Context, *UserRequest) (*User, error) {
return nil, status.Errorf(codes.Unimplemented, "method User not implemented")
func (UnimplementedUserServiceServer) Info(context.Context, *InfoRequest) (*Info, error) {
return nil, status.Errorf(codes.Unimplemented, "method Info not implemented")
}
func (UnimplementedUserServiceServer) UserByEmail(context.Context, *UserByEmailRequest) (*User, error) {
return nil, status.Errorf(codes.Unimplemented, "method UserByEmail not implemented")
@ -470,7 +531,7 @@ func (UnimplementedUserServiceServer) AllUsers(context.Context, *AllUsersRequest
func (UnimplementedUserServiceServer) UpdateUser(context.Context, *UpdateUserRequest) (*User, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateUser not implemented")
}
func (UnimplementedUserServiceServer) AdminUpdateUser(context.Context, *UpdateUserRequest) (*User, error) {
func (UnimplementedUserServiceServer) AdminUpdateUser(context.Context, *AdminUpdateUserRequest) (*User, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminUpdateUser not implemented")
}
func (UnimplementedUserServiceServer) DeleteUser(context.Context, *DeleteUserRequest) (*User, error) {
@ -489,20 +550,20 @@ func RegisterUserServiceServer(s grpc.ServiceRegistrar, srv UserServiceServer) {
s.RegisterService(&UserService_ServiceDesc, srv)
}
func _UserService_User_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(UserRequest)
func _UserService_Info_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(InfoRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserServiceServer).User(ctx, in)
return srv.(UserServiceServer).Info(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/hhhapz.codequest.v1.UserService/User",
FullMethod: "/hhhapz.codequest.v1.UserService/Info",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserServiceServer).User(ctx, req.(*UserRequest))
return srv.(UserServiceServer).Info(ctx, req.(*InfoRequest))
}
return interceptor(ctx, in, info, handler)
}
@ -562,7 +623,7 @@ func _UserService_UpdateUser_Handler(srv interface{}, ctx context.Context, dec f
}
func _UserService_AdminUpdateUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(UpdateUserRequest)
in := new(AdminUpdateUserRequest)
if err := dec(in); err != nil {
return nil, err
}
@ -574,7 +635,7 @@ func _UserService_AdminUpdateUser_Handler(srv interface{}, ctx context.Context,
FullMethod: "/hhhapz.codequest.v1.UserService/AdminUpdateUser",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserServiceServer).AdminUpdateUser(ctx, req.(*UpdateUserRequest))
return srv.(UserServiceServer).AdminUpdateUser(ctx, req.(*AdminUpdateUserRequest))
}
return interceptor(ctx, in, info, handler)
}
@ -605,8 +666,8 @@ var UserService_ServiceDesc = grpc.ServiceDesc{
HandlerType: (*UserServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "User",
Handler: _UserService_User_Handler,
MethodName: "Info",
Handler: _UserService_Info_Handler,
},
{
MethodName: "UserByEmail",

@ -24,18 +24,18 @@ type AuthService interface {
// Method: "DELETE",
// Path: "/v1/auth/token",
// }
DeleteToken(DeleteTokenRequest)
DeleteToken(DeleteTokenRequest) DeleteTokenResponse
}
type Token struct {
Token string `pb:"1" json:"token"`
Expires time.Time `pb:"2" json:"expires"`
Expires string `pb:"2" json:"expires"`
}
type OAuthCodeRequest struct{}
type OAuthCodeResponse struct {
RedirectURI string `pb:"1" json:"redirect_uri"`
RedirectURI string `pb:"1" json:"redirectURI"`
}
type TokenRequest struct {
@ -47,3 +47,5 @@ type DeleteTokenRequest struct {
All bool `pb:"1" json:"all"`
Token Token `pb:"2" json:"token"`
}
type DeleteTokenResponse struct{}

@ -8,6 +8,15 @@ import (
"time"
)
// Info is the current information about the competition.
type Info struct {
CurrentUser User `pb:"1" json:"currentUser"`
Active bool `pb:"2" json:"active"`
Points int `pb:"5" json:"points"`
StartTime string `pb:"3" json:"startTime"`
EndTime string `pb:"4" json:"endTime"`
}
// User is a contestant in the competition.
type User struct {
// ID of the user. Received via Google's OAuth2 API.
@ -21,5 +30,5 @@ type User struct {
// Admin is true if the user is an administrator.
Admin bool `pb:"6" json:"admin"`
// CreatedAt is the time the user was created.
CreatedAt time.Time `pb:"7" json:"created_at"`
CreatedAt string `pb:"7" json:"createdAt"`
}

@ -44,9 +44,9 @@
]
}
},
"/v1/admin/users/{email}": {
"get": {
"operationId": "UserService_UserByEmail",
"/v1/admin/users/{body.email}": {
"patch": {
"operationId": "UserService_AdminUpdateUser",
"responses": {
"200": {
"description": "A successful response.",
@ -63,18 +63,28 @@
},
"parameters": [
{
"name": "email",
"name": "body.email",
"in": "path",
"required": true,
"type": "string"
},
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v1AdminUpdateFields"
}
}
],
"tags": [
"UserService"
]
}
},
"delete": {
"operationId": "UserService_DeleteUser",
"/v1/admin/users/{email}": {
"get": {
"operationId": "UserService_UserByEmail",
"responses": {
"200": {
"description": "A successful response.",
@ -101,8 +111,8 @@
"UserService"
]
},
"put": {
"operationId": "UserService_AdminUpdateUser",
"delete": {
"operationId": "UserService_DeleteUser",
"responses": {
"200": {
"description": "A successful response.",
@ -123,14 +133,6 @@
"in": "path",
"required": true,
"type": "string"
},
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v1UpdateFields"
}
}
],
"tags": [
@ -201,7 +203,7 @@
"200": {
"description": "A successful response.",
"schema": {
"properties": {}
"$ref": "#/definitions/v1DeleteTokenResponse"
}
},
"default": {
@ -228,8 +230,7 @@
"name": "token.expires",
"in": "query",
"required": false,
"type": "string",
"format": "date-time"
"type": "string"
}
],
"tags": [
@ -239,6 +240,8 @@
},
"/v1/questions": {
"get": {
"summary": "Questions returns the list of available questions.\nEach question is also listed with whether it has been answered, and\nthe points awarded for answering it correctly.",
"description": "The Text field will not be populated. To get the text, use QuestionByID.",
"operationId": "QuestService_Questions",
"responses": {
"200": {
@ -259,8 +262,32 @@
]
}
},
"/v1/questions/leaderboard": {
"get": {
"summary": "Leaderboard returns the global ranking of all participatants.\nThe leaderboard is sorted by score, descending.",
"operationId": "QuestService_Leaderboard",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v1LeaderboardResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"tags": [
"QuestService"
]
}
},
"/v1/questions/{id}": {
"get": {
"summary": "QuestionByID returns the question with the given ID.",
"operationId": "QuestService_QuestionByID",
"responses": {
"200": {
@ -287,14 +314,17 @@
"tags": [
"QuestService"
]
}
},
"post": {
"operationId": "QuestService_Submit",
"/v1/questions/{id}/input": {
"get": {
"summary": "QuestionInput returns the question input for the given question.\nThe input is the same for part 1 and part 2.",
"operationId": "QuestService_QuestionInput",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v1SubmitResponse"
"$ref": "#/definitions/v1QuestionInput"
}
},
"default": {
@ -310,14 +340,6 @@
"in": "path",
"required": true,
"type": "string"
},
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v1SubmitRequestData"
}
}
],
"tags": [
@ -325,14 +347,15 @@
]
}
},
"/v1/questions/{id}/input": {
"get": {
"operationId": "QuestService_QuestionInput",
"/v1/questions/{id}/{part}": {
"post": {
"summary": "Submit submits the answer to the given question and part.",
"operationId": "QuestService_Submit",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v1QuestionInput"
"$ref": "#/definitions/v1SubmitResponse"
}
},
"default": {
@ -348,6 +371,21 @@
"in": "path",
"required": true,
"type": "string"
},
{
"name": "part",
"in": "path",
"required": true,
"type": "integer",
"format": "int32"
},
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v1SubmitRequestData"
}
}
],
"tags": [
@ -357,12 +395,12 @@
},
"/v1/users/me": {
"get": {
"operationId": "UserService_User",
"operationId": "UserService_Info",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v1User"
"$ref": "#/definitions/v1Info"
}
},
"default": {
@ -376,7 +414,7 @@
"UserService"
]
},
"put": {
"patch": {
"operationId": "UserService_UpdateUser",
"responses": {
"200": {
@ -400,12 +438,6 @@
"schema": {
"$ref": "#/definitions/v1UpdateFields"
}
},
{
"name": "email",
"in": "query",
"required": false,
"type": "string"
}
],
"tags": [
@ -442,6 +474,24 @@
}
}
},
"v1AdminUpdateFields": {
"type": "object",
"properties": {
"email": {
"type": "string"
},
"name": {
"type": "string"
},
"gradeLevel": {
"type": "integer",
"format": "int32"
},
"admin": {
"type": "boolean"
}
}
},
"v1AllUsersResponse": {
"type": "object",
"properties": {
@ -453,10 +503,66 @@
}
}
},
"v1DeleteTokenResponse": {
"type": "object"
},
"v1Difficulty": {
"type": "string",
"enum": [
"Level1",
"Level2"
],
"default": "Level1"
},
"v1Info": {
"type": "object",
"properties": {
"currentUser": {
"$ref": "#/definitions/v1User"
},
"active": {
"type": "boolean"
},
"points": {
"type": "integer",
"format": "int32"
},
"startTime": {
"type": "string"
},
"endTime": {
"type": "string"
}
},
"description": "Info is the current information about the competition."
},
"v1LeaderboardEntry": {
"type": "object",
"properties": {
"username": {
"type": "string"
},
"points": {
"type": "integer",
"format": "int32"
}
}
},
"v1LeaderboardResponse": {
"type": "object",
"properties": {
"leaderboard": {
"type": "array",
"items": {
"$ref": "#/definitions/v1LeaderboardEntry"
}
}
}
},
"v1OAuthCodeResponse": {
"type": "object",
"properties": {
"redirect_uri": {
"redirectURI": {
"type": "string"
}
}
@ -467,7 +573,7 @@
"completed": {
"type": "boolean"
},
"points_worth": {
"pointsWorth": {
"type": "integer",
"format": "int32"
}
@ -485,6 +591,9 @@
"text": {
"type": "string"
},
"difficulty": {
"$ref": "#/definitions/v1Difficulty"
},
"part1": {
"$ref": "#/definitions/v1PartData"
},
@ -521,10 +630,6 @@
"answer": {
"type": "string"
},
"part": {
"type": "integer",
"format": "int32"
},
"code": {
"type": "string"
}
@ -549,8 +654,7 @@
"type": "string"
},
"expires": {
"type": "string",
"format": "date-time"
"type": "string"
}
}
},
@ -560,7 +664,7 @@
"name": {
"type": "string"
},
"grade_level": {
"gradeLevel": {
"type": "integer",
"format": "int32"
},
@ -592,9 +696,8 @@
"type": "boolean",
"description": "Admin is true if the user is an administrator."
},
"created_at": {
"createdAt": {
"type": "string",
"format": "date-time",
"description": "CreatedAt is the time the user was created."
}
},

@ -8,43 +8,75 @@ import (
)
type QuestService interface {
// Questions returns the list of available questions.
// Each question is also listed with whether it has been answered, and
// the points awarded for answering it correctly.
//
// The Text field will not be populated. To get the text, use QuestionByID.
//
// +gunk http.Match{
// Method: "GET",
// Path: "/v1/questions",
// }
Questions() QuestionsResponse
Questions(QuestionsRequest) QuestionsResponse
// QuestionByID returns the question with the given ID.
//
// +gunk http.Match{
// Method: "GET",
// Path: "/v1/questions/{ID}",
// }
QuestionByID(QuestionByIDRequest) Question
// QuestionInput returns the question input for the given question.
// The input is the same for part 1 and part 2.
//
// +gunk http.Match{
// Method: "GET",
// Path: "/v1/questions/{ID}/input",
// }
QuestionInput(QuestionInputRequest) QuestionInput
// Submit submits the answer to the given question and part.
//
// +gunk http.Match{
// Method: "POST",
// Path: "/v1/questions/{ID}",
// Path: "/v1/questions/{ID}/{Part}",
// Body: "Body",
// }
Submit(SubmitRequest) SubmitResponse
// Leaderboard returns the global ranking of all participatants.
// The leaderboard is sorted by score, descending.
//
// +gunk http.Match{
// Method: "GET",
// Path: "/v1/questions/leaderboard",
// }
Leaderboard(LeaderboardRequest) LeaderboardResponse
}
type QuestionsRequest struct{}
type Difficulty int
const (
Level1 Difficulty = iota
Level2
)
type PartData struct {
Completed bool `pb:"2" json:"completed"`
PointsWorth int `pb:"3" json:"points_worth"`
Completed bool `pb:"1" json:"completed"`
PointsWorth int `pb:"2" json:"pointsWorth"`
}
type Question struct {
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"`
Difficulty Difficulty `pb:"4" json:"difficulty"`
Part1 PartData `pb:"5" json:"part1"`
Part2 PartData `pb:"6" json:"part2"`
}
type QuestionsResponse struct {
@ -66,16 +98,27 @@ type QuestionInput struct {
type SubmitRequestData struct {
Answer string `pb:"2" json:"answer"`
Part int `pb:"3" json:"part"`
Code string `pb:"4" json:"code"`
}
type SubmitRequest struct {
ID string `pb:"1" json:"id"`
Body SubmitRequestData `pb:"2" json:"body"`
Part int `pb:"3" json:"part"`
Body SubmitRequestData `pb:"2" json:"Body"`
}
type SubmitResponse struct {
Correct bool `pb:"3" json:"correct"`
Points int `pb:"5" json:"points"`
}
type LeaderboardRequest struct{}
type LeaderboardResponse struct {
Leaderboard []LeaderboardEntry `pb:"1" json:"leaderboard"`
}
type LeaderboardEntry struct {
Username string `pb:"1" json:"username"`
Points int `pb:"2" json:"points"`
}

@ -12,7 +12,7 @@ type UserService interface {
// Method: "GET",
// Path: "/v1/users/me",
// }
User(UserRequest) User
Info(InfoRequest) Info
// +gunk http.Match{
// Method: "GET",
@ -27,18 +27,18 @@ type UserService interface {
AllUsers(AllUsersRequest) AllUsersResponse
// +gunk http.Match{
// Method: "PUT",
// Method: "PATCH",
// Path: "/v1/users/me",
// Body: "Body",
// }
UpdateUser(UpdateUserRequest) User
// +gunk http.Match{
// Method: "PUT",
// Path: "/v1/admin/users/{Email}",
// Method: "PATCH",
// Path: "/v1/admin/users/{Body.Email}",
// Body: "Body",
// }
AdminUpdateUser(UpdateUserRequest) User
AdminUpdateUser(AdminUpdateUserRequest) User
// +gunk http.Match{
// Method: "DELETE",
@ -47,18 +47,27 @@ type UserService interface {
DeleteUser(DeleteUserRequest) User
}
type InfoRequest struct{}
type UpdateFields struct {
Name string `pb:"1" json:"name"`
GradeLevel int `pb:"3" json:"grade_level"`
GradeLevel int `pb:"3" json:"gradeLevel"`
Admin bool `pb:"4" json:"admin"`
}
type UserRequest struct{}
type AllUsersRequest struct{}
type AdminUpdateFields struct {
Email string `pb:"1" json:"email"`
Name string `pb:"2" json:"name"`
GradeLevel int `pb:"3" json:"gradeLevel"`
Admin bool `pb:"4" json:"admin"`
}
type UpdateUserRequest struct {
Email string `pb:"1" json:"email"`
Body UpdateFields `pb:"2" json:"fields"`
Body UpdateFields `pb:"1" json:"Body"`
}
type AdminUpdateUserRequest struct {
Body AdminUpdateFields `pb:"1" json:"Body"`
}
type UserByEmailRequest struct {
@ -69,6 +78,8 @@ type DeleteUserRequest struct {
Email string `pb:"1" json:"email"`
}
type AllUsersRequest struct{}
type AllUsersResponse struct {
Users []User `pb:"1" json:"users"`
}

@ -7,22 +7,29 @@ import (
"flag"
"fmt"
"io"
"net"
"net/http"
"os"
"strings"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
codequestpb "github.com/hhhapz/codequest/api/v1"
"github.com/kenshaw/redoc"
"github.com/peterbourgon/ff/v3"
"google.golang.org/grpc"
"google.golang.org/grpc/grpclog"
"google.golang.org/protobuf/encoding/protojson"
)
func main() {
addr := flag.String("l", "0.0.0.0:8080", "listen address")
endpoint := flag.String("endpoint", "localhost:10000", "grpc endpoint")
flag.Parse()
fs := flag.NewFlagSet("codequest", flag.ExitOnError)
addr := fs.String("l", "0.0.0.0:8080", "listen address")
endpoint := fs.String("endpoint", "localhost:10000", "grpc endpoint")
if err := ff.Parse(fs, os.Args[1:], ff.WithEnvVarPrefix("CQ")); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
if err := run(context.Background(), *addr, *endpoint); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
@ -48,29 +55,38 @@ func run(ctx context.Context, addr, endpoint string) error {
return err
}
}
// build mux
mux := http.NewServeMux()
// handle swagger
mux.HandleFunc("/v1/swagger.json", func(res http.ResponseWriter, req *http.Request) {
res.Header().Set("Content-Type", "application/json")
_, _ = res.Write(codequestpb.Swagger)
})
// handle gateway
mux.Handle("/v1/", gw)
// add redoc
if err := redoc.New("/v1/swagger.json", "/", redoc.WithServeMux(mux)).Build(ctx, nil); err != nil {
return err
}
log := grpclog.NewLoggerV2(os.Stderr, io.Discard, os.Stderr)
log := grpclog.NewLoggerV2(io.Discard, os.Stderr, os.Stderr)
grpclog.SetLoggerV2(log)
log.Info("Serving gateway on http://", addr)
// listen and serve
l, err := (&net.ListenConfig{}).Listen(ctx, "tcp", addr)
if err != nil {
return err
server := &http.Server{
Addr: addr,
Handler: allowCORS(gw),
}
return server.ListenAndServe()
}
// allowCORS allows Cross Origin Resoruce Sharing from any origin.
// Don't do this without consideration in production systems.
func allowCORS(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if origin := r.Header.Get("Origin"); origin != "" {
w.Header().Set("Access-Control-Allow-Origin", origin)
if r.Method == "OPTIONS" && r.Header.Get("Access-Control-Request-Method") != "" {
preflightHandler(w, r)
return
}
defer l.Close()
return http.Serve(l, mux)
}
h.ServeHTTP(w, r)
})
}
// preflightHandler adds the necessary headers in order to serve
// CORS from any origin using the methods "GET", "HEAD", "POST", "PUT", "DELETE"
// We insist, don't do this without consideration in production systems.
func preflightHandler(w http.ResponseWriter, r *http.Request) {
headers := []string{"Content-Type", "Accept", "Authorization"}
w.Header().Set("Access-Control-Allow-Headers", strings.Join(headers, ","))
methods := []string{"GET", "HEAD", "POST", "PUT", "DELETE"}
w.Header().Set("Access-Control-Allow-Methods", strings.Join(methods, ","))
}

@ -7,6 +7,7 @@ import (
"log"
"net"
"os"
"time"
"github.com/hhhapz/codequest/api"
"github.com/hhhapz/codequest/db"
@ -17,18 +18,30 @@ import (
// questions
_ "github.com/hhhapz/codequest/question/q01"
_ "github.com/hhhapz/codequest/question/q02"
_ "github.com/hhhapz/codequest/question/q03"
)
func run() error {
fs := flag.NewFlagSet("codequest", flag.ExitOnError)
port := fs.Int("port", 10000, "GRPC Server Port")
secretFile := fs.String("secret", "client.secret.json", "Path to google oauth2 secret credentials JSON file.")
redirect := fs.String("redirect", "http://playcode.quest/authorize", "redirect uri")
dbFile := fs.String("db", "db.sqlite", "Path to sqlite3 database file")
debug := fs.Bool("debug", false, "debug sql queries")
if err := ff.Parse(fs, os.Args[1:], ff.WithEnvVarPrefix("HK")); err != nil {
start := fs.Int64("start", -1, "competition start time")
end := fs.Int64("end", -1, "competition start time")
if err := ff.Parse(fs, os.Args[1:], ff.WithEnvVarPrefix("CQ")); err != nil {
return err
}
api.Start = time.Unix(*start, 0)
api.End = time.Unix(*end, 0)
fmt.Println("start:", api.Start)
fmt.Println("end:", api.End)
if *secretFile == "" {
return fmt.Errorf("location to secret credentials file not provided.\nTry -h")
}
@ -41,12 +54,17 @@ func run() error {
models.SetLogger(log.Printf)
}
oaStore, err := db.NewOAuthState(*secretFile)
oaStore, err := db.NewOAuthState(*secretFile, *redirect)
if err != nil {
return fmt.Errorf("could not create oauth config: %w", err)
}
server, err := api.NewServer(oaStore, database, database)
cdStore := db.NewCooldowns()
server, err := api.NewServer(oaStore, database, database, cdStore)
if err != nil {
return fmt.Errorf("could not create server: %w", err)
}
log := grpclog.NewLoggerV2(os.Stderr, io.Discard, os.Stderr)
grpclog.SetLoggerV2(log)

@ -0,0 +1,67 @@
package db
import (
"sync"
"time"
)
type CooldownEntry struct {
UserID string
QuestionID string
}
type Cooldowns struct {
m map[CooldownEntry]time.Time
mu *sync.Mutex
}
func NewCooldowns() Cooldowns {
c := make(map[CooldownEntry]time.Time)
mu := &sync.Mutex{}
go func() {
for {
mu.Lock()
now := time.Now()
for k, v := range c {
if now.After(v) {
delete(c, k)
}
}
mu.Unlock()
time.Sleep(time.Minute * 5)
}
}()
return Cooldowns{c, mu}
}
func (c Cooldowns) NewAttempt(userID string, questionID string, attempts int) {
c.mu.Lock()
defer c.mu.Unlock()
var timeout time.Duration
switch attempts {
case 0:
timeout = time.Minute
case 1:
timeout = time.Minute * 3 / 2 // 90 seconds
case 2:
timeout = time.Minute * 3
default:
timeout = time.Minute * 5
}
c.m[CooldownEntry{userID, questionID}] = time.Now().Add(timeout)
}
func (c Cooldowns) Check(userID string, questionID string) time.Duration {
c.mu.Lock()
defer c.mu.Unlock()
expires, ok := c.m[CooldownEntry{userID, questionID}]
now := time.Now()
if !ok || now.After(expires) {
return 0
}
return expires.Sub(now)
}

@ -20,6 +20,7 @@ const (
type OAuthState struct {
config *oauth2.Config
redirectURI string
states map[string]oauthEntry
m sync.Mutex
}
@ -29,7 +30,7 @@ type oauthEntry struct {
callback string
}
func NewOAuthState(path string) (*OAuthState, error) {
func NewOAuthState(path string, redirectURI string) (*OAuthState, error) {
key, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("could not open file: %w", err)
@ -42,6 +43,7 @@ func NewOAuthState(path string) (*OAuthState, error) {
return &OAuthState{
config: config,
redirectURI: redirectURI,
states: make(map[string]oauthEntry),
m: sync.Mutex{},
}, nil
@ -61,7 +63,6 @@ func (o *OAuthState) Create(callback string) string {
o.states[state] = oauthEntry{
created: time.Now(),
callback: callback,
}
return o.config.AuthCodeURL(state, oauth2.AccessTypeOffline)
}
@ -70,14 +71,14 @@ func (o *OAuthState) Validate(ctx context.Context, state string, code string) (*
o.m.Lock()
defer o.m.Unlock()
entry, ok := o.states[state]
_, ok := o.states[state]
if !ok {
return nil, "", models.NewUserError("invalid state: %q", state)
}
delete(o.states, state)
tk, err := o.config.Exchange(ctx, code, oauth2.AccessTypeOffline)
return tk, entry.callback, err
return tk, o.redirectURI, err
}
func (o *OAuthState) GarbageCycle(period time.Duration, duration time.Duration) {

@ -2,8 +2,6 @@ package db
import (
"context"
"database/sql"
"errors"
"fmt"
"github.com/hhhapz/codequest/models"
@ -11,29 +9,13 @@ import (
)
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 {
for _, q := range question.Questions {
var p1, p2 question.PartData
for _, p := range parts {
if p.QuestionID == q.ID {
@ -44,9 +26,9 @@ func (db *DB) Questions(ctx context.Context, u *models.User) ([]*question.Data,
Completed: p.P2Awarded.Valid,
PointsWorth: int(p.P2Awarded.Int64),
}
}
break
}
}
data = append(data, &question.Data{
Question: q,
@ -61,25 +43,13 @@ func (db *DB) Questions(ctx context.Context, u *models.User) ([]*question.Data,
}
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")
}
if qq.ID == id {
q = qq
break
}
}
if q == nil {
return nil, models.NewUserError("question with the id %q not found", id)
}
@ -96,14 +66,14 @@ func (db *DB) Question(ctx context.Context, u *models.User, id string) (*questio
Text: q.Text,
Level: q.Level,
Part1: question.PartData{
Completed: partData.P1Awarded.Valid,
PointsWorth: int(partData.P1Awarded.Int64),
Solution: partData.P1Answer.String,
Completed: partData.P1Awarded != 0,
PointsWorth: partData.P1Awarded,
Solution: partData.P1Answer,
},
Part2: question.PartData{
Completed: partData.P2Awarded.Valid,
PointsWorth: int(partData.P2Awarded.Int64),
Solution: partData.P2Answer.String,
Completed: partData.P2Awarded != 0,
PointsWorth: partData.P2Awarded,
Solution: partData.P2Answer,
},
}, nil
}
@ -119,3 +89,7 @@ func (db *DB) Submissions(ctx context.Context, u *models.User, questionID string
func (db *DB) AddSubmission(ctx context.Context, submission *models.QuestionAttempt) error {
return submission.Insert(ctx, db.DB)
}
func (db *DB) Leaderboard(ctx context.Context) ([]*models.LeaderboardEntry, error) {
return models.Leaderboard(ctx, db.DB)
}

@ -29,7 +29,7 @@ func (db *DB) CreateToken(ctx context.Context, user *models.User) (*models.Token
return token, nil
}
var e sqlite3.Error
e := new(sqlite3.Error)
if errors.As(err, &e) && e.ExtendedCode == sqlite3.ErrConstraintUnique {
token.Token = createToken(48)
continue
@ -43,7 +43,7 @@ var ErrInvalidToken = errors.New("no results found")
func (db *DB) UserByToken(ctx context.Context, token string) (*models.User, error) {
user, err := models.UserByToken(ctx, db.DB, token)
if err == sql.ErrNoRows {
return nil, models.NewUserError("the provided token(%q) does not exist", token)
return nil, models.NewUserError("invalid token: session expired")
}
if err != nil {
return nil, err

@ -34,6 +34,14 @@ func (db *DB) DeleteUser(ctx context.Context, user *models.User) error {
return nil
}
func (db *DB) UserPoints(ctx context.Context, user *models.User) (int, error) {
points, err := models.UserPoints(ctx, db.DB, user.ID)
if err != nil {
return 0, fmt.Errorf("could not retrieve user points: %w", err)
}
return points.Points, nil
}
const (
userinfoEndpoint = "https://www.googleapis.com/oauth2/v2/userinfo?access_token="
emailDomain = "@jisedu.or.id"
@ -67,7 +75,7 @@ func (db *DB) ConsumeToken(ctx context.Context, token *oauth2.Token) (*models.To
// register new user
if _, err := db.User(ctx, user.Email); err != nil {
if !strings.HasSuffix(user.Email, emailDomain) {
return nil, models.NewUserError("invalid registration email: %s", user.Email)
// return nil, models.NewUserError("invalid registration email: %s", user.Email)
}
err = db.UpdateUser(ctx, user)

@ -84,7 +84,8 @@ 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
FIELDS='P1Awarded int, P1Answer string, P2Awarded int, P2Answer string'
xo query $DB -M -B -2 -1 -Z "$FIELDS" -T PartsData -F QuestionPartsData -o $DEST << ENDSQL
WITH attempts AS (
SELECT *
FROM question_attempt
@ -92,10 +93,10 @@ WITH attempts AS (
AND correct = true
)
SELECT
p1.points_awarded AS p1_awarded,
p1.answer AS p1_answer,
p2.points_awarded AS p2_awarded,
p2.answer AS p2_answer
COALESCE(p1.points_awarded, 0) AS p1_awarded,
COALESCE(p1.answer, '') AS p1_answer,
COALESCE(p2.points_awarded, 0) AS p2_awarded,
COALESCE(p2.answer, '') AS p2_answer
FROM users u
LEFT JOIN attempts AS p1
@ -124,5 +125,24 @@ SELECT
FROM (
SELECT COUNT(*) as count from attempts
) c
LEFT JOIN attempts a ON a.correct;
LEFT JOIN attempts a ON a.correct
ENDSQL
FIELDS='Points int'
xo query $DB -M -B -2 -1 -Z "$FIELDS" -T Points -F UserPoints -o $DEST -S userpoints.xo.go << ENDSQL
SELECT COALESCE(SUM(points_awarded), 0) FROM question_attempt
WHERE user_id=%%user_id string%%
ENDSQL
FIELDS='Name string, Points int,LastValid Time'
xo query $DB -M -B -2 -Z "$FIELDS" -T LeaderboardEntry -F Leaderboard -o $DEST -S leaderboard.xo.go << ENDSQL
SELECT u.name,
COALESCE(SUM(qa.points_awarded), 0) AS points,
COALESCE(MAX(qa.submitted_at), u.created_at) as last_valid_submission
FROM users u
LEFT JOIN question_attempt qa ON u.id = qa.user_id AND qa.correct
GROUP BY u.id
ORDER BY COALESCE(SUM(qa.points_awarded), 0) DESC,
MAX(qa.submitted_at) ASC
ENDSQL

@ -1,14 +1,12 @@
module github.com/hhhapz/codequest
go 1.17
go 1.18
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.8-0.20211130055516-28dbe910808d
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
google.golang.org/genproto v0.0.0-20211116182654-e63d96a377c4
@ -22,24 +20,40 @@ require (
github.com/gobwas/glob v0.2.3 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/gax-go/v2 v2.1.1 // indirect
github.com/gorilla/css v1.0.0 // indirect
github.com/gunk/opt v0.2.0 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kenshaw/diskcache v0.5.1 // indirect
github.com/kenshaw/httplog v0.4.0 // 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/mattn/go-sqlite3 v1.14.12 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
github.com/spf13/afero v1.6.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
github.com/yookoala/realpath v1.0.0 // indirect
go.opencensus.io v0.23.0 // indirect
golang.org/x/mod v0.4.2 // indirect
golang.org/x/net v0.0.0-20211116231205-47ca1ff31462 // indirect
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/tools v0.1.5 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/api v0.60.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
lukechampine.com/uint128 v1.1.1 // indirect
modernc.org/cc/v3 v3.35.24 // indirect
modernc.org/ccgo/v3 v3.15.18 // indirect
modernc.org/libc v1.14.12 // indirect
modernc.org/mathutil v1.4.1 // indirect
modernc.org/memory v1.0.7 // indirect
modernc.org/opt v0.1.1 // indirect
modernc.org/strutil v1.1.1 // indirect
modernc.org/token v1.0.0 // indirect
)

163
go.sum

@ -70,6 +70,7 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+XJPdBE=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@ -162,8 +163,9 @@ 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/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/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/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
@ -185,11 +187,12 @@ 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 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM=
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/k0kubun/pp/v3 v3.0.8-0.20211130055516-28dbe910808d h1:pnhp6N9BrdCXRnnUO+HDeqMaajUzytmMd49UydYP/Wg=
github.com/k0kubun/pp/v3 v3.0.8-0.20211130055516-28dbe910808d/go.mod h1:8nuy25R0zFAdhfsMYvI5UWvUqW4MvF8wwSd3LXGQZiY=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
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=
@ -213,8 +216,8 @@ github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3
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/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0=
github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
github.com/peterbourgon/ff/v3 v3.1.0 h1:5JAeDK5j/zhKFjyHEZQXwXBoDijERaos10RE+xamOsY=
@ -224,6 +227,8 @@ github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZ
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
@ -309,6 +314,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -408,6 +414,7 @@ golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -424,8 +431,10 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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-20210902050250-f475640dd07b/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-20211007075335-d3039528d8ac/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=
@ -487,6 +496,7 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
@ -496,6 +506,7 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -663,6 +674,148 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU=
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.33.9/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.33.11/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.34.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.4/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.5/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.7/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.8/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.10/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.15/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.16/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.17/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.18/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.20/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.22/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.24 h1:vlCqjhVwX15t1uwlMPpOpNRC7JTjMZ9lT9DYHKQTFuA=
modernc.org/cc/v3 v3.35.24/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
modernc.org/ccgo/v3 v3.9.5/go.mod h1:umuo2EP2oDSBnD3ckjaVUXMrmeAw8C8OSICVa0iFf60=
modernc.org/ccgo/v3 v3.10.0/go.mod h1:c0yBmkRFi7uW4J7fwx/JiijwOjeAeR2NoSaRVFPmjMw=
modernc.org/ccgo/v3 v3.11.0/go.mod h1:dGNposbDp9TOZ/1KBxghxtUp/bzErD0/0QW4hhSaBMI=
modernc.org/ccgo/v3 v3.11.1/go.mod h1:lWHxfsn13L3f7hgGsGlU28D9eUOf6y3ZYHKoPaKU0ag=
modernc.org/ccgo/v3 v3.11.3/go.mod h1:0oHunRBMBiXOKdaglfMlRPBALQqsfrCKXgw9okQ3GEw=
modernc.org/ccgo/v3 v3.12.4/go.mod h1:Bk+m6m2tsooJchP/Yk5ji56cClmN6R1cqc9o/YtbgBQ=
modernc.org/ccgo/v3 v3.12.6/go.mod h1:0Ji3ruvpFPpz+yu+1m0wk68pdr/LENABhTrDkMDWH6c=
modernc.org/ccgo/v3 v3.12.8/go.mod h1:Hq9keM4ZfjCDuDXxaHptpv9N24JhgBZmUG5q60iLgUo=
modernc.org/ccgo/v3 v3.12.11/go.mod h1:0jVcmyDwDKDGWbcrzQ+xwJjbhZruHtouiBEvDfoIsdg=
modernc.org/ccgo/v3 v3.12.14/go.mod h1:GhTu1k0YCpJSuWwtRAEHAol5W7g1/RRfS4/9hc9vF5I=
modernc.org/ccgo/v3 v3.12.18/go.mod h1:jvg/xVdWWmZACSgOiAhpWpwHWylbJaSzayCqNOJKIhs=
modernc.org/ccgo/v3 v3.12.20/go.mod h1:aKEdssiu7gVgSy/jjMastnv/q6wWGRbszbheXgWRHc8=
modernc.org/ccgo/v3 v3.12.21/go.mod h1:ydgg2tEprnyMn159ZO/N4pLBqpL7NOkJ88GT5zNU2dE=
modernc.org/ccgo/v3 v3.12.22/go.mod h1:nyDVFMmMWhMsgQw+5JH6B6o4MnZ+UQNw1pp52XYFPRk=
modernc.org/ccgo/v3 v3.12.25/go.mod h1:UaLyWI26TwyIT4+ZFNjkyTbsPsY3plAEB6E7L/vZV3w=
modernc.org/ccgo/v3 v3.12.29/go.mod h1:FXVjG7YLf9FetsS2OOYcwNhcdOLGt8S9bQ48+OP75cE=
modernc.org/ccgo/v3 v3.12.36/go.mod h1:uP3/Fiezp/Ga8onfvMLpREq+KUjUmYMxXPO8tETHtA8=
modernc.org/ccgo/v3 v3.12.38/go.mod h1:93O0G7baRST1vNj4wnZ49b1kLxt0xCW5Hsa2qRaZPqc=
modernc.org/ccgo/v3 v3.12.43/go.mod h1:k+DqGXd3o7W+inNujK15S5ZYuPoWYLpF5PYougCmthU=
modernc.org/ccgo/v3 v3.12.46/go.mod h1:UZe6EvMSqOxaJ4sznY7b23/k13R8XNlyWsO5bAmSgOE=
modernc.org/ccgo/v3 v3.12.47/go.mod h1:m8d6p0zNps187fhBwzY/ii6gxfjob1VxWb919Nk1HUk=
modernc.org/ccgo/v3 v3.12.50/go.mod h1:bu9YIwtg+HXQxBhsRDE+cJjQRuINuT9PUK4orOco/JI=
modernc.org/ccgo/v3 v3.12.51/go.mod h1:gaIIlx4YpmGO2bLye04/yeblmvWEmE4BBBls4aJXFiE=
modernc.org/ccgo/v3 v3.12.53/go.mod h1:8xWGGTFkdFEWBEsUmi+DBjwu/WLy3SSOrqEmKUjMeEg=
modernc.org/ccgo/v3 v3.12.54/go.mod h1:yANKFTm9llTFVX1FqNKHE0aMcQb1fuPJx6p8AcUx+74=
modernc.org/ccgo/v3 v3.12.55/go.mod h1:rsXiIyJi9psOwiBkplOaHye5L4MOOaCjHg1Fxkj7IeU=
modernc.org/ccgo/v3 v3.12.56/go.mod h1:ljeFks3faDseCkr60JMpeDb2GSO3TKAmrzm7q9YOcMU=
modernc.org/ccgo/v3 v3.12.57/go.mod h1:hNSF4DNVgBl8wYHpMvPqQWDQx8luqxDnNGCMM4NFNMc=
modernc.org/ccgo/v3 v3.12.60/go.mod h1:k/Nn0zdO1xHVWjPYVshDeWKqbRWIfif5dtsIOCUVMqM=
modernc.org/ccgo/v3 v3.12.66/go.mod h1:jUuxlCFZTUZLMV08s7B1ekHX5+LIAurKTTaugUr/EhQ=
modernc.org/ccgo/v3 v3.12.67/go.mod h1:Bll3KwKvGROizP2Xj17GEGOTrlvB1XcVaBrC90ORO84=
modernc.org/ccgo/v3 v3.12.73/go.mod h1:hngkB+nUUqzOf3iqsM48Gf1FZhY599qzVg1iX+BT3cQ=
modernc.org/ccgo/v3 v3.12.81/go.mod h1:p2A1duHoBBg1mFtYvnhAnQyI6vL0uw5PGYLSIgF6rYY=
modernc.org/ccgo/v3 v3.12.84/go.mod h1:ApbflUfa5BKadjHynCficldU1ghjen84tuM5jRynB7w=
modernc.org/ccgo/v3 v3.12.86/go.mod h1:dN7S26DLTgVSni1PVA3KxxHTcykyDurf3OgUzNqTSrU=
modernc.org/ccgo/v3 v3.12.90/go.mod h1:obhSc3CdivCRpYZmrvO88TXlW0NvoSVvdh/ccRjJYko=
modernc.org/ccgo/v3 v3.12.92/go.mod h1:5yDdN7ti9KWPi5bRVWPl8UNhpEAtCjuEE7ayQnzzqHA=
modernc.org/ccgo/v3 v3.13.1/go.mod h1:aBYVOUfIlcSnrsRVU8VRS35y2DIfpgkmVkYZ0tpIXi4=
modernc.org/ccgo/v3 v3.15.9/go.mod h1:md59wBwDT2LznX/OTCPoVS6KIsdRgY8xqQwBV+hkTH0=
modernc.org/ccgo/v3 v3.15.10/go.mod h1:wQKxoFn0ynxMuCLfFD09c8XPUCc8obfchoVR9Cn0fI8=
modernc.org/ccgo/v3 v3.15.12/go.mod h1:VFePOWoCd8uDGRJpq/zfJ29D0EVzMSyID8LCMWYbX6I=
modernc.org/ccgo/v3 v3.15.14/go.mod h1:144Sz2iBCKogb9OKwsu7hQEub3EVgOlyI8wMUPGKUXQ=
modernc.org/ccgo/v3 v3.15.15/go.mod h1:z5qltXjU4PJl0pE5nhYQCvA9DhPHiWsl5GWl89+NSYE=
modernc.org/ccgo/v3 v3.15.16/go.mod h1:XbKRMeMWMdq712Tr5ECgATYMrzJ+g9zAZEj2ktzBe24=
modernc.org/ccgo/v3 v3.15.17/go.mod h1:bofnFkpRFf5gLY+mBZIyTW6FEcp26xi2lgOFk2Rlvs0=
modernc.org/ccgo/v3 v3.15.18 h1:X5ym656Ye7/ubL+wox0SeF9aRX5od1UDFn1tAbQR+90=
modernc.org/ccgo/v3 v3.15.18/go.mod h1:/2lv3WjHyanEr2sAPdGKRC38n6f0werut9BRXUjjX+A=
modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q=
modernc.org/libc v1.11.0/go.mod h1:2lOfPmj7cz+g1MrPNmX65QCzVxgNq2C5o0jdLY2gAYg=
modernc.org/libc v1.11.2/go.mod h1:ioIyrl3ETkugDO3SGZ+6EOKvlP3zSOycUETe4XM4n8M=
modernc.org/libc v1.11.5/go.mod h1:k3HDCP95A6U111Q5TmG3nAyUcp3kR5YFZTeDS9v8vSU=
modernc.org/libc v1.11.6/go.mod h1:ddqmzR6p5i4jIGK1d/EiSw97LBcE3dK24QEwCFvgNgE=
modernc.org/libc v1.11.11/go.mod h1:lXEp9QOOk4qAYOtL3BmMve99S5Owz7Qyowzvg6LiZso=
modernc.org/libc v1.11.13/go.mod h1:ZYawJWlXIzXy2Pzghaf7YfM8OKacP3eZQI81PDLFdY8=
modernc.org/libc v1.11.16/go.mod h1:+DJquzYi+DMRUtWI1YNxrlQO6TcA5+dRRiq8HWBWRC8=
modernc.org/libc v1.11.19/go.mod h1:e0dgEame6mkydy19KKaVPBeEnyJB4LGNb0bBH1EtQ3I=
modernc.org/libc v1.11.24/go.mod h1:FOSzE0UwookyT1TtCJrRkvsOrX2k38HoInhw+cSCUGk=
modernc.org/libc v1.11.26/go.mod h1:SFjnYi9OSd2W7f4ct622o/PAYqk7KHv6GS8NZULIjKY=
modernc.org/libc v1.11.27/go.mod h1:zmWm6kcFXt/jpzeCgfvUNswM0qke8qVwxqZrnddlDiE=
modernc.org/libc v1.11.28/go.mod h1:Ii4V0fTFcbq3qrv3CNn+OGHAvzqMBvC7dBNyC4vHZlg=
modernc.org/libc v1.11.31/go.mod h1:FpBncUkEAtopRNJj8aRo29qUiyx5AvAlAxzlx9GNaVM=
modernc.org/libc v1.11.34/go.mod h1:+Tzc4hnb1iaX/SKAutJmfzES6awxfU1BPvrrJO0pYLg=
modernc.org/libc v1.11.37/go.mod h1:dCQebOwoO1046yTrfUE5nX1f3YpGZQKNcITUYWlrAWo=
modernc.org/libc v1.11.39/go.mod h1:mV8lJMo2S5A31uD0k1cMu7vrJbSA3J3waQJxpV4iqx8=
modernc.org/libc v1.11.42/go.mod h1:yzrLDU+sSjLE+D4bIhS7q1L5UwXDOw99PLSX0BlZvSQ=
modernc.org/libc v1.11.44/go.mod h1:KFq33jsma7F5WXiYelU8quMJasCCTnHK0mkri4yPHgA=
modernc.org/libc v1.11.45/go.mod h1:Y192orvfVQQYFzCNsn+Xt0Hxt4DiO4USpLNXBlXg/tM=
modernc.org/libc v1.11.47/go.mod h1:tPkE4PzCTW27E6AIKIR5IwHAQKCAtudEIeAV1/SiyBg=
modernc.org/libc v1.11.49/go.mod h1:9JrJuK5WTtoTWIFQ7QjX2Mb/bagYdZdscI3xrvHbXjE=
modernc.org/libc v1.11.51/go.mod h1:R9I8u9TS+meaWLdbfQhq2kFknTW0O3aw3kEMqDDxMaM=
modernc.org/libc v1.11.53/go.mod h1:5ip5vWYPAoMulkQ5XlSJTy12Sz5U6blOQiYasilVPsU=
modernc.org/libc v1.11.54/go.mod h1:S/FVnskbzVUrjfBqlGFIPA5m7UwB3n9fojHhCNfSsnw=
modernc.org/libc v1.11.55/go.mod h1:j2A5YBRm6HjNkoSs/fzZrSxCuwWqcMYTDPLNx0URn3M=
modernc.org/libc v1.11.56/go.mod h1:pakHkg5JdMLt2OgRadpPOTnyRXm/uzu+Yyg/LSLdi18=
modernc.org/libc v1.11.58/go.mod h1:ns94Rxv0OWyoQrDqMFfWwka2BcaF6/61CqJRK9LP7S8=
modernc.org/libc v1.11.71/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw=
modernc.org/libc v1.11.75/go.mod h1:dGRVugT6edz361wmD9gk6ax1AbDSe0x5vji0dGJiPT0=
modernc.org/libc v1.11.82/go.mod h1:NF+Ek1BOl2jeC7lw3a7Jj5PWyHPwWD4aq3wVKxqV1fI=
modernc.org/libc v1.11.86/go.mod h1:ePuYgoQLmvxdNT06RpGnaDKJmDNEkV7ZPKI2jnsvZoE=
modernc.org/libc v1.11.87/go.mod h1:Qvd5iXTeLhI5PS0XSyqMY99282y+3euapQFxM7jYnpY=
modernc.org/libc v1.11.88/go.mod h1:h3oIVe8dxmTcchcFuCcJ4nAWaoiwzKCdv82MM0oiIdQ=
modernc.org/libc v1.11.98/go.mod h1:ynK5sbjsU77AP+nn61+k+wxUGRx9rOFcIqWYYMaDZ4c=
modernc.org/libc v1.11.101/go.mod h1:wLLYgEiY2D17NbBOEp+mIJJJBGSiy7fLL4ZrGGZ+8jI=
modernc.org/libc v1.12.0/go.mod h1:2MH3DaF/gCU8i/UBiVE1VFRos4o523M7zipmwH8SIgQ=
modernc.org/libc v1.14.1/go.mod h1:npFeGWjmZTjFeWALQLrvklVmAxv4m80jnG3+xI8FdJk=
modernc.org/libc v1.14.2/go.mod h1:MX1GBLnRLNdvmK9azU9LCxZ5lMyhrbEMK8rG3X/Fe34=
modernc.org/libc v1.14.3/go.mod h1:GPIvQVOVPizzlqyRX3l756/3ppsAgg1QgPxjr5Q4agQ=
modernc.org/libc v1.14.6/go.mod h1:2PJHINagVxO4QW/5OQdRrvMYo+bm5ClpUFfyXCYl9ak=
modernc.org/libc v1.14.7/go.mod h1:f8xfWXW8LW41qb4X5+huVQo5dcfPlq7Cbny2TDheMv0=
modernc.org/libc v1.14.8/go.mod h1:9+JCLb1MWSY23smyOpIPbd5ED+rSS/ieiDWUpdyO3mo=
modernc.org/libc v1.14.10/go.mod h1:y1MtIWhwpJFpLYm6grAThtuXJKEsY6xkdZmXbRngIdo=
modernc.org/libc v1.14.11/go.mod h1:l5/Mz/GrZwOqzwRHA3abgSCnSeJzzTl+Ify0bAwKbAw=
modernc.org/libc v1.14.12 h1:pUBZTYoISfbb4pCf4PECENpbvwDBxeKc+/dS9LyOWFM=
modernc.org/libc v1.14.12/go.mod h1:fJdoe23MHu2ruPQkFPPqCpToDi5cckzsbmkI6Ez0LqQ=
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8=
modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc=
modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM=
modernc.org/memory v1.0.6/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
modernc.org/memory v1.0.7 h1:UE3cxTRFa5tfUibAV7Jqq8P7zRY0OlJg+yWVIIaluEE=
modernc.org/memory v1.0.7/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A=
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.16.0 h1:DdvOGaWN0y+X7t2L7RUD63gcwbVjYZjcBZnA68g44EI=
modernc.org/sqlite v1.16.0/go.mod h1:Jwe13ItpESZ+78K5WS6+AjXsUg+JvirsjN3iIDO4C8k=
modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs=
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
modernc.org/tcl v1.11.2 h1:mXpsx3AZqJt83uDiFu9UYQVBjNjaWKGCF1YDSlpCL6Y=
modernc.org/tcl v1.11.2/go.mod h1:BRzgpajcGdS2qTxniOx9c/dcxjlbA7p12eJNmiriQYo=
modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.3.2 h1:4GWBVMa48UDC7KQ9tnaggN/yTlXg+CdCX9bhgHPQ9AM=
modernc.org/z v1.3.2/go.mod h1:PEU2oK2OEA1CfzDTd+8E908qEXhC9s0MfyKp5LZsd+k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

@ -0,0 +1,48 @@
package models
// Code generated by xo. DO NOT EDIT.
import (
"context"
)
// LeaderboardEntry represents a row from 'leaderboard_entry'.
type LeaderboardEntry struct {
Name string `json:"name"` // name
Points int `json:"points"` // points
LastValid Time `json:"last_valid"` // last_valid
}
// Leaderboard runs a custom query, returning results as LeaderboardEntry.
func Leaderboard(ctx context.Context, db DB) ([]*LeaderboardEntry, error) {
// query
const sqlstr = `SELECT u.name, ` +
`COALESCE(SUM(qa.points_awarded), 0) AS points, ` +
`COALESCE(MAX(qa.submitted_at), u.created_at) as last_valid_submission ` +
`FROM users u ` +
`LEFT JOIN question_attempt qa ON u.id = qa.user_id AND qa.correct ` +
`GROUP BY u.id ` +
`ORDER BY COALESCE(SUM(qa.points_awarded), 0) DESC, ` +
`MAX(qa.submitted_at) ASC`
// run
logf(sqlstr)
rows, err := db.QueryContext(ctx, sqlstr)
if err != nil {
return nil, logerror(err)
}
defer rows.Close()
// load results
var res []*LeaderboardEntry
for rows.Next() {
var le LeaderboardEntry
// scan
if err := rows.Scan(&le.Name, &le.Points, &le.LastValid); err != nil {
return nil, logerror(err)
}
res = append(res, &le)
}
if err := rows.Err(); err != nil {
return nil, logerror(err)
}
return res, nil
}

@ -4,15 +4,14 @@ package models
import (
"context"
"database/sql"
)
// PartsData represents a row from 'parts_data'.
type PartsData struct {
P1Awarded sql.NullInt64 `json:"p1_awarded"` // p1_awarded
P1Answer sql.NullString `json:"p1_answer"` // p1_answer
P2Awarded sql.NullInt64 `json:"p2_awarded"` // p2_awarded
P2Answer sql.NullString `json:"p2_answer"` // p2_answer
P1Awarded int `json:"p1awarded"` // p1awarded
P1Answer string `json:"p1answer"` // p1answer
P2Awarded int `json:"p2awarded"` // p2awarded
P2Answer string `json:"p2answer"` // p2answer
}
// QuestionPartsData runs a custom query, returning results as PartsData.
@ -25,10 +24,10 @@ func QuestionPartsData(ctx context.Context, db DB, question_id, user_id string)
`AND correct = true ` +
`) ` +
`SELECT ` +
`p1.points_awarded AS p1_awarded, ` +
`p1.answer AS p1_answer, ` +
`p2.points_awarded AS p2_awarded, ` +
`p2.answer AS p2_answer ` +
`COALESCE(p1.points_awarded, 0) AS p1_awarded, ` +
`COALESCE(p1.answer, '') AS p1_answer, ` +
`COALESCE(p2.points_awarded, 0) AS p2_awarded, ` +
`COALESCE(p2.answer, '') AS p2_answer ` +
`FROM users u ` +
` ` +
`LEFT JOIN attempts AS p1 ` +

@ -27,7 +27,7 @@ func QuestionSubmissions(ctx context.Context, db DB, user_id, question_id string
`FROM ( ` +
`SELECT COUNT(*) as count from attempts ` +
`) c ` +
`LEFT JOIN attempts a ON a.correct;`
`LEFT JOIN attempts a ON a.correct`
// run
logf(sqlstr, user_id, question_id, question_part)
var s Submissions

@ -0,0 +1,26 @@
package models
// Code generated by xo. DO NOT EDIT.
import (
"context"
)
// Points represents a row from 'points'.
type Points struct {
Points int `json:"points"` // points
}
// UserPoints runs a custom query, returning results as Points.
func UserPoints(ctx context.Context, db DB, user_id string) (*Points, error) {
// query
const sqlstr = `SELECT COALESCE(SUM(points_awarded), 0) FROM question_attempt ` +
`WHERE user_id=$1`
// run
logf(sqlstr, user_id)
var p Points
if err := db.QueryRowContext(ctx, sqlstr, user_id).Scan(&p.Points); err != nil {
return nil, logerror(err)
}
return &p, nil
}

5491
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -2,34 +2,45 @@
"name": "cq-ui",
"version": "0.0.1",
"scripts": {
"dev": "svelte-kit dev",
"dev": "svelte-kit dev --host --port 5000",
"build": "svelte-kit build",
"package": "svelte-kit package",
"preview": "svelte-kit preview",
"check": "svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --ignore-path .gitignore --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .",
"format": "prettier --ignore-path .gitignore --write --plugin-search-dir=. ."
"lint": "prettier --ignore-path .gitignore --check --plugin-search-dir=. web_src && eslint --ignore-path .gitignore web_src",
"format": "prettier --ignore-path .gitignore --write --plugin-search-dir=. web_src"
},
"devDependencies": {
"@sveltejs/adapter-auto": "next",
"@sveltejs/kit": "next",
"@sveltejs/kit": "1.0.0-next.320",
"@tailwindcss/forms": "^0.4.0",
"@tailwindcss/typography": "^0.5.0",
"@types/google-protobuf": "^3.15.5",
"@typescript-eslint/eslint-plugin": "^4.31.1",
"@typescript-eslint/parser": "^4.31.1",
"autoprefixer": "^10.4.0",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-svelte3": "^3.2.1",
"prettier": "^2.4.1",
"postcss-import": "^14.0.2",
"postcss-nested": "^5.0.6",
"prettier": "^2.5.1",
"prettier-plugin-svelte": "^2.4.0",
"svelte": "^3.44.0",
"svelte-check": "^2.2.6",
"svelte-loading-spinners": "^0.1.7",
"svelte-preprocess": "^4.10.1",
"tailwindcss": "^3.0.7",
"tslib": "^2.3.1",
"typescript": "^4.4.3"
},
"type": "module"
"type": "module",
"dependencies": {
"@sveltejs/adapter-node": "^1.0.0-next.73",
"dayjs": "^1.11.1",
"postcss": "^8.4.12",
"svelte-countdown": "^1.1.0",
"svelte-markdown": "^0.2.2",
"svelte-simple-modal": "^1.2.0"
}
}

File diff suppressed because it is too large Load Diff

@ -1,10 +1,12 @@
const tailwindcss = require('tailwindcss');
const path = require('path');
const autoprefixer = require('autoprefixer');
const config = {
plugins: [
//Some plugins, like tailwindcss/nesting, need to run before Tailwind,
tailwindcss(),
tailwindcss(path.resolve(__dirname, './tailwind.config.cjs')),
//But others, like autoprefixer, need to run after,
//But others, like autoprefixer, need to run after,
autoprefixer()
]

@ -48,7 +48,7 @@ const (
totalSteps = 100
// stepRange is the range of steps within each step
stepRange = 30
stepRange = 9
// randRage is the range of steps between where the duplicate will be
// present
@ -131,6 +131,9 @@ func Validate(u *models.User, p question.Part, sol string) bool {
return false
}
fmt.Println("given:", sol)
fmt.Println("actual:", t)
return strconv.Itoa(t) == sol
}

@ -61,7 +61,9 @@ always be between 1 and 9, inclusive, steps.
{{ if .Part1.Completed -}}
**Congratulations! You got Part 1 correct. Your answer was `{{ .Part1.Solution }}`.**
### **Congratulations! You got Part 1 correct. Your answer was `{{ .Part1.Solution }}`.**
---
## Part 2
@ -90,7 +92,7 @@ twice?**
{{ if .Part2.Completed -}}
**Congratulations! You have completed both parts! The answer was `{{ .Part2.Solution }}`.**
### **Congratulations! You have completed both parts! The answer was `{{ .Part2.Solution }}`.**
{{- end }}
{{- end }}

@ -1,6 +1,7 @@
package q01
import (
"fmt"
"strconv"
"strings"
"testing"
@ -11,7 +12,7 @@ import (
func TestQ01(t *testing.T) {
u := &models.User{
ID: "12031209571980516",
ID: "7",
}
q := question.QuestionByID("directions")
@ -19,7 +20,11 @@ func TestQ01(t *testing.T) {
raw := q.Generate(u)
input := strings.Split(raw, "\n")
fmt.Println(raw)
res := total(solveP1(input))
fmt.Println(solveP1(input))
fmt.Println(solveP2(input))
if !q.Validate(u, question.Part1, strconv.Itoa(res)) {
t.Errorf("Expected question 1 part 1(%v) to be correct!", res)
}

@ -1,41 +1,35 @@
# Saturnalia's Problem
You've been given the task to schedule and organize the arrival of distinguished guests, to
celebrate the summer solstice at the Isles of Latium.
**Kronos** is the father of the Olympian gods, and is the king of the Titans. He is the Titan god of
Time, Justice, and Evil.
After the Olympians defeated the titans, Kronos was given a new fate: he now rules over the Isles of
the Blessed and the region of Latium near the Roman Empire. Next month, the festival **Saturnalia**.
You've been given the task to schedule and organize the arrival of the citizens of Isles of the
Blessed and Latium to reach Mount Othrys by ship.
Unfortunately, there are far too many ships to calculate this all by hand. Each ship takes a
different amount of time to return back to Mount Othrys, and you need to make sure everyone arrives
at the same time.
Each guest arrives in a different ship, and each ship takes a different amount of time to return
back to the main land. You need to make sure everyone arrives at the same time.
You have been given the shipping planner, that tells you how long it takes for each ship to reach
either Isles of the Blessed or Latium, in minutes, and return back.
the island, in minutes, and return back (round trip time).
Each ship initially departs at the same time, and continues to do as many rounds as needed, such
that **each ship returns back to Mount Othrys at the same time**.
that **each ship returns back to the main land the same time**.
Your task is to determine for the given ships, what is the fewest number of minutes until each ship
returns back at the same time after the leave?
returns back at the same time after they leave?
### Example
- Given the following input
```
3
5
9
8
```
Says that there are 3 different ships. The first one takes `3` minutes to complete a cycle, the
second takes `5`, and `9` minutes for last ship. After `45` minutes, all of them will return back
to Mount Othrys **at the same time**.
second takes `5`, and `8` minutes for last ship. After `120` minutes, all of them will return back
to Mount Othrys **at the same time**:
- `3` will complete exactly 40 cycles
- `5` will complete exactly 24 cycles
- `8` will complete exactly 15 cycles
- Given the following input
@ -52,8 +46,8 @@ returns back at the same time after the leave?
13
```
Says that there are a total of 10 ships, and all of them will return back after `159,033,420`
minutes.
We can count that there are a total of 10 ships, and all of them will return back after
`159,033,420` minutes.
**How many minutes** will your ships take to all return back at the same time?
@ -62,7 +56,9 @@ Hint: The numbers might get a bit large here!
{{ if .Part1.Completed -}}
**Congratulations! You got Part 1 correct. Your answer was `{{ .Part1.Solution }}`.**
### **Congratulations! You got Part 1 correct. Your answer was `{{ .Part1.Solution }}`.**
---
## Part 2
@ -76,18 +72,18 @@ Given the following input:
```
3
5
9
8
```
The total duration will still be `45` minutes before all the ships return again to the same
position. In this time, the first ship will complete `15` trips, the second ship will complete `9`
trips, and the last ship will complete `5` trips, for a total of `29` trips.
The total duration will still be `120` minutes before all the ships return again to the same
position. In this time, the first ship will complete `40` trips, the second ship will complete `24`
trips, and the last ship will complete `15` trips, for a total of `40+24+15=79` trips.
With these new instructions, **how many steps total trips will all the ships complete?**
{{ if .Part2.Completed -}}
**Congratulations! You have completed both parts! The answer was `{{ .Part2.Solution }}`.**
### **Congratulations! You have completed both parts! The answer was `{{ .Part2.Solution }}`.**
{{- end }}
{{- end }}

@ -0,0 +1,10 @@
package q03
func solveP1(nums [][]int) (sum int) {
for _, row := range nums {
for _, n := range row {
sum += n
}
}
return
}

@ -0,0 +1,25 @@
package q03
func solveP2(nums [][]int) int {
var maxR, maxC int
for _, ns := range nums {
var sum int
for _, n := range ns {
sum += n
}
if sum > maxR {
maxR = sum
}
}
for i := range nums[0] {
var sum int
for j := range nums {
sum += nums[j][i]
}
if sum > maxC {
maxC = sum
}
}
return maxR * maxC
}

@ -0,0 +1,119 @@
package q03
import (
_ "embed"
"fmt"
"math/rand"
"strconv"
"strings"
"text/template"
"github.com/hhhapz/codequest/models"
"github.com/hhhapz/codequest/question"
)
type weightedChoice struct {
weight int
value int
}
func weightedRandom(choices []weightedChoice, r *rand.Rand) int {
var total int
for _, c := range choices {
total += c.weight
}
rnd := r.Intn(total)
for _, c := range choices {
if rnd < c.weight {
return c.value
}
rnd -= c.weight
}
panic("unreachable")
}
func init() {
t := template.New("dragon")
var err error
t, err = t.Parse(q03Text)
if err != nil {
panic(err)
}
question.Register(
&question.Question{
ID: "dragon",
Name: "The Dragon Festival",
Text: t,
Level: question.Level1,
Generate: func(u *models.User) string {
inp := generate(u)
res := make([]string, rows)
for i, row := range inp {
for _, n := range row {
res[i] += strconv.Itoa(n)
}
}
return strings.Join(res, "\n")
},
Validate: func(u *models.User, part question.Part, solution string) bool {
return Validate(u, part, solution)
},
})
}
const (
rows = 120
cols = 80
)
var numWeights = []weightedChoice{
{weight: 1, value: 0},
{weight: 4, value: 1},
{weight: 4, value: 2},
{weight: 4, value: 3},
{weight: 4, value: 4},
{weight: 4, value: 5},
{weight: 4, value: 6},
{weight: 2, value: 7},
{weight: 2, value: 8},
{weight: 1, value: 9},
}
func generate(u *models.User) [][]int {
res := make([][]int, rows)
r := question.UserRandom(u)
for i := 0; i < rows; i++ {
res[i] = make([]int, cols)
for j := 0; j < cols; j++ {
res[i][j] = weightedRandom(numWeights, r)
}
}
return res
}
func Validate(u *models.User, p question.Part, sol string) bool {
inp := generate(u)
var n int
switch p {
case question.Part1:
n = solveP1(inp)
case question.Part2:
n = solveP2(inp)
default:
return false
}
fmt.Println("submitted", sol)
fmt.Println("actual", n)
return strconv.Itoa(n) == sol
}
//go:embed q03.md
var q03Text string

@ -0,0 +1,89 @@
As June approaches and summer sports rise in popularity, people are becoming more festive, and want
to celebrate.
You are participating the start of the Dragon Boat Festival, where you will be competing to collect
as many coins as possible within 3 hours. As part of the competition, you have been provided the
number of coins in each section of the lake.
As part of your team strategy, you want to first calculate the total number of coins that can be
collected.
Your map is sectioned into a grid, where each digit represents the number of coins in that area.
### Example
- Given the following input
```
46327
85390
29543
```
Shows that there are a total of `5 * 3 = 15` sections within the lake.
Adding up all of the individual digits, we see that the total number of coins within this lake
is `70`.
**What is the total number of coins within the lake?**
{{ if .Part1.Completed -}}
**Congratulations! You got Part 1 correct. Your answer was `{{ .Part1.Solution }}`.**
## Part 2
You recently learned there there is going to be some serious competition within the Dragon Boat
Festival. Nevertheless, you are still determined to win. You have developed a new strategy in order
to collect coins as efficiently as possible.
As part of this secret strategy, which produces the dragon number, you will need to calculate two
values:
1. The maximum sum of coins in any given column
2. The maximum sum of coins in any given row
Your final answer is the product of these two sums.
### Example
- Given the following input
```
46327
85390
29543
```
In this example, there are 3 rows, and 5 columns.
1. Sum of row 1: `4+6+3+2+7 = 22`
2. Sum of row 2: `8+5+3+9+0 = 25`
3. Sum of row 2: `2+9+5+4+3 = 23`
**The maximum sum from rows 1-3 is 25**
1. Sum of column 1: `4+8+2 = 14`
2. Sum of column 2: `6+5+9 = 20`
3. Sum of column 3: `3+3+5 = 11`
4. Sum of column 4: `2+9+4 = 15`
5. Sum of column 5: `7+0+3 = 10`
**The maximum sum from columns 1-5 is 20**
Using this information, the maximums of the rows and columns is `25` ad `20`.
The answer, and the dragon number, is `25*20 = 500`.
---
**What is the dragon number for your lake?**
### Example
{{ if .Part2.Completed -}}
**Congratulations! You have completed both parts! The answer was `{{ .Part2.Solution }}`.**
{{- end }}
{{- end }}

@ -0,0 +1,50 @@
package q03
import (
"strconv"
"strings"
"testing"
"github.com/hhhapz/codequest/models"
"github.com/hhhapz/codequest/question"
)
func TestQ03(t *testing.T) {
u := &models.User{
ID: "123",
}
q := question.QuestionByID("forestry")
raw := q.Generate(u)
t.Logf("INPUT:\n\n%s\n\n", raw)
input := make([][]int, 0, rows)
for _, row := range strings.Split(raw, "\n") {
s := make([]int, 0, cols)
for _, num := range strings.Split(row, "") {
n, _ := strconv.Atoi(num)
s = append(s, n)
}
input = append(input, s)
}
res := solveP1(input)
t.Logf("part 1 result: %d", res)
if !q.Validate(u, question.Part1, strconv.Itoa(res)) {
t.Errorf("Expected question 1 part 1(%v) to be correct!", res)
}
res = solveP2(input)
if !q.Validate(u, question.Part2, strconv.Itoa(res)) {
t.Errorf("Expected question 2 part 2(%v) to be correct!", res)
}
if q.Validate(u, question.Part1, "") {
t.Errorf("Expected bad input to be invalid")
}
t.Logf("Input:\n%v", raw)
}

@ -36,7 +36,7 @@ type Question struct {
type Level int
const (
Level1 Level = iota + 1
Level1 Level = iota
Level2
)

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 15 KiB

@ -0,0 +1,19 @@
<svg data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="647.63626" height="632.17383"
viewBox="0 0 647.63626 632.17383" xmlns:xlink="http://www.w3.org/1999/xlink">
<path d="M687.3279,276.08691H512.81813a15.01828,15.01828,0,0,0-15,15v387.85l-2,.61005-42.81006,13.11a8.00676,8.00676,0,0,1-9.98974-5.31L315.678,271.39691a8.00313,8.00313,0,0,1,5.31006-9.99l65.97022-20.2,191.25-58.54,65.96972-20.2a7.98927,7.98927,0,0,1,9.99024,5.3l32.5498,106.32Z"
transform="translate(-276.18187 -133.91309)" fill="#BDC7F1"/>
<path d="M725.408,274.08691l-39.23-128.14a16.99368,16.99368,0,0,0-21.23-11.28l-92.75,28.39L380.95827,221.60693l-92.75,28.4a17.0152,17.0152,0,0,0-11.28028,21.23l134.08008,437.93a17.02661,17.02661,0,0,0,16.26026,12.03,16.78926,16.78926,0,0,0,4.96972-.75l63.58008-19.46,2-.62v-2.09l-2,.61-64.16992,19.65a15.01489,15.01489,0,0,1-18.73-9.95l-134.06983-437.94a14.97935,14.97935,0,0,1,9.94971-18.73l92.75-28.4,191.24024-58.54,92.75-28.4a15.15551,15.15551,0,0,1,4.40966-.66,15.01461,15.01461,0,0,1,14.32032,10.61l39.0498,127.56.62012,2h2.08008Z"
transform="translate(-276.18187 -133.91309)" fill="#3f3d56"/>
<path d="M398.86279,261.73389a9.0157,9.0157,0,0,1-8.61133-6.3667l-12.88037-42.07178a8.99884,8.99884,0,0,1,5.9712-11.24023l175.939-53.86377a9.00867,9.00867,0,0,1,11.24072,5.9707l12.88037,42.07227a9.01029,9.01029,0,0,1-5.9707,11.24072L401.49219,261.33887A8.976,8.976,0,0,1,398.86279,261.73389Z"
transform="translate(-276.18187 -133.91309)" fill="#6c63ff"/>
<circle cx="190.15351" cy="24.95465" r="20" fill="#6c63ff"/>
<circle cx="190.15351" cy="24.95465" r="12.66462" fill="#fff"/>
<path d="M878.81836,716.08691h-338a8.50981,8.50981,0,0,1-8.5-8.5v-405a8.50951,8.50951,0,0,1,8.5-8.5h338a8.50982,8.50982,0,0,1,8.5,8.5v405A8.51013,8.51013,0,0,1,878.81836,716.08691Z"
transform="translate(-276.18187 -133.91309)" fill="#BDC7F1"/>
<path d="M723.31813,274.08691h-210.5a17.02411,17.02411,0,0,0-17,17v407.8l2-.61v-407.19a15.01828,15.01828,0,0,1,15-15H723.93825Zm183.5,0h-394a17.02411,17.02411,0,0,0-17,17v458a17.0241,17.0241,0,0,0,17,17h394a17.0241,17.0241,0,0,0,17-17v-458A17.02411,17.02411,0,0,0,906.81813,274.08691Zm15,475a15.01828,15.01828,0,0,1-15,15h-394a15.01828,15.01828,0,0,1-15-15v-458a15.01828,15.01828,0,0,1,15-15h394a15.01828,15.01828,0,0,1,15,15Z"
transform="translate(-276.18187 -133.91309)" fill="#3f3d56"/>
<path d="M801.81836,318.08691h-184a9.01015,9.01015,0,0,1-9-9v-44a9.01016,9.01016,0,0,1,9-9h184a9.01016,9.01016,0,0,1,9,9v44A9.01015,9.01015,0,0,1,801.81836,318.08691Z"
transform="translate(-276.18187 -133.91309)" fill="#6c63ff"/>
<circle cx="433.63626" cy="105.17383" r="20" fill="#6c63ff"/>
<circle cx="433.63626" cy="105.17383" r="12.18187" fill="#fff"/>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 25 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.9 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 15 KiB

@ -1,11 +1,13 @@
import adapter from '@sveltejs/adapter-auto';
import adapter from '@sveltejs/adapter-node';
import preprocess from 'svelte-preprocess';
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://github.com/sveltejs/svelte-preprocess
// for more information about preprocessors
preprocess: [preprocess({})],
preprocess: [
preprocess({
postcss: true
})
],
kit: {
adapter: adapter(),
@ -15,13 +17,9 @@ const config = {
hooks: 'web_src/hooks',
lib: 'web_src/lib',
routes: 'web_src/routes',
serviceWorker: 'web_src/service-worker',
template: 'web_src/app.html'
},
// hydrate the <div id="svelte"> element in src/app.html
target: '#svelte',
vite: {
server: {
fs: {

@ -1,15 +1,37 @@
const config = {
mode: 'jit',
purge: ['./web_src/**/*.{html,js,svelte,ts}'],
content: ['./web_src/**/*.{html,js,svelte,ts}'],
theme: {
extend: {}
extend: {
typography: () => ({
DEFAULT: {
css: {
'code::before': {
content: 'none'
},
'code::after': {
content: 'none'
},
'blockquote p:first-of-type::before': {
content: 'none'
},
'blockquote p:last-of-type::after': {
content: 'none'
},
code: {
padding: '2px 4px',
fontWeight: '400',
backgroundColor: '#eee',
borderRadius: '2px'
}
}
}
})
}
},
plugins: [
require('@tailwindcss/typography'),
require('@tailwindcss/forms'),
],
plugins: [require('@tailwindcss/typography'), require('@tailwindcss/forms')]
// daisyui: {
// themes: [
@ -39,8 +61,6 @@ const config = {
// }
// ]
// },
content: ['./src/**/*.{html,js,svelte,ts}']
};
module.exports = config;

@ -0,0 +1,7 @@
//go:build tools
package tools
import (
_ "github.com/gunk/opt"
)

@ -1,20 +1,14 @@
// @ts-nocheck
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"moduleResolution": "node",
"module": "es2020",
"lib": ["es2020", "DOM"],
"target": "es2020",
/**
svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript
to enforce using \`import type\` instead of \`import\` for Types.
*/
"importsNotUsedAsValues": "error",
"isolatedModules": true,
"resolveJsonModule": true,
/**
To have warnings/errors of the Svelte compiler at the correct position,
enable source maps by default.
*/
"sourceMap": true,
"esModuleInterop": true,
"skipLibCheck": true,
@ -23,9 +17,9 @@
"allowJs": true,
"checkJs": true,
"paths": {
"$lib": ["src/lib"],
"$lib/*": ["src/lib/*"]
"$lib": ["web_src/lib"],
"$lib/*": ["web_src/lib/*"]
}
},
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.ts", "src/**/*.svelte"]
"include": ["web_src/**/*.d.ts", "web_src/**/*.js", "web_src/**/*.ts", "web_src/**/*.svelte"]
}

@ -0,0 +1,19 @@
{
"0 debug pnpm:scope": {
"selected": 1
},
"1 error pnpm": {
"errno": 1,
"code": "ELIFECYCLE",
"pkgid": "cq-ui@0.0.1",
"stage": "lint",
"script": "prettier --ignore-path .gitignore --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .",
"pkgname": "cq-ui",
"err": {
"name": "pnpm",
"message": "cq-ui@0.0.1 lint: `prettier --ignore-path .gitignore --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .`\nExit status 1",
"code": "ELIFECYCLE",
"stack": "pnpm: cq-ui@0.0.1 lint: `prettier --ignore-path .gitignore --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .`\nExit status 1\n at EventEmitter.<anonymous> (/usr/lib/node_modules/pnpm/dist/pnpm.cjs:105830:20)\n at EventEmitter.emit (node:events:527:28)\n at ChildProcess.<anonymous> (/usr/lib/node_modules/pnpm/dist/pnpm.cjs:92391:18)\n at ChildProcess.emit (node:events:527:28)\n at maybeClose (node:internal/child_process:1090:16)\n at Process.ChildProcess._handle.onexit (node:internal/child_process:302:5)"
}
}
}

@ -1,4 +1,10 @@
/* Write your global styles here, in PostCSS syntax */
@tailwind base;
@tailwind components;
@tailwind utilities;
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import './components.css';
@import 'tailwindcss/utilities';
html {
scroll-behavior: smooth;
}

@ -0,0 +1,19 @@
a:not(.btn) {
@apply text-indigo-600 hover:text-indigo-900;
}
.btn {
@apply block bg-indigo-600 hover:bg-indigo-700 text-white text-center font-bold transition duration-200;
}
.btn.inverse {
@apply bg-indigo-100 hover:bg-indigo-200 text-indigo-700;
}
.btn.cancel {
@apply bg-transparent hover:bg-black hover:text-white text-black border border-black font-normal transition-all duration-300;
}
.btn.ghost {
@apply bg-gray-50 hover:bg-gray-300 border border-neutral-500 text-gray-600;
}

@ -0,0 +1,41 @@
<script lang="ts">
import { onDestroy, onMount } from 'svelte';
export let end: string;
$: endTime = new Date(end);
let h = 0;
let m = 0;
let s = 0;
let active = true;
let i;
const calc = () => {
let now = new Date();
let secs = Math.ceil((endTime.getTime() - now.getTime()) / 1000);
h = Math.floor(secs / 3600);
m = Math.floor((secs % 3600) / 60);
s = Math.floor((secs % 3600) % 60);
if (secs < 0) {
active = false;
return;
}
};
onMount(() => {
calc();
i = setInterval(calc, 100);
});
onDestroy(() => {
clearInterval(i);
});
</script>
{#if active}
Active - {h}:{('0' + m).slice(-2)}:{('0' + s).slice(-2)}
{:else}
Contest Ended
{/if}

@ -0,0 +1,52 @@
<script lang="ts">
import { getContext } from 'svelte';
const { close } = getContext('simple-modal');
export let title: string = 'Error';
export let reason: string;
export let btn: { title: string; do: any } = null;
const perform = () => {
close();
btn.do();
};
let cleaned = '';
$: {
let first = reason[0] || '';
let rest = reason.slice(1) || '';
if (!['.', '!'].includes(rest.slice(-1))) {
rest += '.';
}
cleaned = first.toUpperCase() + rest;
}
</script>
<div class="sm:flex sm:items-start pl-3 pt-2">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10"
>
<!-- prettier-ignore -->
<svg class="h-6 w-6 text-red-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-xl leading-6 font-medium text-gray-900" id="modal-title">
{title}
</h3>
<div class="mt-2">
<p class="text-lg text-gray-500">{cleaned}</p>
</div>
</div>
</div>
<div class="px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse gap-4">
{#if btn}
<button type="button" class="btn px-2 text-lg md:py-1 rounded" on:click={perform}>
{btn.title}
</button>
{/if}
<button type="button" class="btn cancel px-2 text-lg md:py-1 rounded" on:click={close}>
Close
</button>
</div>

@ -0,0 +1,27 @@
<div class="bg-indigo-200 shadow-inner">
<div class="max-w-2xl mx-auto py-10">
<div
class="relative flex flex-col md:flex-row md:justify-between items-center text-sm text-gray-800"
>
<p class="order-2 md:order-1 mt-8 md:mt-0">&copy; Hamza Ali, 2022.</p>
<p class="order-1 md:order-2 mt-8 md:mt-0 flex gap-1">
Made with
<span class="text-red-500">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M3.172 5.172a4 4 0 015.656 0L10 6.343l1.172-1.171a4 4 0 115.656 5.656L10 17.657l-6.828-6.829a4 4 0 010-5.656z"
clip-rule="evenodd"
/>
</svg>
</span>
using Go, gRPC, and Svelte Kit.
</p>
</div>
</div>
</div>

@ -0,0 +1,53 @@
<script>
import { AuthService } from '../pb/all.pb';
import { noToken } from '../pb/pbutil';
import { onMount } from 'svelte';
import { auth } from '$lib/stores';
import { goto } from '$app/navigation';
let loggedIn = false;
const login = async () => {
if (loggedIn) {
goto('/dashboard');
return;
}
const url = await AuthService.OAuthCode({}, noToken());
console.log(url);
window.location.href = url.redirectURI;
};
onMount(() => {
auth.subscribe((auth) => {
loggedIn = auth.loggedIn();
});
});
</script>
<div
on:click={login}
class="inline-flex justify-center items-center gap-2
border border-black p-2 text-sm shadow-sm rounded cursor-pointer hover:bg-black hover:text-white transition-all duration-300"
>
<svg viewBox="0 0 24 24" width="20" height="20" xmlns="http://www.w3.org/2000/svg">
<g transform="matrix(1, 0, 0, 1, 27.009001, -39.238998)">
<path
fill="#4285F4"
d="M -3.264 51.509 C -3.264 50.719 -3.334 49.969 -3.454 49.239 L -14.754 49.239 L -14.754 53.749 L -8.284 53.749 C -8.574 55.229 -9.424 56.479 -10.684 57.329 L -10.684 60.329 L -6.824 60.329 C -4.564 58.239 -3.264 55.159 -3.264 51.509 Z"
/>
<path
fill="#34A853"
d="M -14.754 63.239 C -11.514 63.239 -8.804 62.159 -6.824 60.329 L -10.684 57.329 C -11.764 58.049 -13.134 58.489 -14.754 58.489 C -17.884 58.489 -20.534 56.379 -21.484 53.529 L -25.464 53.529 L -25.464 56.619 C -23.494 60.539 -19.444 63.239 -14.754 63.239 Z"
/>
<path
fill="#FBBC05"
d="M -21.484 53.529 C -21.734 52.809 -21.864 52.039 -21.864 51.239 C -21.864 50.439 -21.724 49.669 -21.484 48.949 L -21.484 45.859 L -25.464 45.859 C -26.284 47.479 -26.754 49.299 -26.754 51.239 C -26.754 53.179 -26.284 54.999 -25.464 56.619 L -21.484 53.529 Z"
/>
<path
fill="#EA4335"
d="M -14.754 43.989 C -12.984 43.989 -11.404 44.599 -10.154 45.789 L -6.734 42.369 C -8.804 40.429 -11.514 39.239 -14.754 39.239 C -19.444 39.239 -23.494 41.939 -25.464 45.859 L -21.484 48.949 C -20.534 46.099 -17.884 43.989 -14.754 43.989 Z"
/>
</g>
</svg>
Sign in with Google
</div>

@ -0,0 +1,39 @@
<script>
import { getContext } from 'svelte';
import GoogleButton from './GoogleButton.svelte';
const { close } = getContext('simple-modal');
</script>
<div class="sm:flex sm:items-start pl-3 pt-2">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
class="h-6 w-6 text-red-600"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
/>
</svg>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900" id="modal-title">Login Required</h3>
<div class="mt-2">
<p class="text-sm text-gray-500">To access this page, you must be logged in.</p>
</div>
</div>
</div>
<div class="px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse gap-4">
<GoogleButton />
<button type="button" class="btn cancel px-2 text-sm md:py-1 rounded" on:click={close}>
Cancel
</button>
</div>

@ -0,0 +1,124 @@
<script lang="ts">
import { fade } from 'svelte/transition';
import { onMount } from 'svelte';
import GoogleButton from './GoogleButton.svelte';
import { auth } from '$lib/stores';
let pages = [{ id: 'about', name: 'About', loc: '/about' }];
let hidden = true;
let loggedIn = false;
$: {
if ($auth.loggedIn()) {
pages = [
{ id: 'home', name: 'Home', loc: '/' },
{ id: 'dashboard', name: 'Dashboard', loc: '/dashboard' },
{ id: 'questions', name: 'Puzzles', loc: '/dashboard/questions' },
{ id: 'leaderboard', name: 'Leaderboard', loc: '/dashboard/leaderboard' },
{ id: 'logout', name: 'Log Out', loc: '/logout' }
];
}
}
const toggle = () => {
hidden = !hidden;
};
onMount(() => {
auth.subscribe((auth) => {
loggedIn = auth.loggedIn();
});
});
</script>
<nav class="relative bg-white shadow-xl">
<div class="h-16 flex justify-between items-center container mx-auto">
{#if loggedIn}
<a class="text-3xl font-bold leading-none ml-4 lg:ml-0" href="/dashboard" sveltekit:prefetch>
<h2 class="text-lg">
CodeQuest
<sub class="text-xs font-medium tracking-tight">Alpha</sub>
</h2>
</a>
{:else}
<a class="text-3xl font-bold leading-none ml-4 lg:ml-0" href="/" sveltekit:prefetch>
<h2 class="text-lg">
CodeQuest
<sub class="text-xs font-medium tracking-tight">Alpha</sub>
</h2>
</a>
{/if}
<div class="lg:hidden">
<button on:click={toggle} class="flex items-center text-blue-600 p-3">
<!-- prettier-ignore -->
<svg class="block h-4 w-4 fill-current" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" > <path d="M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z" />
</button
>
</div>
<ul class="hidden lg:flex lg:flex lg:items-center gap-1.5">
{#each pages as p, i}
<li>
<a
class="text-sm text-gray-600 hover:text-gray-800 hover:border-b hover:border-blue-500"
href={p.loc}
>
{p.name}
</a>
</li>
{#if i + 1 < pages.length || !loggedIn}
<li class="text-gray-500">
<!-- prettier-ignore -->
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" class="w-4 h-4 current-fill" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 5v0m0 7v0m0 7v0m0-13a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2z" /> </svg>
</li>
{/if}
{/each}
<li>
{#if !loggedIn}
<GoogleButton />
{/if}
</li>
</ul>
</div>
</nav>
{#if !hidden}
<div class="relative z-50 -left-100" transition:fade={{ duration: 100 }}>
<nav
class="fixed top-0 left-0 bottom-0 flex flex-col w-5/6 max-w-[250px] py-6 px-6 bg-white border-r overflow-y-auto"
>
<div class="flex items-center mb-8">
<a class="mr-auto text-3xl font-bold leading-none" href="/">
<h2 class="text-lg">CodeQuest ALPHA</h2>
</a>
<button on:click={toggle}>
<!-- prettier-ignore -->
<svg class="h-6 w-6 text-gray-400 cursor-pointer hover:text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /> </svg>
</button>
</div>
<div>
<ul>
{#each pages as p}
<li class="mb-1">
<a
class="block p-4 text-sm font-semibold text-gray-400 hover:bg-blue-50 hover:text-blue-600 rounded"
href={p.loc}
>{p.name}
</a>
</li>
{/each}
</ul>
</div>
<div class="mt-auto">
<div class="pt-6">
{#if !loggedIn}
<GoogleButton />
{/if}
</div>
<p class="my-4 text-xs text-center text-gray-400">
<span>Copyright © 2021-2022</span>
</p>
</div>
</nav>
</div>
{/if}

@ -0,0 +1,61 @@
<script lang="ts">
import { Difficulty, type Question } from '../pb/all.pb';
export let q: Question;
type Status = 'available' | 'partial' | 'full';
let status: Status = 'available';
let solved = 0;
$: {
if (q.part1.completed && q.part2.completed) {
status = 'full';
solved = 2;
} else if (q.part1.completed || q.part2.completed) {
status = 'partial';
solved = 1;
} else {
status = 'available';
solved = 0;
}
}
</script>
<a class={`question h-24 border-2 ${status}`} href={`/dashboard/question/${q.id}`}>
<span class="title">{q.title} - {solved}/2</span>
<div class="flex justify-between">
<span class="points">Points: {q.part1.pointsWorth + q.part2.pointsWorth}</span>
<span class="difficulty">
{#if q.difficulty == Difficulty.Level1}
Difficulty: 1
{:else if q.difficulty == Difficulty.Level2}
Difficulty: 2
{/if}
</span>
</div>
</a>
<style lang="postcss">
.question {
@apply flex justify-between flex-col p-4 w-full rounded-md shadow-md border-2;
@apply cursor-pointer transition;
}
.available {
@apply border-gray-400 hover:bg-gray-100;
@apply text-gray-600;
}
.partial {
@apply border-yellow-400 bg-yellow-50 hover:bg-yellow-100;
@apply text-yellow-700;
}
.full {
@apply border-green-400 bg-green-50 hover:bg-green-100;
@apply text-green-700;
}
.title {
@apply font-bold uppercase;
}
</style>

@ -0,0 +1,61 @@
<script>
import { getContext } from 'svelte';
const { close } = getContext('simple-modal');
export let correct = false;
export let points = 0;
export let part = 1;
</script>
<div class="sm:flex sm:items-start pl-3 pt-2">
{#if correct}
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-green-200 sm:mx-0 sm:h-10 sm:w-10"
>
<!-- prettier-ignore -->
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-green-700" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3">
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
</svg>
</div>
{:else}
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10"
>
<!-- prettier-ignore -->
<svg class="h-6 w-6 text-red-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
</div>
{/if}
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-xl leading-6 font-medium text-gray-900" id="modal-title">
{#if correct}
Correct!
{:else}
Incorrect
{/if}
</h3>
<div class="mt-2">
<p class="text-lg text-gray-500">
{#if correct}
{#if part == 1}
You have completed the first part of the question.
<br />
You earned <strong>{points} points</strong> for part 1.
{:else}
You have completed both parts of the question.
<br />
You earned <strong>{points} points</strong> for part 2.
{/if}
{:else}
The answer you have provided was not correct. Try again!
{/if}
</p>
</div>
</div>
</div>
<div class="px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse gap-4">
<button type="button" class="btn cancel px-2 text-lg md:py-1 rounded" on:click={close}>
Close
</button>
</div>

@ -0,0 +1,19 @@
{
"0 debug pnpm:scope": {
"selected": 1
},
"1 error pnpm": {
"errno": 1,
"code": "ELIFECYCLE",
"pkgid": "cq-ui@0.0.1",
"stage": "lint",
"script": "prettier --ignore-path .gitignore --check --plugin-search-dir=. web_src && eslint --ignore-path .gitignore web_src",
"pkgname": "cq-ui",
"err": {
"name": "pnpm",
"message": "cq-ui@0.0.1 lint: `prettier --ignore-path .gitignore --check --plugin-search-dir=. web_src && eslint --ignore-path .gitignore web_src`\nExit status 1",
"code": "ELIFECYCLE",
"stack": "pnpm: cq-ui@0.0.1 lint: `prettier --ignore-path .gitignore --check --plugin-search-dir=. web_src && eslint --ignore-path .gitignore web_src`\nExit status 1\n at EventEmitter.<anonymous> (/usr/pnpm-global/5/node_modules/.pnpm/pnpm@6.32.8/node_modules/pnpm/dist/pnpm.cjs:105794:20)\n at EventEmitter.emit (node:events:527:28)\n at ChildProcess.<anonymous> (/usr/pnpm-global/5/node_modules/.pnpm/pnpm@6.32.8/node_modules/pnpm/dist/pnpm.cjs:92355:18)\n at ChildProcess.emit (node:events:527:28)\n at maybeClose (node:internal/child_process:1090:16)\n at Process.ChildProcess._handle.onexit (node:internal/child_process:302:5)"
}
}
}

@ -0,0 +1,204 @@
/* eslint-disable */
// @ts-nocheck
/*
* This file is a generated Typescript file for GRPC Gateway, DO NOT MODIFY
*/
import * as fm from "./fetch.pb"
export enum Difficulty {
Level1 = "Level1",
Level2 = "Level2",
}
export type Token = {
token?: string
expires?: string
}
export type OAuthCodeRequest = {
}
export type OAuthCodeResponse = {
redirectURI?: string
}
export type TokenRequest = {
code?: string
state?: string
}
export type DeleteTokenRequest = {
all?: boolean
token?: Token
}
export type DeleteTokenResponse = {
}
export type Info = {
currentUser?: User
active?: boolean
points?: number
startTime?: string
endTime?: string
}
export type User = {
id?: string
name?: string
email?: string
picture?: string
admin?: boolean
createdAt?: string
}
export type QuestionsRequest = {
}
export type PartData = {
completed?: boolean
pointsWorth?: number
}
export type Question = {
id?: string
title?: string
text?: string
difficulty?: Difficulty
part1?: PartData
part2?: PartData
}
export type QuestionsResponse = {
questions?: Question[]
}
export type QuestionByIDRequest = {
id?: string
}
export type QuestionInputRequest = {
id?: string
}
export type QuestionInput = {
id?: string
input?: string
}
export type SubmitRequestData = {
answer?: string
code?: string
}
export type SubmitRequest = {
id?: string
part?: number
body?: SubmitRequestData
}
export type SubmitResponse = {
correct?: boolean
points?: number
}
export type LeaderboardRequest = {
}
export type LeaderboardResponse = {
leaderboard?: LeaderboardEntry[]
}
export type LeaderboardEntry = {
username?: string
points?: number
}
export type InfoRequest = {
}
export type UpdateFields = {
name?: string
gradeLevel?: number
admin?: boolean
}
export type AdminUpdateFields = {
email?: string
name?: string
gradeLevel?: number
admin?: boolean
}
export type UpdateUserRequest = {
body?: UpdateFields
}
export type AdminUpdateUserRequest = {
body?: AdminUpdateFields
}
export type UserByEmailRequest = {
email?: string
}
export type DeleteUserRequest = {
email?: string
}
export type AllUsersRequest = {
}
export type AllUsersResponse = {
users?: User[]
}
export class AuthService {
static OAuthCode(req: OAuthCodeRequest, initReq?: fm.InitReq): Promise<OAuthCodeResponse> {
return fm.fetchReq<OAuthCodeRequest, OAuthCodeResponse>(`/v1/auth/code?${fm.renderURLSearchParams(req, [])}`, {...initReq, method: "GET"})
}
static Token(req: TokenRequest, initReq?: fm.InitReq): Promise<Token> {
return fm.fetchReq<TokenRequest, Token>(`/v1/auth/token?${fm.renderURLSearchParams(req, [])}`, {...initReq, method: "GET"})
}
static DeleteToken(req: DeleteTokenRequest, initReq?: fm.InitReq): Promise<DeleteTokenResponse> {
return fm.fetchReq<DeleteTokenRequest, DeleteTokenResponse>(`/v1/auth/token`, {...initReq, method: "DELETE"})
}
}
export class QuestService {
static Questions(req: QuestionsRequest, initReq?: fm.InitReq): Promise<QuestionsResponse> {
return fm.fetchReq<QuestionsRequest, QuestionsResponse>(`/v1/questions?${fm.renderURLSearchParams(req, [])}`, {...initReq, method: "GET"})
}
static QuestionByID(req: QuestionByIDRequest, initReq?: fm.InitReq): Promise<Question> {
return fm.fetchReq<QuestionByIDRequest, Question>(`/v1/questions/${req["id"]}?${fm.renderURLSearchParams(req, ["id"])}`, {...initReq, method: "GET"})
}
static QuestionInput(req: QuestionInputRequest, initReq?: fm.InitReq): Promise<QuestionInput> {
return fm.fetchReq<QuestionInputRequest, QuestionInput>(`/v1/questions/${req["id"]}/input?${fm.renderURLSearchParams(req, ["id"])}`, {...initReq, method: "GET"})
}
static Submit(req: SubmitRequest, initReq?: fm.InitReq): Promise<SubmitResponse> {
return fm.fetchReq<SubmitRequest, SubmitResponse>(`/v1/questions/${req["id"]}/${req["part"]}`, {...initReq, method: "POST", body: JSON.stringify(req["Body"])})
}
static Leaderboard(req: LeaderboardRequest, initReq?: fm.InitReq): Promise<LeaderboardResponse> {
return fm.fetchReq<LeaderboardRequest, LeaderboardResponse>(`/v1/questions/leaderboard?${fm.renderURLSearchParams(req, [])}`, {...initReq, method: "GET"})
}
}
export class UserService {
static Info(req: InfoRequest, initReq?: fm.InitReq): Promise<Info> {
return fm.fetchReq<InfoRequest, Info>(`/v1/users/me?${fm.renderURLSearchParams(req, [])}`, {...initReq, method: "GET"})
}
static UserByEmail(req: UserByEmailRequest, initReq?: fm.InitReq): Promise<User> {
return fm.fetchReq<UserByEmailRequest, User>(`/v1/admin/users/${req["email"]}?${fm.renderURLSearchParams(req, ["email"])}`, {...initReq, method: "GET"})
}
static AllUsers(req: AllUsersRequest, initReq?: fm.InitReq): Promise<AllUsersResponse> {
return fm.fetchReq<AllUsersRequest, AllUsersResponse>(`/v1/admin/users?${fm.renderURLSearchParams(req, [])}`, {...initReq, method: "GET"})
}
static UpdateUser(req: UpdateUserRequest, initReq?: fm.InitReq): Promise<User> {
return fm.fetchReq<UpdateUserRequest, User>(`/v1/users/me`, {...initReq, method: "PATCH", body: JSON.stringify(req["Body"])})
}
static AdminUpdateUser(req: AdminUpdateUserRequest, initReq?: fm.InitReq): Promise<User> {
return fm.fetchReq<AdminUpdateUserRequest, User>(`/v1/admin/users/${req["bodyEmail"]}`, {...initReq, method: "PATCH", body: JSON.stringify(req["Body"])})
}
static DeleteUser(req: DeleteUserRequest, initReq?: fm.InitReq): Promise<User> {
return fm.fetchReq<DeleteUserRequest, User>(`/v1/admin/users/${req["email"]}`, {...initReq, method: "DELETE"})
}
}

@ -0,0 +1,233 @@
/* eslint-disable */
// @ts-nocheck
/*
* This file is a generated Typescript file for GRPC Gateway, DO NOT MODIFY
*/
export interface InitReq extends RequestInit {
pathPrefix?: string;
}
export function fetchReq<I, O>(path: string, init?: InitReq): Promise<O> {
const { pathPrefix, ...req } = init || {};
const url = pathPrefix ? `${pathPrefix}${path}` : path;
return fetch(url, req).then((r) =>
r.json().then((body: O) => {
if (!r.ok) {
throw body;
}
return body;
})
) as Promise<O>;
}
// NotifyStreamEntityArrival is a callback that will be called on streaming entity arrival
export type NotifyStreamEntityArrival<T> = (resp: T) => void;
/**
* fetchStreamingRequest is able to handle grpc-gateway server side streaming call
* it takes NotifyStreamEntityArrival that lets users respond to entity arrival during the call
* all entities will be returned as an array after the call finishes.
**/
export async function fetchStreamingRequest<S, R>(
path: string,
callback?: NotifyStreamEntityArrival<R>,
init?: InitReq
) {
const { pathPrefix, ...req } = init || {};
const url = pathPrefix ? `${pathPrefix}${path}` : path;
const result = await fetch(url, req);
// needs to use the .ok to check the status of HTTP status code
// http other than 200 will not throw an error, instead the .ok will become false.
// see https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#
if (!result.ok) {
const resp = await result.json();
const errMsg = resp.error && resp.error.message ? resp.error.message : '';
throw new Error(errMsg);
}
if (!result.body) {
throw new Error('response doesnt have a body');
}
await result.body
.pipeThrough(new TextDecoderStream())
.pipeThrough<R>(getNewLineDelimitedJSONDecodingStream<R>())
.pipeTo(
getNotifyEntityArrivalSink((e: R) => {
if (callback) {
callback(e);
}
})
);
// wait for the streaming to finish and return the success respond
return;
}
/**
* JSONStringStreamController represents the transform controller that's able to transform the incoming
* new line delimited json content stream into entities and able to push the entity to the down stream
*/
interface JSONStringStreamController<T> extends TransformStreamDefaultController {
buf?: string;
pos?: number;
enqueue: (s: T) => void;
}
/**
* getNewLineDelimitedJSONDecodingStream returns a TransformStream that's able to handle new line delimited json stream content into parsed entities
*/
function getNewLineDelimitedJSONDecodingStream<T>(): TransformStream<string, T> {
return new TransformStream({
start(controller: JSONStringStreamController<T>) {
controller.buf = '';
controller.pos = 0;
},
transform(chunk: string, controller: JSONStringStreamController<T>) {
if (controller.buf === undefined) {
controller.buf = '';
}
if (controller.pos === undefined) {
controller.pos = 0;
}
controller.buf += chunk;
while (controller.pos < controller.buf.length) {
if (controller.buf[controller.pos] === '\n') {
const line = controller.buf.substring(0, controller.pos);
const response = JSON.parse(line);
controller.enqueue(response.result);
controller.buf = controller.buf.substring(controller.pos + 1);
controller.pos = 0;
} else {
++controller.pos;
}
}
}
});
}
/**
* getNotifyEntityArrivalSink takes the NotifyStreamEntityArrival callback and return
* a sink that will call the callback on entity arrival
* @param notifyCallback
*/
function getNotifyEntityArrivalSink<T>(notifyCallback: NotifyStreamEntityArrival<T>) {
return new WritableStream<T>({
write(entity: T) {
notifyCallback(entity);
}
});
}
type Primitive = string | boolean | number;
type RequestPayload = Record<string, unknown>;
type FlattenedRequestPayload = Record<string, Primitive | Array<Primitive>>;
/**
* Checks if given value is a plain object
* Logic copied and adapted from below source:
* https://github.com/char0n/ramda-adjunct/blob/master/src/isPlainObj.js
* @param {unknown} value
* @return {boolean}
*/
function isPlainObject(value: unknown): boolean {
const isObject = Object.prototype.toString.call(value).slice(8, -1) === 'Object';
const isObjLike = value !== null && isObject;
if (!isObjLike || !isObject) {
return false;
}
const proto = Object.getPrototypeOf(value);
const hasObjectConstructor =
typeof proto === 'object' && proto.constructor === Object.prototype.constructor;
return hasObjectConstructor;
}
/**
* Checks if given value is of a primitive type
* @param {unknown} value
* @return {boolean}
*/
function isPrimitive(value: unknown): boolean {
return ['string', 'number', 'boolean'].some((t) => typeof value === t);
}
/**
* Checks if given primitive is zero-value
* @param {Primitive} value
* @return {boolean}
*/
function isZeroValuePrimitive(value: Primitive): boolean {
return value === false || value === 0 || value === '';
}
/**
* Flattens a deeply nested request payload and returns an object
* with only primitive values and non-empty array of primitive values
* as per https://github.com/googleapis/googleapis/blob/master/google/api/http.proto
* @param {RequestPayload} requestPayload
* @param {String} path
* @return {FlattenedRequestPayload>}
*/
function flattenRequestPayload<T extends RequestPayload>(
requestPayload: T,
path: string = ''
): FlattenedRequestPayload {
return Object.keys(requestPayload).reduce((acc: T, key: string): T => {
const value = requestPayload[key];
const newPath = path ? [path, key].join('.') : key;
const isNonEmptyPrimitiveArray =
Array.isArray(value) && value.every((v) => isPrimitive(v)) && value.length > 0;
const isNonZeroValuePrimitive = isPrimitive(value) && !isZeroValuePrimitive(value as Primitive);
let objectToMerge = {};
if (isPlainObject(value)) {
objectToMerge = flattenRequestPayload(value as RequestPayload, newPath);
} else if (isNonZeroValuePrimitive || isNonEmptyPrimitiveArray) {
objectToMerge = { [newPath]: value };
}
return { ...acc, ...objectToMerge };
}, {} as T) as FlattenedRequestPayload;
}
/**
* Renders a deeply nested request payload into a string of URL search
* parameters by first flattening the request payload and then removing keys
* which are already present in the URL path.
* @param {RequestPayload} requestPayload
* @param {string[]} urlPathParams
* @return {string}
*/
export function renderURLSearchParams<T extends RequestPayload>(
requestPayload: T,
urlPathParams: string[] = []
): string {
const flattenedRequestPayload = flattenRequestPayload(requestPayload);
const urlSearchParams = Object.keys(flattenedRequestPayload).reduce(
(acc: string[][], key: string): string[][] => {
// key should not be present in the url path as a parameter
const value = flattenedRequestPayload[key];
if (urlPathParams.find((f) => f === key)) {
return acc;
}
return Array.isArray(value)
? [...acc, ...value.map((m) => [key, m.toString()])]
: (acc = [...acc, [key, value.toString()]]);
},
[] as string[][]
);
return new URLSearchParams(urlSearchParams).toString();
}

@ -0,0 +1,19 @@
import { browser } from '$app/env';
const protocol = browser ? window.location.protocol : 'http:';
const pathPrefix = protocol + '//api.playcode.quest';
export const withToken = (token: string): any => {
return {
pathPrefix,
headers: {
Authorization: `Bearer ${token}`
}
};
};
export const noToken = (): any => {
return {
pathPrefix
};
};

@ -0,0 +1,70 @@
import { goto } from '$app/navigation';
import { browser } from '$app/env';
import { writable } from 'svelte/store';
import type { Info } from '$lib/pb/all.pb';
import { UserService } from '$lib/pb/all.pb';
import { withToken } from '$lib/pb/pbutil';
const token = browser ? window.localStorage.getItem('authToken') : null;
export class Data {
readonly info?: Info;
readonly token?: string;
constructor(token?: string, info?: Info) {
this.info = info;
this.token = token;
}
loggedIn(): boolean {
return this.token !== null;
}
login(newToken: string) {
window.localStorage.setItem('authToken', newToken);
}
logout() {
window.localStorage.removeItem('authToken');
}
refresh() {
UserService.Info({}, withToken(this.token))
.then((info) => {
auth.set(new Data(token, info));
})
.catch(() => {
this.logout();
auth.set(new Data());
goto('/');
});
}
}
export const auth = writable<Data>(new Data(token));
auth.subscribe(async (newAuth) => {
if (!browser) return;
// user is already set
if (newAuth.info || !newAuth.token) return;
if (!newAuth.token) {
newAuth.logout();
auth.set(new Data());
return;
}
// token just got added
window.localStorage.setItem('authToken', newAuth.token);
UserService.Info({}, withToken(newAuth.token))
.then((info) => {
auth.set(new Data(newAuth.token, info));
})
.catch(() => {
newAuth.logout();
auth.set(new Data());
goto('/');
});
});

@ -0,0 +1,16 @@
<script>
import '../app.css';
import Modal from 'svelte-simple-modal';
import Navbar from '$lib/components/Navbar.svelte';
import Footer from '$lib/components/Footer.svelte';
</script>
<Modal>
<div class="bg-indigo-50">
<Navbar />
<div class="mx-auto mt-6 relative min-h-[calc(100vh-164px)]">
<slot />
</div>
<Footer />
</div>
</Modal>

@ -0,0 +1,17 @@
<script>
import '../app.css';
import Modal from 'svelte-simple-modal';
</script>
<Modal>
<slot />
</Modal>
<style>
:global(body) {
font-family: monospace;
font-size: 1.5rem;
margin: 0;
padding: 0;
}
</style>

@ -1,5 +1,14 @@
<script>
import '../app.css';
import Modal from 'svelte-simple-modal';
import Navbar from '$lib/components/Navbar.svelte';
import Footer from '$lib/components/Footer.svelte';
</script>
<slot />
<Modal>
<Navbar />
<div class="mx-auto mt-6 relative min-h-[80vh]">
<slot />
</div>
<Footer />
</Modal>

@ -0,0 +1,125 @@
<script lang="ts">
import SvelteMarkdown from 'svelte-markdown';
const about = `
### About
CodeQuest is a programming contest that was designed and created as a CAS
project. The project was primarily created by [Hamza
Ali](https://github.com/hhhapz), Smriti Srinivasan, Iona Campbell and Aaliyah
Aman. The development of this website and the design of the puzzles was done
by Hamza Ali.
**CodeQuest** is designed to be a programming competition that is approachable
by all programmers. The puzzles are designed to be able to be solved using
any programming language you like, and can use any method you wish. The first
two problems designed for this competition were able to be solved without any
programming at all, just using excel spreadsheets and WolframAlpha.
This contest was heavily inspired by the [Advent of
Code](https://adventofcode.com), a competition I have been participating in
yearly since 2017. I hope to be able to continue the ethos and the passion for
programming that the advent of code gave me through this competition.
If you have any questions, feel free to reach out at hello@<this website's
domain>.
### How Do I Begin?
If you are looking at the calendar, and can see that the current time is after
the 29th of April 2022, that most likely means that the official competition is
already over, and you will not be able to participate for prizes.
However, if you register an account, you should still be able to see all of the
puzzles as well as try to solve them. Each puzzle is split into 2 different
parts, the first part is generally a simpler version of the second.
Once you solve a puzzle, you simply need to submit the final answer. Your code
nor the method you took to get the answer are checked, however each user gets a
unique input and the answer they will obtain is specific to them.
### General Tips and Advice
#### I'm stuck - what do I do?
Each puzzle will come with at least one example. Does your answer for the
example input match? Make sure you fully understand what the puzzle is asking.
One common mistake is that while copying the input, some of it is left over.
Double check you have the full input.
Try building your own examples and solving them, see if your program does as
you expected. If you're still having difficulties, try solving a different
problem and circling back.
If you still have difficulties and the contest is finished, try asking a friend
for assistance, see if they can help you understand what's going on.
#### I'm programming in Java, how do I read the user input in my program?
Unlike in other programming languages, where you have strings with new lines in
them, Java makes that a little bit more difficult to do.
For example, in Python you can do:
\`\`\`py
input = """this is
a multi line
string literal"""
\`\`\`
and in JavaScript, you can do:
\`\`\`
const input = \`this is
a multi line
string literal\`
\`\`\`
In Java you have two options:
1. Keep reading from your Scanner until you reach an empty line.
2. Read input from a file
(These solutions will work in other languages too!).
Here is an example for the first option:
\`\`\`
Scanner sc = new Scanner(System.in);
/* either */
String input = "";
while(true) {
String line = sc.nextLine(); // Get the next line of user input.
if (line.equals("") { // If the line is empty, we have read everything.
break; // Jump out of the while loop.
}
input += line + "\n"; // If not, add the input line to the input string.
}
\`\`\`
or
\`\`\`
Scanner sc = new Scanner(System.in);
ArrayList<String> input = new ArrayList<>();
while(true) {
String line = sc.nextLine(); // Get the next line of user input.
if (line.equals("") { // If the line is empty, we have read everything.
break; // Jump out of the while loop.
}
input.add(line); // If not, add the line to the input array list.
}
\`\`\`
`;
</script>
<svelte:head>
<title>CodeQuest</title>
</svelte:head>
<main class="min-h-screen">
<section class="mx-auto py-[7vw] prose prose-lg">
<SvelteMarkdown source={about} />
</section>
</main>

@ -0,0 +1,43 @@
<script lang="ts" context="module">
export const router = false;
</script>
<script lang="ts">
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { onMount } from 'svelte';
import { AuthService } from '$lib/pb/all.pb';
import { noToken } from '$lib/pb/pbutil';
import { GoogleSpin } from 'svelte-loading-spinners';
import { auth } from '$lib/stores';
const perform = (code, state) => {
AuthService.Token({ state: state, code: code }, noToken())
.then((res) => {
$auth.login(res.token);
window.location.href = '/dashboard';
})
.catch((err) => {
console.log(err);
goto('/');
});
};
onMount(() => {
const query = $page.url.searchParams;
const code = query.get('code'),
state = query.get('state');
perform(code, state);
});
</script>
<svelte:head>
<title>Authenticating... | CodeQuest</title>
</svelte:head>
<div
class="absolute -top-10 left-0 w-full h-full flex flex-col gap-4 justify-center items-center pointer-events-none bg-transparent"
>
<GoogleSpin size="48px" duration="3s" />
<h1 class="text-2xl font-bold">Processing</h1>
</div>

@ -0,0 +1,22 @@
<script lang="ts">
import { getContext, onMount } from 'svelte';
import { auth } from '$lib/stores';
import LoginModal from '$lib/components/LoginModal.svelte';
const { open } = getContext('simple-modal');
const goHome = () => {
open(LoginModal);
window.location.href = '/';
};
onMount(() => {
auth.subscribe((auth) => {
if (!auth.loggedIn()) {
goHome();
}
});
});
</script>
<slot />

@ -0,0 +1,109 @@
<script lang="ts" context="module">
export const prerender = false;
</script>
<script lang="ts">
import { onMount } from 'svelte';
import Countdown from '$lib/components/Countdown.svelte';
import { auth } from '$lib/stores';
onMount(() => {
$auth.refresh();
});
</script>
<svelte:head><title>Dashboard | CodeQuest</title></svelte:head>
<div class="relative flex min-h-[calc(100vh-164px)] pb-8">
<main class="container mx-auto pt-4">
<div class="p-4 text-white bg-indigo-300 rounded-md shadow-md">
<div class="flex items-center justify-center">
<span class="text-3xl font-semibold tracking-wider uppercase">
{#if !$auth.info}
&nbsp;
{:else if $auth.info?.active}
<Countdown end={$auth.info.endTime} />
{:else if new Date($auth.info.endTime) < new Date()}
Contest Over
{:else}
Starting Soon
{/if}
</span>
</div>
</div>
<div class="grid grid-cols-1 gap-6 mt-4 md:grid-cols-2">
<div
class="flex justify-between w-full h-32 bg-white rounded-md shadow-md p-6 pb-2 border border-indigo-400"
>
<div class="flex flex-col">
<span class="text-xs text-indigo-400 uppercase">Welcome</span>
<span class="text-xl text-indigo-500 font-bold">
{$auth.info?.currentUser.name || ''}
</span>
</div>
<div class="flex flex-col items-end justify-end">
<span class="text-xs text-purple-400 uppercase">Points</span>
<span class="text-purple-700">
{$auth.info?.points || 0}
</span>
</div>
</div>
<a class="box h-32 border-sky-600 hover:bg-sky-50" href="/dashboard/leaderboard">
<span class="txt text-sky-600">
<!-- prettier-ignore -->
<svg class="h-6 w-6" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M22,7H16.333V4a1,1,0,0,0-1-1H8.667a1,1,0,0,0-1,1v7H2a1,1,0,0,0-1,1v8a1,1,0,0,0,1,1H22a1,1,0,0,0,1-1V8A1,1,0,0,0,22,7ZM7.667,19H3V13H7.667ZM14.333,8V19H9.667V5h4.666ZM21,19H16.333V9H21Z"/>
</svg>
Leaderboard
</span>
</a>
<div class="box h-32 border-green-600 hover:bg-green-50">
<span class="txt text-green-700">
<!-- prettier-ignore -->
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 7h6m0 10v-3m-3 3h.01M9 17h.01M9 14h.01M12 14h.01M15 11h.01M12 11h.01M9 11h.01M7 21h10a2 2 0 002-2V5a2 2 0 00-2-2H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
</svg>
Personal Stats
</span>
</div>
<a class="box h-32 border-red-700 hover:bg-red-50" href="/about">
<span class="txt text-red-700">
<!-- prettier-ignore -->
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M18.364 5.636l-3.536 3.536m0 5.656l3.536 3.536M9.172 9.172L5.636 5.636m3.536 9.192l-3.536 3.536M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-5 0a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
About
</span>
</a>
</div>
<a class="grid grid-cols-1 gap-6 my-4 mt-4" href="/dashboard/questions">
<div class="box h-56 border-2 border-indigo-700 hover:bg-indigo-50">
<span class="txt txt-lg text-4xl text-indigo-700">
<!-- prettier-ignore -->
<svg viewBox="0 0 24 24" class="h-9 w-9 mt-1" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<g><path d="M20,8V3a1,1,0,0,0-1-1H13a1,1,0,0,0-1,1V4A2,2,0,0,1,8,4V3A1,1,0,0,0,7,2H1A1,1,0,0,0,0,3V21a1,1,0,0,0,1,1H7a1,1,0,0,0,1-1V20a2,2,0,0,1,4,0v1a1,1,0,0,0,1,1h6a1,1,0,0,0,1-1V16a4,4,0,0,0,0-8Zm0,6H19a1,1,0,0,0-1,1v5H14a4,4,0,0,0-8,0H2V4H6a4,4,0,0,0,8,0h4V9a1,1,0,0,0,1,1h1a2,2,0,0,1,0,4Z"/></g>
</svg>
Puzzles
</span>
</div>
</a>
</main>
</div>
<style lang="postcss">
.box {
@apply flex items-center justify-center w-full rounded-md shadow-md border;
@apply transition;
}
.txt {
@apply inline-flex items-center gap-2;
@apply text-xl font-bold tracking-wider uppercase;
}
.txt-lg {
@apply text-4xl;
}
</style>

@ -0,0 +1,129 @@
<script lang="ts" context="module">
export const prerender = false;
</script>
<script lang="ts">
import { goto } from '$app/navigation';
import { getContext, onMount } from 'svelte';
import { type LeaderboardResponse, QuestService } from '$lib/pb/all.pb';
import { withToken } from '$lib/pb/pbutil';
import Countdown from '$lib/components/Countdown.svelte';
import { auth } from '$lib/stores';
import ErrorModal from '$lib/components/ErrorModal.svelte';
const { open } = getContext('simple-modal');
let ranking: LeaderboardResponse = null;
let queried = false;
onMount(() => {
auth.subscribe((data) => {
if (queried) return;
if (!data.loggedIn()) return;
queried = true;
QuestService.Leaderboard({}, withToken(data.token))
.then((lr) => {
ranking = lr;
})
.catch((err) => {
open(ErrorModal, {
title: 'Could not fetch',
reason: err.message || 'Something went wrong',
btn: {
title: 'Go to dashboard',
do: () => {
goto('/dashboard');
}
}
});
});
});
});
</script>
<svelte:head>
<title>Questions | CodeQuest</title>
</svelte:head>
<div class="relative flex min-h-[calc(100vh-164px)] pb-8 mx-2">
<main class="container mx-auto pt-4 flex flex-col gap-4">
<div class="grid grid-cols-1 gap-4">
<div class="box h-24 border-2 border-sky-600 hover:bg-sky-50">
<span class="font-bold text-4xl text-sky-600">Leaderboard</span>
</div>
</div>
<div class="grid grid-cols-1 gap-4 md:grid-cols-3 xl:grid-cols-5">
<a class="box h-12 hover:bg-neutral-200 border-neutral-300" href="/dashboard">
<span class="txt text-neutral-600 flex items-center gap-1">
<!-- prettier-ignore -->
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
</svg>
Dashboard
</span>
</a>
<div class="box h-12 border-indigo-600 col-span-1 md:col-end-4 xl:col-end-6">
<span class="txt text-indigo-600">
{#if !$auth.info}
&nbsp;
{:else if $auth.info?.active}
<Countdown end={String($auth.info.endTime)} />
{:else if new Date(String($auth.info.endTime)) < new Date()}
Contest Over
{:else}
Starting Soon
{/if}
</span>
</div>
</div>
<div class="grid grid-cols-1 xl:grid-cols-2 gap-6">
{#if ranking}
{#each ranking.leaderboard as item, i}
<div class="ranking h-24 xl:col-span-2">
<div class="inline-flex items-end gap-2">
<span class="txt text-4xl">{i + 1}.</span>
<span class="txt text-2xl">{item.username}</span>
</div>
<div>
<span class="txt text-2xl">{item.points} points</span>
</div>
</div>
{/each}
{/if}
</div>
</main>
</div>
<style lang="postcss">
.box {
@apply flex items-center justify-center w-full rounded-md shadow-md border;
@apply transition;
}
.ranking {
@apply flex items-center justify-between w-full rounded-md shadow-md border;
@apply px-8;
@apply transition;
@apply text-gray-400 border-gray-400 hover:bg-sky-50;
}
.ranking:nth-child(1) {
@apply text-yellow-500 border-yellow-500 hover:bg-amber-50;
}
.ranking:nth-child(2) {
@apply text-zinc-700 border-zinc-700 hover:bg-zinc-50;
}
.ranking:nth-child(3) {
@apply text-amber-600 border-amber-600 hover:bg-amber-50;
}
.txt {
@apply font-bold tracking-wider uppercase;
}
</style>

@ -0,0 +1,52 @@
<script lang="ts">
import { page } from '$app/stores';
import { goto } from '$app/navigation';
import { getContext, onMount } from 'svelte';
import { QuestService } from '$lib/pb/all.pb';
import { withToken } from '$lib/pb/pbutil';
import { auth } from '$lib/stores';
import ErrorModal from '$lib/components/ErrorModal.svelte';
type Status = 'available' | 'partial' | 'full';
const { open } = getContext('simple-modal');
let id = $page.params.id;
let input = 'Loading...';
let queried = false;
onMount(() => {
auth.subscribe((data) => {
if (queried) return;
if (!data.loggedIn()) return;
queried = true;
QuestService.QuestionInput({ id }, withToken(data.token))
.then((inp) => {
input = inp.input;
})
.catch((err) => {
open(ErrorModal, {
title: 'Could not load',
reason: err.message || 'Something went wrong',
btn: {
title: 'Go to questions',
do: () => {
goto('/dashboard/questions');
}
}
});
});
});
});
let value = '';
const submit = () => {
if (value == '') {
open(ErrorModal, { title: 'Could not Submit', reason: 'Please provide a value to submit' });
}
};
</script>
<pre class="font-mono text-sm">{input}</pre>

@ -0,0 +1,224 @@
<script lang="ts">
import SvelteMarkdown from 'svelte-markdown';
import { page } from '$app/stores';
import { goto } from '$app/navigation';
import { getContext, onMount } from 'svelte';
import { type Question, QuestService } from '$lib/pb/all.pb';
import { withToken } from '$lib/pb/pbutil';
import { auth } from '$lib/stores';
import ErrorModal from '$lib/components/ErrorModal.svelte';
import SubmitModal from '$lib/components/SubmitModal.svelte';
type Status = 'available' | 'partial' | 'full';
const { open } = getContext('simple-modal');
let id = $page.params.id;
let q: Question;
let status: Status = 'available';
let solved = 0;
$: {
if (q?.part1?.completed && q?.part2?.completed) {
status = 'full';
solved = 2;
} else if (q?.part1?.completed || q?.part2?.completed) {
status = 'partial';
solved = 1;
} else {
status = 'available';
solved = 0;
}
}
const loadQuestion = () => {
QuestService.QuestionByID({ id }, withToken($auth.token))
.then((question) => {
q = question;
})
.catch((err) => {
open(ErrorModal, {
title: 'Could not load',
reason: err.message || 'Something went wrong',
btn: {
title: 'Go to questions',
do: () => {
goto('/dashboard/questions');
}
}
});
});
};
let queried = false;
onMount(() => {
auth.subscribe(() => {
if (!queried && $auth.loggedIn()) {
queried = true;
loadQuestion();
}
});
});
let value = '';
const submit = () => {
if (value == '') {
open(ErrorModal, { title: 'Could not Submit', reason: 'Please provide a value to submit' });
return;
}
let part = 1;
switch (solved) {
case 0:
part = 1;
break;
case 1:
part = 2;
break;
default:
return;
}
// @ts-ignore
QuestService.Submit({ id, Body: { answer: value, code: '' }, part }, withToken($auth.token))
.then((sr) => {
open(SubmitModal, { ...sr, part });
value = '';
if (sr.correct) {
$auth.info.points += sr.points;
}
loadQuestion();
})
.catch((err) => {
open(ErrorModal, {
reason: err.message || 'Something went wrong'
});
});
};
</script>
<svelte:head>
<title>{q?.title || ''} | CodeQuest</title>
</svelte:head>
{#if q}
<div class="relative flex min-h-[calc(100vh-164px)] pb-8 mx-2">
<main class="container mx-auto pt-4 flex flex-col gap-4">
<div class="grid grid-cols-1 gap-4">
<div class={`box h-24 ${status}`}>
<span class="font-bold text-4xl">{q.title}</span>
</div>
</div>
<div class="grid grid-cols-1 gap-4 md:grid-cols-4 xl:grid-cols-5">
<a class="box h-12 hover:bg-neutral-200 border-neutral-300" href="/dashboard/questions">
<span class="txt text-neutral-600 flex items-center gap-1">
<!-- prettier-ignore -->
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
</svg>
Puzzles
</span>
</a>
<div class="box h-12 border-indigo-600 md:col-span-2 md:col-end-5 xl:col-end-6">
<div class="txt text-indigo-600 flex px-4 w-full items-center justify-between">
<span>
Progress: {solved}/2
</span>
<span>
Points Earned: {q.part1.pointsWorth + q.part2.pointsWorth}
</span>
</div>
</div>
</div>
<div class="relative">
<div class="grid grid-cols-1 lg:grid-cols-5 gap-4">
<div
class="prose text-justify md:col-span-1 lg:col-span-3 md:justify-self-center lg:justify-self-start"
>
<SvelteMarkdown source={q.text} />
</div>
{#if solved != 2}
<div class="submit h-[600px]">
<a
class="btn ghost mb-2 py-2 rounded-md"
href={`/dashboard/question/${id}/input`}
target="_blank"
>
View Puzzle Input
</a>
<form class="flex gap-4 h-12" on:submit|preventDefault={submit}>
<input
type="text"
bind:value
placeholder={`Answer Part ${solved + 1}`}
class="rounded-md shadow-md border w-3/4 font-mono"
/>
<input
type="submit"
value="Submit"
class="btn ghost rounded-md shadow-md w-1/4 cursor-pointer"
/>
</form>
</div>
{:else}
<div class="submit h-[600px]">
<a
class="btn ghost mb-2 py-2 rounded-md"
href={`/dashboard/question/${id}/input`}
target="_blank"
>
View Puzzle Input
</a>
<form class="flex gap-4 h-12">
<input
type="text"
disabled
value={'Puzzle Solved'}
class="rounded-md shadow-md border font-mono bg-neutral-300 w-full"
/>
</form>
</div>
{/if}
</div>
</div>
</main>
</div>
{/if}
<style lang="postcss">
.box {
@apply flex items-center justify-center w-full rounded-md shadow-md border;
@apply transition;
}
.available {
@apply border-gray-400 hover:bg-gray-100;
@apply text-gray-600;
}
.partial {
@apply border-yellow-400 bg-yellow-50 hover:bg-yellow-100;
@apply text-yellow-700;
}
.full {
@apply border-green-400 bg-green-50 hover:bg-green-100;
@apply text-green-700;
}
.txt {
@apply font-bold tracking-wider uppercase;
}
.question {
}
.submit {
@apply w-3/4 md:w-1/2 lg:w-3/4 sticky top-12 mt-8;
@apply md:justify-self-center xl:justify-self-end;
@apply lg:col-span-2 lg:col-end-6;
}
</style>

@ -0,0 +1,104 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { getContext, onMount } from 'svelte';
import * as pb from '$lib/pb/all.pb';
import { withToken } from '$lib/pb/pbutil';
import Countdown from '$lib/components/Countdown.svelte';
import { auth } from '$lib/stores';
import QuestionListing from '$lib/components/QuestionListing.svelte';
import ErrorModal from '$lib/components/ErrorModal.svelte';
const { open } = getContext('simple-modal');
let questions: pb.Question[] = null;
let queried = false;
onMount(() => {
auth.subscribe((data) => {
if (queried) return;
if (!data.loggedIn()) return;
queried = true;
pb.QuestService.Questions({}, withToken(data.token))
.then((qs) => {
questions = qs.questions;
})
.catch((err) => {
questions = [];
open(ErrorModal, {
title: 'Could not fetch',
reason: err.message || 'Something went wrong',
btn: {
title: 'Go to dashboard',
do: () => {
goto('/dashboard');
}
}
});
});
});
});
</script>
<svelte:head>
<title>Puzzles | CodeQuest</title>
</svelte:head>
<div class="relative flex min-h-[calc(100vh-164px)] pb-8 mx-2">
<main class="container mx-auto pt-4 flex flex-col gap-4">
<div class="grid grid-cols-1 gap-4">
<div class="box h-24 border-2 border-indigo-700 hover:bg-indigo-50">
<span class="font-bold text-4xl text-indigo-700">Puzzles</span>
</div>
</div>
<div class="grid grid-cols-1 gap-4 md:grid-cols-3 xl:grid-cols-5">
<a class="box h-12 hover:bg-neutral-200 border-neutral-300" href="/dashboard">
<span class="txt text-neutral-600 flex items-center gap-1">
<!-- prettier-ignore -->
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
</svg>
Dashboard
</span>
</a>
<div class="box h-12 border-indigo-600 col-span-1 md:col-end-4 xl:col-end-6">
<span class="txt text-indigo-600">
{#if !$auth.info}
&nbsp;
{:else if $auth.info?.active}
<Countdown end={String($auth.info.endTime)} />
{:else if new Date(String($auth.info.endTime)) < new Date()}
Contest Over
{:else}
Starting Soon
{/if}
</span>
</div>
</div>
<div class="grid grid-cols-1 xl:grid-cols-2 gap-6">
{#if $auth.info?.active}
{#each questions || [] as question}
<QuestionListing q={question} />
{/each}
{:else}
<dev class="box h-36 hover:bg-red-50 border-red-300 xl:col-span-2">
<span class="txt text-2xl text-red-600">The Contest is not Currently Active</span>
</dev>
{/if}
</div>
</main>
</div>
<style lang="postcss">
.box {
@apply flex items-center justify-center w-full rounded-md shadow-md border;
@apply cursor-pointer transition;
}
.txt {
@apply font-bold tracking-wider uppercase;
}
</style>

@ -1,2 +1,176 @@
<h1>Welcome to SvelteKit</h1>
<p>Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation</p>
<script lang="ts">
import GoogleButton from '$lib/components/GoogleButton.svelte';
</script>
<svelte:head>
<title>CodeQuest</title>
</svelte:head>
<div class="relative overflow-hidden min-h-[70vh]">
<div class="max-w-7xl mx-auto">
<div
class="relative z-10 pb-8 bg-white sm:pb-16 md:pb-20 lg:max-w-lg lg:w-full lg:pb-28 xl:max-w-xl xl:pb-32 "
>
<main
class="mt-10 mx-auto max-w-7xl px-4 sm:mt-12 sm:px-6 md:mt-16 lg:mt-20 lg:px-8 xl:mt-28"
>
<div class="sm:text-center lg:text-left">
<h1 class="text-4xl tracking-tight font-extrabold text-gray-900 sm:text-5xl">
<div class="hidden lg:block">
<span class="block">Solve Some Problems</span>
<span class="block text-indigo-600">Hone Your Skills</span>
</div>
</h1>
<p
class="mt-3 text-base text-gray-500 text-center lg:text-left
sm:mt-5 sm:text-lg sm:max-w-xl sm:mx-auto md:mt-5 md:text-lg lg:mx-0"
>
CodeQuest is a challenge of 10 different programming puzzles.
<br />
Designed to be open and accessible to all.
<br />
Open to <i>any</i> language and technology you like.
</p>
<div class="mt-5 sm:mt-8 sm:flex sm:justify-center lg:justify-start">
<div class="rounded-md shadow w-48 md:w-auto mx-auto md:m-0">
<a href="#details" class="btn px-8 py-3 md:px-10 md:py-4 text-lg rounded">
Learn More
</a>
</div>
</div>
</div>
</main>
</div>
</div>
<div class="lg:absolute lg:inset-y-0 lg:right-3 lg:w-1/2">
<img
class="h-56 w-full object-fit sm:h-72 md:h-96 lg:w-full lg:h-full"
src="/undraw_code_inspection.svg"
alt=""
/>
</div>
</div>
<section class="slant bg-indigo-200">
<div class="wrapper">
<div
id="details"
class="container mx-auto md:min-h-[40vh] p-12 md:p-0 md:flex items-center justify-evenly flex-row-reverse gap-12 lg:gap-8"
>
<div class="mt-8 md:mt-8 w-full md:max-w-sm lg:max-w-lg">
<div class="text-center md:text-left">
<h1 class=" tracking-tight font-semibold text-gray-900 text-3xl flex items-center">
How it Works
<!-- prettier-ignore -->
<svg xmlns="http://www.w3.org/2000/svg" class="h-7 w-7 mt-[5px] ml-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" />
</svg>
</h1>
</div>
<p class="lg:text-lg font-medium mt-2">
CodeQuest is not like most other programming competitions. Instead, it is heavily inspired
by the <a href="https://adventofcode.com">Advent of Code </a>. Instead of submitting code
which will be tested and evaluated by CodeQuest, <i>only your final answers</i> will be
validated to an input you can inspect and verify.
<br /> <br />
This is an opportunity for you to use tools that
<strong>you enjoy</strong>, and demonstrate your skills and expertise.
</p>
<a class="flex items-center mt-2" href="/about">
Learn More
<!-- prettier-ignore -->
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mt-[2px]" viewBox="0 0 20 20" fill="currentColor"> <path fill-rule="evenodd" d="M10.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L12.586 11H5a1 1 0 110-2h7.586l-2.293-2.293a1 1 0 010-1.414z" clip-rule="evenodd" /> </svg>
</a>
</div>
<div class="mt-16 md:mt-8">
<img
class="w-96 object-fit md:w-96 lg:h-80 lg:h-full lg:max-w-lg md:scale-x-[-1]"
src="/undraw_thought_process.svg"
alt=""
/>
</div>
</div>
</div>
</section>
<section class="bg-indigo-100 pt-[7vw]">
<div
class="container mx-auto lg:min-h-[40vh] p-12 md:p-0 lg:flex items-end justify-evenly gap-12 lg:gap-8"
>
<div class="mt-8 w-full md:max-w-lg mx-auto lg:mx-0 lg:max-w-lg">
<div class="text-center lg:text-left">
<h1
class=" tracking-tight font-semibold text-gray-900 text-3xl flex items-center flex gap-2"
>
Details
<!-- prettier-ignore -->
<svg xmlns="http://www.w3.org/2000/svg" class="h-7 w-7 mt-.5 ml-1" viewBox="0 0 20 20" fill="currentColor"> <path d="M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z" /> <path d="M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z" /> </svg>
</h1>
</div>
<p class="lg:text-lg font-medium mt-2">
<strong>CodeQuest</strong> will be hosted on Friday, April 29 in F-201. We highly encourage everyone
to join. The entire competition will take place over approximately 2 hours, where we will spend
10-15 minutes preparing and geting ready, before the competition of 90 minutes will start. Please
try to get here by 15:15 so we can finish by 5pm!
</p>
<a class="flex items-center mt-2" href="/#">
Calendar Invite
<!-- prettier-ignore -->
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mt-[2px]" viewBox="0 0 20 20" fill="currentColor"> <path fill-rule="evenodd" d="M10.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L12.586 11H5a1 1 0 110-2h7.586l-2.293-2.293a1 1 0 010-1.414z" clip-rule="evenodd" /> </svg>
</a>
</div>
<div class="mt-8 w-full md:max-w-lg mx-auto lg:mx-0 lg:max-w-lg">
<div class="text-center lg:text-left">
<h1
class="tracking-tight font-semibold text-gray-900 text-3xl flex items-center gap flex-2"
>
Contest Prizes
<!-- prettier-ignore -->
<svg xmlns="http://www.w3.org/2000/svg" class="h-7 w-7 mt-.5 ml-1" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" /> </svg>
</h1>
</div>
<p class="lg:text-lg font-medium mt-2">
While we encourage everyone to join and have fun, prizes will be awarded. We want to
recognize, promote, and encourage students who have cultivated their programming skills both
within class, and outside of school.
<br />
</p>
<div class="mt-2">
<strong>Top 3 Performance Prizes</strong>
<ol class="list-decimal font-medium ml-6">
<li>IDR 350,000</li>
<li>IDR 125,000</li>
<li>IDR 75,000</li>
</ol>
</div>
<a class="flex items-center mt-2" href="/about">
Learn More
<!-- prettier-ignore -->
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mt-[2px]" viewBox="0 0 20 20" fill="currentColor" > <path fill-rule="evenodd" d="M10.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L12.586 11H5a1 1 0 110-2h7.586l-2.293-2.293a1 1 0 010-1.414z" clip-rule="evenodd" /> </svg>
</a>
</div>
</div>
<hr class="border-indigo-400 border-2 max-w-xs mx-auto my-12 lg:my-24" />
<div class="mt-8 md:mt-8 w-full md:max-w-sm lg:max-w-lg mx-auto">
<h1 class="text-center tracking-tight font-semibold text-gray-900 text-2xl">Join Now</h1>
<p class="mt-2 pb-8 text-center">
Log in with your JIS Email and join CodeQuest!
<br />
<br />
<GoogleButton />
</p>
</div>
</section>
<style lang="postcss">
.slant {
clip-path: polygon(0 7vw, 0 100%, 100% calc(100% - 7vw), 100% 0);
margin: 0 0 -7vw;
}
.slant .wrapper {
padding: 9vw 0;
}
</style>

@ -0,0 +1,18 @@
<script lang="ts" context="module">
export const router = false;
</script>
<script lang="ts">
import { onMount } from 'svelte';
import { auth } from '$lib/stores';
onMount(() => {
$auth.logout();
window.location.href = '/';
});
</script>
<svelte:head>
<title>Logging out... | CodeQuest</title>
</svelte:head>