Ver código fonte

Rename SessionManager

andrea 5 meses atrás
pai
commit
7ff0d348b8

+ 3 - 3
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)
 							}

+ 1 - 4
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) {

+ 1 - 1
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"`
 }

+ 29 - 0
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)
+}

+ 76 - 43
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("<p>Corretto!</p>"))
 			return
 		}

+ 174 - 173
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"])
-			_, err = os.Stat(path)
-			defer os.RemoveAll(path)
+			path := filepath.Join(GetDefaultSessionDir(), "session_fe0a7ee0-f31a-413d-f123-ab5068bcaaaa.json")
 
-			files, err := os.ReadDir(path)
+			_, err = os.Stat(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)
 

+ 3 - 3
server/templates/exam.tpl

@@ -3,10 +3,10 @@
   <head>
     <meta charset="UTF-8">
     <link rel="stylesheet" type="text/css" href="/static/css/neat.css">
-    <title>{{.Name}}</title>                                          
+    <title>Exam</title>                                          
   </head>                                                                
   <body>                                                                 
-    <h1>{{.Name}}</h1>                                                
+    <h1>Exam</h1>                                                
     <h2>{{.Participant.Firstname}} {{.Participant.Lastname}}</h2>                                                                      
     <form action="/{{.SessionID}}/{{.Participant.Token}}" method="post">                                                             
       {{range $index, $quiz := .Quizzes}}                            
@@ -15,7 +15,7 @@
       {{range $answer := $quiz.Answers}}           
       <input type="radio"                                    
 	     id="{{$answer.ID}}" name="answer"        
-	     value="{{$answer.ID}}">                                                      
+	     value="{{$quiz.Question.ID}}_{{$answer.ID}}">                                                      
       <label                                                 
 	for="{{$answer.ID}}">{{$answer.Text}}</label><br>    
       {{end}}                                                    

+ 0 - 74
session/session.go

@@ -1,74 +0,0 @@
-package session
-
-import (
-	"bytes"
-	"encoding/json"
-	"io"
-	"net/http"
-
-	"git.andreafazzi.eu/andrea/probo/models"
-	"git.andreafazzi.eu/andrea/probo/store"
-)
-
-type Session struct {
-	Name string
-
-	ParticipantStore *store.ParticipantStore
-	QuizStore        *store.QuizStore
-
-	ParticipantFilter map[string]string
-	QuizFilter        map[string]string
-	ServerURL         string
-	Token             int
-
-	examStore *store.ExamStore
-}
-
-func NewSession(url string, name string, pStore *store.ParticipantStore, qStore *store.QuizStore, pFilter map[string]string, qFilter map[string]string) (*Session, error) {
-	session := new(Session)
-
-	session.ServerURL = url
-	session.examStore = store.NewStore[*models.Exam]()
-
-	for _, p := range pStore.ReadAll() {
-		_, err := session.examStore.Create(&models.Exam{
-			Name:        name,
-			Participant: p,
-			Quizzes:     qStore.ReadAll(),
-		})
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	return session, nil
-}
-
-func (s *Session) GetExams() []*models.Exam {
-	return s.examStore.ReadAll()
-}
-
-func (s *Session) Push() (string, error) {
-	payload, err := json.Marshal(s.examStore.ReadAll())
-	if err != nil {
-		return "", err
-	}
-
-	response, err := http.Post(s.ServerURL, "application/json", bytes.NewReader(payload))
-	if err != nil {
-		return "", err
-	}
-
-	responseBody, err := io.ReadAll(response.Body)
-	if err != nil {
-		return "", err
-	}
-
-	result := map[string]string{}
-	err = json.Unmarshal(responseBody, &result)
-	if err != nil {
-		return "", err
-	}
-
-	return result["id"], nil
-}

+ 5 - 0
store/file/defaults.go

@@ -10,6 +10,7 @@ var (
 	DefaultGroupsSubdir       = "groups"
 	DefaultExamsSubdir        = "exams"
 	DefaultResponsesSubdir    = "responses"
+	DefaultSessionSubdir      = "sessions"
 )
 
 func GetDefaultQuizzesDir() string {
@@ -32,6 +33,10 @@ func GetDefaultExamsDir() string {
 	return filepath.Join(DefaultBaseDir, DefaultExamsSubdir)
 }
 
+func GetDefaultSessionDir() string {
+	return filepath.Join(DefaultBaseDir, DefaultSessionSubdir)
+}
+
 func GetDefaultResponsesDir() string {
 	return filepath.Join(DefaultBaseDir, DefaultResponsesSubdir)
 }

+ 1 - 1
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-09T19:59:19.383186974+01:00","Firstname":"Jack","Lastname":"Sparrow","Token":333444,"Attributes":{"class":"2 D LIN"}}
+{"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"}}

+ 1 - 1
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-09T19:59:19.383367508+01:00","Firstname":"John","Lastname":"Smith","Token":111222,"Attributes":{"class":"1 D LIN"}}
+{"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"}}

+ 1 - 1
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-09T19:59:19.383472724+01:00","Firstname":"Wendy","Lastname":"Darling","Token":333444,"Attributes":{"class":"2 D LIN"}}
+{"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"}}