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 }