diff --git a/cli/.gitignore b/cli/.gitignore index 45b31d1..2de4cc4 100644 --- a/cli/.gitignore +++ b/cli/.gitignore @@ -1,3 +1,3 @@ cli testdata - +*.bk diff --git a/cli/main.go b/cli/main.go index e6d6ac1..c1bfe44 100644 --- a/cli/main.go +++ b/cli/main.go @@ -1,10 +1,11 @@ package main import ( + "fmt" "log" "os" - sessionmanager "git.andreafazzi.eu/andrea/probo/session" + "git.andreafazzi.eu/andrea/probo/sessionmanager" "git.andreafazzi.eu/andrea/probo/store/file" "github.com/urfave/cli/v2" ) @@ -12,10 +13,43 @@ import ( func main() { file.DefaultBaseDir = "testdata" + sStore, err := file.NewDefaultSessionFileStore() + if err != nil { + log.Fatalf("An error occurred: %v", err) + } + + pStore, err := file.NewParticipantDefaultFileStore() + if err != nil { + log.Fatalf("An error occurred: %v", err) + } + qStore, err := file.NewDefaultQuizFileStore() + if err != nil { + log.Fatalf("An error occurred: %v", err) + } + + sm, err := sessionmanager.NewSessionManager( + "http://localhost:8080/", + pStore.Storer, + qStore.Storer, + nil, + nil, + ) + if err != nil { + log.Fatalf("An error occurred: %v", err) + } + app := &cli.App{ Name: "probo-cli", - Usage: "Quiz Management System for Power Teachers!", + Usage: "Quiz Management System for Hackers!", Commands: []*cli.Command{ + { + Name: "init", + Aliases: []string{"i"}, + Usage: "Initialize the current directory", + Action: func(cCtx *cli.Context) error { + return nil + }, + }, { Name: "session", Aliases: []string{"s"}, @@ -28,42 +62,77 @@ func main() { if cCtx.Args().Len() < 1 { log.Fatalf("Please provide a session name as first argument of create.") } - pStore, err := file.NewParticipantDefaultFileStore() - if err != nil { - log.Fatalf("An error occurred: %v", err) - } - qStore, err := file.NewDefaultQuizFileStore() + + session, err := sm.Push(cCtx.Args().First()) if err != nil { log.Fatalf("An error occurred: %v", err) } - sm, err := sessionmanager.NewSessionManager( - "http://localhost:8080/create", - cCtx.Args().First(), - pStore.Storer, - qStore.Storer, - nil, - nil, - ) + _, err = sStore.Create(session) if err != nil { log.Fatalf("An error occurred: %v", err) } - id, err := sm.Push() - if err != nil { - log.Fatalf("An error occurred: %v", err) - } - - log.Printf("Session upload completed with success. URL: https://localhost:8080/%v", id) + log.Println("Session upload completed with success!") for _, p := range pStore.ReadAll() { - log.Printf("http://localhost:8080/%v/%v", id, p.Token) + log.Printf("http://localhost:8080/%v/%v", session.ID, p.Token) } return nil }, }, + { + Name: "pull", + Usage: "Download responses from a session", + Action: func(cCtx *cli.Context) error { + if cCtx.Args().Len() < 1 { + log.Fatalf("Please provide a session ID as first argument of pull.") + } + + rStore, err := file.NewDefaultResponseFileStore() + if err != nil { + log.Fatalf("An error occurred: %v", err) + } + err = sm.Pull(rStore, cCtx.Args().First()) + if err != nil { + log.Fatalf("An error occurred: %v", err) + } + + log.Println("Responses download completed with success!") + + return nil + + }, + }, + { + Name: "scores", + Usage: "Show the scores for the given session", + Action: func(cCtx *cli.Context) error { + if cCtx.Args().Len() < 1 { + log.Fatalf("Please provide a session name as first argument of 'score'.") + } + session, err := sStore.Read(cCtx.Args().First()) + if err != nil { + log.Fatalf("An error occurred: %v", err) + } + rStore, err := file.NewDefaultResponseFileStore() + if err != nil { + log.Fatalf("An error occurred: %v", err) + } + + scores, err := sessionmanager.NewScores(rStore, session) + if err != nil { + log.Fatalf("An error occurred: %v", err) + } + + fmt.Println(scores) + + return nil + + }, + }, }, }, }, diff --git a/models/exam.go b/models/exam.go index 508d14d..cd36235 100644 --- a/models/exam.go +++ b/models/exam.go @@ -1,12 +1,16 @@ package models import ( + "crypto/sha256" "encoding/json" "fmt" + "strings" ) type Exam struct { Meta + + SessionID string Participant *Participant Quizzes []*Quiz } @@ -16,7 +20,11 @@ func (e *Exam) String() string { } func (e *Exam) GetHash() string { - return "" + qHashes := "" + for _, q := range e.Quizzes { + qHashes += q.GetHash() + } + return fmt.Sprintf("%x", sha256.Sum256([]byte(strings.Join([]string{e.Participant.GetHash(), qHashes}, "")))) } func (e *Exam) Marshal() ([]byte, error) { diff --git a/models/meta.go b/models/meta.go index 49e814a..19646d6 100644 --- a/models/meta.go +++ b/models/meta.go @@ -6,9 +6,14 @@ type Meta struct { ID string `json:"id" yaml:"id" gorm:"primaryKey"` CreatedAt time.Time `json:"created_at" yaml:"created_at"` UpdatedAt time.Time `json:"updated_at" yaml:"updated_at"` + + UniqueIDFunc func() string `json:"-" yaml:"-"` } func (m *Meta) GetID() string { + if m.UniqueIDFunc != nil { + return m.UniqueIDFunc() + } return m.ID } diff --git a/models/response.go b/models/response.go index bbdb775..82eda44 100644 --- a/models/response.go +++ b/models/response.go @@ -1,23 +1,23 @@ package models import ( - "crypto/sha256" "encoding/json" "fmt" ) type Response struct { Meta - QuestionID string - AnswerID string + + Questions map[string]string } func (r *Response) String() string { - return fmt.Sprintf("QID: %v, AID:%v", r.QuestionID, r.AnswerID) + return fmt.Sprintf("Questions/Answers: %v", r.Questions) } func (r *Response) GetHash() string { - return fmt.Sprintf("%x", sha256.Sum256([]byte(r.QuestionID+r.AnswerID))) + // return fmt.Sprintf("%x", sha256.Sum256([]byte(r.QuestionID+r.AnswerID))) + return "" } func (r *Response) Marshal() ([]byte, error) { diff --git a/server/.gitignore b/server/.gitignore index 254defd..a1e9e7d 100644 --- a/server/.gitignore +++ b/server/.gitignore @@ -1 +1,2 @@ server +data diff --git a/server/data/sessions/session_24e5a5f5-8293-4e13-a825-656117db4d5b.json b/server/data/sessions/session_24e5a5f5-8293-4e13-a825-656117db4d5b.json deleted file mode 100644 index 500d4aa..0000000 --- a/server/data/sessions/session_24e5a5f5-8293-4e13-a825-656117db4d5b.json +++ /dev/null @@ -1 +0,0 @@ -{"id":"24e5a5f5-8293-4e13-a825-656117db4d5b","created_at":"2023-12-12T09:00:57.405268351+01:00","updated_at":"2023-12-12T09:20:41.050431537+01:00","Name":"My session","Exams":{"111222":{"id":"c0140b05-a843-4599-b405-3517357bd2b5","created_at":"2023-12-12T09:00:57.402306603+01:00","updated_at":"2023-12-12T09:00:57.402306722+01:00","Participant":{"id":"1234","created_at":"2023-12-05T21:11:37.566330708+01:00","updated_at":"2023-12-12T09:00:57.402100526+01:00","Firstname":"Giuly","Lastname":"Mon Amour","Token":"111222","Attributes":{"class":"1 D LIN"}},"Quizzes":[{"id":"908bb025-6370-4273-8448-8470f9336f26","created_at":"2023-12-05T21:22:06.322398595+01:00","updated_at":"2023-12-12T09:00:57.40230252+01:00","hash":"","question":{"id":"8e963b00-477a-4a1a-bf22-d74b931f9058","created_at":"2023-12-12T09:00:57.402266804+01:00","updated_at":"2023-12-12T09:00:57.402266953+01:00","text":"Chi è l'#amore più grande della mia vita?"},"answers":[{"id":"df78ba57-b53b-411d-a8fc-ef57bce961d2","created_at":"2023-12-12T09:00:57.402274057+01:00","updated_at":"2023-12-12T09:00:57.40227419+01:00","text":"Giuly (❤️)"},{"id":"eb89e7fa-0909-4f84-bcbb-5fe89a015757","created_at":"2023-12-12T09:00:57.402276551+01:00","updated_at":"2023-12-12T09:00:57.402276604+01:00","text":"Daisy"},{"id":"3185a66b-5fce-4e22-bcd0-6dc06f2d4261","created_at":"2023-12-12T09:00:57.402278951+01:00","updated_at":"2023-12-12T09:00:57.402279003+01:00","text":"Jenny"},{"id":"49bbe2ca-9408-40e9-8625-442b27b32026","created_at":"2023-12-12T09:00:57.402281071+01:00","updated_at":"2023-12-12T09:00:57.402281125+01:00","text":"Lilly"}],"tags":[],"correct":{"id":"df78ba57-b53b-411d-a8fc-ef57bce961d2","created_at":"2023-12-12T09:00:57.402274057+01:00","updated_at":"2023-12-12T09:00:57.40227419+01:00","text":"Giuly (❤️)"},"CorrectPos":0,"type":0}]},"333222":{"id":"5b6e3f92-1a6f-4028-b618-ad434a1f6e16","created_at":"2023-12-12T09:00:57.402308673+01:00","updated_at":"2023-12-12T09:00:57.402308733+01:00","Participant":{"id":"564d0c7f-4840-443c-8325-ecd02d03624d","created_at":"2023-12-05T21:11:37.566330708+01:00","updated_at":"2023-12-12T09:00:57.401968656+01:00","Firstname":"Andrea","Lastname":"Fazzi","Token":"333222","Attributes":{"class":"1 D LIN"}},"Quizzes":[{"id":"908bb025-6370-4273-8448-8470f9336f26","created_at":"2023-12-05T21:22:06.322398595+01:00","updated_at":"2023-12-12T09:00:57.40230252+01:00","hash":"","question":{"id":"8e963b00-477a-4a1a-bf22-d74b931f9058","created_at":"2023-12-12T09:00:57.402266804+01:00","updated_at":"2023-12-12T09:00:57.402266953+01:00","text":"Chi è l'#amore più grande della mia vita?"},"answers":[{"id":"df78ba57-b53b-411d-a8fc-ef57bce961d2","created_at":"2023-12-12T09:00:57.402274057+01:00","updated_at":"2023-12-12T09:00:57.40227419+01:00","text":"Giuly (❤️)"},{"id":"eb89e7fa-0909-4f84-bcbb-5fe89a015757","created_at":"2023-12-12T09:00:57.402276551+01:00","updated_at":"2023-12-12T09:00:57.402276604+01:00","text":"Daisy"},{"id":"3185a66b-5fce-4e22-bcd0-6dc06f2d4261","created_at":"2023-12-12T09:00:57.402278951+01:00","updated_at":"2023-12-12T09:00:57.402279003+01:00","text":"Jenny"},{"id":"49bbe2ca-9408-40e9-8625-442b27b32026","created_at":"2023-12-12T09:00:57.402281071+01:00","updated_at":"2023-12-12T09:00:57.402281125+01:00","text":"Lilly"}],"tags":[],"correct":{"id":"df78ba57-b53b-411d-a8fc-ef57bce961d2","created_at":"2023-12-12T09:00:57.402274057+01:00","updated_at":"2023-12-12T09:00:57.40227419+01:00","text":"Giuly (❤️)"},"CorrectPos":0,"type":0}]}}} \ No newline at end of file diff --git a/server/main.go b/server/main.go index b4d422c..2f90bb8 100644 --- a/server/main.go +++ b/server/main.go @@ -119,6 +119,7 @@ func NewServer(config *Config) (*Server, error) { s.mux.Handle("/static/", http.StripPrefix("/static", http.FileServer(http.Dir(config.StaticDir)))) s.mux.HandleFunc("/create", s.createExamSessionHandler) + s.mux.HandleFunc("/responses/", s.getResponsesHandler) s.mux.HandleFunc("/", s.getExamHandler) return s, nil @@ -133,6 +134,35 @@ func NewDefaultServer() (*Server, error) { }) } +func (s *Server) getResponsesHandler(w http.ResponseWriter, r *http.Request) { + result := make([]*models.Response, 0) + + urlParts := strings.Split(r.URL.Path, "/") + + sessionID := urlParts[2] + + if r.Method == "GET" { + session, err := s.sessionFileStore.Read(sessionID) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + for _, exam := range session.Exams { + responses := s.responseFileStore.ReadAll() + for _, r := range responses { + if r.ID == exam.ID { + result = append(result, r) + } + } + } + err = json.NewEncoder(w).Encode(result) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } +} + func (s *Server) createExamSessionHandler(w http.ResponseWriter, r *http.Request) { session := new(models.Session) @@ -154,8 +184,12 @@ func (s *Server) createExamSessionHandler(w http.ResponseWriter, r *http.Request return } - response := map[string]string{"id": memorySession.ID} - json.NewEncoder(w).Encode(response) + err = json.NewEncoder(w).Encode(memorySession) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } func (s *Server) getExamHandler(w http.ResponseWriter, r *http.Request) { @@ -197,22 +231,25 @@ func (s *Server) getExamHandler(w http.ResponseWriter, r *http.Request) { return } - parts := strings.Split(r.FormValue("answer"), "_") - _, err = s.responseFileStore.Create(&models.Response{ - QuestionID: parts[0], - AnswerID: parts[1], - }) + response := new(models.Response) + response.UniqueIDFunc = func() string { + return exam.GetID() + } + + response.Questions = make(map[string]string) + for qID, values := range r.Form { + for _, aID := range values { + response.Questions[qID] = aID + } + } + + _, err = s.responseFileStore.Create(response) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } - w.Header().Set("Content-Type", "text/html") - if parts[1] == exam.Quizzes[0].Correct.ID { - w.Write([]byte("

Corretto!

")) - return - } - w.Write([]byte("

Errato!

")) + w.Write([]byte("

Thank you for your response.

")) return } @@ -236,6 +273,6 @@ func main() { panic(err) } - log.Println("Probo server started.", "Config", server.config) + log.Println("Probo server started.") http.ListenAndServe(":8080", server) } diff --git a/server/server_test.go b/server/server_test.go index 46463f8..ea7f28a 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -10,6 +10,7 @@ import ( "strings" "testing" + "git.andreafazzi.eu/andrea/probo/models" "github.com/remogatto/prettytest" ) @@ -215,9 +216,9 @@ func (t *serverTestSuite) TestCreate() { t.Equal(http.StatusOK, response.Code) if !t.Failed() { - result := map[string]string{} + var session *models.Session - err := json.Unmarshal(response.Body.Bytes(), &result) + err := json.Unmarshal(response.Body.Bytes(), &session) t.Nil(err) path := filepath.Join(GetDefaultSessionDir(), "session_fe0a7ee0-f31a-413d-f123-ab5068bcaaaa.json") @@ -227,7 +228,7 @@ func (t *serverTestSuite) TestCreate() { defer os.Remove(path) - t.Equal("fe0a7ee0-f31a-413d-f123-ab5068bcaaaa", result["id"]) + t.Equal("fe0a7ee0-f31a-413d-f123-ab5068bcaaaa", session.ID) } } @@ -251,9 +252,9 @@ func (t *serverTestSuite) TestRead() { t.Equal(http.StatusOK, response.Code) if !t.Failed() { - result := map[string]string{} + var session *models.Session - err := json.Unmarshal(response.Body.Bytes(), &result) + err := json.Unmarshal(response.Body.Bytes(), &session) t.Nil(err) path := filepath.Join(GetDefaultSessionDir(), "session_fe0a7ee0-f31a-413d-f123-ab5068bcaaaa.json") @@ -263,7 +264,7 @@ func (t *serverTestSuite) TestRead() { if !t.Failed() { defer os.RemoveAll(path) - request, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("/%s/%s", result["id"], "111222"), nil) + request, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("/%s/%s", session.ID, "111222"), nil) response := httptest.NewRecorder() handler := http.HandlerFunc(s.getExamHandler) diff --git a/server/templates/exam.tpl b/server/templates/exam.tpl index 6513d02..2c23708 100644 --- a/server/templates/exam.tpl +++ b/server/templates/exam.tpl @@ -2,6 +2,7 @@ + Exam @@ -14,8 +15,8 @@

{{$quiz.Question.Text}}

{{range $answer := $quiz.Answers}} + id="{{$quiz.Question.ID}}_{{$answer.ID}}" name="{{$quiz.Question.ID}}" + value="{{$answer.ID}}">
{{end}} diff --git a/sessionmanager/score.go b/sessionmanager/score.go new file mode 100644 index 0000000..a2974b6 --- /dev/null +++ b/sessionmanager/score.go @@ -0,0 +1,52 @@ +package sessionmanager + +import ( + "fmt" + "log" + + "git.andreafazzi.eu/andrea/probo/models" + "git.andreafazzi.eu/andrea/probo/store/file" +) + +type Score struct { + Exam *models.Exam + Score float32 +} + +type Scores []*Score + +func NewScores(responseFileStore *file.ResponseFileStore, session *models.Session) (Scores, error) { + var scores Scores + + for _, exam := range session.Exams { + response, err := responseFileStore.Read(exam.ID) + if err != nil { + log.Println(err) + continue + } + scores = append(scores, CalcScore(response, exam)) + } + + return scores, nil +} + +func (ss Scores) String() string { + var result string + + for _, s := range ss { + result += fmt.Sprintf("%v\t%f\n", s.Exam.Participant, s.Score) + } + + return result +} + +func CalcScore(response *models.Response, exam *models.Exam) *Score { + var score float32 + for _, q := range exam.Quizzes { + answerId := response.Questions[q.Question.ID] + if answerId == q.Correct.ID { + score++ + } + } + return &Score{exam, score} +} diff --git a/sessionmanager/sessionmanager.go b/sessionmanager/sessionmanager.go index 0908f06..acbf4c2 100644 --- a/sessionmanager/sessionmanager.go +++ b/sessionmanager/sessionmanager.go @@ -5,14 +5,14 @@ import ( "encoding/json" "io" "net/http" + "net/url" "git.andreafazzi.eu/andrea/probo/models" "git.andreafazzi.eu/andrea/probo/store" + "git.andreafazzi.eu/andrea/probo/store/file" ) type SessionManager struct { - Name string - ParticipantStore *store.ParticipantStore QuizStore *store.QuizStore @@ -21,18 +21,23 @@ type SessionManager struct { ServerURL string Token int - examStore *store.ExamStore + examFileStore *file.ExamFileStore } -func NewSessionManager(url string, name string, pStore *store.ParticipantStore, qStore *store.QuizStore, pFilter map[string]string, qFilter map[string]string) (*SessionManager, error) { +func NewSessionManager(url string, pStore *store.ParticipantStore, qStore *store.QuizStore, pFilter map[string]string, qFilter map[string]string) (*SessionManager, error) { sm := new(SessionManager) - sm.Name = name sm.ServerURL = url - sm.examStore = store.NewStore[*models.Exam]() + + var err error + + sm.examFileStore, err = file.NewDefaultExamFileStore() + if err != nil { + return nil, err + } for _, p := range pStore.ReadAll() { - _, err := sm.examStore.Create(&models.Exam{ + _, err := sm.examFileStore.Create(&models.Exam{ Participant: p, Quizzes: qStore.ReadAll(), }) @@ -45,12 +50,45 @@ func NewSessionManager(url string, name string, pStore *store.ParticipantStore, } func (sm *SessionManager) GetExams() []*models.Exam { - return sm.examStore.ReadAll() + return sm.examFileStore.ReadAll() } -func (sm *SessionManager) Push() (string, error) { +func (sm *SessionManager) Pull(rStore *file.ResponseFileStore, sessionID string) error { + url, err := url.JoinPath(sm.ServerURL, "responses/", sessionID) + if err != nil { + return err + } + + rr, err := http.Get(url) + if err != nil { + return err + } + + responseBody, err := io.ReadAll(rr.Body) + if err != nil { + return err + } + + responses := make([]*models.Response, 0) + + err = json.Unmarshal(responseBody, &responses) + if err != nil { + return err + } + + for _, response := range responses { + _, err := rStore.Create(response) + if err != nil { + return err + } + } + + return nil +} + +func (sm *SessionManager) Push(name string) (*models.Session, error) { session := &models.Session{ - Name: sm.Name, + Name: name, Exams: make(map[string]*models.Exam), } @@ -60,24 +98,28 @@ func (sm *SessionManager) Push() (string, error) { payload, err := session.Marshal() if err != nil { - return "", err + return nil, err } - response, err := http.Post(sm.ServerURL, "application/json", bytes.NewReader(payload)) + url, err := url.JoinPath(sm.ServerURL, "create") if err != nil { - return "", err + return nil, err + } + + response, err := http.Post(url, "application/json", bytes.NewReader(payload)) + if err != nil { + return nil, err } responseBody, err := io.ReadAll(response.Body) if err != nil { - return "", err + return nil, err } - result := map[string]string{} - err = json.Unmarshal(responseBody, &result) + err = json.Unmarshal(responseBody, &session) if err != nil { - return "", err + return nil, err } - return result["id"], nil + return session, nil } diff --git a/store/collection.go b/store/collection.go deleted file mode 100644 index 365bad1..0000000 --- a/store/collection.go +++ /dev/null @@ -1,5 +0,0 @@ -package store - -import "git.andreafazzi.eu/andrea/probo/models" - -type CollectionStore = Store[*models.Collection] diff --git a/store/collection_test.go b/store/collection_test.go deleted file mode 100644 index 64ab324..0000000 --- a/store/collection_test.go +++ /dev/null @@ -1,72 +0,0 @@ -package store - -import ( - "git.andreafazzi.eu/andrea/probo/models" - "github.com/remogatto/prettytest" -) - -type collectionTestSuite struct { - prettytest.Suite -} - -func (t *collectionTestSuite) TestCreateCollection() { - quizStore := NewQuizStore() - quiz_1, _ := quizStore.Create( - &models.Quiz{ - Question: &models.Question{Text: "Question text #tag1 #tag3."}, - Answers: []*models.Answer{ - {Text: "Answer 1"}, - {Text: "Answer 2"}, - {Text: "Answer 3"}, - {Text: "Answer 4"}, - }, - }) - - quizStore.Create( - &models.Quiz{ - Question: &models.Question{Text: "Question text #tag2."}, - Answers: []*models.Answer{ - {Text: "Answer 1"}, - {Text: "Answer 2"}, - {Text: "Answer 3"}, - {Text: "Answer 4"}, - }, - }) - - quiz_2, _ := quizStore.Create( - &models.Quiz{ - Question: &models.Question{Text: "Question text #tag3."}, - Answers: []*models.Answer{ - {Text: "Answer 1"}, - {Text: "Answer 2"}, - {Text: "Answer 3"}, - {Text: "Answer 4"}, - }, - }) - - collectionStore := NewStore[*models.Collection]() - collection, err := collectionStore.Create( - &models.Collection{ - Name: "My Collection", - }) - t.Nil(err, "Collection should be created without error") - - if !t.Failed() { - quizzes := quizStore.FilterInCollection(collection, map[string]string{ - "tags": "#tag1,#tag3", - }) - - t.Equal(1, len(quizzes)) - - count := 0 - for _, q := range collection.Quizzes { - if quiz_1.ID == q.ID || quiz_2.ID == q.ID { - count++ - } - } - - t.Equal(1, count) - t.Equal(1, len(collection.Quizzes)) - } - -} diff --git a/store/file/collection.go b/store/file/collection.go deleted file mode 100644 index a980a53..0000000 --- a/store/file/collection.go +++ /dev/null @@ -1,27 +0,0 @@ -package file - -import ( - "git.andreafazzi.eu/andrea/probo/models" - "git.andreafazzi.eu/andrea/probo/store" -) - -type CollectionFileStore = FileStore[*models.Collection, *store.Store[*models.Collection]] - -func NewCollectionFileStore(config *FileStoreConfig[*models.Collection, *store.CollectionStore]) (*CollectionFileStore, error) { - return NewFileStore[*models.Collection](config, store.NewStore[*models.Collection]()) -} - -func NewDefaultCollectionFileStore() (*CollectionFileStore, error) { - return NewCollectionFileStore( - &FileStoreConfig[*models.Collection, *store.CollectionStore]{ - FilePathConfig: FilePathConfig{GetDefaultCollectionsDir(), "collection", ".json"}, - IndexDirFunc: DefaultIndexDirFunc[*models.Collection, *store.CollectionStore], - CreateEntityFunc: func() *models.Collection { - return &models.Collection{ - Quizzes: make([]*models.Quiz, 0), - } - }, - }, - ) - -} diff --git a/store/file/collection_test.go b/store/file/collection_test.go deleted file mode 100644 index 79ac320..0000000 --- a/store/file/collection_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package file - -import ( - "os" - - "git.andreafazzi.eu/andrea/probo/models" - "git.andreafazzi.eu/andrea/probo/store" - "github.com/remogatto/prettytest" -) - -type collectionTestSuite struct { - prettytest.Suite -} - -func (t *collectionTestSuite) TestCreateCollection() { - quizStore := store.NewQuizStore() - - quizStore.Create( - &models.Quiz{ - Question: &models.Question{Text: "Question text #tag1 #tag3."}, - Answers: []*models.Answer{ - {Text: "Answer 1"}, - {Text: "Answer 2"}, - {Text: "Answer 3"}, - {Text: "Answer 4"}, - }, - }) - - quizStore.Create( - &models.Quiz{ - Question: &models.Question{Text: "Question text #tag2."}, - Answers: []*models.Answer{ - {Text: "Answer 1"}, - {Text: "Answer 2"}, - {Text: "Answer 3"}, - {Text: "Answer 4"}, - }, - }) - - quizStore.Create( - &models.Quiz{ - Question: &models.Question{Text: "Question text #tag3."}, - Answers: []*models.Answer{ - {Text: "Answer 1"}, - {Text: "Answer 2"}, - {Text: "Answer 3"}, - {Text: "Answer 4"}, - }, - }) - - store, err := NewDefaultCollectionFileStore() - t.Nil(err) - - c := new(models.Collection) - c.Name = "MyCollection" - - quizStore.FilterInCollection(c, map[string]string{"tags": "#tag3"}) - - _, err = store.Create(c) - - exists, err := os.Stat(store.GetPath(c)) - - t.Nil(err) - t.Not(t.Nil(exists)) - t.Equal(2, len(c.Quizzes)) - - defer os.Remove(store.GetPath(c)) -} diff --git a/store/file/defaults.go b/store/file/defaults.go index 6ee1425..b2a7f62 100644 --- a/store/file/defaults.go +++ b/store/file/defaults.go @@ -11,6 +11,13 @@ var ( DefaultExamsSubdir = "exams" DefaultResponsesSubdir = "responses" DefaultSessionSubdir = "sessions" + + Dirs = []string{ + GetDefaultQuizzesDir(), + GetDefaultParticipantsDir(), + GetDefaultExamsDir(), + GetDefaultSessionDir(), + } ) func GetDefaultQuizzesDir() string { diff --git a/store/file/file_test.go b/store/file/file_test.go index e3e35d7..73fd9f0 100644 --- a/store/file/file_test.go +++ b/store/file/file_test.go @@ -14,9 +14,7 @@ func TestRunner(t *testing.T) { prettytest.Run( t, new(quizTestSuite), - new(collectionTestSuite), new(participantTestSuite), - new(groupTestSuite), new(examTestSuite), ) } diff --git a/store/file/group.go b/store/file/group.go deleted file mode 100644 index 0c2f431..0000000 --- a/store/file/group.go +++ /dev/null @@ -1,27 +0,0 @@ -package file - -import ( - "git.andreafazzi.eu/andrea/probo/models" - "git.andreafazzi.eu/andrea/probo/store" -) - -type GroupFileStore = FileStore[*models.Group, *store.Store[*models.Group]] - -func NewGroupFileStore(config *FileStoreConfig[*models.Group, *store.GroupStore]) (*GroupFileStore, error) { - return NewFileStore[*models.Group](config, store.NewStore[*models.Group]()) -} - -func NewDefaultGroupFileStore() (*GroupFileStore, error) { - return NewGroupFileStore( - &FileStoreConfig[*models.Group, *store.GroupStore]{ - FilePathConfig: FilePathConfig{GetDefaultGroupsDir(), "group", ".csv"}, - IndexDirFunc: DefaultIndexDirFunc[*models.Group, *store.GroupStore], - CreateEntityFunc: func() *models.Group { - return &models.Group{ - Participants: make([]*models.Participant, 0), - } - }, - }, - ) - -} diff --git a/store/file/group_test.go b/store/file/group_test.go deleted file mode 100644 index 4674bc1..0000000 --- a/store/file/group_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package file - -import ( - "fmt" - "os" - - "git.andreafazzi.eu/andrea/probo/models" - "git.andreafazzi.eu/andrea/probo/store" - "github.com/gocarina/gocsv" - "github.com/remogatto/prettytest" -) - -type groupTestSuite struct { - prettytest.Suite -} - -func (t *groupTestSuite) TestCreate() { - participantStore := store.NewParticipantStore() - - participantStore.Create( - &models.Participant{ - Firstname: "John", - Lastname: "Smith", - Token: 111222, - Attributes: models.AttributeList{"class": "1 D LIN"}, - }) - - participantStore.Create( - &models.Participant{ - Firstname: "Jack", - Lastname: "Sparrow", - Token: 222333, - Attributes: models.AttributeList{"class": "2 D LIN"}, - }) - - groupStore, err := NewDefaultGroupFileStore() - t.Nil(err) - - if !t.Failed() { - g := new(models.Group) - g.Name = "Test Group" - - participantStore.FilterInGroup(g, map[string]string{"class": "1 D LIN"}) - - _, err = groupStore.Create(g) - t.Nil(err) - - defer os.Remove(groupStore.GetPath(g)) - - participantsFromDisk, err := readGroupFromCSV(g.GetID()) - t.Nil(err) - - if !t.Failed() { - t.Equal("Smith", participantsFromDisk[0].Lastname) - } - } -} - -func readGroupFromCSV(groupID string) ([]*models.Participant, error) { - // Build the path to the CSV file - csvPath := fmt.Sprintf("testdata/groups/group_%s.csv", groupID) - - // Open the CSV file - file, err := os.Open(csvPath) - if err != nil { - return nil, fmt.Errorf("failed to open CSV file: %w", err) - } - defer file.Close() - - // Parse the CSV file - var participants []*models.Participant - if err := gocsv.UnmarshalFile(file, &participants); err != nil { - return nil, fmt.Errorf("failed to parse CSV file: %w", err) - } - - return participants, nil -} diff --git a/store/file/testdata/exams/participants/jack.json b/store/file/testdata/exams/participants/jack.json index 6e2093f..33e17d0 100644 --- a/store/file/testdata/exams/participants/jack.json +++ b/store/file/testdata/exams/participants/jack.json @@ -1 +1 @@ -{"id":"5467","created_at":"2023-12-05T22:00:51.525533451+01:00","updated_at":"2023-12-11T17:20:07.682915159+01:00","Firstname":"Jack","Lastname":"Sparrow","Token":333444,"Attributes":{"class":"2 D LIN"}} \ No newline at end of file +{"id":"5467","created_at":"2023-12-05T22:00:51.525533451+01:00","updated_at":"2023-12-17T18:54:31.169024304+01:00","Firstname":"Jack","Lastname":"Sparrow","Token":"333444","Attributes":{"class":"2 D LIN"}} \ No newline at end of file diff --git a/store/file/testdata/exams/participants/john.json b/store/file/testdata/exams/participants/john.json index 34f23a7..8b0e8a5 100644 --- a/store/file/testdata/exams/participants/john.json +++ b/store/file/testdata/exams/participants/john.json @@ -1 +1 @@ -{"id":"1234","created_at":"2023-12-05T22:00:51.525601298+01:00","updated_at":"2023-12-11T17:20:07.682995386+01:00","Firstname":"John","Lastname":"Smith","Token":111222,"Attributes":{"class":"1 D LIN"}} \ No newline at end of file +{"id":"1234","created_at":"2023-12-05T22:00:51.525601298+01:00","updated_at":"2023-12-17T18:54:31.169085845+01:00","Firstname":"John","Lastname":"Smith","Token":"111222","Attributes":{"class":"1 D LIN"}} \ No newline at end of file diff --git a/store/file/testdata/exams/participants/wendy.json b/store/file/testdata/exams/participants/wendy.json index 1d03169..aea8b9d 100644 --- a/store/file/testdata/exams/participants/wendy.json +++ b/store/file/testdata/exams/participants/wendy.json @@ -1 +1 @@ -{"id":"567812","created_at":"2023-12-05T22:00:51.525667963+01:00","updated_at":"2023-12-11T17:20:07.683058985+01:00","Firstname":"Wendy","Lastname":"Darling","Token":333444,"Attributes":{"class":"2 D LIN"}} \ No newline at end of file +{"id":"567812","created_at":"2023-12-05T22:00:51.525667963+01:00","updated_at":"2023-12-17T18:54:31.169144803+01:00","Firstname":"Wendy","Lastname":"Darling","Token":"333444","Attributes":{"class":"2 D LIN"}} \ No newline at end of file diff --git a/store/group.go b/store/group.go deleted file mode 100644 index 5b314c3..0000000 --- a/store/group.go +++ /dev/null @@ -1,5 +0,0 @@ -package store - -import "git.andreafazzi.eu/andrea/probo/models" - -type GroupStore = Store[*models.Group]