Completed first refactoring with Go generics
This commit is contained in:
parent
8382cc4222
commit
3cdfa72403
16 changed files with 501 additions and 164 deletions
1
go.mod
1
go.mod
|
@ -15,6 +15,7 @@ require (
|
||||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||||
github.com/glebarez/sqlite v1.9.0 // indirect
|
github.com/glebarez/sqlite v1.9.0 // indirect
|
||||||
github.com/go-yaml/yaml v2.1.0+incompatible // indirect
|
github.com/go-yaml/yaml v2.1.0+incompatible // indirect
|
||||||
|
github.com/gocarina/gocsv v0.0.0-20230616125104-99d496ca653d // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/kr/pretty v0.2.1 // indirect
|
github.com/kr/pretty v0.2.1 // indirect
|
||||||
|
|
3
go.sum
3
go.sum
|
@ -7,6 +7,8 @@ github.com/glebarez/sqlite v1.9.0 h1:Aj6bPA12ZEx5GbSF6XADmCkYXlljPNUY+Zf1EQxynXs
|
||||||
github.com/glebarez/sqlite v1.9.0/go.mod h1:YBYCoyupOao60lzp1MVBLEjZfgkq0tdB1voAQ09K9zw=
|
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 h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o=
|
||||||
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
|
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
|
||||||
|
github.com/gocarina/gocsv v0.0.0-20230616125104-99d496ca653d h1:KbPOUXFUDJxwZ04vbmDOc3yuruGvVO+LOa7cVER3yWw=
|
||||||
|
github.com/gocarina/gocsv v0.0.0-20230616125104-99d496ca653d/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
|
||||||
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/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||||
|
@ -37,6 +39,7 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
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 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
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=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
|
|
@ -1,12 +1,44 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
type Participant struct {
|
type Participant struct {
|
||||||
ID string `gorm:"primaryKey"`
|
ID string `csv:"id" gorm:"primaryKey"`
|
||||||
|
|
||||||
Firstname string
|
Firstname string `csv:"firstname"`
|
||||||
Lastname string
|
Lastname string `csv:"lastname"`
|
||||||
|
|
||||||
Token uint
|
Token uint `csv:"token"`
|
||||||
|
|
||||||
Attributes map[string]string
|
Attributes map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Participant) String() string {
|
||||||
|
return fmt.Sprintf("%s %s", p.Lastname, p.Firstname)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Participant) GetID() string {
|
||||||
|
return p.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Participant) SetID(id string) {
|
||||||
|
p.ID = id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Participant) GetHash() string {
|
||||||
|
return fmt.Sprintf("%x", sha256.Sum256([]byte(strings.Join(append([]string{p.Lastname, p.Firstname}, p.AttributesToSlice()...), ""))))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Participant) AttributesToSlice() []string {
|
||||||
|
result := make([]string, len(p.Attributes)*2)
|
||||||
|
|
||||||
|
for k, v := range p.Attributes {
|
||||||
|
result = append(result, k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
5
store/collection.go
Normal file
5
store/collection.go
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
package store
|
||||||
|
|
||||||
|
import "git.andreafazzi.eu/andrea/probo/models"
|
||||||
|
|
||||||
|
type CollectionStore = Store[*models.Collection]
|
|
@ -9,45 +9,46 @@ import (
|
||||||
"git.andreafazzi.eu/andrea/probo/store"
|
"git.andreafazzi.eu/andrea/probo/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewCollectionFileStore() (*FileStore[*models.Collection, *store.Store[*models.Collection]], error) {
|
type CollectionFileStore = FileStore[*models.Collection, *store.Store[*models.Collection]]
|
||||||
return NewFileStore[*models.Collection](
|
|
||||||
store.NewStore[*models.Collection](),
|
|
||||||
filepath.Join(BaseDir, CollectionsDir),
|
|
||||||
"collection",
|
|
||||||
".json",
|
|
||||||
func(s *store.Store[*models.Collection], filepath string, content []byte) (*models.Collection, error) {
|
|
||||||
collection := new(models.Collection)
|
|
||||||
err := json.Unmarshal(content, &collection)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := s.Create(collection)
|
var DefaultCollectionDir = filepath.Join(BaseDir, CollectionsDir)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, nil
|
func NewCollectionFileStore(config *FileStoreConfig[*models.Collection, *store.CollectionStore]) (*CollectionFileStore, error) {
|
||||||
},
|
return NewFileStore[*models.Collection](config, store.NewStore[*models.Collection]())
|
||||||
func(s *store.Store[*models.Collection], filePath string, collection *models.Collection) error {
|
}
|
||||||
jsonData, err := json.Marshal(collection)
|
|
||||||
if err != nil {
|
func DefaultUnmarshalCollectionFunc(s *store.Store[*models.Collection], filepath string, content []byte) (*models.Collection, error) {
|
||||||
return err
|
collection := new(models.Collection)
|
||||||
}
|
err := json.Unmarshal(content, &collection)
|
||||||
|
if err != nil {
|
||||||
file, err := os.Create(filePath)
|
return nil, err
|
||||||
if err != nil {
|
}
|
||||||
return err
|
|
||||||
}
|
c, err := s.Create(collection)
|
||||||
|
if err != nil {
|
||||||
defer file.Close()
|
return nil, err
|
||||||
|
}
|
||||||
_, err = file.Write(jsonData)
|
|
||||||
if err != nil {
|
return c, nil
|
||||||
return err
|
}
|
||||||
}
|
|
||||||
|
func DefaultMarshalCollectionFunc(s *store.Store[*models.Collection], filePath string, collection *models.Collection) error {
|
||||||
return nil
|
jsonData, err := json.Marshal(collection)
|
||||||
},
|
if err != nil {
|
||||||
)
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Create(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
_, err = file.Write(jsonData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,14 @@ func (t *collectionTestSuite) TestCreateCollection() {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
store, err := NewCollectionFileStore()
|
store, err := NewCollectionFileStore(
|
||||||
|
&FileStoreConfig[*models.Collection, *store.CollectionStore]{
|
||||||
|
FilePathConfig: FilePathConfig{"testdata", "collection", ".json"},
|
||||||
|
IndexDirFunc: DefaultIndexDirFunc[*models.Collection, *store.CollectionStore],
|
||||||
|
UnmarshalFunc: DefaultUnmarshalCollectionFunc,
|
||||||
|
MarshalFunc: DefaultMarshalCollectionFunc,
|
||||||
|
},
|
||||||
|
)
|
||||||
t.Nil(err)
|
t.Nil(err)
|
||||||
|
|
||||||
c := new(models.Collection)
|
c := new(models.Collection)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package file
|
package file
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
@ -12,49 +13,121 @@ import (
|
||||||
"git.andreafazzi.eu/andrea/probo/store"
|
"git.andreafazzi.eu/andrea/probo/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type IndexDirFunc[T store.Storable, K Storer[T]] func(s *FileStore[T, K]) error
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrorMetaHeaderIsNotPresent = errors.New("Meta header was not found in file.")
|
ErrorMetaHeaderIsNotPresent = errors.New("Meta header was not found in file.")
|
||||||
|
|
||||||
BaseDir = "data"
|
BaseDir = "data"
|
||||||
QuizzesDir = "quizzes"
|
QuizzesDir = "quizzes"
|
||||||
CollectionsDir = "collections"
|
CollectionsDir = "collections"
|
||||||
|
ParticipantsDir = "participants"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Storer[T store.Storable] interface {
|
type Storer[T store.Storable] interface {
|
||||||
store.Storer[T]
|
store.Storer[T]
|
||||||
// store.FilterStorer[T]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type FileStore[T store.Storable, K Storer[T]] struct {
|
type FilePathConfig struct {
|
||||||
Storer K
|
|
||||||
|
|
||||||
Dir string
|
Dir string
|
||||||
FilePrefix string
|
FilePrefix string
|
||||||
FileSuffix string
|
FileSuffix string
|
||||||
|
}
|
||||||
|
|
||||||
MarshalFunc func(K, string, []byte) (T, error)
|
type FileStoreConfig[T store.Storable, K Storer[T]] struct {
|
||||||
UnmarshalFunc func(K, string, T) error
|
FilePathConfig
|
||||||
|
FilepathFunc func(T, *FilePathConfig) string
|
||||||
|
UnmarshalFunc func(K, string, []byte) (T, error)
|
||||||
|
MarshalFunc func(K, string, T) error
|
||||||
|
IndexDirFunc func(*FileStore[T, K]) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileStore[T store.Storable, K Storer[T]] struct {
|
||||||
|
*FileStoreConfig[T, K]
|
||||||
|
|
||||||
|
Storer K
|
||||||
|
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
paths map[string]string
|
paths map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFileStore[T store.Storable, K Storer[T]](
|
func DefaultJSONUnmarshalFunc[T store.Storable, K Storer[T]](s K, filepath string, content []byte) (T, error) {
|
||||||
storer K,
|
entity := new(T)
|
||||||
dir string,
|
err := json.Unmarshal(content, &entity)
|
||||||
prefix string,
|
if err != nil {
|
||||||
suffix string,
|
return *entity, err
|
||||||
marshalFunc func(K, string, []byte) (T, error),
|
}
|
||||||
unmarshalFunc func(K, string, T) error,
|
|
||||||
) (*FileStore[T, K], error) {
|
c, err := s.Create(*entity)
|
||||||
|
if err != nil {
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultJSONMarshalFunc[T store.Storable, K Storer[T]](s K, filePath string, entity T) error {
|
||||||
|
jsonData, err := json.Marshal(entity)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Create(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
_, err = file.Write(jsonData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultIndexDirFunc[T store.Storable, K Storer[T]](s *FileStore[T, K]) error {
|
||||||
|
files, err := os.ReadDir(s.Dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
entityFiles := make([]fs.DirEntry, 0)
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
filename := file.Name()
|
||||||
|
if !file.IsDir() && strings.HasSuffix(filename, s.FileSuffix) {
|
||||||
|
entityFiles = append(entityFiles, file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range entityFiles {
|
||||||
|
filename := file.Name()
|
||||||
|
fullPath := filepath.Join(s.Dir, filename)
|
||||||
|
|
||||||
|
content, err := os.ReadFile(fullPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
entity, err := s.UnmarshalFunc(s.Storer, fullPath, content)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.SetPath(entity, fullPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFileStore[T store.Storable, K Storer[T]](config *FileStoreConfig[T, K], storer K) (*FileStore[T, K], error) {
|
||||||
store := &FileStore[T, K]{
|
store := &FileStore[T, K]{
|
||||||
Storer: storer,
|
FileStoreConfig: config,
|
||||||
Dir: dir,
|
Storer: storer,
|
||||||
FilePrefix: prefix,
|
paths: make(map[string]string, 0),
|
||||||
FileSuffix: suffix,
|
|
||||||
MarshalFunc: marshalFunc,
|
|
||||||
UnmarshalFunc: unmarshalFunc,
|
|
||||||
paths: make(map[string]string, 0),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err := store.IndexDir()
|
err := store.IndexDir()
|
||||||
|
@ -72,9 +145,15 @@ func (s *FileStore[T, K]) Create(entity T) (T, error) {
|
||||||
return e, err
|
return e, err
|
||||||
}
|
}
|
||||||
|
|
||||||
filePath := filepath.Join(s.Dir, fmt.Sprintf("%s_%v%s", s.FilePrefix, e.GetID(), s.FileSuffix))
|
var filePath string
|
||||||
|
|
||||||
err = s.UnmarshalFunc(s.Storer, filePath, e)
|
if s.FilepathFunc == nil {
|
||||||
|
filePath = filepath.Join(s.Dir, fmt.Sprintf("%s_%v%s", s.FilePrefix, e.GetID(), s.FileSuffix))
|
||||||
|
} else {
|
||||||
|
filePath = s.FilepathFunc(entity, &s.FilePathConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.MarshalFunc(s.Storer, filePath, e)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return e, err
|
return e, err
|
||||||
}
|
}
|
||||||
|
@ -90,15 +169,13 @@ func (s *FileStore[T, K]) Update(entity T, id string) (T, error) {
|
||||||
return e, err
|
return e, err
|
||||||
}
|
}
|
||||||
|
|
||||||
filePath := filepath.Join(s.Dir, fmt.Sprintf("%s_%v%s", s.FilePrefix, e.GetID(), s.FileSuffix))
|
filePath := s.GetPath(e)
|
||||||
|
|
||||||
err = s.UnmarshalFunc(s.Storer, filePath, e)
|
err = s.MarshalFunc(s.Storer, filePath, e)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return e, err
|
return e, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.SetPath(e, filePath)
|
|
||||||
|
|
||||||
return e, nil
|
return e, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,39 +202,7 @@ func (s *FileStore[T, K]) Delete(id string) (T, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FileStore[T, K]) IndexDir() error {
|
func (s *FileStore[T, K]) IndexDir() error {
|
||||||
files, err := os.ReadDir(s.Dir)
|
return s.IndexDirFunc(s)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
entityFiles := make([]fs.DirEntry, 0)
|
|
||||||
|
|
||||||
for _, file := range files {
|
|
||||||
filename := file.Name()
|
|
||||||
if !file.IsDir() && strings.HasSuffix(filename, s.FileSuffix) {
|
|
||||||
entityFiles = append(entityFiles, file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, file := range entityFiles {
|
|
||||||
filename := file.Name()
|
|
||||||
fullPath := filepath.Join(s.Dir, filename)
|
|
||||||
|
|
||||||
content, err := os.ReadFile(fullPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
entity, err := s.MarshalFunc(s.Storer, fullPath, content)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.SetPath(entity, fullPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FileStore[T, K]) GetPath(entity T) string {
|
func (s *FileStore[T, K]) GetPath(entity T) string {
|
||||||
|
|
|
@ -13,5 +13,6 @@ func TestRunner(t *testing.T) {
|
||||||
t,
|
t,
|
||||||
new(quizTestSuite),
|
new(quizTestSuite),
|
||||||
new(collectionTestSuite),
|
new(collectionTestSuite),
|
||||||
|
new(participantTestSuite),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
51
store/file/participant.go
Normal file
51
store/file/participant.go
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.andreafazzi.eu/andrea/probo/models"
|
||||||
|
"git.andreafazzi.eu/andrea/probo/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ParticipantFileStore = FileStore[*models.Participant, *store.Store[*models.Participant]]
|
||||||
|
|
||||||
|
func NewParticipantFileStore(config *FileStoreConfig[*models.Participant, *store.Store[*models.Participant]]) (*ParticipantFileStore, error) {
|
||||||
|
return NewFileStore[*models.Participant](config, store.NewStore[*models.Participant]())
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultUnmarshalParticipantFunc(s *store.Store[*models.Participant], filepath string, content []byte) (*models.Participant, error) {
|
||||||
|
participant := new(models.Participant)
|
||||||
|
err := json.Unmarshal(content, &participant)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := s.Create(participant)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultMarshalParticipantFunc(s *store.Store[*models.Participant], filePath string, participant *models.Participant) error {
|
||||||
|
jsonData, err := json.Marshal(participant)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Create(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
_, err = file.Write(jsonData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
44
store/file/participant_test.go
Normal file
44
store/file/participant_test.go
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
package file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.andreafazzi.eu/andrea/probo/models"
|
||||||
|
"git.andreafazzi.eu/andrea/probo/store"
|
||||||
|
"github.com/remogatto/prettytest"
|
||||||
|
)
|
||||||
|
|
||||||
|
type participantTestSuite struct {
|
||||||
|
prettytest.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *participantTestSuite) TestCreate() {
|
||||||
|
filePathConfig := FilePathConfig{"testdata/participants", "participant", ".json"}
|
||||||
|
pStore, err := NewParticipantFileStore(
|
||||||
|
&FileStoreConfig[*models.Participant, *store.ParticipantStore]{
|
||||||
|
FilePathConfig: filePathConfig,
|
||||||
|
IndexDirFunc: DefaultIndexDirFunc[*models.Participant, *store.ParticipantStore],
|
||||||
|
UnmarshalFunc: DefaultJSONUnmarshalFunc[*models.Participant, *store.ParticipantStore],
|
||||||
|
MarshalFunc: DefaultJSONMarshalFunc[*models.Participant, *store.ParticipantStore],
|
||||||
|
})
|
||||||
|
t.Nil(err)
|
||||||
|
|
||||||
|
if !t.Failed() {
|
||||||
|
p, err := pStore.Create(&models.Participant{
|
||||||
|
Lastname: "Doe",
|
||||||
|
Firstname: "John",
|
||||||
|
Attributes: map[string]string{"class": "1 D LIN"},
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Nil(err)
|
||||||
|
|
||||||
|
defer os.Remove(pStore.GetPath(p))
|
||||||
|
|
||||||
|
if !t.Failed() {
|
||||||
|
exists, err := os.Stat(pStore.GetPath(p))
|
||||||
|
|
||||||
|
t.Nil(err)
|
||||||
|
t.Not(t.Nil(exists))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -15,60 +14,59 @@ import (
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewQuizFileStore() (*FileStore[*models.Quiz, *store.QuizStore], error) {
|
type QuizFileStore = FileStore[*models.Quiz, *store.QuizStore]
|
||||||
return NewFileStore[*models.Quiz, *store.QuizStore](
|
|
||||||
store.NewQuizStore(),
|
|
||||||
filepath.Join(BaseDir, QuizzesDir),
|
|
||||||
"quiz",
|
|
||||||
".md",
|
|
||||||
func(s *store.QuizStore, filepath string, content []byte) (*models.Quiz, error) {
|
|
||||||
quiz, meta, err := models.MarkdownToQuiz(string(content))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var errQuizAlreadyPresent *store.ErrQuizAlreadyPresent
|
func NewQuizFileStore(config *FileStoreConfig[*models.Quiz, *store.QuizStore]) (*QuizFileStore, error) {
|
||||||
|
return NewFileStore[*models.Quiz, *store.QuizStore](config, store.NewQuizStore())
|
||||||
|
}
|
||||||
|
|
||||||
q, err := s.Create(quiz)
|
func DefaultUnmarshalQuizFunc(s *store.QuizStore, filepath string, content []byte) (*models.Quiz, error) {
|
||||||
if err != nil && !errors.As(err, &errQuizAlreadyPresent) {
|
quiz, meta, err := models.MarkdownToQuiz(string(content))
|
||||||
return nil, err
|
if err != nil {
|
||||||
}
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if meta == nil {
|
var errQuizAlreadyPresent *store.ErrQuizAlreadyPresent
|
||||||
writeQuizHeader(filepath, &models.Meta{
|
|
||||||
ID: q.ID,
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return q, nil
|
q, err := s.Create(quiz)
|
||||||
},
|
if err != nil && !errors.As(err, &errQuizAlreadyPresent) {
|
||||||
func(s *store.QuizStore, filePath string, quiz *models.Quiz) error {
|
return nil, err
|
||||||
markdown, err := models.QuizToMarkdown(quiz)
|
}
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
file, err := os.Create(filePath)
|
if meta == nil {
|
||||||
if err != nil {
|
writeQuizHeader(filepath, &models.Meta{
|
||||||
return err
|
ID: q.ID,
|
||||||
}
|
CreatedAt: time.Now(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
defer file.Close()
|
return q, nil
|
||||||
|
}
|
||||||
|
|
||||||
markdownWithMetaHeader, err := addMetaHeaderToMarkdown(markdown, &quiz.Meta)
|
func DefaultMarshalQuizFunc(s *store.QuizStore, filePath string, quiz *models.Quiz) error {
|
||||||
if err != nil {
|
markdown, err := models.QuizToMarkdown(quiz)
|
||||||
return err
|
if err != nil {
|
||||||
}
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
_, err = file.Write([]byte(markdownWithMetaHeader))
|
file, err := os.Create(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
defer file.Close()
|
||||||
},
|
|
||||||
)
|
markdownWithMetaHeader, err := addMetaHeaderToMarkdown(markdown, &quiz.Meta)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = file.Write([]byte(markdownWithMetaHeader))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeQuizHeader(path string, meta *models.Meta) (*models.Meta, error) {
|
func writeQuizHeader(path string, meta *models.Meta) (*models.Meta, error) {
|
||||||
|
@ -214,3 +212,57 @@ func removeQuizHeader(path string) (*models.Meta, error) {
|
||||||
|
|
||||||
return &meta, nil
|
return &meta, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// filepath.Join(BaseDir, QuizzesDir),
|
||||||
|
// "quiz",
|
||||||
|
// ".md",
|
||||||
|
// DefaultIndexDirFunc,
|
||||||
|
// nil,
|
||||||
|
// func(s *store.QuizStore, filepath string, content []byte) (*models.Quiz, error) {
|
||||||
|
// quiz, meta, err := models.MarkdownToQuiz(string(content))
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
|
||||||
|
// var errQuizAlreadyPresent *store.ErrQuizAlreadyPresent
|
||||||
|
|
||||||
|
// q, err := s.Create(quiz)
|
||||||
|
// if err != nil && !errors.As(err, &errQuizAlreadyPresent) {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if meta == nil {
|
||||||
|
// writeQuizHeader(filepath, &models.Meta{
|
||||||
|
// ID: q.ID,
|
||||||
|
// CreatedAt: time.Now(),
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return q, nil
|
||||||
|
// },
|
||||||
|
// func(s *store.QuizStore, filePath string, quiz *models.Quiz) error {
|
||||||
|
// markdown, err := models.QuizToMarkdown(quiz)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
|
||||||
|
// file, err := os.Create(filePath)
|
||||||
|
// 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
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return nil
|
||||||
|
// },
|
||||||
|
// )
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"git.andreafazzi.eu/andrea/probo/models"
|
"git.andreafazzi.eu/andrea/probo/models"
|
||||||
|
"git.andreafazzi.eu/andrea/probo/store"
|
||||||
"github.com/remogatto/prettytest"
|
"github.com/remogatto/prettytest"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -13,12 +14,16 @@ type quizTestSuite struct {
|
||||||
prettytest.Suite
|
prettytest.Suite
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *quizTestSuite) BeforeAll() {
|
|
||||||
BaseDir = "testdata"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *quizTestSuite) TestReadAll() {
|
func (t *quizTestSuite) TestReadAll() {
|
||||||
store, err := NewQuizFileStore()
|
filePathConfig := FilePathConfig{"testdata/quizzes", "quiz", ".md"}
|
||||||
|
store, err := NewQuizFileStore(
|
||||||
|
&FileStoreConfig[*models.Quiz, *store.QuizStore]{
|
||||||
|
FilePathConfig: filePathConfig,
|
||||||
|
IndexDirFunc: DefaultIndexDirFunc[*models.Quiz, *store.QuizStore],
|
||||||
|
UnmarshalFunc: DefaultUnmarshalQuizFunc,
|
||||||
|
MarshalFunc: DefaultMarshalQuizFunc,
|
||||||
|
},
|
||||||
|
)
|
||||||
t.Nil(err)
|
t.Nil(err)
|
||||||
|
|
||||||
if !t.Failed() {
|
if !t.Failed() {
|
||||||
|
@ -36,7 +41,15 @@ func (t *quizTestSuite) TestReadAll() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *quizTestSuite) TestCreate() {
|
func (t *quizTestSuite) TestCreate() {
|
||||||
store, err := NewQuizFileStore()
|
filePathConfig := FilePathConfig{"testdata/quizzes", "quiz", ".md"}
|
||||||
|
store, err := NewQuizFileStore(
|
||||||
|
&FileStoreConfig[*models.Quiz, *store.QuizStore]{
|
||||||
|
FilePathConfig: filePathConfig,
|
||||||
|
IndexDirFunc: DefaultIndexDirFunc[*models.Quiz, *store.QuizStore],
|
||||||
|
UnmarshalFunc: DefaultUnmarshalQuizFunc,
|
||||||
|
MarshalFunc: DefaultMarshalQuizFunc,
|
||||||
|
},
|
||||||
|
)
|
||||||
t.Nil(err)
|
t.Nil(err)
|
||||||
|
|
||||||
if !t.Failed() {
|
if !t.Failed() {
|
||||||
|
@ -90,7 +103,15 @@ func (t *quizTestSuite) TestCreate() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *quizTestSuite) TestDelete() {
|
func (t *quizTestSuite) TestDelete() {
|
||||||
store, err := NewQuizFileStore()
|
filePathConfig := FilePathConfig{"testdata/quizzes", "quiz", ".md"}
|
||||||
|
store, err := NewQuizFileStore(
|
||||||
|
&FileStoreConfig[*models.Quiz, *store.QuizStore]{
|
||||||
|
FilePathConfig: filePathConfig,
|
||||||
|
IndexDirFunc: DefaultIndexDirFunc[*models.Quiz, *store.QuizStore],
|
||||||
|
UnmarshalFunc: DefaultUnmarshalQuizFunc,
|
||||||
|
MarshalFunc: DefaultMarshalQuizFunc,
|
||||||
|
},
|
||||||
|
)
|
||||||
t.Nil(err)
|
t.Nil(err)
|
||||||
|
|
||||||
if !t.Failed() {
|
if !t.Failed() {
|
||||||
|
@ -121,7 +142,15 @@ func (t *quizTestSuite) TestDelete() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *quizTestSuite) TestUpdate() {
|
func (t *quizTestSuite) TestUpdate() {
|
||||||
store, err := NewQuizFileStore()
|
filePathConfig := FilePathConfig{"testdata/quizzes", "quiz", ".md"}
|
||||||
|
store, err := NewQuizFileStore(
|
||||||
|
&FileStoreConfig[*models.Quiz, *store.QuizStore]{
|
||||||
|
FilePathConfig: filePathConfig,
|
||||||
|
IndexDirFunc: DefaultIndexDirFunc[*models.Quiz, *store.QuizStore],
|
||||||
|
UnmarshalFunc: DefaultUnmarshalQuizFunc,
|
||||||
|
MarshalFunc: DefaultMarshalQuizFunc,
|
||||||
|
},
|
||||||
|
)
|
||||||
t.Nil(err)
|
t.Nil(err)
|
||||||
|
|
||||||
if !t.Failed() {
|
if !t.Failed() {
|
||||||
|
@ -161,7 +190,15 @@ func (t *quizTestSuite) TestUpdate() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *quizTestSuite) TestAutowriteHeader() {
|
func (t *quizTestSuite) TestAutowriteHeader() {
|
||||||
store, err := NewQuizFileStore()
|
filePathConfig := FilePathConfig{"testdata/quizzes", "quiz", ".md"}
|
||||||
|
store, err := NewQuizFileStore(
|
||||||
|
&FileStoreConfig[*models.Quiz, *store.QuizStore]{
|
||||||
|
FilePathConfig: filePathConfig,
|
||||||
|
IndexDirFunc: DefaultIndexDirFunc[*models.Quiz, *store.QuizStore],
|
||||||
|
UnmarshalFunc: DefaultUnmarshalQuizFunc,
|
||||||
|
MarshalFunc: DefaultMarshalQuizFunc,
|
||||||
|
},
|
||||||
|
)
|
||||||
t.Nil(err)
|
t.Nil(err)
|
||||||
|
|
||||||
if !t.Failed() {
|
if !t.Failed() {
|
||||||
|
|
4
store/file/testdata/quizzes/quiz_5.md
vendored
4
store/file/testdata/quizzes/quiz_5.md
vendored
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
id: 57ebde24-51a0-421e-a641-4a5c198473c3
|
id: edadaa90-802f-4a74-83cc-4b4cb5192f28
|
||||||
created_at: 2023-11-13T21:02:55.786449849+01:00
|
created_at: 2023-11-17T16:50:38.479350601+01:00
|
||||||
updated_at: 0001-01-01T00:00:00Z
|
updated_at: 0001-01-01T00:00:00Z
|
||||||
---
|
---
|
||||||
This quiz is initially without metadata.
|
This quiz is initially without metadata.
|
||||||
|
|
5
store/participant.go
Normal file
5
store/participant.go
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
package store
|
||||||
|
|
||||||
|
import "git.andreafazzi.eu/andrea/probo/models"
|
||||||
|
|
||||||
|
type ParticipantStore = Store[*models.Participant]
|
52
store/participant_test.go
Normal file
52
store/participant_test.go
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
package store
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.andreafazzi.eu/andrea/probo/models"
|
||||||
|
"github.com/remogatto/prettytest"
|
||||||
|
)
|
||||||
|
|
||||||
|
type participantTestSuite struct {
|
||||||
|
prettytest.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *participantTestSuite) TestCreate() {
|
||||||
|
store := NewStore[*models.Participant]()
|
||||||
|
|
||||||
|
p_1, err := store.Create(&models.Participant{
|
||||||
|
Lastname: "Doe",
|
||||||
|
Firstname: "John",
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Nil(err)
|
||||||
|
|
||||||
|
p_2, err := store.Create(&models.Participant{
|
||||||
|
Lastname: "Doe",
|
||||||
|
Firstname: "John",
|
||||||
|
Attributes: map[string]string{"class": "1 D LIN"},
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Nil(err)
|
||||||
|
|
||||||
|
t.False(p_1.GetHash() == p_2.GetHash())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *participantTestSuite) TestUpdate() {
|
||||||
|
store := NewStore[*models.Participant]()
|
||||||
|
|
||||||
|
p, err := store.Create(&models.Participant{
|
||||||
|
Lastname: "Doe",
|
||||||
|
Firstname: "John",
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Nil(err)
|
||||||
|
|
||||||
|
updatedP, err := store.Update(&models.Participant{
|
||||||
|
Lastname: "Doe",
|
||||||
|
Firstname: "John",
|
||||||
|
Attributes: map[string]string{"class": "1 D LIN"},
|
||||||
|
}, p.ID)
|
||||||
|
|
||||||
|
t.Nil(err)
|
||||||
|
|
||||||
|
t.False(p.GetHash() == updatedP.GetHash())
|
||||||
|
}
|
|
@ -11,5 +11,6 @@ func TestRunner(t *testing.T) {
|
||||||
t,
|
t,
|
||||||
new(quizTestSuite),
|
new(quizTestSuite),
|
||||||
new(collectionTestSuite),
|
new(collectionTestSuite),
|
||||||
|
new(participantTestSuite),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue