Refactoring
This commit is contained in:
parent
ee45c1ba9c
commit
cb56e4c56f
9 changed files with 263 additions and 158 deletions
|
@ -2,11 +2,6 @@ package client
|
||||||
|
|
||||||
import "git.andreafazzi.eu/andrea/probo/models"
|
import "git.andreafazzi.eu/andrea/probo/models"
|
||||||
|
|
||||||
type Response struct {
|
|
||||||
Status string `json:"status"`
|
|
||||||
Content interface{} `json:"content"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Question struct {
|
type Question struct {
|
||||||
Text string `json:"text"`
|
Text string `json:"text"`
|
||||||
}
|
}
|
||||||
|
@ -21,18 +16,23 @@ type Quiz struct {
|
||||||
Answers []*Answer `json:"answers"`
|
Answers []*Answer `json:"answers"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReadAllQuizResponse struct {
|
type BaseResponse struct {
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReadAllQuizResponse struct {
|
||||||
|
BaseResponse
|
||||||
Content []*models.Quiz `json:"content"`
|
Content []*models.Quiz `json:"content"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateQuizResponse struct {
|
type CreateQuizResponse struct {
|
||||||
Status string `json:"status"`
|
BaseResponse
|
||||||
Content *models.Quiz `json:"content"`
|
Content *models.Quiz `json:"content"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateQuizResponse struct {
|
type UpdateQuizResponse struct {
|
||||||
Status string `json:"status"`
|
BaseResponse
|
||||||
Content *models.Quiz `json:"content"`
|
Content *models.Quiz `json:"content"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,11 +44,6 @@ type CreateAnswerRequest struct {
|
||||||
*Answer
|
*Answer
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateQuizRequest struct {
|
type CreateUpdateQuizRequest struct {
|
||||||
*Quiz
|
|
||||||
}
|
|
||||||
|
|
||||||
type UpdateQuizRequest struct {
|
|
||||||
ID string
|
|
||||||
*Quiz
|
*Quiz
|
||||||
}
|
}
|
||||||
|
|
4
main.go
4
main.go
|
@ -15,8 +15,8 @@ const port = "3000"
|
||||||
func main() {
|
func main() {
|
||||||
logger.SetLevel(logger.DebugLevel)
|
logger.SetLevel(logger.DebugLevel)
|
||||||
|
|
||||||
server := NewQuizHubCollectorServer(
|
server := NewProboCollectorServer(
|
||||||
memory.NewMemoryQuizHubCollectorStore(
|
memory.NewMemoryProboCollectorStore(
|
||||||
sha256.NewDefault256Hasher(sha256.DefaultSHA256HashingFn),
|
sha256.NewDefault256Hasher(sha256.DefaultSHA256HashingFn),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -55,17 +55,21 @@ const main = () => {
|
||||||
const proboId = `probo_${id}`;
|
const proboId = `probo_${id}`;
|
||||||
|
|
||||||
logseq.provideModel({
|
logseq.provideModel({
|
||||||
async postQuiz() {
|
async createOrUpdateQuiz() {
|
||||||
const parentBlock = await logseq.Editor.getBlock(payload.uuid, { includeChildren: true });
|
const parentBlock = await logseq.Editor.getBlock(payload.uuid, { includeChildren: true });
|
||||||
const quizzes = parentBlock.children.map((child: BlockEntity) => {
|
|
||||||
const question = { text: child.content }
|
|
||||||
const answers = child.children.map((answer: BlockEntity, i: number) => {
|
const answers = child.children.map((answer: BlockEntity, i: number) => {
|
||||||
return { text: answer.content, correct: (i == 0) ? true : false }
|
return { text: answer.content, correct: (i == 0) ? true : false }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const quiz = {
|
||||||
|
question: {text: parentBlock.children[0].content },
|
||||||
|
answers: answers
|
||||||
|
}
|
||||||
|
const question = { text: child.content }
|
||||||
return { question: question, answers: answers, uuid: child.uuid }
|
return { question: question, answers: answers, uuid: child.uuid }
|
||||||
});
|
});
|
||||||
quizzes.forEach(async (quiz, i) => {
|
quizzes.forEach(async (quiz, i) => {
|
||||||
const res = await fetch(endpoint, { method: 'POST', body: JSON.stringify(quiz) })
|
const res = await fetch(endpoint+'/create', { method: 'POST', body: JSON.stringify(quiz) })
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
const block = await logseq.Editor.getBlock(quiz.uuid)
|
const block = await logseq.Editor.getBlock(quiz.uuid)
|
||||||
await logseq.Editor.upsertBlockProperty(parentBlock.uuid, `probo-quiz-uuid`, data.content.ID)
|
await logseq.Editor.upsertBlockProperty(parentBlock.uuid, `probo-quiz-uuid`, data.content.ID)
|
||||||
|
@ -93,7 +97,7 @@ const main = () => {
|
||||||
key: `${proboId}`,
|
key: `${proboId}`,
|
||||||
slot,
|
slot,
|
||||||
reset: true,
|
reset: true,
|
||||||
template: `<button data-on-click="postQuiz" class="renderBtn">Save</button>`,
|
template: `<button data-on-click="createOrUpdateQuiz" class="renderBtn">Save</button>`,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
91
server.go
91
server.go
|
@ -12,84 +12,115 @@ import (
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/julienschmidt/httprouter"
|
||||||
)
|
)
|
||||||
|
|
||||||
const jsonContentType = "application/json"
|
type ProboCollectorServer struct {
|
||||||
|
store store.ProboCollectorStore
|
||||||
type QuizHubCollectorServer struct {
|
|
||||||
store store.QuizHubCollectorStore
|
|
||||||
http.Handler
|
http.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewQuizHubCollectorServer(store store.QuizHubCollectorStore) *QuizHubCollectorServer {
|
func NewProboCollectorServer(store store.ProboCollectorStore) *ProboCollectorServer {
|
||||||
ps := new(QuizHubCollectorServer)
|
ps := new(ProboCollectorServer)
|
||||||
ps.store = store
|
ps.store = store
|
||||||
|
|
||||||
router := httprouter.New()
|
router := httprouter.New()
|
||||||
|
|
||||||
router.GET("/quizzes", httprouter.Handle(ps.readAllQuizzesHandler))
|
router.GET("/quizzes", httprouter.Handle(ps.readAllQuizzesHandler))
|
||||||
router.POST("/quizzes/create", httprouter.Handle(ps.createQuizHandler))
|
router.POST("/quizzes/create", httprouter.Handle(ps.createQuizHandler))
|
||||||
router.POST("/quizzes/:id/update", httprouter.Handle(ps.updateQuizHandler))
|
router.PUT("/quizzes/update/:id", httprouter.Handle(ps.updateQuizHandler))
|
||||||
// router.Handle("/quizzes", logger.WithLogging(http.HandlerFunc(ps.testHandler)))
|
|
||||||
|
|
||||||
ps.Handler = logger.WithLogging(router)
|
ps.Handler = logger.WithLogging(ps.jsonHeaderMiddleware(router))
|
||||||
|
|
||||||
return ps
|
return ps
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ps *QuizHubCollectorServer) readAllQuizzesHandler(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
func (ps *ProboCollectorServer) jsonHeaderMiddleware(next http.Handler) http.Handler {
|
||||||
w.Header().Set("content-type", jsonContentType)
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
json.NewEncoder(w).Encode(ps.readAllQuizzes(w, r))
|
w.Header().Add("Content-Type", "application/json")
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ps *QuizHubCollectorServer) createQuizHandler(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
func (ps *ProboCollectorServer) readAllQuizzesHandler(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||||
response := new(client.Response)
|
response := new(client.ReadAllQuizResponse)
|
||||||
|
|
||||||
|
quizzes, err := ps.readAllQuiz(w, r)
|
||||||
|
if err != nil {
|
||||||
|
response = &client.ReadAllQuizResponse{
|
||||||
|
BaseResponse: client.BaseResponse{Status: "error", Message: err.Error()},
|
||||||
|
Content: nil,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
response = &client.ReadAllQuizResponse{
|
||||||
|
BaseResponse: client.BaseResponse{Status: "success", Message: ""},
|
||||||
|
Content: quizzes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
json.NewEncoder(w).Encode(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps *ProboCollectorServer) createQuizHandler(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||||
|
response := new(client.CreateQuizResponse)
|
||||||
|
|
||||||
quiz, err := ps.createQuiz(w, r)
|
quiz, err := ps.createQuiz(w, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response = &client.Response{Status: "error", Content: err.Error()}
|
response = &client.CreateQuizResponse{
|
||||||
|
BaseResponse: client.BaseResponse{Status: "error", Message: err.Error()},
|
||||||
|
Content: nil,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response = &client.Response{Status: "success", Content: quiz}
|
response = &client.CreateQuizResponse{
|
||||||
|
BaseResponse: client.BaseResponse{Status: "success", Message: ""},
|
||||||
|
Content: quiz,
|
||||||
|
}
|
||||||
|
|
||||||
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) {
|
func (ps *ProboCollectorServer) updateQuizHandler(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||||
response := new(client.Response)
|
response := new(client.UpdateQuizResponse)
|
||||||
|
|
||||||
quiz, err := ps.updateQuiz(w, r, params.ByName("id"))
|
quiz, err := ps.updateQuiz(w, r, params.ByName("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response = &client.Response{Status: "error", Content: err.Error()}
|
response = &client.UpdateQuizResponse{
|
||||||
|
BaseResponse: client.BaseResponse{Status: "error", Message: err.Error()},
|
||||||
|
Content: nil,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
response = &client.UpdateQuizResponse{
|
||||||
|
BaseResponse: client.BaseResponse{Status: "success", Message: ""},
|
||||||
|
Content: quiz,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response = &client.Response{Status: "success", Content: quiz}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusAccepted)
|
w.WriteHeader(http.StatusAccepted)
|
||||||
json.NewEncoder(w).Encode(response)
|
json.NewEncoder(w).Encode(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ps *QuizHubCollectorServer) readAllQuizzes(w http.ResponseWriter, r *http.Request) *client.Response {
|
func (ps *ProboCollectorServer) readAllQuiz(w http.ResponseWriter, r *http.Request) ([]*models.Quiz, error) {
|
||||||
tests, err := ps.store.ReadAllQuizzes()
|
quizzes, err := ps.store.ReadAllQuizzes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &client.Response{Status: "error", Content: err.Error()}
|
return nil, err
|
||||||
}
|
}
|
||||||
return &client.Response{Status: "success", Content: tests}
|
return quizzes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ps *QuizHubCollectorServer) updateQuiz(w http.ResponseWriter, r *http.Request, id string) (*models.Quiz, error) {
|
func (ps *ProboCollectorServer) updateQuiz(w http.ResponseWriter, r *http.Request, id string) (*models.Quiz, error) {
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
updateQuizReq := new(client.UpdateQuizRequest)
|
updateQuizReq := new(client.CreateUpdateQuizRequest)
|
||||||
|
|
||||||
err = json.Unmarshal(body, &updateQuizReq)
|
err = json.Unmarshal(body, &updateQuizReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedQuiz, err := ps.store.UpdateQuiz(updateQuizReq)
|
updatedQuiz, err := ps.store.UpdateQuiz(updateQuizReq, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -97,13 +128,13 @@ func (ps *QuizHubCollectorServer) updateQuiz(w http.ResponseWriter, r *http.Requ
|
||||||
return updatedQuiz, nil
|
return updatedQuiz, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ps *QuizHubCollectorServer) createQuiz(w http.ResponseWriter, r *http.Request) (*models.Quiz, error) {
|
func (ps *ProboCollectorServer) 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 {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
createQuizReq := new(client.CreateQuizRequest)
|
createQuizReq := new(client.CreateUpdateQuizRequest)
|
||||||
|
|
||||||
err = json.Unmarshal(body, &createQuizReq)
|
err = json.Unmarshal(body, &createQuizReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
@ -18,8 +19,8 @@ type integrationTestSuite struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *integrationTestSuite) TestQuizCreateAndReadAll() {
|
func (t *integrationTestSuite) TestQuizCreateAndReadAll() {
|
||||||
server := NewQuizHubCollectorServer(
|
server := NewProboCollectorServer(
|
||||||
memory.NewMemoryQuizHubCollectorStore(
|
memory.NewMemoryProboCollectorStore(
|
||||||
sha256.NewDefault256Hasher(sha256.DefaultSHA256HashingFn),
|
sha256.NewDefault256Hasher(sha256.DefaultSHA256HashingFn),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -60,9 +61,70 @@ func (t *integrationTestSuite) TestQuizCreateAndReadAll() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *integrationTestSuite) TestQuizCreateAndUpdate() {
|
||||||
|
server := NewProboCollectorServer(
|
||||||
|
memory.NewMemoryProboCollectorStore(
|
||||||
|
sha256.NewDefault256Hasher(sha256.DefaultSHA256HashingFn),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
// POST a new question using a JSON payload
|
||||||
|
|
||||||
|
payload := `
|
||||||
|
{
|
||||||
|
"question": {"text": "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"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`
|
||||||
|
createQuizResponse, err := t.createQuiz(server, payload)
|
||||||
|
t.True(err == nil, "Response should be decoded properly")
|
||||||
|
|
||||||
|
if !t.Failed() {
|
||||||
|
payload = `
|
||||||
|
{
|
||||||
|
"question": {"text": "Updated Question 1"},
|
||||||
|
"answers": [
|
||||||
|
{"text": "Text of the answer 1"},
|
||||||
|
{"text": "Text of the answer 2"},
|
||||||
|
{"text": "Text of the answer 3", "correct": true},
|
||||||
|
{"text": "Text of the answer 4"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
`
|
||||||
|
updateQuizResponse, err := t.updateQuiz(server, payload, createQuizResponse.Content.ID)
|
||||||
|
t.True(err == nil, "Response should be decoded properly")
|
||||||
|
|
||||||
|
if !t.Failed() {
|
||||||
|
t.Equal("Updated Question 1", updateQuizResponse.Content.Question.Text)
|
||||||
|
t.Equal("Text of the answer 3", updateQuizResponse.Content.Correct.Text)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *integrationTestSuite) TestUpdateNotExistentQuiz() {
|
||||||
|
server := NewProboCollectorServer(
|
||||||
|
memory.NewMemoryProboCollectorStore(
|
||||||
|
sha256.NewDefault256Hasher(sha256.DefaultSHA256HashingFn),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
payload := ""
|
||||||
|
r, err := t.updateQuiz(server, payload, "1234")
|
||||||
|
|
||||||
|
t.True(err == nil, fmt.Sprintf("The operation should not return an error: %v", err))
|
||||||
|
t.Equal("error", r.Status)
|
||||||
|
}
|
||||||
|
|
||||||
func (t *integrationTestSuite) TestCatchDuplicateQuiz() {
|
func (t *integrationTestSuite) TestCatchDuplicateQuiz() {
|
||||||
server := NewQuizHubCollectorServer(
|
server := NewProboCollectorServer(
|
||||||
memory.NewMemoryQuizHubCollectorStore(
|
memory.NewMemoryProboCollectorStore(
|
||||||
sha256.NewDefault256Hasher(sha256.DefaultSHA256HashingFn),
|
sha256.NewDefault256Hasher(sha256.DefaultSHA256HashingFn),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -82,7 +144,7 @@ func (t *integrationTestSuite) TestCatchDuplicateQuiz() {
|
||||||
`
|
`
|
||||||
quiz1, err := t.createQuiz(server, payload)
|
quiz1, err := t.createQuiz(server, payload)
|
||||||
|
|
||||||
t.True(err == nil)
|
t.True(err == nil, fmt.Sprintf("Create quiz should not raise an error: %v", err))
|
||||||
|
|
||||||
quiz2, err := t.createQuiz(server, payload)
|
quiz2, err := t.createQuiz(server, payload)
|
||||||
|
|
||||||
|
@ -90,8 +152,8 @@ func (t *integrationTestSuite) TestCatchDuplicateQuiz() {
|
||||||
t.True(reflect.DeepEqual(quiz1, quiz2), "Quizzes shold be exactly the same")
|
t.True(reflect.DeepEqual(quiz1, quiz2), "Quizzes shold be exactly the same")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *integrationTestSuite) createQuiz(server *QuizHubCollectorServer, payload string) (*client.CreateQuizResponse, error) {
|
func (t *integrationTestSuite) createQuiz(server *ProboCollectorServer, payload string) (*client.CreateQuizResponse, error) {
|
||||||
request, _ := http.NewRequest(http.MethodPost, "/quizzes", strings.NewReader(payload))
|
request, _ := http.NewRequest(http.MethodPost, "/quizzes/create", strings.NewReader(payload))
|
||||||
response := httptest.NewRecorder()
|
response := httptest.NewRecorder()
|
||||||
|
|
||||||
server.ServeHTTP(response, request)
|
server.ServeHTTP(response, request)
|
||||||
|
@ -106,7 +168,23 @@ func (t *integrationTestSuite) createQuiz(server *QuizHubCollectorServer, payloa
|
||||||
return decodedResponse, err
|
return decodedResponse, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *integrationTestSuite) readAllQuiz(server *QuizHubCollectorServer) (*client.ReadAllQuizResponse, error) {
|
func (t *integrationTestSuite) updateQuiz(server *ProboCollectorServer, payload string, id string) (*client.UpdateQuizResponse, error) {
|
||||||
|
request, _ := http.NewRequest(http.MethodPut, "/quizzes/update/"+id, strings.NewReader(payload))
|
||||||
|
response := httptest.NewRecorder()
|
||||||
|
|
||||||
|
server.ServeHTTP(response, request)
|
||||||
|
|
||||||
|
decodedResponse := new(client.UpdateQuizResponse)
|
||||||
|
|
||||||
|
err := json.Unmarshal(response.Body.Bytes(), decodedResponse)
|
||||||
|
if err != nil {
|
||||||
|
return decodedResponse, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return decodedResponse, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *integrationTestSuite) readAllQuiz(server *ProboCollectorServer) (*client.ReadAllQuizResponse, error) {
|
||||||
request, _ := http.NewRequest(http.MethodGet, "/quizzes", nil)
|
request, _ := http.NewRequest(http.MethodGet, "/quizzes", nil)
|
||||||
response := httptest.NewRecorder()
|
response := httptest.NewRecorder()
|
||||||
|
|
||||||
|
|
|
@ -24,10 +24,16 @@ type StubTestHubCollectorStore struct {
|
||||||
tests []*models.Quiz
|
tests []*models.Quiz
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store *StubTestHubCollectorStore) CreateQuiz(test *client.CreateQuizRequest) (*models.Quiz, error) {
|
func (store *StubTestHubCollectorStore) CreateQuiz(test *client.CreateUpdateQuizRequest) (*models.Quiz, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (store *StubTestHubCollectorStore) UpdateQuiz(quiz *client.CreateUpdateQuizRequest, id string) (*models.Quiz, error) {
|
||||||
|
store.tests[0].Question.Text = quiz.Question.Text
|
||||||
|
|
||||||
|
return store.tests[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
func (store *StubTestHubCollectorStore) ReadAllQuizzes() ([]*models.Quiz, error) {
|
func (store *StubTestHubCollectorStore) ReadAllQuizzes() ([]*models.Quiz, error) {
|
||||||
return store.tests, nil
|
return store.tests, nil
|
||||||
}
|
}
|
||||||
|
@ -44,9 +50,9 @@ func (t *testSuite) BeforeAll() {
|
||||||
logger.SetLevel(logger.Disabled)
|
logger.SetLevel(logger.Disabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *testSuite) TestGETQuestions() {
|
func (t *testSuite) TestReadAllQuizzes() {
|
||||||
expectedResult := &client.ReadAllQuizResponse{
|
expectedResult := &client.ReadAllQuizResponse{
|
||||||
Status: "success",
|
BaseResponse: client.BaseResponse{Status: "success"},
|
||||||
Content: []*models.Quiz{
|
Content: []*models.Quiz{
|
||||||
{
|
{
|
||||||
Question: &models.Question{ID: "1", Text: "Question 1"},
|
Question: &models.Question{ID: "1", Text: "Question 1"},
|
||||||
|
@ -62,7 +68,7 @@ func (t *testSuite) TestGETQuestions() {
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
|
||||||
server := NewQuizHubCollectorServer(store)
|
server := NewProboCollectorServer(store)
|
||||||
|
|
||||||
request, _ := http.NewRequest(http.MethodGet, "/quizzes", nil)
|
request, _ := http.NewRequest(http.MethodGet, "/quizzes", nil)
|
||||||
response := httptest.NewRecorder()
|
response := httptest.NewRecorder()
|
||||||
|
@ -71,7 +77,7 @@ func (t *testSuite) TestGETQuestions() {
|
||||||
|
|
||||||
result := getResponse(response.Body)
|
result := getResponse(response.Body)
|
||||||
|
|
||||||
t.True(result.Status == expectedResult.Status)
|
t.Equal("application/json", response.Header().Get("Content-Type"))
|
||||||
t.True(testsAreEqual(result, expectedResult))
|
t.True(testsAreEqual(result, expectedResult))
|
||||||
|
|
||||||
t.Equal(http.StatusOK, response.Code)
|
t.Equal(http.StatusOK, response.Code)
|
||||||
|
@ -79,9 +85,9 @@ func (t *testSuite) TestGETQuestions() {
|
||||||
|
|
||||||
func (t *testSuite) TestUpdateQuiz() {
|
func (t *testSuite) TestUpdateQuiz() {
|
||||||
expectedResponse := &client.UpdateQuizResponse{
|
expectedResponse := &client.UpdateQuizResponse{
|
||||||
Status: "success",
|
BaseResponse: client.BaseResponse{Status: "success"},
|
||||||
Content: &models.Quiz{
|
Content: &models.Quiz{
|
||||||
Question: &models.Question{ID: "1", Text: "Question 1"},
|
Question: &models.Question{ID: "1", Text: "Updated Question 1"},
|
||||||
Answers: []*models.Answer{{}, {}, {}},
|
Answers: []*models.Answer{{}, {}, {}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -93,11 +99,11 @@ func (t *testSuite) TestUpdateQuiz() {
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
|
||||||
server := NewQuizHubCollectorServer(store)
|
server := NewProboCollectorServer(store)
|
||||||
|
|
||||||
payload := `
|
payload := `
|
||||||
{
|
{
|
||||||
"question": {"text": "Update Question 1"},
|
"question": {"text": "Updated Question 1"},
|
||||||
"answers": [
|
"answers": [
|
||||||
{"text": "Text of the answer 1", "correct": true},
|
{"text": "Text of the answer 1", "correct": true},
|
||||||
{"text": "Text of the answer 2"},
|
{"text": "Text of the answer 2"},
|
||||||
|
@ -106,7 +112,7 @@ func (t *testSuite) TestUpdateQuiz() {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
request, _ := http.NewRequest(http.MethodPut, "/quizzes/1/update", strings.NewReader(payload))
|
request, _ := http.NewRequest(http.MethodPut, "/quizzes/update/1", strings.NewReader(payload))
|
||||||
response := httptest.NewRecorder()
|
response := httptest.NewRecorder()
|
||||||
|
|
||||||
server.ServeHTTP(response, request)
|
server.ServeHTTP(response, request)
|
||||||
|
@ -118,14 +124,15 @@ func (t *testSuite) TestUpdateQuiz() {
|
||||||
if !t.Failed() {
|
if !t.Failed() {
|
||||||
t.True(result.Status == expectedResponse.Status)
|
t.True(result.Status == expectedResponse.Status)
|
||||||
t.True(reflect.DeepEqual(result, expectedResponse))
|
t.True(reflect.DeepEqual(result, expectedResponse))
|
||||||
t.Equal(http.StatusOK, response.Code)
|
t.Equal("application/json", response.Header().Get("Content-Type"))
|
||||||
|
t.Equal(http.StatusAccepted, 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 {
|
||||||
panic(fmt.Errorf("Unable to parse response from server %q into slice of Test, '%v'", body, err))
|
panic(fmt.Errorf("Unable to parse response from server %q: %v", body, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package memory
|
package memory
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"git.andreafazzi.eu/andrea/probo/client"
|
"git.andreafazzi.eu/andrea/probo/client"
|
||||||
|
@ -9,32 +10,46 @@ import (
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MemoryQuizHubCollectorStore struct {
|
type MemoryProboCollectorStore struct {
|
||||||
questions map[string]*models.Question
|
|
||||||
answers map[string]*models.Answer
|
|
||||||
quizzes map[string]*models.Quiz
|
quizzes map[string]*models.Quiz
|
||||||
|
|
||||||
questionAnswer map[string][]string
|
questionsHashes map[string]*models.Question
|
||||||
testQuestion map[string]uint
|
answersHashes map[string]*models.Answer
|
||||||
|
quizzesHashes map[string]*models.Quiz
|
||||||
|
|
||||||
|
hasher hasher.Hasher
|
||||||
|
|
||||||
// A mutex is used to synchronize read/write access to the map
|
// A mutex is used to synchronize read/write access to the map
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
|
|
||||||
hasher hasher.Hasher
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMemoryQuizHubCollectorStore(hasher hasher.Hasher) *MemoryQuizHubCollectorStore {
|
func NewMemoryProboCollectorStore(hasher hasher.Hasher) *MemoryProboCollectorStore {
|
||||||
s := new(MemoryQuizHubCollectorStore)
|
s := new(MemoryProboCollectorStore)
|
||||||
|
|
||||||
s.hasher = hasher
|
s.hasher = hasher
|
||||||
s.questions = make(map[string]*models.Question)
|
|
||||||
s.answers = make(map[string]*models.Answer)
|
s.questionsHashes = make(map[string]*models.Question)
|
||||||
|
s.answersHashes = make(map[string]*models.Answer)
|
||||||
|
s.quizzesHashes = make(map[string]*models.Quiz)
|
||||||
|
|
||||||
s.quizzes = make(map[string]*models.Quiz)
|
s.quizzes = make(map[string]*models.Quiz)
|
||||||
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MemoryQuizHubCollectorStore) readQuiz(id string) *models.Quiz {
|
func (s *MemoryProboCollectorStore) getQuizFromHash(hash string) *models.Quiz {
|
||||||
|
s.lock.RLock()
|
||||||
|
defer s.lock.RUnlock()
|
||||||
|
|
||||||
|
quiz, ok := s.quizzesHashes[hash]
|
||||||
|
if ok {
|
||||||
|
return quiz
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MemoryProboCollectorStore) getQuizFromID(id string) *models.Quiz {
|
||||||
s.lock.RLock()
|
s.lock.RLock()
|
||||||
defer s.lock.RUnlock()
|
defer s.lock.RUnlock()
|
||||||
|
|
||||||
|
@ -46,11 +61,11 @@ func (s *MemoryQuizHubCollectorStore) readQuiz(id string) *models.Quiz {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MemoryQuizHubCollectorStore) readQuestion(id string) *models.Question {
|
func (s *MemoryProboCollectorStore) getQuestionFromHash(hash string) *models.Question {
|
||||||
s.lock.RLock()
|
s.lock.RLock()
|
||||||
defer s.lock.RUnlock()
|
defer s.lock.RUnlock()
|
||||||
|
|
||||||
question, ok := s.questions[id]
|
question, ok := s.questionsHashes[hash]
|
||||||
if ok {
|
if ok {
|
||||||
return question
|
return question
|
||||||
}
|
}
|
||||||
|
@ -58,11 +73,11 @@ func (s *MemoryQuizHubCollectorStore) readQuestion(id string) *models.Question {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MemoryQuizHubCollectorStore) readAnswer(id string) *models.Answer {
|
func (s *MemoryProboCollectorStore) getAnswerFromHash(hash string) *models.Answer {
|
||||||
s.lock.RLock()
|
s.lock.RLock()
|
||||||
defer s.lock.RUnlock()
|
defer s.lock.RUnlock()
|
||||||
|
|
||||||
answer, ok := s.answers[id]
|
answer, ok := s.answersHashes[hash]
|
||||||
if ok {
|
if ok {
|
||||||
return answer
|
return answer
|
||||||
}
|
}
|
||||||
|
@ -70,122 +85,96 @@ func (s *MemoryQuizHubCollectorStore) readAnswer(id string) *models.Answer {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MemoryQuizHubCollectorStore) createQuiz(id string, hash string, quiz *models.Quiz) *models.Quiz {
|
func (s *MemoryProboCollectorStore) createQuizFromHash(id string, hash string, quiz *models.Quiz) *models.Quiz {
|
||||||
s.lock.Lock()
|
s.lock.Lock()
|
||||||
defer s.lock.Unlock()
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
quiz.ID = id
|
quiz.ID = id
|
||||||
s.quizzes[hash] = quiz
|
|
||||||
|
s.quizzesHashes[hash] = quiz
|
||||||
|
s.quizzes[id] = quiz
|
||||||
|
|
||||||
return quiz
|
return quiz
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MemoryQuizHubCollectorStore) createQuestion(id string, question *models.Question) *models.Question {
|
func (s *MemoryProboCollectorStore) createQuestionFromHash(hash string, question *models.Question) *models.Question {
|
||||||
s.lock.Lock()
|
s.lock.Lock()
|
||||||
defer s.lock.Unlock()
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
s.questions[id] = question
|
s.questionsHashes[hash] = question
|
||||||
|
|
||||||
return question
|
return question
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MemoryQuizHubCollectorStore) createAnswer(id string, answer *models.Answer) *models.Answer {
|
func (s *MemoryProboCollectorStore) createAnswerFromHash(hash string, answer *models.Answer) *models.Answer {
|
||||||
s.lock.Lock()
|
s.lock.Lock()
|
||||||
defer s.lock.Unlock()
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
s.answers[id] = answer
|
s.answersHashes[hash] = answer
|
||||||
|
|
||||||
return answer
|
return answer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MemoryQuizHubCollectorStore) ReadAllQuizzes() ([]*models.Quiz, error) {
|
func (s *MemoryProboCollectorStore) ReadAllQuizzes() ([]*models.Quiz, error) {
|
||||||
result := make([]*models.Quiz, 0)
|
result := make([]*models.Quiz, 0)
|
||||||
for id, _ := range s.quizzes {
|
for hash := range s.quizzesHashes {
|
||||||
result = append(result, s.readQuiz(id))
|
result = append(result, s.getQuizFromHash(hash))
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MemoryQuizHubCollectorStore) CreateQuiz(r *client.CreateQuizRequest) (*models.Quiz, error) {
|
func (s *MemoryProboCollectorStore) createOrUpdateQuiz(r *client.CreateUpdateQuizRequest, id string) (*models.Quiz, error) {
|
||||||
hashes := s.hasher.QuizHashes(r.Quiz)
|
hashes := s.hasher.QuizHashes(r.Quiz)
|
||||||
|
|
||||||
quizID := uuid.New().String()
|
|
||||||
quizHash := hashes[len(hashes)-1]
|
quizHash := hashes[len(hashes)-1]
|
||||||
|
|
||||||
quiz := s.readQuiz(quizHash)
|
quiz := s.getQuizFromHash(quizHash)
|
||||||
if quiz != nil { // Quiz is already present in the store
|
if quiz != nil { // Quiz is already present in the store
|
||||||
return quiz, nil
|
return quiz, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if id != "" {
|
||||||
|
quiz = s.getQuizFromID(id)
|
||||||
|
if quiz == nil { // Quiz is already present in the store
|
||||||
|
return nil, fmt.Errorf("Quiz ID %v doesn't exist in the store!", id)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
id = uuid.New().String()
|
||||||
quiz = new(models.Quiz)
|
quiz = new(models.Quiz)
|
||||||
|
}
|
||||||
|
|
||||||
questionHash := hashes[0]
|
questionHash := hashes[0]
|
||||||
q := s.readQuestion(questionHash)
|
q := s.getQuestionFromHash(questionHash)
|
||||||
if q == nil { // if the question is not in the store then we should add it
|
if q == nil { // if the question is not in the store then we should add it
|
||||||
q = s.createQuestion(questionHash, &models.Question{
|
q = s.createQuestionFromHash(questionHash, &models.Question{
|
||||||
ID: uuid.New().String(),
|
ID: uuid.New().String(),
|
||||||
Text: r.Question.Text,
|
Text: r.Quiz.Question.Text,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populate Question field
|
// Populate Question field
|
||||||
quiz.Question = q
|
quiz.Question = q
|
||||||
for i, answer := range r.Answers {
|
for i, answer := range r.Quiz.Answers {
|
||||||
answerHash := hashes[i+1]
|
answerHash := hashes[i+1]
|
||||||
a := s.readAnswer(answerHash)
|
a := s.getAnswerFromHash(answerHash)
|
||||||
if a == nil { // if the answer is not in the store add it
|
if a == nil { // if the answer is not in the store add it
|
||||||
a = s.createAnswer(answerHash, &models.Answer{
|
a = s.createAnswerFromHash(answerHash, &models.Answer{
|
||||||
ID: uuid.New().String(),
|
ID: uuid.New().String(),
|
||||||
Text: answer.Text,
|
Text: answer.Text,
|
||||||
})
|
})
|
||||||
|
}
|
||||||
if answer.Correct {
|
if answer.Correct {
|
||||||
quiz.Correct = a // s.readAnswer(answerID)
|
quiz.Correct = a // s.readAnswer(answerID)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
quiz.Answers = append(quiz.Answers, a)
|
quiz.Answers = append(quiz.Answers, a)
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.createQuiz(quizID, quizHash, quiz), nil
|
return s.createQuizFromHash(id, quizHash, quiz), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MemoryQuizHubCollectorStore) UpdateQuiz(r *client.UpdateQuizRequest) (*models.Quiz, error) {
|
func (s *MemoryProboCollectorStore) CreateQuiz(r *client.CreateUpdateQuizRequest) (*models.Quiz, error) {
|
||||||
hashes := s.hasher.QuizHashes(r.Quiz)
|
return s.createOrUpdateQuiz(r, "")
|
||||||
|
|
||||||
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)
|
func (s *MemoryProboCollectorStore) UpdateQuiz(r *client.CreateUpdateQuizRequest, id string) (*models.Quiz, error) {
|
||||||
|
return s.createOrUpdateQuiz(r, id)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,9 @@ import (
|
||||||
"git.andreafazzi.eu/andrea/probo/models"
|
"git.andreafazzi.eu/andrea/probo/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
type QuizHubCollectorStore interface {
|
type ProboCollectorStore interface {
|
||||||
ReadAllQuizzes() ([]*models.Quiz, error)
|
ReadAllQuizzes() ([]*models.Quiz, error)
|
||||||
CreateQuiz(r *client.CreateQuizRequest) (*models.Quiz, error)
|
|
||||||
UpdateQuiz(r *client.UpdateQuizRequest) (*models.Quiz, error)
|
CreateQuiz(r *client.CreateUpdateQuizRequest) (*models.Quiz, error)
|
||||||
|
UpdateQuiz(r *client.CreateUpdateQuizRequest, id string) (*models.Quiz, error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
POST http://localhost:3000/quizzes
|
POST http://localhost:3000/quizzes/create
|
||||||
{
|
{
|
||||||
"question": {"text": "Text of Question 1"},
|
"question": {"text": "Text of Question 1"},
|
||||||
"answers": [
|
"answers": [
|
||||||
|
|
Loading…
Reference in a new issue