package memory import ( "errors" "fmt" "strings" "sync" "git.andreafazzi.eu/andrea/probo/client" "git.andreafazzi.eu/andrea/probo/hasher" "git.andreafazzi.eu/andrea/probo/models" "github.com/google/uuid" ) type MemoryProboCollectorStore struct { quizzes map[string]*models.Quiz collections map[string]*models.Collection questionsHashes map[string]*models.Question 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 lock sync.RWMutex } func NewMemoryProboCollectorStore(hasher hasher.Hasher) *MemoryProboCollectorStore { s := new(MemoryProboCollectorStore) s.hasher = hasher 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.collections = make(map[string]*models.Collection) return s } 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() defer s.lock.RUnlock() quiz, ok := s.quizzes[id] if ok { return quiz } return nil } func (s *MemoryProboCollectorStore) getCollectionFromID(id string) *models.Collection { s.lock.RLock() defer s.lock.RUnlock() collection, ok := s.collections[id] if ok { return collection } return nil } func (s *MemoryProboCollectorStore) getQuestionFromHash(hash string) *models.Question { s.lock.RLock() defer s.lock.RUnlock() question, ok := s.questionsHashes[hash] if ok { return question } return nil } func (s *MemoryProboCollectorStore) getAnswerFromHash(hash string) *models.Answer { s.lock.RLock() defer s.lock.RUnlock() answer, ok := s.answersHashes[hash] if ok { return answer } return nil } func (s *MemoryProboCollectorStore) createQuizFromHash(id string, hash string, quiz *models.Quiz) *models.Quiz { s.lock.Lock() defer s.lock.Unlock() quiz.ID = id quiz.Hash = hash s.quizzesHashes[hash] = quiz s.quizzes[id] = quiz return quiz } func (s *MemoryProboCollectorStore) createQuestionFromHash(hash string, question *models.Question) *models.Question { s.lock.Lock() defer s.lock.Unlock() s.questionsHashes[hash] = question return question } func (s *MemoryProboCollectorStore) createAnswerFromHash(hash string, answer *models.Answer) *models.Answer { s.lock.Lock() defer s.lock.Unlock() s.answersHashes[hash] = answer return answer } 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 } func (s *MemoryProboCollectorStore) ReadAllQuizzes() ([]*models.Quiz, error) { result := make([]*models.Quiz, 0) for id := range s.quizzes { result = append(result, s.getQuizFromID(id)) } return result, nil } 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] } 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 } func (s *MemoryProboCollectorStore) createOrUpdateQuiz(r *client.CreateUpdateQuizRequest, id string) (*models.Quiz, bool, error) { if r.Quiz == nil { return nil, false, errors.New("A request was made passing a nil quiz object") } hashes := s.hasher.QuizHashes(r.Quiz) quizHash := hashes[len(hashes)-1] quiz := s.getQuizFromHash(quizHash) if quiz != nil { // Quiz is already present in the store return quiz, false, nil } if id != "" { // we're updating a quiz quiz = s.getQuizFromID(id) if quiz == nil { // Quiz is not present in the store return nil, false, fmt.Errorf("Quiz ID %v doesn't exist in the store!", id) } } else { if r.Meta != nil { if r.Meta.ID != "" { id = r.Meta.ID } else { id = uuid.New().String() } } else { id = uuid.New().String() } quiz = new(models.Quiz) } if quiz.Tags == nil { quiz.Tags = make([]*models.Tag, 0) } questionHash := hashes[0] q := s.getQuestionFromHash(questionHash) if q == nil { // if the question is not in the store then we should add it q = s.createQuestionFromHash(questionHash, &models.Question{ ID: uuid.New().String(), Text: s.parseTextForTags(r.Quiz.Question.Text, &quiz.Tags), }) } // Populate Question field quiz.Question = q // Reset answer slice quiz.Answers = make([]*models.Answer, 0) for i, answer := range r.Quiz.Answers { answerHash := hashes[i+1] a := s.getAnswerFromHash(answerHash) if a == nil { // if the answer is not in the store add it a = s.createAnswerFromHash(answerHash, &models.Answer{ ID: uuid.New().String(), Text: s.parseTextForTags(answer.Text, &quiz.Tags), }) } if answer.Correct { quiz.Correct = a } quiz.Answers = append(quiz.Answers, a) } return s.createQuizFromHash(id, quizHash, quiz), true, nil } func (s *MemoryProboCollectorStore) CreateQuiz(r *client.CreateUpdateQuizRequest) (*models.Quiz, error) { q, _, err := s.createOrUpdateQuiz(r, "") return q, err } func (s *MemoryProboCollectorStore) UpdateQuiz(r *client.CreateUpdateQuizRequest, id string) (*models.Quiz, bool, error) { return s.createOrUpdateQuiz(r, id) } func (s *MemoryProboCollectorStore) DeleteQuiz(id string) (*models.Quiz, error) { return s.deleteQuiz(id) } func (s *MemoryProboCollectorStore) CreateCollection(r *client.CreateUpdateCollectionRequest) (*models.Collection, error) { q, _, err := s.createOrUpdateCollection(r, "") return q, err } func (s *MemoryProboCollectorStore) UpdateCollection(r *client.CreateUpdateCollectionRequest, id string) (*models.Collection, bool, error) { return s.createOrUpdateCollection(r, id) } func (s *MemoryProboCollectorStore) ReadCollectionByID(id string) (*models.Collection, error) { collection := s.getCollectionFromID(id) if collection == nil { return nil, fmt.Errorf("Collection ID %v not found in the store", collection.ID) } return collection, nil } func (s *MemoryProboCollectorStore) createOrUpdateCollection(r *client.CreateUpdateCollectionRequest, id string) (*models.Collection, bool, error) { var collection *models.Collection if r.Collection == nil { return nil, false, errors.New("A request was made passing a nil collection object") } if id != "" { // we're updating a collection collection = s.getCollectionFromID(id) if collection == nil { // Quiz is not present in the store return nil, false, fmt.Errorf("Collection ID %v doesn't exist in the store!", id) } } else { id = uuid.New().String() collection = new(models.Collection) } collection.Name = r.Collection.Name collection.Query = r.Collection.Query collection.IDs = s.query(collection.Query) return s.createCollectionFromID(id, collection), true, nil } func (s *MemoryProboCollectorStore) query(query string) []string { s.lock.Lock() defer s.lock.Unlock() result := make([]string, 0) for id, quiz := range s.quizzes { for _, tag := range quiz.Tags { if query == tag.Name { result = append(result, id) break } } } return result } func (s *MemoryProboCollectorStore) createCollectionFromID(id string, collection *models.Collection) *models.Collection { s.lock.Lock() defer s.lock.Unlock() collection.ID = id s.collections[id] = collection return collection }