2022-06-24 18:56:06 +02:00
|
|
|
package memory
|
|
|
|
|
|
|
|
import (
|
2022-06-29 16:08:55 +02:00
|
|
|
"fmt"
|
2023-10-02 12:55:03 +02:00
|
|
|
"strings"
|
2022-06-24 18:56:06 +02:00
|
|
|
"sync"
|
|
|
|
|
|
|
|
"git.andreafazzi.eu/andrea/probo/client"
|
|
|
|
"git.andreafazzi.eu/andrea/probo/hasher"
|
|
|
|
"git.andreafazzi.eu/andrea/probo/models"
|
2022-06-27 15:11:37 +02:00
|
|
|
"github.com/google/uuid"
|
2022-06-24 18:56:06 +02:00
|
|
|
)
|
|
|
|
|
2022-06-29 16:08:55 +02:00
|
|
|
type MemoryProboCollectorStore struct {
|
|
|
|
quizzes map[string]*models.Quiz
|
2022-06-24 18:56:06 +02:00
|
|
|
|
2022-06-29 16:08:55 +02:00
|
|
|
questionsHashes map[string]*models.Question
|
|
|
|
answersHashes map[string]*models.Answer
|
|
|
|
quizzesHashes map[string]*models.Quiz
|
|
|
|
|
|
|
|
hasher hasher.Hasher
|
2022-06-24 18:56:06 +02:00
|
|
|
|
|
|
|
// A mutex is used to synchronize read/write access to the map
|
|
|
|
lock sync.RWMutex
|
|
|
|
}
|
|
|
|
|
2022-06-29 16:08:55 +02:00
|
|
|
func NewMemoryProboCollectorStore(hasher hasher.Hasher) *MemoryProboCollectorStore {
|
|
|
|
s := new(MemoryProboCollectorStore)
|
2022-06-24 18:56:06 +02:00
|
|
|
|
|
|
|
s.hasher = hasher
|
2022-06-29 16:08:55 +02:00
|
|
|
|
|
|
|
s.questionsHashes = make(map[string]*models.Question)
|
|
|
|
s.answersHashes = make(map[string]*models.Answer)
|
|
|
|
s.quizzesHashes = make(map[string]*models.Quiz)
|
|
|
|
|
2022-06-24 18:56:06 +02:00
|
|
|
s.quizzes = make(map[string]*models.Quiz)
|
|
|
|
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
2022-06-29 16:08:55 +02:00
|
|
|
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 {
|
2022-06-24 18:56:06 +02:00
|
|
|
s.lock.RLock()
|
|
|
|
defer s.lock.RUnlock()
|
|
|
|
|
|
|
|
quiz, ok := s.quizzes[id]
|
|
|
|
if ok {
|
|
|
|
return quiz
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-06-29 16:08:55 +02:00
|
|
|
func (s *MemoryProboCollectorStore) getQuestionFromHash(hash string) *models.Question {
|
2022-06-24 18:56:06 +02:00
|
|
|
s.lock.RLock()
|
|
|
|
defer s.lock.RUnlock()
|
|
|
|
|
2022-06-29 16:08:55 +02:00
|
|
|
question, ok := s.questionsHashes[hash]
|
2022-06-24 18:56:06 +02:00
|
|
|
if ok {
|
|
|
|
return question
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-06-29 16:08:55 +02:00
|
|
|
func (s *MemoryProboCollectorStore) getAnswerFromHash(hash string) *models.Answer {
|
2022-06-24 18:56:06 +02:00
|
|
|
s.lock.RLock()
|
|
|
|
defer s.lock.RUnlock()
|
|
|
|
|
2022-06-29 16:08:55 +02:00
|
|
|
answer, ok := s.answersHashes[hash]
|
2022-06-24 18:56:06 +02:00
|
|
|
if ok {
|
|
|
|
return answer
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-06-29 16:08:55 +02:00
|
|
|
func (s *MemoryProboCollectorStore) createQuizFromHash(id string, hash string, quiz *models.Quiz) *models.Quiz {
|
2022-06-24 18:56:06 +02:00
|
|
|
s.lock.Lock()
|
|
|
|
defer s.lock.Unlock()
|
|
|
|
|
|
|
|
quiz.ID = id
|
2023-06-28 17:21:59 +02:00
|
|
|
quiz.Hash = hash
|
2022-06-29 16:08:55 +02:00
|
|
|
|
|
|
|
s.quizzesHashes[hash] = quiz
|
|
|
|
s.quizzes[id] = quiz
|
2022-06-24 18:56:06 +02:00
|
|
|
|
|
|
|
return quiz
|
|
|
|
}
|
|
|
|
|
2022-06-29 16:08:55 +02:00
|
|
|
func (s *MemoryProboCollectorStore) createQuestionFromHash(hash string, question *models.Question) *models.Question {
|
2022-06-24 18:56:06 +02:00
|
|
|
s.lock.Lock()
|
|
|
|
defer s.lock.Unlock()
|
|
|
|
|
2022-06-29 16:08:55 +02:00
|
|
|
s.questionsHashes[hash] = question
|
2022-06-24 18:56:06 +02:00
|
|
|
|
|
|
|
return question
|
|
|
|
}
|
|
|
|
|
2022-06-29 16:08:55 +02:00
|
|
|
func (s *MemoryProboCollectorStore) createAnswerFromHash(hash string, answer *models.Answer) *models.Answer {
|
2022-06-24 18:56:06 +02:00
|
|
|
s.lock.Lock()
|
|
|
|
defer s.lock.Unlock()
|
|
|
|
|
2022-06-29 16:08:55 +02:00
|
|
|
s.answersHashes[hash] = answer
|
2022-06-24 18:56:06 +02:00
|
|
|
|
|
|
|
return answer
|
|
|
|
}
|
|
|
|
|
2023-09-01 11:48:09 +02:00
|
|
|
func (s *MemoryProboCollectorStore) deleteQuiz(id string) (*models.Quiz, error) {
|
|
|
|
s.lock.Lock()
|
|
|
|
defer s.lock.Unlock()
|
|
|
|
|
|
|
|
quiz := s.quizzes[id]
|
|
|
|
if quiz == nil {
|
|
|
|
return nil, fmt.Errorf("Trying to delete a quiz that doesn't exist in memory (ID: %v)", id)
|
|
|
|
}
|
|
|
|
|
|
|
|
s.quizzes[id] = nil
|
|
|
|
s.quizzesHashes[quiz.Hash] = nil
|
|
|
|
|
|
|
|
return quiz, nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-06-29 16:08:55 +02:00
|
|
|
func (s *MemoryProboCollectorStore) ReadAllQuizzes() ([]*models.Quiz, error) {
|
2022-06-24 18:56:06 +02:00
|
|
|
result := make([]*models.Quiz, 0)
|
2022-09-06 14:41:32 +02:00
|
|
|
for id := range s.quizzes {
|
|
|
|
result = append(result, s.getQuizFromID(id))
|
2022-06-24 18:56:06 +02:00
|
|
|
}
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
2023-07-10 13:23:46 +02:00
|
|
|
func (s *MemoryProboCollectorStore) ReadQuizByHash(hash string) (*models.Quiz, error) {
|
|
|
|
quiz := s.getQuizFromHash(hash)
|
|
|
|
if quiz == nil {
|
|
|
|
return nil, fmt.Errorf("Quiz with hash %s was not found in the store.", hash)
|
|
|
|
}
|
|
|
|
return quiz, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *MemoryProboCollectorStore) CalculateQuizHash(quiz *client.Quiz) string {
|
|
|
|
hashes := s.hasher.QuizHashes(quiz)
|
|
|
|
return hashes[len(hashes)-1]
|
|
|
|
}
|
|
|
|
|
2023-10-02 12:55:03 +02:00
|
|
|
func (s *MemoryProboCollectorStore) parseTextForTags(text string, tags *[]*models.Tag) string {
|
|
|
|
|
|
|
|
// Trim the following chars
|
|
|
|
trimChars := "*:.,/\\@()[]{}<>"
|
|
|
|
|
|
|
|
// Split the text into words
|
|
|
|
words := strings.Fields(text)
|
|
|
|
|
|
|
|
for _, word := range words {
|
|
|
|
// If the word starts with '#', it is considered as a tag
|
|
|
|
if strings.HasPrefix(word, "#") {
|
|
|
|
// Check if the tag already exists in the tags slice
|
|
|
|
exists := false
|
|
|
|
for _, tag := range *tags {
|
|
|
|
if tag.Name == word {
|
|
|
|
exists = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the tag does not exist in the tags slice, add it
|
|
|
|
if !exists {
|
|
|
|
*tags = append(*tags, &models.Tag{Name: strings.TrimRight(word, trimChars)})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return text
|
|
|
|
}
|
|
|
|
|
2023-07-12 17:21:46 +02:00
|
|
|
func (s *MemoryProboCollectorStore) createOrUpdateQuiz(r *client.CreateUpdateQuizRequest, id string) (*models.Quiz, bool, error) {
|
2022-06-28 13:49:35 +02:00
|
|
|
hashes := s.hasher.QuizHashes(r.Quiz)
|
|
|
|
quizHash := hashes[len(hashes)-1]
|
|
|
|
|
2022-06-29 16:08:55 +02:00
|
|
|
quiz := s.getQuizFromHash(quizHash)
|
2022-06-28 13:49:35 +02:00
|
|
|
if quiz != nil { // Quiz is already present in the store
|
2023-07-12 17:21:46 +02:00
|
|
|
return quiz, false, nil
|
2022-06-28 13:49:35 +02:00
|
|
|
}
|
|
|
|
|
2023-09-22 10:29:10 +02:00
|
|
|
if id != "" { // we're updating a quiz
|
2022-06-29 16:08:55 +02:00
|
|
|
quiz = s.getQuizFromID(id)
|
2023-07-12 15:57:10 +02:00
|
|
|
if quiz == nil { // Quiz is not present in the store
|
2023-07-12 17:21:46 +02:00
|
|
|
return nil, false, fmt.Errorf("Quiz ID %v doesn't exist in the store!", id)
|
2022-06-29 16:08:55 +02:00
|
|
|
}
|
|
|
|
} else {
|
2023-09-22 10:29:10 +02:00
|
|
|
if r.Meta != nil {
|
|
|
|
if r.Meta.ID != "" {
|
|
|
|
id = r.Meta.ID
|
2023-10-02 12:55:03 +02:00
|
|
|
} else {
|
|
|
|
id = uuid.New().String()
|
2023-09-22 10:29:10 +02:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
id = uuid.New().String()
|
|
|
|
}
|
2022-06-29 16:08:55 +02:00
|
|
|
quiz = new(models.Quiz)
|
|
|
|
}
|
2022-06-28 13:49:35 +02:00
|
|
|
|
2023-10-02 12:55:03 +02:00
|
|
|
if quiz.Tags == nil {
|
|
|
|
quiz.Tags = make([]*models.Tag, 0)
|
|
|
|
}
|
|
|
|
|
2022-06-28 13:49:35 +02:00
|
|
|
questionHash := hashes[0]
|
2022-06-29 16:08:55 +02:00
|
|
|
q := s.getQuestionFromHash(questionHash)
|
2022-06-28 13:49:35 +02:00
|
|
|
if q == nil { // if the question is not in the store then we should add it
|
2022-06-29 16:08:55 +02:00
|
|
|
q = s.createQuestionFromHash(questionHash, &models.Question{
|
2022-06-28 13:49:35 +02:00
|
|
|
ID: uuid.New().String(),
|
2023-10-02 12:55:03 +02:00
|
|
|
Text: s.parseTextForTags(r.Quiz.Question.Text, &quiz.Tags),
|
2022-06-28 13:49:35 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Populate Question field
|
|
|
|
quiz.Question = q
|
2023-07-13 06:46:36 +02:00
|
|
|
|
|
|
|
// Reset answer slice
|
|
|
|
quiz.Answers = make([]*models.Answer, 0)
|
|
|
|
|
2022-06-29 16:08:55 +02:00
|
|
|
for i, answer := range r.Quiz.Answers {
|
2022-06-28 13:49:35 +02:00
|
|
|
answerHash := hashes[i+1]
|
2022-06-29 16:08:55 +02:00
|
|
|
a := s.getAnswerFromHash(answerHash)
|
2022-06-28 13:49:35 +02:00
|
|
|
if a == nil { // if the answer is not in the store add it
|
2022-06-29 16:08:55 +02:00
|
|
|
a = s.createAnswerFromHash(answerHash, &models.Answer{
|
2022-06-28 13:49:35 +02:00
|
|
|
ID: uuid.New().String(),
|
2023-10-02 12:55:03 +02:00
|
|
|
Text: s.parseTextForTags(answer.Text, &quiz.Tags),
|
2022-06-28 13:49:35 +02:00
|
|
|
})
|
2022-06-29 16:08:55 +02:00
|
|
|
}
|
|
|
|
if answer.Correct {
|
2022-09-06 14:41:32 +02:00
|
|
|
quiz.Correct = a
|
2022-06-28 13:49:35 +02:00
|
|
|
}
|
|
|
|
quiz.Answers = append(quiz.Answers, a)
|
|
|
|
}
|
|
|
|
|
2023-07-12 17:21:46 +02:00
|
|
|
return s.createQuizFromHash(id, quizHash, quiz), true, nil
|
2022-06-28 13:49:35 +02:00
|
|
|
}
|
|
|
|
|
2022-06-29 16:08:55 +02:00
|
|
|
func (s *MemoryProboCollectorStore) CreateQuiz(r *client.CreateUpdateQuizRequest) (*models.Quiz, error) {
|
2023-07-12 17:21:46 +02:00
|
|
|
q, _, err := s.createOrUpdateQuiz(r, "")
|
|
|
|
return q, err
|
2022-06-29 16:08:55 +02:00
|
|
|
}
|
2022-06-24 18:56:06 +02:00
|
|
|
|
2023-07-12 17:21:46 +02:00
|
|
|
func (s *MemoryProboCollectorStore) UpdateQuiz(r *client.CreateUpdateQuizRequest, id string) (*models.Quiz, bool, error) {
|
2022-06-29 16:08:55 +02:00
|
|
|
return s.createOrUpdateQuiz(r, id)
|
2022-06-24 18:56:06 +02:00
|
|
|
}
|
2023-09-01 11:48:09 +02:00
|
|
|
|
|
|
|
func (s *MemoryProboCollectorStore) DeleteQuiz(id string) (*models.Quiz, error) {
|
|
|
|
return s.deleteQuiz(id)
|
|
|
|
}
|