hackathon/api/quest.go

168 lines
4.5 KiB
Go

package api
import (
"bytes"
"context"
"errors"
"log"
"time"
codequestpb "github.com/hhhapz/codequest/api/v1"
"github.com/hhhapz/codequest/models"
"github.com/hhhapz/codequest/question"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
)
type QuestService struct {
codequestpb.UnimplementedQuestServiceServer
questStore QuestStore
userStore UserStore
}
func (qs *QuestService) Questions(ctx context.Context, req *emptypb.Empty) (*codequestpb.QuestionsResponse, error) {
u := UserCtx(ctx)
questions, err := qs.questStore.Questions(ctx, u)
if err != nil {
if errors.As(err, &models.UserError{}) {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
return nil, status.Errorf(codes.Internal, "could not get questions: internal error")
}
res := new(codequestpb.QuestionsResponse)
for _, q := range questions {
res.Questions = append(res.Questions, &codequestpb.Question{
ID: q.Question.ID,
Title: q.Name,
Part1: &codequestpb.PartData{
Completed: q.Part1.Completed,
PointsWorth: int32(q.Part1.PointsWorth),
},
Part2: &codequestpb.PartData{
Completed: q.Part2.Completed,
PointsWorth: int32(q.Part2.PointsWorth),
},
})
}
return res, nil
}
func (qs *QuestService) QuestionByID(ctx context.Context, req *codequestpb.QuestionByIDRequest) (*codequestpb.Question, error) {
u := UserCtx(ctx)
question, err := qs.questStore.Question(ctx, u, req.ID)
if err != nil {
if errors.As(err, &models.UserError{}) {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
return nil, status.Errorf(codes.Internal, "could not get question: internal error")
}
content := new(bytes.Buffer)
err = question.Text.Execute(content, question)
if err != nil {
log.Printf("could gen write question %s template: %v", req.ID, err)
return nil, status.Errorf(codes.Internal, "could not get question: internal error")
}
q := &codequestpb.Question{
ID: question.Question.ID,
Title: question.Name,
Text: content.String(),
Part1: &codequestpb.PartData{
Completed: question.Part1.Completed,
PointsWorth: int32(question.Part1.PointsWorth),
},
Part2: &codequestpb.PartData{
Completed: question.Part2.Completed,
PointsWorth: int32(question.Part2.PointsWorth),
},
}
return q, nil
}
func (qs *QuestService) QuestionInput(ctx context.Context, req *codequestpb.QuestionInputRequest) (*codequestpb.QuestionInput, error) {
u := UserCtx(ctx)
question, err := qs.questStore.Question(ctx, u, req.ID)
if err != nil {
if errors.As(err, &models.UserError{}) {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
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,
}, nil
}
func (qs *QuestService) Submit(ctx context.Context, req *codequestpb.SubmitRequest) (*codequestpb.SubmitResponse, error) {
u := UserCtx(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")
}
data := req.Body
completed, attempts, err := qs.questStore.Submissions(ctx, u, req.ID, question.Part(data.Part))
if err != nil {
if errors.As(err, &models.UserError{}) {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
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)
}
// TODO: cooldowns
correct := q.Question.Validate(u, question.Part(data.Part), data.Answer)
var points int
if correct {
points = 5000 - (attempts * 100)
if points < 2000 {
points = 2000
}
}
qa := &models.QuestionAttempt{
UserID: u.ID,
QuestionID: q.Question.ID,
QuestionPart: int(data.Part),
Correct: correct,
PointsAwarded: points,
Answer: data.Answer,
Code: data.Code,
SubmittedAt: models.NewTime(time.Now()),
}
qs.questStore.AddSubmission(ctx, qa)
res := &codequestpb.SubmitResponse{
Correct: qa.Correct,
Points: int32(qa.PointsAwarded),
}
return res, nil
}