Refactoring

This commit is contained in:
Andrea Fazzi 2022-06-29 16:08:55 +02:00
parent ee45c1ba9c
commit cb56e4c56f
9 changed files with 263 additions and 158 deletions

View file

@ -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
} }

View file

@ -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),
), ),
) )

View file

@ -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>`,
}); });
}); });

View file

@ -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 {

View file

@ -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()

View file

@ -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

View file

@ -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
} }

View file

@ -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)
} }

View file

@ -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": [