Update quiz
This commit is contained in:
parent
405113e639
commit
ee45c1ba9c
10 changed files with 205 additions and 42 deletions
|
@ -7,6 +7,20 @@ type Response struct {
|
||||||
Content interface{} `json:"content"`
|
Content interface{} `json:"content"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Question struct {
|
||||||
|
Text string `json:"text"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Answer struct {
|
||||||
|
Text string
|
||||||
|
Correct bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Quiz struct {
|
||||||
|
Question *Question `json:"question"`
|
||||||
|
Answers []*Answer `json:"answers"`
|
||||||
|
}
|
||||||
|
|
||||||
type ReadAllQuizResponse struct {
|
type ReadAllQuizResponse struct {
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Content []*models.Quiz `json:"content"`
|
Content []*models.Quiz `json:"content"`
|
||||||
|
@ -17,16 +31,24 @@ type CreateQuizResponse struct {
|
||||||
Content *models.Quiz `json:"content"`
|
Content *models.Quiz `json:"content"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UpdateQuizResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Content *models.Quiz `json:"content"`
|
||||||
|
}
|
||||||
|
|
||||||
type CreateQuestionRequest struct {
|
type CreateQuestionRequest struct {
|
||||||
Text string `json:"text"`
|
*Question
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateAnswerRequest struct {
|
type CreateAnswerRequest struct {
|
||||||
Text string
|
*Answer
|
||||||
Correct bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateQuizRequest struct {
|
type CreateQuizRequest struct {
|
||||||
Question *CreateQuestionRequest `json:"question"`
|
*Quiz
|
||||||
Answers []*CreateAnswerRequest `json:"answers"`
|
}
|
||||||
|
|
||||||
|
type UpdateQuizRequest struct {
|
||||||
|
ID string
|
||||||
|
*Quiz
|
||||||
}
|
}
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -6,6 +6,7 @@ require github.com/sirupsen/logrus v1.8.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/google/uuid v1.3.0 // indirect
|
github.com/google/uuid v1.3.0 // indirect
|
||||||
|
github.com/julienschmidt/httprouter v1.3.0 // indirect
|
||||||
github.com/kr/pretty v0.2.1 // indirect
|
github.com/kr/pretty v0.2.1 // indirect
|
||||||
github.com/kr/text v0.1.0 // indirect
|
github.com/kr/text v0.1.0 // indirect
|
||||||
github.com/remogatto/prettytest v0.0.0-20200211072524-6d385e11dcb8 // indirect
|
github.com/remogatto/prettytest v0.0.0-20200211072524-6d385e11dcb8 // indirect
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -1,6 +1,8 @@
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
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/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
|
||||||
|
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
|
|
@ -11,15 +11,15 @@ type Hasher interface {
|
||||||
// hash calculated from the question using
|
// hash calculated from the question using
|
||||||
// QuestionHash. Following hashes are calculated from the
|
// QuestionHash. Following hashes are calculated from the
|
||||||
// answers using AnswerHash.
|
// answers using AnswerHash.
|
||||||
QuizHashes(quiz *client.CreateQuizRequest) []string
|
QuizHashes(quiz *client.Quiz) []string
|
||||||
|
|
||||||
// QuestionHash returns an hash calculated from a field of
|
// QuestionHash returns an hash calculated from a field of
|
||||||
// Question struct.
|
// Question struct.
|
||||||
QuestionHash(question *client.CreateQuestionRequest) string
|
QuestionHash(question *client.Question) string
|
||||||
|
|
||||||
// AnswerHash returns an hash calculated from a field of
|
// AnswerHash returns an hash calculated from a field of
|
||||||
// Answer struct.
|
// Answer struct.
|
||||||
AnswerHash(answer *client.CreateAnswerRequest) string
|
AnswerHash(answer *client.Answer) string
|
||||||
|
|
||||||
// Calculate calculates a checksum from all the given hashes.
|
// Calculate calculates a checksum from all the given hashes.
|
||||||
Calculate(hashes []string) string
|
Calculate(hashes []string) string
|
||||||
|
|
|
@ -22,7 +22,7 @@ func NewDefault256Hasher(hashFn hasher.HashFunc) *Default256Hasher {
|
||||||
return &Default256Hasher{hashFn}
|
return &Default256Hasher{hashFn}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Default256Hasher) QuizHashes(quiz *client.CreateQuizRequest) []string {
|
func (h *Default256Hasher) QuizHashes(quiz *client.Quiz) []string {
|
||||||
result := make([]string, 0)
|
result := make([]string, 0)
|
||||||
|
|
||||||
result = append(result, h.QuestionHash(quiz.Question))
|
result = append(result, h.QuestionHash(quiz.Question))
|
||||||
|
@ -36,11 +36,11 @@ func (h *Default256Hasher) QuizHashes(quiz *client.CreateQuizRequest) []string {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Default256Hasher) QuestionHash(question *client.CreateQuestionRequest) string {
|
func (h *Default256Hasher) QuestionHash(question *client.Question) string {
|
||||||
return h.hashFn(question.Text)
|
return h.hashFn(question.Text)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Default256Hasher) AnswerHash(answer *client.CreateAnswerRequest) string {
|
func (h *Default256Hasher) AnswerHash(answer *client.Answer) string {
|
||||||
return h.hashFn(answer.Text)
|
return h.hashFn(answer.Text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
51
server.go
51
server.go
|
@ -9,6 +9,7 @@ import (
|
||||||
"git.andreafazzi.eu/andrea/probo/logger"
|
"git.andreafazzi.eu/andrea/probo/logger"
|
||||||
"git.andreafazzi.eu/andrea/probo/models"
|
"git.andreafazzi.eu/andrea/probo/models"
|
||||||
"git.andreafazzi.eu/andrea/probo/store"
|
"git.andreafazzi.eu/andrea/probo/store"
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
)
|
)
|
||||||
|
|
||||||
const jsonContentType = "application/json"
|
const jsonContentType = "application/json"
|
||||||
|
@ -22,22 +23,24 @@ func NewQuizHubCollectorServer(store store.QuizHubCollectorStore) *QuizHubCollec
|
||||||
ps := new(QuizHubCollectorServer)
|
ps := new(QuizHubCollectorServer)
|
||||||
ps.store = store
|
ps.store = store
|
||||||
|
|
||||||
router := http.NewServeMux()
|
router := httprouter.New()
|
||||||
|
|
||||||
router.Handle("/quizzes", logger.WithLogging(http.HandlerFunc(ps.testHandler)))
|
router.GET("/quizzes", httprouter.Handle(ps.readAllQuizzesHandler))
|
||||||
|
router.POST("/quizzes/create", httprouter.Handle(ps.createQuizHandler))
|
||||||
|
router.POST("/quizzes/:id/update", httprouter.Handle(ps.updateQuizHandler))
|
||||||
|
// router.Handle("/quizzes", logger.WithLogging(http.HandlerFunc(ps.testHandler)))
|
||||||
|
|
||||||
ps.Handler = router
|
ps.Handler = logger.WithLogging(router)
|
||||||
|
|
||||||
return ps
|
return ps
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ps *QuizHubCollectorServer) testHandler(w http.ResponseWriter, r *http.Request) {
|
func (ps *QuizHubCollectorServer) readAllQuizzesHandler(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||||
switch r.Method {
|
|
||||||
case http.MethodGet:
|
|
||||||
w.Header().Set("content-type", jsonContentType)
|
w.Header().Set("content-type", jsonContentType)
|
||||||
json.NewEncoder(w).Encode(ps.readAllQuizzes(w, r))
|
json.NewEncoder(w).Encode(ps.readAllQuizzes(w, r))
|
||||||
|
}
|
||||||
|
|
||||||
case http.MethodPost:
|
func (ps *QuizHubCollectorServer) createQuizHandler(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||||
response := new(client.Response)
|
response := new(client.Response)
|
||||||
|
|
||||||
quiz, err := ps.createQuiz(w, r)
|
quiz, err := ps.createQuiz(w, r)
|
||||||
|
@ -49,7 +52,20 @@ func (ps *QuizHubCollectorServer) testHandler(w http.ResponseWriter, r *http.Req
|
||||||
|
|
||||||
w.WriteHeader(http.StatusAccepted)
|
w.WriteHeader(http.StatusAccepted)
|
||||||
json.NewEncoder(w).Encode(response)
|
json.NewEncoder(w).Encode(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps *QuizHubCollectorServer) updateQuizHandler(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||||
|
response := new(client.Response)
|
||||||
|
|
||||||
|
quiz, err := ps.updateQuiz(w, r, params.ByName("id"))
|
||||||
|
if err != nil {
|
||||||
|
response = &client.Response{Status: "error", Content: err.Error()}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
response = &client.Response{Status: "success", Content: quiz}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusAccepted)
|
||||||
|
json.NewEncoder(w).Encode(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ps *QuizHubCollectorServer) readAllQuizzes(w http.ResponseWriter, r *http.Request) *client.Response {
|
func (ps *QuizHubCollectorServer) readAllQuizzes(w http.ResponseWriter, r *http.Request) *client.Response {
|
||||||
|
@ -60,6 +76,27 @@ func (ps *QuizHubCollectorServer) readAllQuizzes(w http.ResponseWriter, r *http.
|
||||||
return &client.Response{Status: "success", Content: tests}
|
return &client.Response{Status: "success", Content: tests}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ps *QuizHubCollectorServer) updateQuiz(w http.ResponseWriter, r *http.Request, id string) (*models.Quiz, error) {
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
updateQuizReq := new(client.UpdateQuizRequest)
|
||||||
|
|
||||||
|
err = json.Unmarshal(body, &updateQuizReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedQuiz, err := ps.store.UpdateQuiz(updateQuizReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedQuiz, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (ps *QuizHubCollectorServer) createQuiz(w http.ResponseWriter, r *http.Request) (*models.Quiz, error) {
|
func (ps *QuizHubCollectorServer) createQuiz(w http.ResponseWriter, r *http.Request) (*models.Quiz, error) {
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -38,8 +38,9 @@ func (t *integrationTestSuite) TestQuizCreateAndReadAll() {
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
createQuizResponse, err := t.createQuiz(server, payload)
|
createQuizResponse, err := t.createQuiz(server, payload)
|
||||||
t.True(err == nil)
|
t.True(err == nil, "Response should be decoded properly")
|
||||||
|
|
||||||
|
if !t.Failed() {
|
||||||
t.Equal("success", createQuizResponse.Status)
|
t.Equal("success", createQuizResponse.Status)
|
||||||
t.Equal("Question 1", createQuizResponse.Content.Question.Text)
|
t.Equal("Question 1", createQuizResponse.Content.Question.Text)
|
||||||
t.Equal("Text of the answer 1", createQuizResponse.Content.Answers[0].Text)
|
t.Equal("Text of the answer 1", createQuizResponse.Content.Answers[0].Text)
|
||||||
|
@ -48,12 +49,15 @@ func (t *integrationTestSuite) TestQuizCreateAndReadAll() {
|
||||||
t.True(createQuizResponse.Content.ID != "", "Test ID should not be empty")
|
t.True(createQuizResponse.Content.ID != "", "Test ID should not be empty")
|
||||||
t.True(createQuizResponse.Content.Question.ID != "", "Question ID should not be empty")
|
t.True(createQuizResponse.Content.Question.ID != "", "Question ID should not be empty")
|
||||||
t.True(createQuizResponse.Content.Answers[0].ID != "", "Answer ID should not be empty")
|
t.True(createQuizResponse.Content.Answers[0].ID != "", "Answer ID should not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
readAllQuizResponse, err := t.readAllQuiz(server)
|
readAllQuizResponse, err := t.readAllQuiz(server)
|
||||||
t.True(err == nil)
|
t.True(err == nil, "Response should be decoded properly")
|
||||||
|
|
||||||
|
if !t.Failed() {
|
||||||
t.True(len(readAllQuizResponse.Content) == 1, "Length of returned tests should be 1")
|
t.True(len(readAllQuizResponse.Content) == 1, "Length of returned tests should be 1")
|
||||||
t.Equal("Question 1", readAllQuizResponse.Content[0].Question.Text)
|
t.Equal("Question 1", readAllQuizResponse.Content[0].Question.Text)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *integrationTestSuite) TestCatchDuplicateQuiz() {
|
func (t *integrationTestSuite) TestCatchDuplicateQuiz() {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.andreafazzi.eu/andrea/probo/client"
|
"git.andreafazzi.eu/andrea/probo/client"
|
||||||
|
@ -76,6 +77,51 @@ func (t *testSuite) TestGETQuestions() {
|
||||||
t.Equal(http.StatusOK, response.Code)
|
t.Equal(http.StatusOK, response.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *testSuite) TestUpdateQuiz() {
|
||||||
|
expectedResponse := &client.UpdateQuizResponse{
|
||||||
|
Status: "success",
|
||||||
|
Content: &models.Quiz{
|
||||||
|
Question: &models.Question{ID: "1", Text: "Question 1"},
|
||||||
|
Answers: []*models.Answer{{}, {}, {}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
store := &StubTestHubCollectorStore{[]*models.Quiz{
|
||||||
|
{
|
||||||
|
Question: &models.Question{ID: "1", Text: "Question 1"},
|
||||||
|
Answers: []*models.Answer{{}, {}, {}},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
|
||||||
|
server := NewQuizHubCollectorServer(store)
|
||||||
|
|
||||||
|
payload := `
|
||||||
|
{
|
||||||
|
"question": {"text": "Update Question 1"},
|
||||||
|
"answers": [
|
||||||
|
{"text": "Text of the answer 1", "correct": true},
|
||||||
|
{"text": "Text of the answer 2"},
|
||||||
|
{"text": "Text of the answer 3"},
|
||||||
|
{"text": "Text of the answer 4"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`
|
||||||
|
request, _ := http.NewRequest(http.MethodPut, "/quizzes/1/update", strings.NewReader(payload))
|
||||||
|
response := httptest.NewRecorder()
|
||||||
|
|
||||||
|
server.ServeHTTP(response, request)
|
||||||
|
|
||||||
|
result, err := decodeUpdateQuizResponse(response.Body)
|
||||||
|
|
||||||
|
t.True(err == nil, fmt.Sprintf("Response should be decoded without errors: %v", err))
|
||||||
|
|
||||||
|
if !t.Failed() {
|
||||||
|
t.True(result.Status == expectedResponse.Status)
|
||||||
|
t.True(reflect.DeepEqual(result, expectedResponse))
|
||||||
|
t.Equal(http.StatusOK, response.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func getResponse(body io.Reader) (response *client.ReadAllQuizResponse) {
|
func getResponse(body io.Reader) (response *client.ReadAllQuizResponse) {
|
||||||
err := json.NewDecoder(body).Decode(&response)
|
err := json.NewDecoder(body).Decode(&response)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -85,6 +131,14 @@ func getResponse(body io.Reader) (response *client.ReadAllQuizResponse) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func decodeUpdateQuizResponse(body io.Reader) (response *client.UpdateQuizResponse, err error) {
|
||||||
|
err = json.NewDecoder(body).Decode(&response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Unable to parse the response %q from the server: %v", body, err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func testsAreEqual(got, want *client.ReadAllQuizResponse) bool {
|
func testsAreEqual(got, want *client.ReadAllQuizResponse) bool {
|
||||||
return reflect.DeepEqual(got, want)
|
return reflect.DeepEqual(got, want)
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,7 +107,49 @@ func (s *MemoryQuizHubCollectorStore) ReadAllQuizzes() ([]*models.Quiz, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MemoryQuizHubCollectorStore) CreateQuiz(r *client.CreateQuizRequest) (*models.Quiz, error) {
|
func (s *MemoryQuizHubCollectorStore) CreateQuiz(r *client.CreateQuizRequest) (*models.Quiz, error) {
|
||||||
hashes := s.hasher.QuizHashes(r)
|
hashes := s.hasher.QuizHashes(r.Quiz)
|
||||||
|
|
||||||
|
quizID := uuid.New().String()
|
||||||
|
quizHash := hashes[len(hashes)-1]
|
||||||
|
|
||||||
|
quiz := s.readQuiz(quizHash)
|
||||||
|
if quiz != nil { // Quiz is already present in the store
|
||||||
|
return quiz, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
quiz = new(models.Quiz)
|
||||||
|
|
||||||
|
questionHash := hashes[0]
|
||||||
|
q := s.readQuestion(questionHash)
|
||||||
|
if q == nil { // if the question is not in the store then we should add it
|
||||||
|
q = s.createQuestion(questionHash, &models.Question{
|
||||||
|
ID: uuid.New().String(),
|
||||||
|
Text: r.Question.Text,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate Question field
|
||||||
|
quiz.Question = q
|
||||||
|
for i, answer := range r.Answers {
|
||||||
|
answerHash := hashes[i+1]
|
||||||
|
a := s.readAnswer(answerHash)
|
||||||
|
if a == nil { // if the answer is not in the store add it
|
||||||
|
a = s.createAnswer(answerHash, &models.Answer{
|
||||||
|
ID: uuid.New().String(),
|
||||||
|
Text: answer.Text,
|
||||||
|
})
|
||||||
|
if answer.Correct {
|
||||||
|
quiz.Correct = a // s.readAnswer(answerID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
quiz.Answers = append(quiz.Answers, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.createQuiz(quizID, quizHash, quiz), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MemoryQuizHubCollectorStore) UpdateQuiz(r *client.UpdateQuizRequest) (*models.Quiz, error) {
|
||||||
|
hashes := s.hasher.QuizHashes(r.Quiz)
|
||||||
|
|
||||||
quizID := uuid.New().String()
|
quizID := uuid.New().String()
|
||||||
quizHash := hashes[len(hashes)-1]
|
quizHash := hashes[len(hashes)-1]
|
||||||
|
|
|
@ -8,4 +8,5 @@ import (
|
||||||
type QuizHubCollectorStore interface {
|
type QuizHubCollectorStore interface {
|
||||||
ReadAllQuizzes() ([]*models.Quiz, error)
|
ReadAllQuizzes() ([]*models.Quiz, error)
|
||||||
CreateQuiz(r *client.CreateQuizRequest) (*models.Quiz, error)
|
CreateQuiz(r *client.CreateQuizRequest) (*models.Quiz, error)
|
||||||
|
UpdateQuiz(r *client.UpdateQuizRequest) (*models.Quiz, error)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue