diff --git a/cli/main.go b/cli/main.go index 7b826f6..e6d6ac1 100644 --- a/cli/main.go +++ b/cli/main.go @@ -4,7 +4,7 @@ import ( "log" "os" - "git.andreafazzi.eu/andrea/probo/session" + sessionmanager "git.andreafazzi.eu/andrea/probo/session" "git.andreafazzi.eu/andrea/probo/store/file" "github.com/urfave/cli/v2" ) @@ -37,7 +37,7 @@ func main() { log.Fatalf("An error occurred: %v", err) } - session, err := session.NewSession( + sm, err := sessionmanager.NewSessionManager( "http://localhost:8080/create", cCtx.Args().First(), pStore.Storer, @@ -49,7 +49,7 @@ func main() { log.Fatalf("An error occurred: %v", err) } - id, err := session.Push() + id, err := sm.Push() if err != nil { log.Fatalf("An error occurred: %v", err) } diff --git a/models/exam.go b/models/exam.go index 27ccd32..508d14d 100644 --- a/models/exam.go +++ b/models/exam.go @@ -1,15 +1,12 @@ package models import ( - "crypto/sha256" "encoding/json" "fmt" - "strings" ) type Exam struct { Meta - Name string Participant *Participant Quizzes []*Quiz } @@ -19,7 +16,7 @@ func (e *Exam) String() string { } func (e *Exam) GetHash() string { - return fmt.Sprintf("%x", sha256.Sum256([]byte(strings.Join([]string{e.Name, e.Participant.GetHash()}, "")))) + return "" } func (e *Exam) Marshal() ([]byte, error) { diff --git a/models/participant.go b/models/participant.go index b61f319..c2b8472 100644 --- a/models/participant.go +++ b/models/participant.go @@ -17,7 +17,7 @@ type Participant struct { Firstname string `csv:"firstname"` Lastname string `csv:"lastname"` - Token int `csv:"token"` + Token string `csv:"token"` Attributes AttributeList `csv:"attributes"` } diff --git a/models/session.go b/models/session.go index 2640e7f..3e3e0fa 100644 --- a/models/session.go +++ b/models/session.go @@ -1 +1,30 @@ package models + +import ( + "crypto/sha256" + "encoding/json" + "fmt" +) + +type Session struct { + Meta + + Name string + Exams map[string]*Exam +} + +func (s *Session) String() string { + return s.Name +} + +func (s *Session) GetHash() string { + return fmt.Sprintf("%x", sha256.Sum256([]byte(s.Name))) +} + +func (s *Session) Marshal() ([]byte, error) { + return json.Marshal(s) +} + +func (s *Session) Unmarshal(data []byte) error { + return json.Unmarshal(data, s) +} diff --git a/server/main.go b/server/main.go index 4a2ffc2..b4d422c 100644 --- a/server/main.go +++ b/server/main.go @@ -2,6 +2,7 @@ package main import ( "encoding/json" + "io" "log" "math/rand" "net/http" @@ -12,24 +13,25 @@ import ( "text/template" "git.andreafazzi.eu/andrea/probo/models" + "git.andreafazzi.eu/andrea/probo/store" "git.andreafazzi.eu/andrea/probo/store/file" ) var ( DefaultDataDir = "data" DefaultSessionDir = "sessions" + DefaultResponseDir = "responses" DefaultTemplateDir = "templates" DefaultStaticDir = "static" ) type Config struct { SessionDir string + ResponseDir string TemplateDir string StaticDir string } -type ExamSession []*models.Exam - type ExamTemplateData struct { *models.Exam @@ -37,13 +39,11 @@ type ExamTemplateData struct { } type Server struct { - config *Config - mux *http.ServeMux - responseStore *file.ResponseFileStore -} + config *Config + mux *http.ServeMux -func GetDefaultSessionDir() string { - return filepath.Join(DefaultDataDir, DefaultSessionDir) + sessionFileStore *file.SessionFileStore + responseFileStore *file.ResponseFileStore } func GetDefaultTemplateDir() string { @@ -54,8 +54,15 @@ func GetDefaultStaticDir() string { return DefaultStaticDir } -func NewServer(config *Config) (*Server, error) { +func GetDefaultSessionDir() string { + return filepath.Join(DefaultDataDir, DefaultSessionDir) +} +func GetDefaultResponseDir() string { + return filepath.Join(DefaultDataDir, DefaultResponseDir) +} + +func NewServer(config *Config) (*Server, error) { _, err := os.Stat(config.SessionDir) if err != nil { return nil, err @@ -71,10 +78,43 @@ func NewServer(config *Config) (*Server, error) { return nil, err } + sStore, err := file.NewSessionFileStore( + &file.FileStoreConfig[*models.Session, *store.SessionStore]{ + FilePathConfig: file.FilePathConfig{Dir: config.SessionDir, FilePrefix: "session", FileSuffix: ".json"}, + IndexDirFunc: file.DefaultIndexDirFunc[*models.Session, *store.SessionStore], + CreateEntityFunc: func() *models.Session { + return &models.Session{} + }, + }, + ) + if err != nil { + return nil, err + } + + rStore, err := file.NewResponseFileStore( + &file.FileStoreConfig[*models.Response, *store.ResponseStore]{ + FilePathConfig: file.FilePathConfig{Dir: config.ResponseDir, FilePrefix: "response", FileSuffix: ".json"}, + IndexDirFunc: file.DefaultIndexDirFunc[*models.Response, *store.ResponseStore], + CreateEntityFunc: func() *models.Response { + return &models.Response{} + }, + }, + ) + if err != nil { + return nil, err + } + + rStore.FilePathConfig = file.FilePathConfig{ + Dir: config.ResponseDir, + FilePrefix: "response", + FileSuffix: ".json", + } + s := &Server{ config, http.NewServeMux(), - nil, + sStore, + rStore, } s.mux.Handle("/static/", http.StripPrefix("/static", http.FileServer(http.Dir(config.StaticDir)))) @@ -87,69 +127,52 @@ func NewServer(config *Config) (*Server, error) { func NewDefaultServer() (*Server, error) { return NewServer(&Config{ SessionDir: GetDefaultSessionDir(), + ResponseDir: GetDefaultResponseDir(), TemplateDir: GetDefaultTemplateDir(), StaticDir: GetDefaultStaticDir(), }) } func (s *Server) createExamSessionHandler(w http.ResponseWriter, r *http.Request) { - var p ExamSession - err := json.NewDecoder(r.Body).Decode(&p) + session := new(models.Session) + + data, err := io.ReadAll(r.Body) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } - id := generateRandomID() - path := filepath.Join(s.config.SessionDir, id) + err = session.Unmarshal(data) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } - err = os.MkdirAll(path, os.ModePerm) + memorySession, err := s.sessionFileStore.Create(session) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } - for _, exam := range p { - file, err := os.Create(filepath.Join(path, strconv.Itoa(exam.Participant.Token)) + ".json") - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - defer file.Close() - - err = json.NewEncoder(file).Encode(exam) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - } - response := map[string]string{"id": id} + response := map[string]string{"id": memorySession.ID} json.NewEncoder(w).Encode(response) } func (s *Server) getExamHandler(w http.ResponseWriter, r *http.Request) { urlParts := strings.Split(r.URL.Path, "/") - examID := urlParts[1] + sessionID := urlParts[1] token := urlParts[2] - filePath := filepath.Join(s.config.SessionDir, examID, token+".json") - - data, err := os.ReadFile(filePath) + session, err := s.sessionFileStore.Read(sessionID) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } - exam := new(models.Exam) - err = json.Unmarshal(data, &exam) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } + exam := session.Exams[token] if r.Method == "GET" { - w.Header().Set("Content-Type", "text/html") tplData, err := os.ReadFile(filepath.Join(GetDefaultTemplateDir(), "exam.tpl")) @@ -160,7 +183,7 @@ func (s *Server) getExamHandler(w http.ResponseWriter, r *http.Request) { } tmpl := template.Must(template.New("exam").Parse(string(tplData))) - err = tmpl.Execute(w, ExamTemplateData{exam, examID}) + err = tmpl.Execute(w, ExamTemplateData{exam, session.ID}) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -174,8 +197,18 @@ 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], + }) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + w.Header().Set("Content-Type", "text/html") - if r.FormValue("answer") == exam.Quizzes[0].Correct.ID { + if parts[1] == exam.Quizzes[0].Correct.ID { w.Write([]byte("
Corretto!
")) return } diff --git a/server/server_test.go b/server/server_test.go index 02a229e..46463f8 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -14,174 +14,176 @@ import ( ) var examPayload = ` -[ - { - "id": "fe0a7ee0-f31a-413d-ba81-ab5068bc4c73", - "created_at": "0001-01-01T00:00:00Z", - "updated_at": "0001-01-01T00:00:00Z", - "Participant": { - "ID": "1234", - "Firstname": "John", - "Lastname": "Smith", - "Token": 111222, - "Attributes": { - "class": "1 D LIN" - } - }, +{ + "ID": "fe0a7ee0-f31a-413d-f123-ab5068bcaaaa", "Name": "Test session", - "Quizzes": [ - { - "id": "0610939b-a1a3-4d0e-bbc4-2aae0e8ee4b9", - "created_at": "2023-11-27T17:51:53.910642221+01:00", - "updated_at": "0001-01-01T00:00:00Z", - "hash": "", - "question": { - "id": "98c0eec9-677f-464e-9e3e-864a859f29a3", - "created_at": "0001-01-01T00:00:00Z", - "updated_at": "0001-01-01T00:00:00Z", - "text": "Question text with #tag1." - }, - "answers": [ - { - "id": "1ab5ff1f-74c8-4efc-abdc-d03a3640f3bc", - "text": "Answer 1" - }, - { - "id": "74547724-b905-476f-8cfc-6ee633f92ef3", - "text": "Answer 2" - }, - { - "id": "96c1a8ee-c50c-4ebc-89e4-9f3ca356adbd", - "text": "Answer 3" - }, - { - "id": "16c4b95e-64ce-4666-8cbe-b66fa59eb23b", - "text": "Answer 4" - } - ], - "tags": [ - { - "CreatedAt": "0001-01-01T00:00:00Z", - "UpdatedAt": "0001-01-01T00:00:00Z", - "DeletedAt": null, - "name": "#tag1" - } - ], - "correct": { - "id": "1ab5ff1f-74c8-4efc-abdc-d03a3640f3bc", - "text": "Answer 1" - }, - "CorrectPos": 0, - "type": 0 - }, - { - "id": "915818c4-b0ce-4efc-8def-7fc8d5fa7454", - "created_at": "2023-11-27T17:51:53.91077796+01:00", - "updated_at": "0001-01-01T00:00:00Z", - "hash": "", - "question": { - "id": "70793f0d-2855-4140-814e-40167464424b", - "created_at": "0001-01-01T00:00:00Z", - "updated_at": "0001-01-01T00:00:00Z", - "text": "Another question text with #tag1." - }, - "answers": [ - { - "id": "1ab5ff1f-74c8-4efc-abdc-d03a3640f3bc", - "text": "Answer 1" - }, - { - "id": "74547724-b905-476f-8cfc-6ee633f92ef3", - "text": "Answer 2" - }, - { - "id": "96c1a8ee-c50c-4ebc-89e4-9f3ca356adbd", - "text": "Answer 3" - }, - { - "id": "16c4b95e-64ce-4666-8cbe-b66fa59eb23b", - "text": "Answer 4" - } - ], - "tags": [ - { - "CreatedAt": "0001-01-01T00:00:00Z", - "UpdatedAt": "0001-01-01T00:00:00Z", - "DeletedAt": null, - "name": "#tag1" - } - ], - "correct": { - "id": "1ab5ff1f-74c8-4efc-abdc-d03a3640f3bc", - "text": "Answer 1" - }, - "CorrectPos": 0, - "type": 0 - } - ] - }, - { - "id": "12345678-abcd-efgh-ijkl-9876543210ef", - "created_at": "2023-12-01T12:00:00Z", - "updated_at": "2023-12-01T12:00:00Z", - "Participant": { - "ID": "5678", - "Firstname": "Jane", - "Lastname": "Doe", - "Token": 333444, - "Attributes": { - "class": "2 A SCI" - } + "Exams": { + "111222": { + "id": "fe0a7ee0-f31a-413d-ba81-ab5068bc4c73", + "created_at": "0001-01-01T00:00:00Z", + "updated_at": "0001-01-01T00:00:00Z", + "Participant": { + "ID": "1234", + "Firstname": "John", + "Lastname": "Smith", + "Token": "111222", + "Attributes": { + "class": "1 D LIN" + } + }, + "Quizzes": [ + { + "id": "0610939b-a1a3-4d0e-bbc4-2aae0e8ee4b9", + "created_at": "2023-11-27T17:51:53.910642221+01:00", + "updated_at": "0001-01-01T00:00:00Z", + "hash": "", + "question": { + "id": "98c0eec9-677f-464e-9e3e-864a859f29a3", + "created_at": "0001-01-01T00:00:00Z", + "updated_at": "0001-01-01T00:00:00Z", + "text": "Question text with #tag1." + }, + "answers": [ + { + "id": "1ab5ff1f-74c8-4efc-abdc-d03a3640f3bc", + "text": "Answer 1" + }, + { + "id": "74547724-b905-476f-8cfc-6ee633f92ef3", + "text": "Answer 2" + }, + { + "id": "96c1a8ee-c50c-4ebc-89e4-9f3ca356adbd", + "text": "Answer 3" + }, + { + "id": "16c4b95e-64ce-4666-8cbe-b66fa59eb23b", + "text": "Answer 4" + } + ], + "tags": [ + { + "CreatedAt": "0001-01-01T00:00:00Z", + "UpdatedAt": "0001-01-01T00:00:00Z", + "DeletedAt": null, + "name": "#tag1" + } + ], + "correct": { + "id": "1ab5ff1f-74c8-4efc-abdc-d03a3640f3bc", + "text": "Answer 1" + }, + "CorrectPos": 0, + "type": 0 + }, + { + "id": "915818c4-b0ce-4efc-8def-7fc8d5fa7454", + "created_at": "2023-11-27T17:51:53.91077796+01:00", + "updated_at": "0001-01-01T00:00:00Z", + "hash": "", + "question": { + "id": "70793f0d-2855-4140-814e-40167464424b", + "created_at": "0001-01-01T00:00:00Z", + "updated_at": "0001-01-01T00:00:00Z", + "text": "Another question text with #tag1." + }, + "answers": [ + { + "id": "1ab5ff1f-74c8-4efc-abdc-d03a3640f3bc", + "text": "Answer 1" + }, + { + "id": "74547724-b905-476f-8cfc-6ee633f92ef3", + "text": "Answer 2" + }, + { + "id": "96c1a8ee-c50c-4ebc-89e4-9f3ca356adbd", + "text": "Answer 3" + }, + { + "id": "16c4b95e-64ce-4666-8cbe-b66fa59eb23b", + "text": "Answer 4" + } + ], + "tags": [ + { + "CreatedAt": "0001-01-01T00:00:00Z", + "UpdatedAt": "0001-01-01T00:00:00Z", + "DeletedAt": null, + "name": "#tag1" + } + ], + "correct": { + "id": "1ab5ff1f-74c8-4efc-abdc-d03a3640f3bc", + "text": "Answer 1" + }, + "CorrectPos": 0, + "type": 0 + } + ] + } }, - "Name": "Test session", - "Quizzes": [ - { - "id": "22222222-abcd-efgh-ijkl-9876543210ef", - "created_at": "2023-12-01T12:00:00Z", - "updated_at": "2023-12-01T12:00:00Z", - "hash": "", - "question": { - "id": "33333333-abcd-efgh-ijkl-9876543210ef", - "created_at": "2023-12-01T12:00:00Z", - "updated_at": "2023-12-01T12:00:00Z", - "text": "Sample question text." - }, - "answers": [ - { - "id": "44444444-abcd-efgh-ijkl-9876543210ef", - "text": "Option 1" - }, - { - "id": "55555555-abcd-efgh-ijkl-9876543210ef", - "text": "Option 2" - }, - { - "id": "66666666-abcd-efgh-ijkl-9876543210ef", - "text": "Option 3" - }, - { - "id": "77777777-abcd-efgh-ijkl-9876543210ef", - "text": "Option 4" - } - ], - "tags": [ - { - "CreatedAt": "2023-12-01T12:00:00Z", - "UpdatedAt": "2023-12-01T12:00:00Z", - "DeletedAt": null, - "name": "#tag2" - } - ], - "correct": { - "id": "44444444-abcd-efgh-ijkl-9876543210ef", - "text": "Option 1" - }, - "CorrectPos": 0, - "type": 0 - } - ] - } -] + "333444": { + "id": "12345678-abcd-efgh-ijkl-9876543210ef", + "created_at": "2023-12-01T12:00:00Z", + "updated_at": "2023-12-01T12:00:00Z", + "Participant": { + "ID": "5678", + "Firstname": "Jane", + "Lastname": "Doe", + "Token": "333444", + "Attributes": { + "class": "2 A SCI" + } + }, + "Quizzes": [ + { + "id": "22222222-abcd-efgh-ijkl-9876543210ef", + "created_at": "2023-12-01T12:00:00Z", + "updated_at": "2023-12-01T12:00:00Z", + "hash": "", + "question": { + "id": "33333333-abcd-efgh-ijkl-9876543210ef", + "created_at": "2023-12-01T12:00:00Z", + "updated_at": "2023-12-01T12:00:00Z", + "text": "Sample question text." + }, + "answers": [ + { + "id": "44444444-abcd-efgh-ijkl-9876543210ef", + "text": "Option 1" + }, + { + "id": "55555555-abcd-efgh-ijkl-9876543210ef", + "text": "Option 2" + }, + { + "id": "66666666-abcd-efgh-ijkl-9876543210ef", + "text": "Option 3" + }, + { + "id": "77777777-abcd-efgh-ijkl-9876543210ef", + "text": "Option 4" + } + ], + "tags": [ + { + "CreatedAt": "2023-12-01T12:00:00Z", + "UpdatedAt": "2023-12-01T12:00:00Z", + "DeletedAt": null, + "name": "#tag2" + } + ], + "correct": { + "id": "44444444-abcd-efgh-ijkl-9876543210ef", + "text": "Option 1" + }, + "CorrectPos": 0, + "type": 0 + } + ] + } +} ` type serverTestSuite struct { @@ -218,16 +220,15 @@ func (t *serverTestSuite) TestCreate() { err := json.Unmarshal(response.Body.Bytes(), &result) t.Nil(err) - path := filepath.Join(GetDefaultSessionDir(), result["id"]) + path := filepath.Join(GetDefaultSessionDir(), "session_fe0a7ee0-f31a-413d-f123-ab5068bcaaaa.json") + _, err = os.Stat(path) - defer os.RemoveAll(path) - - files, err := os.ReadDir(path) t.Nil(err) - t.Equal(2, len(files)) + defer os.Remove(path) + + t.Equal("fe0a7ee0-f31a-413d-f123-ab5068bcaaaa", result["id"]) - t.Nil(err) } } } @@ -255,7 +256,7 @@ func (t *serverTestSuite) TestRead() { err := json.Unmarshal(response.Body.Bytes(), &result) t.Nil(err) - path := filepath.Join(GetDefaultSessionDir(), result["id"]) + path := filepath.Join(GetDefaultSessionDir(), "session_fe0a7ee0-f31a-413d-f123-ab5068bcaaaa.json") _, err = os.Stat(path) t.Nil(err) diff --git a/server/templates/exam.tpl b/server/templates/exam.tpl index aa6071e..6513d02 100644 --- a/server/templates/exam.tpl +++ b/server/templates/exam.tpl @@ -3,10 +3,10 @@ -