First implementation of SQlite store
This commit is contained in:
parent
4878bf5e18
commit
578b4e2079
19 changed files with 345 additions and 722 deletions
|
@ -21,6 +21,21 @@ type Collection struct {
|
|||
Query string `json:"query"`
|
||||
}
|
||||
|
||||
type Participant struct {
|
||||
Firstname string `json:"firstname"`
|
||||
Lastname string `json:"lastname"`
|
||||
Class string `json:"class"`
|
||||
Token uint `json:"token"`
|
||||
}
|
||||
|
||||
type Exam struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
|
||||
ParticipantID string `json:"participant_id"`
|
||||
CollectionID string `json:"collection_id"`
|
||||
}
|
||||
|
||||
type BaseResponse struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
|
@ -65,3 +80,27 @@ type CreateUpdateCollectionRequest struct {
|
|||
type DeleteCollectionRequest struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
type CreateUpdateExamRequest struct {
|
||||
*Exam
|
||||
}
|
||||
|
||||
type DeleteExamRequest struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
type ReadExamByIDRequest struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
type CreateUpdateParticipantRequest struct {
|
||||
*Participant
|
||||
}
|
||||
|
||||
type DeleteParticipantRequest struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
type ReadParticipantByIDRequest struct {
|
||||
ID string
|
||||
}
|
||||
|
|
16
go.mod
16
go.mod
|
@ -5,12 +5,24 @@ go 1.17
|
|||
require github.com/sirupsen/logrus v1.8.1
|
||||
|
||||
require (
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||
github.com/glebarez/sqlite v1.9.0 // indirect
|
||||
github.com/go-yaml/yaml v2.1.0+incompatible // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/google/uuid v1.3.1 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/julienschmidt/httprouter v1.3.0 // indirect
|
||||
github.com/kr/pretty v0.2.1 // indirect
|
||||
github.com/kr/text v0.1.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/remogatto/prettytest v0.0.0-20200211072524-6d385e11dcb8 // indirect
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gorm.io/gorm v1.25.5 // indirect
|
||||
modernc.org/libc v1.24.1 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.7.2 // indirect
|
||||
modernc.org/sqlite v1.26.0 // indirect
|
||||
)
|
||||
|
|
29
go.sum
29
go.sum
|
@ -1,8 +1,20 @@
|
|||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
|
||||
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
|
||||
github.com/glebarez/sqlite v1.9.0 h1:Aj6bPA12ZEx5GbSF6XADmCkYXlljPNUY+Zf1EQxynXs=
|
||||
github.com/glebarez/sqlite v1.9.0/go.mod h1:YBYCoyupOao60lzp1MVBLEjZfgkq0tdB1voAQ09K9zw=
|
||||
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/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
|
@ -10,13 +22,30 @@ github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
|
|||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/remogatto/prettytest v0.0.0-20200211072524-6d385e11dcb8 h1:nRDwTcxV9B3elxMt+1xINX0bwaPdpouqp5fbynexY8U=
|
||||
github.com/remogatto/prettytest v0.0.0-20200211072524-6d385e11dcb8/go.mod h1:jOEnp79oIHy5cvQSHeLcgVJk1GHOOHJHQWps/d1N5Yo=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
|
||||
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
modernc.org/libc v1.24.1 h1:uvJSeCKL/AgzBo2yYIPPTy82v21KgGnizcGYfBHaNuM=
|
||||
modernc.org/libc v1.24.1/go.mod h1:FmfO1RLrU3MHJfyi9eYYmZBfi/R+tqZ6+hQ3yQQUkak=
|
||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
|
||||
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
|
||||
modernc.org/sqlite v1.26.0 h1:SocQdLRSYlA8W99V8YH0NES75thx19d9sB/aFc4R8Lw=
|
||||
modernc.org/sqlite v1.26.0/go.mod h1:FL3pVXie73rg3Rii6V/u5BoHlSoyeZeIgKZEgHARyCU=
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package models
|
||||
|
||||
type Answer struct {
|
||||
ID string `json:"id"`
|
||||
ID string `json:"id" gorm:"primaryKey"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
package models
|
||||
|
||||
type Collection struct {
|
||||
ID string `json:"id"`
|
||||
Meta
|
||||
|
||||
Name string `json:"name"`
|
||||
Query string `json:"query"`
|
||||
|
||||
IDs []string `json:"ids"`
|
||||
Quizzes []*Quiz `json:"quizzes" gorm:"many2many:collection_quizzes"`
|
||||
}
|
||||
|
|
|
@ -1,13 +1,23 @@
|
|||
package models
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Exam struct {
|
||||
ID string `gorm:"primaryKey"`
|
||||
ID string `gorm:"primaryKey"`
|
||||
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
|
||||
Quizzes []*Quiz
|
||||
Participant *Participant
|
||||
Name string
|
||||
Description string
|
||||
|
||||
Responses []string
|
||||
Collection *Collection `gorm:"foreignKey:ID"`
|
||||
Participant []*Participant `gorm:"many2many:exam_participants"`
|
||||
|
||||
// Responses []string
|
||||
}
|
||||
|
|
|
@ -3,7 +3,8 @@ package models
|
|||
import "time"
|
||||
|
||||
type Meta struct {
|
||||
ID string `json:"id" yaml:"id"`
|
||||
ID string `json:"id" yaml:"id" gorm:"primaryKey"`
|
||||
CreatedAt time.Time `json:"created_at" yaml:"created_at"`
|
||||
Tags []*Tag `json:"tags" yaml:"-"`
|
||||
UpdatedAt time.Time `json:"updated_at" yaml:"updated_at"`
|
||||
Tags []*Tag `json:"tags" yaml:"-" gorm:"-"`
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package models
|
||||
|
||||
type Participant struct {
|
||||
ID string
|
||||
ID string `gorm:"primaryKey"`
|
||||
|
||||
Firstname string
|
||||
Lastname string
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package models
|
||||
|
||||
type Question struct {
|
||||
ID string `json:"id"`
|
||||
Text string `json:"text"`
|
||||
AnswerIDs []string `json:"answer_ids"`
|
||||
Meta
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
|
|
@ -2,9 +2,10 @@ package models
|
|||
|
||||
type Quiz struct {
|
||||
Meta
|
||||
|
||||
Hash string `json:"hash"`
|
||||
Question *Question `json:"question"`
|
||||
Answers []*Answer `json:"answers"`
|
||||
Correct *Answer `json:"correct"`
|
||||
Question *Question `json:"question" gorm:"foreignKey:ID"`
|
||||
Answers []*Answer `json:"answers" gorm:"many2many:quiz_answers"`
|
||||
Correct *Answer `json:"correct" gorm:"foreignKey:ID"`
|
||||
Type int `json:"type"`
|
||||
}
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Tag struct {
|
||||
Name string `json:"name"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
Name string `json:"name" gorm:"primaryKey"`
|
||||
}
|
||||
|
|
|
@ -1,9 +1,79 @@
|
|||
package db
|
||||
|
||||
import (
|
||||
"git.andreafazzi.eu/andrea/probo/client"
|
||||
"git.andreafazzi.eu/andrea/probo/models"
|
||||
"git.andreafazzi.eu/andrea/probo/store"
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type DBProboCollectorStore struct {
|
||||
Path string
|
||||
|
||||
db *gorm.DB
|
||||
si store.ProboCollectorStore
|
||||
}
|
||||
|
||||
func NewDBProboCollectorStore(path string) (*DBProboCollectorStore, error) {
|
||||
return nil, nil
|
||||
func NewDBProboCollectorStore(path string, s store.ProboCollectorStore) (*DBProboCollectorStore, error) {
|
||||
var err error
|
||||
|
||||
store := new(DBProboCollectorStore)
|
||||
|
||||
store.db, err = gorm.Open(sqlite.Open(path), &gorm.Config{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = store.db.AutoMigrate(
|
||||
&models.Question{},
|
||||
&models.Answer{},
|
||||
&models.Quiz{},
|
||||
&models.Collection{},
|
||||
&models.Exam{},
|
||||
&models.Participant{},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
store.si = s
|
||||
|
||||
return store, nil
|
||||
}
|
||||
|
||||
func (s *DBProboCollectorStore) CreateExam(r *client.CreateUpdateExamRequest) (*models.Exam, error) {
|
||||
exam := new(models.Exam)
|
||||
|
||||
exam.ID = uuid.New().String()
|
||||
exam.Name = r.Name
|
||||
exam.Description = r.Description
|
||||
|
||||
collection, err := s.si.ReadCollectionByID(r.CollectionID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
exam.Collection = collection
|
||||
|
||||
result := s.db.Create(exam)
|
||||
if result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
|
||||
return exam, nil
|
||||
}
|
||||
|
||||
func (s *DBProboCollectorStore) ReadExamByID(r *client.ReadExamByIDRequest) (*models.Exam, error) {
|
||||
exam := &models.Exam{
|
||||
ID: r.ID,
|
||||
}
|
||||
|
||||
s.db.First(&exam)
|
||||
if err := s.db.Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return exam, nil
|
||||
}
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
package db
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"git.andreafazzi.eu/andrea/probo/client"
|
||||
"git.andreafazzi.eu/andrea/probo/hasher/sha256"
|
||||
"git.andreafazzi.eu/andrea/probo/store/memory"
|
||||
"github.com/remogatto/prettytest"
|
||||
)
|
||||
|
||||
|
@ -18,7 +22,55 @@ func TestRunner(t *testing.T) {
|
|||
}
|
||||
|
||||
func (t *dbTestSuite) TestCreateExam() {
|
||||
store, err := NewDBProboCollectorStore("testdata/test.sqlite")
|
||||
memStore := memory.NewMemoryProboCollectorStore(
|
||||
sha256.NewDefault256Hasher(sha256.DefaultSHA256HashingFn),
|
||||
)
|
||||
memStore.CreateQuiz(
|
||||
&client.CreateUpdateQuizRequest{
|
||||
Quiz: &client.Quiz{
|
||||
Question: &client.Question{Text: "Newly created 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},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
store, err := NewDBProboCollectorStore("testdata/test.sqlite", memStore)
|
||||
defer os.Remove("testdata/test.sqlite")
|
||||
|
||||
t.Nil(err)
|
||||
t.Not(t.Nil(store))
|
||||
|
||||
if !t.Failed() {
|
||||
collection, err := memStore.CreateCollection(&client.CreateUpdateCollectionRequest{
|
||||
Collection: &client.Collection{
|
||||
Name: "Collection name",
|
||||
Query: "#tag1",
|
||||
},
|
||||
})
|
||||
|
||||
t.Nil(err)
|
||||
|
||||
if !t.Failed() {
|
||||
exam, err := store.CreateExam(&client.CreateUpdateExamRequest{
|
||||
Exam: &client.Exam{
|
||||
Name: "Exam",
|
||||
Description: "Exam description",
|
||||
CollectionID: collection.ID,
|
||||
},
|
||||
})
|
||||
|
||||
t.Nil(err)
|
||||
if !t.Failed() {
|
||||
t.Not(t.Nil(exam))
|
||||
examFromDb, err := store.ReadExamByID(&client.ReadExamByIDRequest{ID: exam.ID})
|
||||
t.Nil(err)
|
||||
if !t.Failed() {
|
||||
t.Equal(examFromDb.ID, exam.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,7 +86,7 @@ func (t *collectionTestSuite) TestCreateCollection() {
|
|||
collectionPath, _ := store.GetCollectionPath(collection)
|
||||
|
||||
if !t.Failed() {
|
||||
t.Equal(2, len(collection.IDs))
|
||||
t.Equal(2, len(collection.Quizzes))
|
||||
|
||||
os.Remove(path_1)
|
||||
os.Remove(path_2)
|
||||
|
|
|
@ -1,26 +1,12 @@
|
|||
package file
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.andreafazzi.eu/andrea/probo/client"
|
||||
"git.andreafazzi.eu/andrea/probo/hasher/sha256"
|
||||
"git.andreafazzi.eu/andrea/probo/models"
|
||||
"git.andreafazzi.eu/andrea/probo/store/memory"
|
||||
"github.com/go-yaml/yaml"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -61,109 +47,6 @@ func NewFileProboCollectorStore(dirname string) (*FileProboCollectorStore, error
|
|||
return s, nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
markdownFiles := make([]fs.FileInfo, 0)
|
||||
|
||||
for _, file := range files {
|
||||
filename := file.Name()
|
||||
if !file.IsDir() && strings.HasSuffix(filename, ".md") {
|
||||
markdownFiles = append(markdownFiles, file)
|
||||
}
|
||||
}
|
||||
|
||||
if len(markdownFiles) == 0 {
|
||||
return fmt.Errorf("The directory is empty.")
|
||||
}
|
||||
|
||||
for _, file := range markdownFiles {
|
||||
filename := file.Name()
|
||||
fullPath := filepath.Join(s.quizzesDir, filename)
|
||||
|
||||
content, err := os.ReadFile(fullPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
quiz, meta, err := QuizFromMarkdown(string(content))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q, err := s.memoryStore.CreateQuiz(&client.CreateUpdateQuizRequest{
|
||||
Quiz: quiz,
|
||||
Meta: meta,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if meta == nil {
|
||||
s.WriteMetaHeaderToFile(filename, &models.Meta{
|
||||
ID: q.ID,
|
||||
CreatedAt: time.Now(),
|
||||
})
|
||||
}
|
||||
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),
|
||||
|
@ -184,501 +67,3 @@ func (s *FileProboCollectorStore) Reindex() error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *FileProboCollectorStore) ReadAllQuizzes() ([]*models.Quiz, error) {
|
||||
return s.memoryStore.ReadAllQuizzes()
|
||||
}
|
||||
|
||||
func (s *FileProboCollectorStore) CreateQuiz(r *client.CreateUpdateQuizRequest) (*models.Quiz, error) {
|
||||
quiz, err := s.memoryStore.CreateQuiz(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = s.createOrUpdateMarkdownFile(quiz)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = s.Reindex()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.memoryStore.ReadQuizByHash(quiz.Hash)
|
||||
}
|
||||
|
||||
func (s *FileProboCollectorStore) UpdateQuiz(r *client.CreateUpdateQuizRequest, id string) (*models.Quiz, error) {
|
||||
quiz, updated, err := s.memoryStore.UpdateQuiz(r, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if updated { // Update and re-index only if quiz hash is changed
|
||||
err = s.createOrUpdateMarkdownFile(quiz)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = s.Reindex()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return s.memoryStore.ReadQuizByHash(quiz.Hash)
|
||||
}
|
||||
|
||||
func (s *FileProboCollectorStore) DeleteQuiz(r *client.DeleteQuizRequest) (*models.Quiz, error) {
|
||||
quiz, err := s.memoryStore.DeleteQuiz(r.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
path, err := s.GetQuizPath(quiz)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = os.Remove(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = s.Reindex()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return quiz, nil
|
||||
}
|
||||
|
||||
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.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) 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.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
|
||||
}
|
||||
|
||||
func MarkdownFromQuiz(quiz *models.Quiz) (string, error) {
|
||||
if quiz.Question == nil {
|
||||
return "", errors.New("Quiz should contain a question but it wasn't provided.")
|
||||
}
|
||||
|
||||
if len(quiz.Answers) < 2 {
|
||||
return "", errors.New("Quiz should contain at least 2 answers but none was provided.")
|
||||
}
|
||||
|
||||
if quiz.Correct == nil {
|
||||
return "", errors.New("Quiz should contain a correct answer but not was provided.")
|
||||
}
|
||||
|
||||
correctAnswer := "* " + quiz.Correct.Text
|
||||
var otherAnswers string
|
||||
|
||||
for _, answer := range quiz.Answers {
|
||||
if quiz.Correct.ID != answer.ID {
|
||||
otherAnswers += "* " + answer.Text + "\n"
|
||||
}
|
||||
}
|
||||
|
||||
markdown := quiz.Question.Text + "\n\n" + correctAnswer + "\n" + otherAnswers
|
||||
|
||||
return markdown, nil
|
||||
}
|
||||
|
||||
func QuizFromMarkdown(markdown string) (*client.Quiz, *models.Meta, error) {
|
||||
meta, remainingMarkdown, err := parseMetaHeaderFromMarkdown(markdown)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
lines := strings.Split(remainingMarkdown, "\n")
|
||||
|
||||
questionText := ""
|
||||
answers := []*client.Answer{}
|
||||
|
||||
for _, line := range lines {
|
||||
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 (s *FileProboCollectorStore) ReadMetaHeaderFromFile(filename string) (*models.Meta, error) {
|
||||
data, err := ioutil.ReadFile(path.Join(s.quizzesDir, 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.quizzesDir, filename), meta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return meta, nil
|
||||
}
|
||||
|
||||
func (s *FileProboCollectorStore) ReadAllCollections() ([]*models.Collection, error) {
|
||||
return s.memoryStore.ReadAllCollections()
|
||||
}
|
||||
|
||||
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) UpdateCollection(r *client.CreateUpdateCollectionRequest, id string) (*models.Collection, error) {
|
||||
collection, _, err := s.memoryStore.UpdateCollection(r, id)
|
||||
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.quizzesDir, 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.quizzesDir, 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 {
|
||||
markdown, err := MarkdownFromQuiz(quiz)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fn, _ := s.GetQuizPath(quiz)
|
||||
if fn == "" {
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = file.Write([]byte(markdownWithMetaHeader))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
|
|
5
store/file/testdata/quizzes/quiz_5.md
vendored
5
store/file/testdata/quizzes/quiz_5.md
vendored
|
@ -1,6 +1,7 @@
|
|||
---
|
||||
id: e4ee7eb2-5608-4709-ad85-1381b457de35
|
||||
created_at: !!timestamp 2023-10-16T12:47:06.100160075+02:00
|
||||
id: 9243c924-5a91-4fa1-9cf3-db9ce1e19ca4
|
||||
created_at: !!timestamp 2023-10-28T20:49:22.688075744+02:00
|
||||
updated_at: !!timestamp 0001-01-01T00:00:00Z
|
||||
---
|
||||
This quiz is initially without metadata.
|
||||
|
||||
|
|
|
@ -140,21 +140,30 @@ func (s *MemoryProboCollectorStore) deleteQuiz(id string) (*models.Quiz, error)
|
|||
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
|
||||
delete(s.quizzes, id)
|
||||
delete(s.quizzesHashes, quiz.Hash)
|
||||
|
||||
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))
|
||||
if quiz := s.getQuizFromID(id); quiz != nil {
|
||||
result = append(result, quiz)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *MemoryProboCollectorStore) ReadQuizByID(id string) (*models.Quiz, error) {
|
||||
quiz := s.getQuizFromID(id)
|
||||
if quiz == nil {
|
||||
return nil, fmt.Errorf("Quiz with ID %s was not found in the store.", id)
|
||||
}
|
||||
return quiz, nil
|
||||
}
|
||||
|
||||
func (s *MemoryProboCollectorStore) ReadQuizByHash(hash string) (*models.Quiz, error) {
|
||||
quiz := s.getQuizFromHash(hash)
|
||||
if quiz == nil {
|
||||
|
@ -236,7 +245,7 @@ func (s *MemoryProboCollectorStore) createOrUpdateQuiz(r *client.CreateUpdateQui
|
|||
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(),
|
||||
Meta: models.Meta{ID: uuid.New().String()},
|
||||
Text: s.parseTextForTags(r.Quiz.Question.Text, &quiz.Tags),
|
||||
})
|
||||
}
|
||||
|
@ -274,14 +283,16 @@ func (s *MemoryProboCollectorStore) UpdateQuiz(r *client.CreateUpdateQuizRequest
|
|||
return s.createOrUpdateQuiz(r, id)
|
||||
}
|
||||
|
||||
func (s *MemoryProboCollectorStore) DeleteQuiz(id string) (*models.Quiz, error) {
|
||||
return s.deleteQuiz(id)
|
||||
func (s *MemoryProboCollectorStore) DeleteQuiz(r *client.DeleteQuizRequest) (*models.Quiz, error) {
|
||||
return s.deleteQuiz(r.ID)
|
||||
}
|
||||
|
||||
func (s *MemoryProboCollectorStore) ReadAllCollections() ([]*models.Collection, error) {
|
||||
result := make([]*models.Collection, 0)
|
||||
for id := range s.collections {
|
||||
result = append(result, s.getCollectionFromID(id))
|
||||
if collection := s.getCollectionFromID(id); collection != nil {
|
||||
result = append(result, collection)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
@ -296,13 +307,34 @@ func (s *MemoryProboCollectorStore) UpdateCollection(r *client.CreateUpdateColle
|
|||
}
|
||||
|
||||
func (s *MemoryProboCollectorStore) ReadCollectionByID(id string) (*models.Collection, error) {
|
||||
if id == "" {
|
||||
return nil, errors.New("ID should not be an empty string!")
|
||||
}
|
||||
collection := s.getCollectionFromID(id)
|
||||
if collection == nil {
|
||||
return nil, fmt.Errorf("Collection ID %v not found in the store", collection.ID)
|
||||
return nil, fmt.Errorf("Collection ID %v not found in the store", id)
|
||||
}
|
||||
return collection, nil
|
||||
}
|
||||
|
||||
func (s *MemoryProboCollectorStore) DeleteCollection(r *client.DeleteCollectionRequest) (*models.Collection, error) {
|
||||
return s.deleteCollection(r.ID)
|
||||
}
|
||||
|
||||
func (s *MemoryProboCollectorStore) deleteCollection(id string) (*models.Collection, error) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
collection := s.collections[id]
|
||||
if collection == nil {
|
||||
return nil, fmt.Errorf("Trying to delete a collection that doesn't exist in memory (ID: %v)", id)
|
||||
}
|
||||
|
||||
delete(s.collections, id)
|
||||
|
||||
return collection, nil
|
||||
}
|
||||
|
||||
func (s *MemoryProboCollectorStore) createOrUpdateCollection(r *client.CreateUpdateCollectionRequest, id string) (*models.Collection, bool, error) {
|
||||
var collection *models.Collection
|
||||
|
||||
|
@ -323,21 +355,21 @@ func (s *MemoryProboCollectorStore) createOrUpdateCollection(r *client.CreateUpd
|
|||
collection.Name = r.Collection.Name
|
||||
collection.Query = r.Collection.Query
|
||||
|
||||
collection.IDs = s.query(collection.Query)
|
||||
collection.Quizzes = s.query(collection.Query)
|
||||
|
||||
return s.createCollectionFromID(id, collection), true, nil
|
||||
}
|
||||
|
||||
func (s *MemoryProboCollectorStore) query(query string) []string {
|
||||
func (s *MemoryProboCollectorStore) query(query string) []*models.Quiz {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
result := make([]string, 0)
|
||||
result := make([]*models.Quiz, 0)
|
||||
|
||||
for id, quiz := range s.quizzes {
|
||||
for _, quiz := range s.quizzes {
|
||||
for _, tag := range quiz.Tags {
|
||||
if query == tag.Name {
|
||||
result = append(result, id)
|
||||
result = append(result, quiz)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ func TestRunner(t *testing.T) {
|
|||
prettytest.Run(
|
||||
t,
|
||||
new(testSuite),
|
||||
new(collectionTestSuite),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -131,70 +132,11 @@ func (t *testSuite) TestDeleteQuiz() {
|
|||
},
|
||||
})
|
||||
|
||||
deletedQuiz, err := store.DeleteQuiz(quiz.ID)
|
||||
deletedQuiz, err := store.DeleteQuiz(&client.DeleteQuizRequest{ID: quiz.ID})
|
||||
|
||||
t.Equal(quiz.ID, deletedQuiz.ID, "Returned deleted quiz ID should be equal to the request")
|
||||
t.Nil(err, fmt.Sprintf("The update returned an error: %v", err))
|
||||
|
||||
_, err = store.ReadQuizByHash(quiz.Hash)
|
||||
_, err = store.ReadQuizByHash(deletedQuiz.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])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,16 +5,56 @@ import (
|
|||
"git.andreafazzi.eu/andrea/probo/models"
|
||||
)
|
||||
|
||||
type ProboCollectorStore interface {
|
||||
type QuizReader interface {
|
||||
ReadAllQuizzes() ([]*models.Quiz, error)
|
||||
ReadQuizByID(id string) (*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)
|
||||
}
|
||||
|
||||
type QuizWriter interface {
|
||||
CreateQuiz(r *client.CreateUpdateQuizRequest) (*models.Quiz, error)
|
||||
UpdateQuiz(r *client.CreateUpdateQuizRequest, id string) (*models.Quiz, bool, error)
|
||||
DeleteQuiz(r *client.DeleteQuizRequest) (*models.Quiz, error)
|
||||
}
|
||||
|
||||
type CollectionReader interface {
|
||||
ReadAllCollections() ([]*models.Collection, error)
|
||||
ReadCollectionByID(id string) (*models.Collection, error)
|
||||
}
|
||||
|
||||
type CollectionWriter interface {
|
||||
CreateCollection(r *client.CreateUpdateCollectionRequest) (*models.Collection, error)
|
||||
UpdateCollection(r *client.CreateUpdateCollectionRequest, id string) (*models.Collection, error)
|
||||
UpdateCollection(r *client.CreateUpdateCollectionRequest, id string) (*models.Collection, bool, error)
|
||||
DeleteCollection(r *client.DeleteCollectionRequest) (*models.Collection, error)
|
||||
}
|
||||
|
||||
type ParticipantReader interface {
|
||||
ReadAllParticipants() ([]*models.Participant, error)
|
||||
ReadParticipantByID(id string) (*models.Participant, error)
|
||||
}
|
||||
|
||||
type ParticipantWriter interface {
|
||||
CreateParticipant(r *client.CreateUpdateParticipantRequest) (*models.Participant, error)
|
||||
UpdateParticipant(r *client.CreateUpdateParticipantRequest, id string) (*models.Participant, bool, error)
|
||||
DeleteParticipant(r *client.DeleteParticipantRequest) (*models.Participant, error)
|
||||
}
|
||||
|
||||
type ExamReader interface {
|
||||
ReadAllExams() ([]*models.Exam, error)
|
||||
ReadExamByID(id string) (*models.Exam, error)
|
||||
}
|
||||
|
||||
type ExamWriter interface {
|
||||
CreateExam(r *client.CreateUpdateExamRequest) (*models.Exam, error)
|
||||
UpdateExam(r *client.CreateUpdateExamRequest, id string) (*models.Exam, bool, error)
|
||||
DeleteExam(r *client.DeleteExamRequest) (*models.Exam, error)
|
||||
}
|
||||
|
||||
type ProboCollectorStore interface {
|
||||
QuizReader
|
||||
QuizWriter
|
||||
CollectionReader
|
||||
CollectionWriter
|
||||
ParticipantReader
|
||||
ParticipantWriter
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue