Add metadata headers
This commit is contained in:
parent
9780956432
commit
4f3ecda14c
13 changed files with 358 additions and 21 deletions
|
@ -46,6 +46,7 @@ type CreateAnswerRequest struct {
|
||||||
|
|
||||||
type CreateUpdateQuizRequest struct {
|
type CreateUpdateQuizRequest struct {
|
||||||
*Quiz
|
*Quiz
|
||||||
|
*models.Meta
|
||||||
}
|
}
|
||||||
|
|
||||||
type DeleteQuizRequest struct {
|
type DeleteQuizRequest struct {
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -5,6 +5,7 @@ go 1.17
|
||||||
require github.com/sirupsen/logrus v1.8.1
|
require github.com/sirupsen/logrus v1.8.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/go-yaml/yaml v2.1.0+incompatible // indirect
|
||||||
github.com/google/uuid v1.3.0 // indirect
|
github.com/google/uuid v1.3.0 // indirect
|
||||||
github.com/julienschmidt/httprouter v1.3.0 // indirect
|
github.com/julienschmidt/httprouter v1.3.0 // indirect
|
||||||
github.com/kr/pretty v0.2.1 // indirect
|
github.com/kr/pretty v0.2.1 // indirect
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -1,4 +1,6 @@
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o=
|
||||||
|
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
|
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
|
||||||
|
|
8
models/meta.go
Normal file
8
models/meta.go
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Meta struct {
|
||||||
|
ID string `json:"id" yaml:"id"`
|
||||||
|
CreatedAt time.Time `json:"created_at" yaml:"created_at"`
|
||||||
|
}
|
|
@ -1,7 +1,8 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
type Quiz struct {
|
type Quiz struct {
|
||||||
ID string `json:"id"`
|
Meta
|
||||||
|
// ID string `json:"id"`
|
||||||
Hash string `json:"hash"`
|
Hash string `json:"hash"`
|
||||||
Question *Question `json:"question"`
|
Question *Question `json:"question"`
|
||||||
Answers []*Answer `json:"answers"`
|
Answers []*Answer `json:"answers"`
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
package file
|
package file
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -15,8 +19,11 @@ import (
|
||||||
"git.andreafazzi.eu/andrea/probo/hasher/sha256"
|
"git.andreafazzi.eu/andrea/probo/hasher/sha256"
|
||||||
"git.andreafazzi.eu/andrea/probo/models"
|
"git.andreafazzi.eu/andrea/probo/models"
|
||||||
"git.andreafazzi.eu/andrea/probo/store/memory"
|
"git.andreafazzi.eu/andrea/probo/store/memory"
|
||||||
|
"github.com/go-yaml/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ErrorMetaHeaderIsNotPresent = errors.New("Meta header was not found in file.")
|
||||||
|
|
||||||
type FileProboCollectorStore struct {
|
type FileProboCollectorStore struct {
|
||||||
Dir string
|
Dir string
|
||||||
|
|
||||||
|
@ -73,17 +80,25 @@ func (s *FileProboCollectorStore) Reindex() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
quiz, err := QuizFromMarkdown(string(content))
|
quiz, meta, err := QuizFromMarkdown(string(content))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
q, err := s.memoryStore.CreateQuiz(&client.CreateUpdateQuizRequest{
|
q, err := s.memoryStore.CreateQuiz(&client.CreateUpdateQuizRequest{
|
||||||
Quiz: quiz,
|
Quiz: quiz,
|
||||||
|
Meta: meta,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if meta == nil {
|
||||||
|
s.WriteMetaHeaderToFile(filename, &models.Meta{
|
||||||
|
ID: q.ID,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
})
|
||||||
|
}
|
||||||
s.SetPath(q, fullPath)
|
s.SetPath(q, fullPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,8 +217,13 @@ func MarkdownFromQuiz(quiz *models.Quiz) (string, error) {
|
||||||
return markdown, nil
|
return markdown, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func QuizFromMarkdown(markdown string) (*client.Quiz, error) {
|
func QuizFromMarkdown(markdown string) (*client.Quiz, *models.Meta, error) {
|
||||||
lines := strings.Split(markdown, "\n")
|
meta, remainingMarkdown, err := parseMetaHeaderFromMarkdown(markdown)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := strings.Split(remainingMarkdown, "\n")
|
||||||
|
|
||||||
questionText := ""
|
questionText := ""
|
||||||
answers := []*client.Answer{}
|
answers := []*client.Answer{}
|
||||||
|
@ -225,17 +245,113 @@ func QuizFromMarkdown(markdown string) (*client.Quiz, error) {
|
||||||
questionText = strings.TrimRight(questionText, "\n")
|
questionText = strings.TrimRight(questionText, "\n")
|
||||||
|
|
||||||
if questionText == "" {
|
if questionText == "" {
|
||||||
return nil, fmt.Errorf("Question text should not be empty.")
|
return nil, nil, fmt.Errorf("Question text should not be empty.")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(answers) < 2 {
|
if len(answers) < 2 {
|
||||||
return nil, fmt.Errorf("Number of answers should be at least 2 but parsed answers are %d.", len(answers))
|
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}
|
question := &client.Question{Text: questionText}
|
||||||
quiz := &client.Quiz{Question: question, Answers: answers}
|
quiz := &client.Quiz{Question: question, Answers: answers}
|
||||||
|
|
||||||
return quiz, nil
|
return quiz, meta, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FileProboCollectorStore) ReadMetaHeaderFromFile(filename string) (*models.Meta, error) {
|
||||||
|
data, err := ioutil.ReadFile(path.Join(s.Dir, filename))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
meta, _, err := parseMetaHeaderFromMarkdown(string(data))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return meta, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FileProboCollectorStore) WriteMetaHeaderToFile(filename string, meta *models.Meta) (*models.Meta, error) {
|
||||||
|
readMeta, err := s.ReadMetaHeaderFromFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if readMeta == nil {
|
||||||
|
_, err := writeMetaHeader(path.Join(s.Dir, filename), meta)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return meta, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FileProboCollectorStore) removeMetaFromFile(filename string) (*models.Meta, error) {
|
||||||
|
file, err := os.Open(path.Join(s.Dir, filename))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
|
||||||
|
reader := bufio.NewReader(file)
|
||||||
|
|
||||||
|
var meta models.Meta
|
||||||
|
var line string
|
||||||
|
var sb strings.Builder
|
||||||
|
for {
|
||||||
|
line, err = reader.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(line) == "---" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
line, err = reader.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(line) == "---" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.WriteString(line)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = yaml.Unmarshal([]byte(sb.String()), &meta)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(&buffer, reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err = os.Create(path.Join(s.Dir, filename))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(file, &buffer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &meta, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FileProboCollectorStore) createOrUpdateMarkdownFile(quiz *models.Quiz) error {
|
func (s *FileProboCollectorStore) createOrUpdateMarkdownFile(quiz *models.Quiz) error {
|
||||||
|
@ -253,10 +369,14 @@ func (s *FileProboCollectorStore) createOrUpdateMarkdownFile(quiz *models.Quiz)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
_, err = file.Write([]byte(markdown))
|
markdownWithMetaHeader, err := addMetaHeaderToMarkdown(markdown, &quiz.Meta)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = file.Write([]byte(markdownWithMetaHeader))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -265,3 +385,136 @@ func (s *FileProboCollectorStore) createOrUpdateMarkdownFile(quiz *models.Quiz)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addMetaHeaderToMarkdown(content string, meta *models.Meta) (string, error) {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
|
||||||
|
header, err := yaml.Marshal(meta)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
_, err = buffer.WriteString("---\n" + string(header) + "---\n")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = buffer.WriteString(content)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseMetaHeaderFromMarkdown(markdown string) (*models.Meta, string, error) {
|
||||||
|
reader := strings.NewReader(markdown)
|
||||||
|
var sb strings.Builder
|
||||||
|
var line string
|
||||||
|
var err error
|
||||||
|
for {
|
||||||
|
line, err = readLine(reader)
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(line) == "---" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
line, err = readLine(reader)
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(line) == "---" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.WriteString(line)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sb.String() == "" {
|
||||||
|
return nil, markdown, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var meta models.Meta
|
||||||
|
err = yaml.Unmarshal([]byte(sb.String()), &meta)
|
||||||
|
if err != nil {
|
||||||
|
return nil, markdown, err
|
||||||
|
}
|
||||||
|
|
||||||
|
remainingMarkdown := markdown[strings.Index(markdown, "---\n"+sb.String()+"---\n")+len("---\n"+sb.String()+"---\n"):]
|
||||||
|
|
||||||
|
return &meta, remainingMarkdown, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readLine(reader *strings.Reader) (string, error) {
|
||||||
|
var sb strings.Builder
|
||||||
|
for {
|
||||||
|
r, _, err := reader.ReadRune()
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return sb.String(), io.EOF
|
||||||
|
}
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.WriteRune(r)
|
||||||
|
if r == '\n' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeMetaHeader(filename string, meta *models.Meta) (*models.Meta, error) {
|
||||||
|
// Apri il file in lettura
|
||||||
|
file, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// Crea un buffer in memoria
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
|
||||||
|
// Scrivi l'intestazione YAML nel buffer
|
||||||
|
header, err := yaml.Marshal(meta)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = buffer.WriteString("---\n" + string(header) + "---\n")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copia il contenuto del file originale nel buffer
|
||||||
|
_, err = io.Copy(&buffer, file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Riapri il file in scrittura
|
||||||
|
file, err = os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// Scrivi il contenuto del buffer nel file
|
||||||
|
_, err = io.Copy(file, &buffer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return meta, nil
|
||||||
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ Question text (3).
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
quiz, err := QuizFromMarkdown(markdown)
|
quiz, _, err := QuizFromMarkdown(markdown)
|
||||||
t.Nil(err, fmt.Sprintf("Quiz should be parsed without errors: %v", err))
|
t.Nil(err, fmt.Sprintf("Quiz should be parsed without errors: %v", err))
|
||||||
|
|
||||||
if !t.Failed() {
|
if !t.Failed() {
|
||||||
|
@ -65,9 +65,9 @@ func (t *testSuite) TestReadAllQuizzes() {
|
||||||
|
|
||||||
if !t.Failed() {
|
if !t.Failed() {
|
||||||
t.Equal(
|
t.Equal(
|
||||||
2,
|
4,
|
||||||
len(result),
|
len(result),
|
||||||
fmt.Sprintf("The store contains 3 files but only 2 should be parsed (duplicated quiz). Total of parsed quizzes are instead %v", len(result)),
|
fmt.Sprintf("The store contains 5 files but only 4 should be parsed (duplicated quiz). Total of parsed quizzes are instead %v", len(result)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -136,7 +136,7 @@ func (t *testSuite) TestCreateQuiz() {
|
||||||
if !t.Failed() {
|
if !t.Failed() {
|
||||||
t.True(exists != nil, "The new quiz file was not created.")
|
t.True(exists != nil, "The new quiz file was not created.")
|
||||||
if !t.Failed() {
|
if !t.Failed() {
|
||||||
quizFromDisk, err := readQuizFromDisk(path)
|
quizFromDisk, _, err := readQuizFromDisk(path)
|
||||||
t.Nil(err, "Quiz should be read from disk without errors.")
|
t.Nil(err, "Quiz should be read from disk without errors.")
|
||||||
if !t.Failed() {
|
if !t.Failed() {
|
||||||
t.True(reflect.DeepEqual(quizFromDisk, clientQuiz), "Quiz read from disk and stored in memory should be equal.")
|
t.True(reflect.DeepEqual(quizFromDisk, clientQuiz), "Quiz read from disk and stored in memory should be equal.")
|
||||||
|
@ -205,7 +205,7 @@ func (t *testSuite) TestUpdateQuiz() {
|
||||||
|
|
||||||
if !t.Failed() {
|
if !t.Failed() {
|
||||||
clientQuiz := &client.Quiz{
|
clientQuiz := &client.Quiz{
|
||||||
Question: &client.Question{Text: "Newly created question text."},
|
Question: &client.Question{Text: "Updated question text."},
|
||||||
Answers: []*client.Answer{
|
Answers: []*client.Answer{
|
||||||
{Text: "Answer 1", Correct: true},
|
{Text: "Answer 1", Correct: true},
|
||||||
{Text: "Answer 2", Correct: false},
|
{Text: "Answer 2", Correct: false},
|
||||||
|
@ -220,7 +220,7 @@ func (t *testSuite) TestUpdateQuiz() {
|
||||||
}, quiz.ID)
|
}, quiz.ID)
|
||||||
|
|
||||||
t.Nil(err, fmt.Sprintf("Quiz should be updated without errors: %v", err))
|
t.Nil(err, fmt.Sprintf("Quiz should be updated without errors: %v", err))
|
||||||
// t.Equal(updatedQuiz.ID, quiz.ID, "Quiz ID should remain the same")
|
t.Equal(updatedQuiz.ID, quiz.ID, fmt.Sprintf("IDs should remain the same after an update: updated ID %v original ID %v", updatedQuiz.ID, quiz.ID))
|
||||||
|
|
||||||
if !t.Failed() {
|
if !t.Failed() {
|
||||||
path, err := store.GetPath(updatedQuiz)
|
path, err := store.GetPath(updatedQuiz)
|
||||||
|
@ -229,7 +229,7 @@ func (t *testSuite) TestUpdateQuiz() {
|
||||||
t.Nil(err, "GetPath should not raise an error.")
|
t.Nil(err, "GetPath should not raise an error.")
|
||||||
|
|
||||||
if !t.Failed() {
|
if !t.Failed() {
|
||||||
quizFromDisk, err := readQuizFromDisk(path)
|
quizFromDisk, _, err := readQuizFromDisk(path)
|
||||||
t.Nil(err, "Quiz should be read from disk without errors.")
|
t.Nil(err, "Quiz should be read from disk without errors.")
|
||||||
if !t.Failed() {
|
if !t.Failed() {
|
||||||
t.True(reflect.DeepEqual(clientQuiz, quizFromDisk), "Quiz should be updated.")
|
t.True(reflect.DeepEqual(clientQuiz, quizFromDisk), "Quiz should be updated.")
|
||||||
|
@ -244,15 +244,52 @@ func (t *testSuite) TestUpdateQuiz() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *testSuite) TestReadMetaHeaderFromFile() {
|
||||||
|
dirname := "./testdata/quizzes"
|
||||||
|
store, err := NewFileProboCollectorStore(dirname)
|
||||||
|
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() {
|
||||||
|
t.True(meta.ID != "")
|
||||||
|
t.True(meta.CreatedAt.String() != "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testSuite) TestWriteMetaHeaderToFile() {
|
||||||
|
dirname := "./testdata/quizzes"
|
||||||
|
store, err := NewFileProboCollectorStore(dirname)
|
||||||
|
|
||||||
|
t.True(err == nil, fmt.Sprintf("A file store should be initialized without problems but an error occurred: %v", err))
|
||||||
|
|
||||||
|
if !t.Failed() {
|
||||||
|
meta, err := store.ReadMetaHeaderFromFile("quiz_5.md")
|
||||||
|
t.True(err == nil)
|
||||||
|
if !t.Failed() {
|
||||||
|
t.True(meta != nil, "Meta header should not be nil")
|
||||||
|
|
||||||
|
if !t.Failed() {
|
||||||
|
t.True(meta.ID != "", "ID should not be empty")
|
||||||
|
|
||||||
|
if !t.Failed() {
|
||||||
|
_, err = store.removeMetaFromFile("quiz_5.md")
|
||||||
|
t.True(err == nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func createQuizOnDisk(store *FileProboCollectorStore, req *client.CreateUpdateQuizRequest) (*models.Quiz, error) {
|
func createQuizOnDisk(store *FileProboCollectorStore, req *client.CreateUpdateQuizRequest) (*models.Quiz, error) {
|
||||||
return store.CreateQuiz(req)
|
return store.CreateQuiz(req)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func readQuizFromDisk(path string) (*client.Quiz, error) {
|
func readQuizFromDisk(path string) (*client.Quiz, *models.Meta, error) {
|
||||||
content, err := os.ReadFile(path)
|
content, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
return QuizFromMarkdown(string(content))
|
return QuizFromMarkdown(string(content))
|
||||||
}
|
}
|
||||||
|
|
4
store/file/testdata/quizzes/quiz_1.md
vendored
4
store/file/testdata/quizzes/quiz_1.md
vendored
|
@ -1,3 +1,7 @@
|
||||||
|
---
|
||||||
|
id: 42982769-2168-45e8-b307-8764c18bec55
|
||||||
|
created_at: !!timestamp 2023-09-22T09:07:47.744571823+02:00
|
||||||
|
---
|
||||||
Question text 1.
|
Question text 1.
|
||||||
|
|
||||||
* Answer 1
|
* Answer 1
|
||||||
|
|
4
store/file/testdata/quizzes/quiz_2.md
vendored
4
store/file/testdata/quizzes/quiz_2.md
vendored
|
@ -1,3 +1,7 @@
|
||||||
|
---
|
||||||
|
id: a09045c3-af87-4a83-a2bb-7283a2ac67d6
|
||||||
|
created_at: !!timestamp 2023-09-22T09:08:50.366639817+02:00
|
||||||
|
---
|
||||||
Question text 2.
|
Question text 2.
|
||||||
|
|
||||||
* Answer 1
|
* Answer 1
|
||||||
|
|
4
store/file/testdata/quizzes/quiz_3.md
vendored
4
store/file/testdata/quizzes/quiz_3.md
vendored
|
@ -1,3 +1,7 @@
|
||||||
|
---
|
||||||
|
id: a09045c3-af87-4a83-a2bb-7283a2ac67d6
|
||||||
|
created_at: !!timestamp 2023-09-22T09:08:50.366772426+02:00
|
||||||
|
---
|
||||||
Question text 2.
|
Question text 2.
|
||||||
|
|
||||||
* Answer 1
|
* Answer 1
|
||||||
|
|
10
store/file/testdata/quizzes/quiz_4.md
vendored
Normal file
10
store/file/testdata/quizzes/quiz_4.md
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
id: 0000-1234-5678-9101
|
||||||
|
created_at: 2006-01-02T15:04:05Z
|
||||||
|
---
|
||||||
|
This quiz file contains a meta header.
|
||||||
|
|
||||||
|
* Answer 1
|
||||||
|
* Answer 2
|
||||||
|
* Answer 3
|
||||||
|
* Answer 4
|
7
store/file/testdata/quizzes/quiz_5.md
vendored
Normal file
7
store/file/testdata/quizzes/quiz_5.md
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
This quiz is initially without metadata.
|
||||||
|
|
||||||
|
* Answer 1
|
||||||
|
* Answer 2
|
||||||
|
* Answer 3
|
||||||
|
* Answer 4
|
||||||
|
|
|
@ -154,7 +154,6 @@ func (s *MemoryProboCollectorStore) CalculateQuizHash(quiz *client.Quiz) string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MemoryProboCollectorStore) createOrUpdateQuiz(r *client.CreateUpdateQuizRequest, id string) (*models.Quiz, bool, error) {
|
func (s *MemoryProboCollectorStore) createOrUpdateQuiz(r *client.CreateUpdateQuizRequest, id string) (*models.Quiz, bool, error) {
|
||||||
|
|
||||||
hashes := s.hasher.QuizHashes(r.Quiz)
|
hashes := s.hasher.QuizHashes(r.Quiz)
|
||||||
quizHash := hashes[len(hashes)-1]
|
quizHash := hashes[len(hashes)-1]
|
||||||
|
|
||||||
|
@ -163,13 +162,19 @@ func (s *MemoryProboCollectorStore) createOrUpdateQuiz(r *client.CreateUpdateQui
|
||||||
return quiz, false, nil
|
return quiz, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if id != "" {
|
if id != "" { // we're updating a quiz
|
||||||
quiz = s.getQuizFromID(id)
|
quiz = s.getQuizFromID(id)
|
||||||
if quiz == nil { // Quiz is not present in the store
|
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)
|
return nil, false, fmt.Errorf("Quiz ID %v doesn't exist in the store!", id)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
id = uuid.New().String()
|
if r.Meta != nil {
|
||||||
|
if r.Meta.ID != "" {
|
||||||
|
id = r.Meta.ID
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
id = uuid.New().String()
|
||||||
|
}
|
||||||
quiz = new(models.Quiz)
|
quiz = new(models.Quiz)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue