Collections
This commit is contained in:
parent
4da6162dd4
commit
15830d888f
10 changed files with 474 additions and 115 deletions
|
@ -16,6 +16,11 @@ type Quiz struct {
|
|||
Answers []*Answer `json:"answers"`
|
||||
}
|
||||
|
||||
type Collection struct {
|
||||
Name string `json:"name"`
|
||||
Query string `json:"query"`
|
||||
}
|
||||
|
||||
type BaseResponse struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
|
@ -52,3 +57,11 @@ type CreateUpdateQuizRequest struct {
|
|||
type DeleteQuizRequest struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
type CreateUpdateCollectionRequest struct {
|
||||
*Collection
|
||||
}
|
||||
|
||||
type DeleteCollectionRequest struct {
|
||||
ID string
|
||||
}
|
||||
|
|
9
models/collection.go
Normal file
9
models/collection.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package models
|
||||
|
||||
type Collection struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Query string `json:"query"`
|
||||
|
||||
IDs []string `json:"ids"`
|
||||
}
|
|
@ -5,5 +5,5 @@ import "time"
|
|||
type Meta struct {
|
||||
ID string `json:"id" yaml:"id"`
|
||||
CreatedAt time.Time `json:"created_at" yaml:"created_at"`
|
||||
Tags []*Tag `json:"tags" yaml:"tags"`
|
||||
Tags []*Tag `json:"tags" yaml:"-"`
|
||||
}
|
||||
|
|
98
store/file/collection_test.go
Normal file
98
store/file/collection_test.go
Normal file
|
@ -0,0 +1,98 @@
|
|||
package file
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"git.andreafazzi.eu/andrea/probo/client"
|
||||
"github.com/remogatto/prettytest"
|
||||
)
|
||||
|
||||
type collectionTestSuite struct {
|
||||
prettytest.Suite
|
||||
}
|
||||
|
||||
func (t *collectionTestSuite) TestCreateCollection() {
|
||||
store, err := NewFileProboCollectorStore(testdataDir)
|
||||
t.Nil(err, fmt.Sprintf("A file store should be initialized without problems but an error occurred: %v", err))
|
||||
|
||||
if !t.Failed() {
|
||||
quiz_1, err := createQuizOnDisk(store, &client.CreateUpdateQuizRequest{
|
||||
Quiz: &client.Quiz{
|
||||
Question: &client.Question{Text: "Question text with #tag1."},
|
||||
Answers: []*client.Answer{
|
||||
{Text: "Answer 1", Correct: true},
|
||||
{Text: "Answer 2", Correct: false},
|
||||
{Text: "Answer 3", Correct: false},
|
||||
{Text: "Answer 4", Correct: false},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
t.Nil(err, "The quiz to be updated should be created without issue")
|
||||
|
||||
path_1, _ := store.GetQuizPath(quiz_1)
|
||||
|
||||
if !t.Failed() {
|
||||
quiz_2, err := createQuizOnDisk(store, &client.CreateUpdateQuizRequest{
|
||||
Quiz: &client.Quiz{
|
||||
Question: &client.Question{Text: "Another question text with #tag1."},
|
||||
Answers: []*client.Answer{
|
||||
{Text: "Answer 1", Correct: true},
|
||||
{Text: "Answer 2", Correct: false},
|
||||
{Text: "Answer 3", Correct: false},
|
||||
{Text: "Answer 4", Correct: false},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
t.Nil(err, "The quiz to be updated should be created without issue")
|
||||
|
||||
path_2, _ := store.GetQuizPath(quiz_2)
|
||||
|
||||
if !t.Failed() {
|
||||
|
||||
quiz_3, err := createQuizOnDisk(store, &client.CreateUpdateQuizRequest{
|
||||
Quiz: &client.Quiz{
|
||||
Question: &client.Question{Text: "Question text without tags."},
|
||||
Answers: []*client.Answer{
|
||||
{Text: "Answer 1", Correct: true},
|
||||
{Text: "Answer 2", Correct: false},
|
||||
{Text: "Answer 3", Correct: false},
|
||||
{Text: "Answer 4", Correct: false},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
t.Nil(err, "The quiz to be updated should be created without issue")
|
||||
|
||||
path_3, _ := store.GetQuizPath(quiz_3)
|
||||
|
||||
if !t.Failed() {
|
||||
|
||||
collection, err := store.CreateCollection(
|
||||
&client.CreateUpdateCollectionRequest{
|
||||
Collection: &client.Collection{
|
||||
Name: "MyCollection",
|
||||
Query: "#tag1",
|
||||
},
|
||||
})
|
||||
|
||||
t.Nil(err, "Creating a collection should not return an error")
|
||||
|
||||
collectionPath, _ := store.GetCollectionPath(collection)
|
||||
|
||||
if !t.Failed() {
|
||||
t.Equal(2, len(collection.IDs))
|
||||
|
||||
os.Remove(path_1)
|
||||
os.Remove(path_2)
|
||||
os.Remove(path_3)
|
||||
os.Remove(collectionPath)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ package file
|
|||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -22,13 +23,23 @@ import (
|
|||
"github.com/go-yaml/yaml"
|
||||
)
|
||||
|
||||
var ErrorMetaHeaderIsNotPresent = errors.New("Meta header was not found in file.")
|
||||
var (
|
||||
ErrorMetaHeaderIsNotPresent = errors.New("Meta header was not found in file.")
|
||||
|
||||
DefaultQuizzesDir = "quizzes"
|
||||
DefaultCollectionsDir = "collections"
|
||||
)
|
||||
|
||||
type FileProboCollectorStore struct {
|
||||
Dir string
|
||||
|
||||
memoryStore *memory.MemoryProboCollectorStore
|
||||
paths map[string]string
|
||||
|
||||
quizzesPaths map[string]string
|
||||
collectionsPaths map[string]string
|
||||
|
||||
quizzesDir string
|
||||
collectionsDir string
|
||||
|
||||
// A mutex is used to synchronize read/write access to the map
|
||||
lock sync.RWMutex
|
||||
|
@ -39,6 +50,9 @@ func NewFileProboCollectorStore(dirname string) (*FileProboCollectorStore, error
|
|||
|
||||
s.Dir = dirname
|
||||
|
||||
s.quizzesDir = filepath.Join(s.Dir, DefaultQuizzesDir)
|
||||
s.collectionsDir = filepath.Join(s.Dir, DefaultCollectionsDir)
|
||||
|
||||
err := s.Reindex()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -47,14 +61,20 @@ func NewFileProboCollectorStore(dirname string) (*FileProboCollectorStore, error
|
|||
return s, nil
|
||||
}
|
||||
|
||||
func (s *FileProboCollectorStore) Reindex() error {
|
||||
files, err := ioutil.ReadDir(s.Dir)
|
||||
func (s *FileProboCollectorStore) GetQuizzesDir() string {
|
||||
return s.quizzesDir
|
||||
}
|
||||
|
||||
func (s *FileProboCollectorStore) GetCollectionsDir() string {
|
||||
return s.collectionsDir
|
||||
}
|
||||
|
||||
func (s *FileProboCollectorStore) reindexQuizzes() error {
|
||||
files, err := ioutil.ReadDir(s.quizzesDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.paths = make(map[string]string)
|
||||
|
||||
markdownFiles := make([]fs.FileInfo, 0)
|
||||
|
||||
for _, file := range files {
|
||||
|
@ -68,13 +88,9 @@ func (s *FileProboCollectorStore) Reindex() error {
|
|||
return fmt.Errorf("The directory is empty.")
|
||||
}
|
||||
|
||||
s.memoryStore = memory.NewMemoryProboCollectorStore(
|
||||
sha256.NewDefault256Hasher(sha256.DefaultSHA256HashingFn),
|
||||
)
|
||||
|
||||
for _, file := range markdownFiles {
|
||||
filename := file.Name()
|
||||
fullPath := filepath.Join(s.Dir, filename)
|
||||
fullPath := filepath.Join(s.quizzesDir, filename)
|
||||
|
||||
content, err := os.ReadFile(fullPath)
|
||||
if err != nil {
|
||||
|
@ -98,7 +114,72 @@ func (s *FileProboCollectorStore) Reindex() error {
|
|||
CreatedAt: time.Now(),
|
||||
})
|
||||
}
|
||||
s.SetPath(q, fullPath)
|
||||
s.SetQuizPath(q, fullPath)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *FileProboCollectorStore) reindexCollections() error {
|
||||
files, err := ioutil.ReadDir(s.collectionsDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
jsonFiles := make([]fs.FileInfo, 0)
|
||||
|
||||
for _, file := range files {
|
||||
filename := file.Name()
|
||||
if !file.IsDir() && strings.HasSuffix(filename, ".json") {
|
||||
jsonFiles = append(jsonFiles, file)
|
||||
}
|
||||
}
|
||||
|
||||
for _, file := range jsonFiles {
|
||||
filename := file.Name()
|
||||
fullPath := filepath.Join(s.collectionsDir, filename)
|
||||
|
||||
content, err := os.ReadFile(fullPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var clientCollection *client.Collection
|
||||
|
||||
err = json.Unmarshal(content, &clientCollection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
collection, err := s.memoryStore.CreateCollection(&client.CreateUpdateCollectionRequest{
|
||||
Collection: clientCollection,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.SetCollectionPath(collection, fullPath)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *FileProboCollectorStore) Reindex() error {
|
||||
s.memoryStore = memory.NewMemoryProboCollectorStore(
|
||||
sha256.NewDefault256Hasher(sha256.DefaultSHA256HashingFn),
|
||||
)
|
||||
|
||||
s.quizzesPaths = make(map[string]string)
|
||||
s.collectionsPaths = make(map[string]string)
|
||||
|
||||
err := s.reindexQuizzes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.reindexCollections()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -153,7 +234,7 @@ func (s *FileProboCollectorStore) DeleteQuiz(r *client.DeleteQuizRequest) (*mode
|
|||
return nil, err
|
||||
}
|
||||
|
||||
path, err := s.GetPath(quiz)
|
||||
path, err := s.GetQuizPath(quiz)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -171,21 +252,47 @@ func (s *FileProboCollectorStore) DeleteQuiz(r *client.DeleteQuizRequest) (*mode
|
|||
return quiz, nil
|
||||
}
|
||||
|
||||
func (s *FileProboCollectorStore) GetPath(quiz *models.Quiz) (string, error) {
|
||||
func (s *FileProboCollectorStore) GetQuizPath(quiz *models.Quiz) (string, error) {
|
||||
if quiz == nil {
|
||||
return "", errors.New("Quiz object passed as argument is nil!")
|
||||
}
|
||||
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
path, ok := s.paths[quiz.ID]
|
||||
path, ok := s.quizzesPaths[quiz.ID]
|
||||
if !ok {
|
||||
return "", errors.New(fmt.Sprintf("Path not found for quiz ID %v", quiz.ID))
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func (s *FileProboCollectorStore) SetPath(quiz *models.Quiz, path string) string {
|
||||
func (s *FileProboCollectorStore) GetCollectionPath(collection *models.Collection) (string, error) {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
path, ok := s.collectionsPaths[collection.ID]
|
||||
if !ok {
|
||||
return "", errors.New(fmt.Sprintf("Path not found for collection ID %v", collection.ID))
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func (s *FileProboCollectorStore) SetQuizPath(quiz *models.Quiz, path string) string {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
s.paths[quiz.ID] = path
|
||||
|
||||
s.quizzesPaths[quiz.ID] = path
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
func (s *FileProboCollectorStore) SetCollectionPath(collection *models.Collection, path string) string {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
s.collectionsPaths[collection.ID] = path
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
|
@ -216,67 +323,6 @@ func MarkdownFromQuiz(quiz *models.Quiz) (string, error) {
|
|||
return markdown, nil
|
||||
}
|
||||
|
||||
// func QuizFromMarkdown(markdown string) (*client.Quiz, *models.Meta, error) {
|
||||
// meta, remainingMarkdown, err := parseMetaHeaderFromMarkdown(markdown)
|
||||
// if err != nil {
|
||||
// return nil, nil, err
|
||||
// }
|
||||
|
||||
// if meta == nil {
|
||||
// meta = new(models.Meta)
|
||||
// if meta.Tags == nil {
|
||||
// meta.Tags = make([]*models.Tag, 0)
|
||||
// }
|
||||
// } else if meta.Tags == nil {
|
||||
// meta.Tags = make([]*models.Tag, 0)
|
||||
// }
|
||||
|
||||
// lines := strings.Split(remainingMarkdown, "\n")
|
||||
|
||||
// questionText := ""
|
||||
// answers := []*client.Answer{}
|
||||
|
||||
// for _, line := range lines {
|
||||
// // Check if the line contains a tag
|
||||
// if strings.Contains(line, "#") {
|
||||
// // Split the line into words
|
||||
// words := strings.Split(line, " ")
|
||||
// for _, word := range words {
|
||||
// // If the word starts with '#', add it to the tags
|
||||
// if strings.HasPrefix(word, "#") {
|
||||
// meta.Tags = append(meta.Tags, &models.Tag{Name: word[1:]})
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// if strings.HasPrefix(line, "*") {
|
||||
// answerText := strings.TrimPrefix(line, "* ")
|
||||
// correct := len(answers) == 0
|
||||
// answer := &client.Answer{Text: answerText, Correct: correct}
|
||||
// answers = append(answers, answer)
|
||||
// } else {
|
||||
// if questionText != "" {
|
||||
// questionText += "\n"
|
||||
// }
|
||||
// questionText += line
|
||||
// }
|
||||
// }
|
||||
|
||||
// questionText = strings.TrimRight(questionText, "\n")
|
||||
|
||||
// if questionText == "" {
|
||||
// return nil, nil, fmt.Errorf("Question text should not be empty.")
|
||||
// }
|
||||
|
||||
// if len(answers) < 2 {
|
||||
// return nil, nil, fmt.Errorf("Number of answers should be at least 2 but parsed answers are %d.", len(answers))
|
||||
// }
|
||||
// question := &client.Question{Text: questionText}
|
||||
// quiz := &client.Quiz{Question: question, Answers: answers}
|
||||
|
||||
// return quiz, meta, nil
|
||||
// }
|
||||
|
||||
func QuizFromMarkdown(markdown string) (*client.Quiz, *models.Meta, error) {
|
||||
meta, remainingMarkdown, err := parseMetaHeaderFromMarkdown(markdown)
|
||||
if err != nil {
|
||||
|
@ -319,7 +365,7 @@ func QuizFromMarkdown(markdown string) (*client.Quiz, *models.Meta, error) {
|
|||
}
|
||||
|
||||
func (s *FileProboCollectorStore) ReadMetaHeaderFromFile(filename string) (*models.Meta, error) {
|
||||
data, err := ioutil.ReadFile(path.Join(s.Dir, filename))
|
||||
data, err := ioutil.ReadFile(path.Join(s.quizzesDir, filename))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -336,7 +382,7 @@ func (s *FileProboCollectorStore) WriteMetaHeaderToFile(filename string, meta *m
|
|||
return nil, err
|
||||
}
|
||||
if readMeta == nil {
|
||||
_, err := writeMetaHeader(path.Join(s.Dir, filename), meta)
|
||||
_, err := writeMetaHeader(path.Join(s.quizzesDir, filename), meta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -345,8 +391,22 @@ func (s *FileProboCollectorStore) WriteMetaHeaderToFile(filename string, meta *m
|
|||
return meta, nil
|
||||
}
|
||||
|
||||
func (s *FileProboCollectorStore) CreateCollection(r *client.CreateUpdateCollectionRequest) (*models.Collection, error) {
|
||||
collection, err := s.memoryStore.CreateCollection(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = s.createOrUpdateCollectionFile(collection)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.memoryStore.ReadCollectionByID(collection.ID)
|
||||
}
|
||||
|
||||
func (s *FileProboCollectorStore) removeMetaFromFile(filename string) (*models.Meta, error) {
|
||||
file, err := os.Open(path.Join(s.Dir, filename))
|
||||
file, err := os.Open(path.Join(s.quizzesDir, filename))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -399,7 +459,7 @@ func (s *FileProboCollectorStore) removeMetaFromFile(filename string) (*models.M
|
|||
return nil, err
|
||||
}
|
||||
|
||||
file, err = os.Create(path.Join(s.Dir, filename))
|
||||
file, err = os.Create(path.Join(s.quizzesDir, filename))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -419,15 +479,16 @@ func (s *FileProboCollectorStore) createOrUpdateMarkdownFile(quiz *models.Quiz)
|
|||
return err
|
||||
}
|
||||
|
||||
fn, _ := s.GetPath(quiz)
|
||||
fn, _ := s.GetQuizPath(quiz)
|
||||
if fn == "" {
|
||||
fn = filepath.Join(s.Dir, fmt.Sprintf("quiz_%v.%s", time.Now().Unix(), "md"))
|
||||
fn = filepath.Join(s.quizzesDir, fmt.Sprintf("quiz_%v.%s", quiz.ID, "md"))
|
||||
}
|
||||
|
||||
file, err := os.Create(fn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
markdownWithMetaHeader, err := addMetaHeaderToMarkdown(markdown, &quiz.Meta)
|
||||
|
@ -440,11 +501,37 @@ func (s *FileProboCollectorStore) createOrUpdateMarkdownFile(quiz *models.Quiz)
|
|||
return err
|
||||
}
|
||||
|
||||
s.SetPath(quiz, fn)
|
||||
s.SetQuizPath(quiz, fn)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *FileProboCollectorStore) createOrUpdateCollectionFile(collection *models.Collection) error {
|
||||
json, err := json.Marshal(collection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fn, _ := s.GetCollectionPath(collection)
|
||||
if fn == "" {
|
||||
fn = filepath.Join(s.collectionsDir, fmt.Sprintf("collection_%v.%s", collection.ID, "json"))
|
||||
}
|
||||
|
||||
file, err := os.Create(fn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
_, err = file.Write([]byte(json))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.SetCollectionPath(collection, fn)
|
||||
|
||||
return nil
|
||||
}
|
||||
func addMetaHeaderToMarkdown(content string, meta *models.Meta) (string, error) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
|
|
|
@ -13,18 +13,21 @@ import (
|
|||
"github.com/remogatto/prettytest"
|
||||
)
|
||||
|
||||
type testSuite struct {
|
||||
var testdataDir = "./testdata"
|
||||
|
||||
type quizTestSuite struct {
|
||||
prettytest.Suite
|
||||
}
|
||||
|
||||
func TestRunner(t *testing.T) {
|
||||
prettytest.Run(
|
||||
t,
|
||||
new(testSuite),
|
||||
new(quizTestSuite),
|
||||
new(collectionTestSuite),
|
||||
)
|
||||
}
|
||||
|
||||
func (t *testSuite) TestQuizFromMarkdown() {
|
||||
func (t *quizTestSuite) TestQuizFromMarkdown() {
|
||||
markdown := `Question text (1).
|
||||
|
||||
Question text (2).
|
||||
|
@ -55,8 +58,8 @@ Question text with #tag1 #tag2 (3).
|
|||
}
|
||||
}
|
||||
|
||||
func (t *testSuite) TestReadAllQuizzes() {
|
||||
store, err := NewFileProboCollectorStore("./testdata/quizzes")
|
||||
func (t *quizTestSuite) TestReadAllQuizzes() {
|
||||
store, err := NewFileProboCollectorStore("./testdata/")
|
||||
t.True(err == nil, fmt.Sprintf("A file store should be initialized without problems but an error occurred: %v", err))
|
||||
|
||||
if !t.Failed() {
|
||||
|
@ -75,7 +78,7 @@ func (t *testSuite) TestReadAllQuizzes() {
|
|||
|
||||
}
|
||||
|
||||
func (t *testSuite) TestMarkdownFromQuiz() {
|
||||
func (t *quizTestSuite) TestMarkdownFromQuiz() {
|
||||
store := memory.NewMemoryProboCollectorStore(sha256.NewDefault256Hasher(sha256.DefaultSHA256HashingFn))
|
||||
quiz, err := store.CreateQuiz(
|
||||
&client.CreateUpdateQuizRequest{
|
||||
|
@ -102,9 +105,8 @@ func (t *testSuite) TestMarkdownFromQuiz() {
|
|||
}
|
||||
}
|
||||
|
||||
func (t *testSuite) TestCreateQuiz() {
|
||||
dirname := "./testdata/quizzes"
|
||||
store, err := NewFileProboCollectorStore(dirname)
|
||||
func (t *quizTestSuite) TestCreateQuiz() {
|
||||
store, err := NewFileProboCollectorStore(testdataDir)
|
||||
|
||||
t.True(err == nil, fmt.Sprintf("A file store should be initialized without problems but an error occurred: %v", err))
|
||||
|
||||
|
@ -127,7 +129,7 @@ func (t *testSuite) TestCreateQuiz() {
|
|||
t.Nil(err, fmt.Sprintf("An error was raised when saving the quiz on disk: %v", err))
|
||||
|
||||
if !t.Failed() {
|
||||
path, err := store.GetPath(quiz)
|
||||
path, err := store.GetQuizPath(quiz)
|
||||
t.Nil(err, "GetPath should not raise an error.")
|
||||
|
||||
if !t.Failed() {
|
||||
|
@ -151,9 +153,8 @@ func (t *testSuite) TestCreateQuiz() {
|
|||
}
|
||||
}
|
||||
|
||||
func (t *testSuite) TestDeleteQuiz() {
|
||||
dirname := "./testdata/quizzes"
|
||||
store, err := NewFileProboCollectorStore(dirname)
|
||||
func (t *quizTestSuite) TestDeleteQuiz() {
|
||||
store, err := NewFileProboCollectorStore(testdataDir)
|
||||
t.True(err == nil, fmt.Sprintf("A file store should be initialized without problems but an error occurred: %v", err))
|
||||
|
||||
if !t.Failed() {
|
||||
|
@ -171,7 +172,7 @@ func (t *testSuite) TestDeleteQuiz() {
|
|||
|
||||
t.Nil(err, "The quiz to be deleted should be created without issue")
|
||||
|
||||
path, err := store.GetPath(quiz)
|
||||
path, err := store.GetQuizPath(quiz)
|
||||
t.True(path != "", "Quiz path should be obtained without errors")
|
||||
|
||||
if !t.Failed() {
|
||||
|
@ -184,9 +185,8 @@ func (t *testSuite) TestDeleteQuiz() {
|
|||
}
|
||||
}
|
||||
|
||||
func (t *testSuite) TestUpdateQuiz() {
|
||||
dirname := "./testdata/quizzes"
|
||||
store, err := NewFileProboCollectorStore(dirname)
|
||||
func (t *quizTestSuite) TestUpdateQuiz() {
|
||||
store, err := NewFileProboCollectorStore(testdataDir)
|
||||
t.True(err == nil, fmt.Sprintf("A file store should be initialized without problems but an error occurred: %v", err))
|
||||
|
||||
if !t.Failed() {
|
||||
|
@ -225,7 +225,7 @@ func (t *testSuite) TestUpdateQuiz() {
|
|||
t.True(len(updatedQuiz.Tags) == 1, "Length of tags array should be 1")
|
||||
|
||||
if !t.Failed() {
|
||||
path, err := store.GetPath(updatedQuiz)
|
||||
path, err := store.GetQuizPath(updatedQuiz)
|
||||
|
||||
if !t.Failed() {
|
||||
t.Nil(err, "GetPath should not raise an error.")
|
||||
|
@ -246,11 +246,9 @@ func (t *testSuite) TestUpdateQuiz() {
|
|||
|
||||
}
|
||||
|
||||
func (t *testSuite) TestReadMetaHeaderFromFile() {
|
||||
dirname := "./testdata/quizzes"
|
||||
store, err := NewFileProboCollectorStore(dirname)
|
||||
func (t *quizTestSuite) TestReadMetaHeaderFromFile() {
|
||||
store, err := NewFileProboCollectorStore(testdataDir)
|
||||
t.True(err == nil, fmt.Sprintf("A file store should be initialized without problems but an error occurred: %v", err))
|
||||
|
||||
meta, err := store.ReadMetaHeaderFromFile("quiz_4.md")
|
||||
t.True(err == nil, fmt.Sprintf("An error occurred: %v", err))
|
||||
if !t.Failed() {
|
||||
|
@ -259,9 +257,8 @@ func (t *testSuite) TestReadMetaHeaderFromFile() {
|
|||
}
|
||||
}
|
||||
|
||||
func (t *testSuite) TestWriteMetaHeaderToFile() {
|
||||
dirname := "./testdata/quizzes"
|
||||
store, err := NewFileProboCollectorStore(dirname)
|
||||
func (t *quizTestSuite) TestWriteMetaHeaderToFile() {
|
||||
store, err := NewFileProboCollectorStore(testdataDir)
|
||||
|
||||
t.True(err == nil, fmt.Sprintf("A file store should be initialized without problems but an error occurred: %v", err))
|
||||
|
||||
|
|
4
store/file/testdata/quizzes/quiz_5.md
vendored
4
store/file/testdata/quizzes/quiz_5.md
vendored
|
@ -1,3 +1,7 @@
|
|||
---
|
||||
id: f7034ebc-d62d-43eb-a6cf-57f4886c3a7c
|
||||
created_at: !!timestamp 2023-10-07T11:38:32.049804383+02:00
|
||||
---
|
||||
This quiz is initially without metadata.
|
||||
|
||||
* Answer 1
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package memory
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -12,8 +13,8 @@ import (
|
|||
)
|
||||
|
||||
type MemoryProboCollectorStore struct {
|
||||
quizzes map[string]*models.Quiz
|
||||
|
||||
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
|
||||
|
@ -34,6 +35,7 @@ func NewMemoryProboCollectorStore(hasher hasher.Hasher) *MemoryProboCollectorSto
|
|||
s.quizzesHashes = make(map[string]*models.Quiz)
|
||||
|
||||
s.quizzes = make(map[string]*models.Quiz)
|
||||
s.collections = make(map[string]*models.Collection)
|
||||
|
||||
return s
|
||||
}
|
||||
|
@ -62,6 +64,18 @@ func (s *MemoryProboCollectorStore) getQuizFromID(id string) *models.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()
|
||||
|
@ -185,6 +199,9 @@ func (s *MemoryProboCollectorStore) parseTextForTags(text string, tags *[]*model
|
|||
}
|
||||
|
||||
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]
|
||||
|
||||
|
@ -260,3 +277,74 @@ func (s *MemoryProboCollectorStore) UpdateQuiz(r *client.CreateUpdateQuizRequest
|
|||
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
|
||||
}
|
||||
|
|
|
@ -139,3 +139,62 @@ func (t *testSuite) TestDeleteQuiz() {
|
|||
_, err = store.ReadQuizByHash(quiz.Hash)
|
||||
t.True(err != nil, "Reading a non existent quiz should return an error")
|
||||
}
|
||||
|
||||
func (t *testSuite) TestUpdateCollection() {
|
||||
store := NewMemoryProboCollectorStore(
|
||||
sha256.NewDefault256Hasher(sha256.DefaultSHA256HashingFn),
|
||||
)
|
||||
|
||||
quiz_1, _ := store.CreateQuiz(
|
||||
&client.CreateUpdateQuizRequest{
|
||||
Quiz: &client.Quiz{
|
||||
Question: &client.Question{Text: "Question text with #tag1."},
|
||||
Answers: []*client.Answer{
|
||||
{Text: "Answer 1", Correct: true},
|
||||
{Text: "Answer 2", Correct: false},
|
||||
{Text: "Answer 3", Correct: false},
|
||||
{Text: "Answer 4", Correct: false},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
quiz_2, _ := store.CreateQuiz(
|
||||
&client.CreateUpdateQuizRequest{
|
||||
Quiz: &client.Quiz{
|
||||
Question: &client.Question{Text: "Another question text with #tag1."},
|
||||
Answers: []*client.Answer{
|
||||
{Text: "Answer 1", Correct: true},
|
||||
{Text: "Answer 2", Correct: false},
|
||||
{Text: "Answer 3", Correct: false},
|
||||
{Text: "Answer 4", Correct: false},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
collection, _ := store.CreateCollection(
|
||||
&client.CreateUpdateCollectionRequest{
|
||||
Collection: &client.Collection{
|
||||
Name: "MyCollection",
|
||||
},
|
||||
})
|
||||
|
||||
updatedCollection, updated, err := store.UpdateCollection(
|
||||
&client.CreateUpdateCollectionRequest{
|
||||
Collection: &client.Collection{
|
||||
Name: "MyUpdatedCollection",
|
||||
Query: "#tag1",
|
||||
},
|
||||
}, collection.ID)
|
||||
|
||||
t.Nil(err, fmt.Sprintf("The update returned an error: %v", err))
|
||||
|
||||
if !t.Failed() {
|
||||
t.True(updated)
|
||||
t.Equal("MyUpdatedCollection", updatedCollection.Name)
|
||||
t.True(len(updatedCollection.IDs) == 2)
|
||||
if !t.Failed() {
|
||||
t.Equal(quiz_1.ID, updatedCollection.IDs[0])
|
||||
t.Equal(quiz_2.ID, updatedCollection.IDs[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,10 +7,14 @@ import (
|
|||
|
||||
type ProboCollectorStore interface {
|
||||
ReadAllQuizzes() ([]*models.Quiz, error)
|
||||
|
||||
ReadQuizByHash(hash string) (*models.Quiz, error)
|
||||
|
||||
CreateQuiz(r *client.CreateUpdateQuizRequest) (*models.Quiz, error)
|
||||
UpdateQuiz(r *client.CreateUpdateQuizRequest, id string) (*models.Quiz, error)
|
||||
DeleteQuiz(r *client.DeleteQuizRequest) (*models.Quiz, error)
|
||||
|
||||
ReadAllCollections() ([]*models.Collection, error)
|
||||
ReadCollectionByID(id string) (*models.Collection, error)
|
||||
CreateCollection(r *client.CreateUpdateCollectionRequest) (*models.Collection, error)
|
||||
UpdateCollection(r *client.CreateUpdateCollectionRequest, id string) (*models.Collection, error)
|
||||
DeleteCollection(r *client.DeleteCollectionRequest) (*models.Collection, error)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue