Rename SessionManager
This commit is contained in:
parent
142741ab5f
commit
7ff0d348b8
12 changed files with 295 additions and 304 deletions
|
@ -4,7 +4,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"git.andreafazzi.eu/andrea/probo/session"
|
sessionmanager "git.andreafazzi.eu/andrea/probo/session"
|
||||||
"git.andreafazzi.eu/andrea/probo/store/file"
|
"git.andreafazzi.eu/andrea/probo/store/file"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
@ -37,7 +37,7 @@ func main() {
|
||||||
log.Fatalf("An error occurred: %v", err)
|
log.Fatalf("An error occurred: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
session, err := session.NewSession(
|
sm, err := sessionmanager.NewSessionManager(
|
||||||
"http://localhost:8080/create",
|
"http://localhost:8080/create",
|
||||||
cCtx.Args().First(),
|
cCtx.Args().First(),
|
||||||
pStore.Storer,
|
pStore.Storer,
|
||||||
|
@ -49,7 +49,7 @@ func main() {
|
||||||
log.Fatalf("An error occurred: %v", err)
|
log.Fatalf("An error occurred: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
id, err := session.Push()
|
id, err := sm.Push()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("An error occurred: %v", err)
|
log.Fatalf("An error occurred: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Exam struct {
|
type Exam struct {
|
||||||
Meta
|
Meta
|
||||||
Name string
|
|
||||||
Participant *Participant
|
Participant *Participant
|
||||||
Quizzes []*Quiz
|
Quizzes []*Quiz
|
||||||
}
|
}
|
||||||
|
@ -19,7 +16,7 @@ func (e *Exam) String() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Exam) GetHash() 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) {
|
func (e *Exam) Marshal() ([]byte, error) {
|
||||||
|
|
|
@ -17,7 +17,7 @@ type Participant struct {
|
||||||
Firstname string `csv:"firstname"`
|
Firstname string `csv:"firstname"`
|
||||||
Lastname string `csv:"lastname"`
|
Lastname string `csv:"lastname"`
|
||||||
|
|
||||||
Token int `csv:"token"`
|
Token string `csv:"token"`
|
||||||
|
|
||||||
Attributes AttributeList `csv:"attributes"`
|
Attributes AttributeList `csv:"attributes"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1,30 @@
|
||||||
package models
|
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)
|
||||||
|
}
|
||||||
|
|
119
server/main.go
119
server/main.go
|
@ -2,6 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -12,24 +13,25 @@ import (
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"git.andreafazzi.eu/andrea/probo/models"
|
"git.andreafazzi.eu/andrea/probo/models"
|
||||||
|
"git.andreafazzi.eu/andrea/probo/store"
|
||||||
"git.andreafazzi.eu/andrea/probo/store/file"
|
"git.andreafazzi.eu/andrea/probo/store/file"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
DefaultDataDir = "data"
|
DefaultDataDir = "data"
|
||||||
DefaultSessionDir = "sessions"
|
DefaultSessionDir = "sessions"
|
||||||
|
DefaultResponseDir = "responses"
|
||||||
DefaultTemplateDir = "templates"
|
DefaultTemplateDir = "templates"
|
||||||
DefaultStaticDir = "static"
|
DefaultStaticDir = "static"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
SessionDir string
|
SessionDir string
|
||||||
|
ResponseDir string
|
||||||
TemplateDir string
|
TemplateDir string
|
||||||
StaticDir string
|
StaticDir string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExamSession []*models.Exam
|
|
||||||
|
|
||||||
type ExamTemplateData struct {
|
type ExamTemplateData struct {
|
||||||
*models.Exam
|
*models.Exam
|
||||||
|
|
||||||
|
@ -37,13 +39,11 @@ type ExamTemplateData struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
config *Config
|
config *Config
|
||||||
mux *http.ServeMux
|
mux *http.ServeMux
|
||||||
responseStore *file.ResponseFileStore
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetDefaultSessionDir() string {
|
sessionFileStore *file.SessionFileStore
|
||||||
return filepath.Join(DefaultDataDir, DefaultSessionDir)
|
responseFileStore *file.ResponseFileStore
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDefaultTemplateDir() string {
|
func GetDefaultTemplateDir() string {
|
||||||
|
@ -54,8 +54,15 @@ func GetDefaultStaticDir() string {
|
||||||
return DefaultStaticDir
|
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)
|
_, err := os.Stat(config.SessionDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -71,10 +78,43 @@ func NewServer(config *Config) (*Server, error) {
|
||||||
return nil, err
|
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{
|
s := &Server{
|
||||||
config,
|
config,
|
||||||
http.NewServeMux(),
|
http.NewServeMux(),
|
||||||
nil,
|
sStore,
|
||||||
|
rStore,
|
||||||
}
|
}
|
||||||
|
|
||||||
s.mux.Handle("/static/", http.StripPrefix("/static", http.FileServer(http.Dir(config.StaticDir))))
|
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) {
|
func NewDefaultServer() (*Server, error) {
|
||||||
return NewServer(&Config{
|
return NewServer(&Config{
|
||||||
SessionDir: GetDefaultSessionDir(),
|
SessionDir: GetDefaultSessionDir(),
|
||||||
|
ResponseDir: GetDefaultResponseDir(),
|
||||||
TemplateDir: GetDefaultTemplateDir(),
|
TemplateDir: GetDefaultTemplateDir(),
|
||||||
StaticDir: GetDefaultStaticDir(),
|
StaticDir: GetDefaultStaticDir(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) createExamSessionHandler(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) createExamSessionHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
var p ExamSession
|
session := new(models.Session)
|
||||||
err := json.NewDecoder(r.Body).Decode(&p)
|
|
||||||
|
data, err := io.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
id := generateRandomID()
|
err = session.Unmarshal(data)
|
||||||
path := filepath.Join(s.config.SessionDir, id)
|
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 {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, exam := range p {
|
response := map[string]string{"id": memorySession.ID}
|
||||||
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}
|
|
||||||
json.NewEncoder(w).Encode(response)
|
json.NewEncoder(w).Encode(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) getExamHandler(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) getExamHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
urlParts := strings.Split(r.URL.Path, "/")
|
urlParts := strings.Split(r.URL.Path, "/")
|
||||||
|
|
||||||
examID := urlParts[1]
|
sessionID := urlParts[1]
|
||||||
token := urlParts[2]
|
token := urlParts[2]
|
||||||
|
|
||||||
filePath := filepath.Join(s.config.SessionDir, examID, token+".json")
|
session, err := s.sessionFileStore.Read(sessionID)
|
||||||
|
|
||||||
data, err := os.ReadFile(filePath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
exam := new(models.Exam)
|
exam := session.Exams[token]
|
||||||
err = json.Unmarshal(data, &exam)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Method == "GET" {
|
if r.Method == "GET" {
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
|
|
||||||
tplData, err := os.ReadFile(filepath.Join(GetDefaultTemplateDir(), "exam.tpl"))
|
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)))
|
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 {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
@ -174,8 +197,18 @@ func (s *Server) getExamHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
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")
|
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>"))
|
w.Write([]byte("<p>Corretto!</p>"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,174 +14,176 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var examPayload = `
|
var examPayload = `
|
||||||
[
|
{
|
||||||
{
|
"ID": "fe0a7ee0-f31a-413d-f123-ab5068bcaaaa",
|
||||||
"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"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Name": "Test session",
|
"Name": "Test session",
|
||||||
"Quizzes": [
|
"Exams": {
|
||||||
{
|
"111222": {
|
||||||
"id": "0610939b-a1a3-4d0e-bbc4-2aae0e8ee4b9",
|
"id": "fe0a7ee0-f31a-413d-ba81-ab5068bc4c73",
|
||||||
"created_at": "2023-11-27T17:51:53.910642221+01:00",
|
"created_at": "0001-01-01T00:00:00Z",
|
||||||
"updated_at": "0001-01-01T00:00:00Z",
|
"updated_at": "0001-01-01T00:00:00Z",
|
||||||
"hash": "",
|
"Participant": {
|
||||||
"question": {
|
"ID": "1234",
|
||||||
"id": "98c0eec9-677f-464e-9e3e-864a859f29a3",
|
"Firstname": "John",
|
||||||
"created_at": "0001-01-01T00:00:00Z",
|
"Lastname": "Smith",
|
||||||
"updated_at": "0001-01-01T00:00:00Z",
|
"Token": "111222",
|
||||||
"text": "Question text with #tag1."
|
"Attributes": {
|
||||||
},
|
"class": "1 D LIN"
|
||||||
"answers": [
|
}
|
||||||
{
|
},
|
||||||
"id": "1ab5ff1f-74c8-4efc-abdc-d03a3640f3bc",
|
"Quizzes": [
|
||||||
"text": "Answer 1"
|
{
|
||||||
},
|
"id": "0610939b-a1a3-4d0e-bbc4-2aae0e8ee4b9",
|
||||||
{
|
"created_at": "2023-11-27T17:51:53.910642221+01:00",
|
||||||
"id": "74547724-b905-476f-8cfc-6ee633f92ef3",
|
"updated_at": "0001-01-01T00:00:00Z",
|
||||||
"text": "Answer 2"
|
"hash": "",
|
||||||
},
|
"question": {
|
||||||
{
|
"id": "98c0eec9-677f-464e-9e3e-864a859f29a3",
|
||||||
"id": "96c1a8ee-c50c-4ebc-89e4-9f3ca356adbd",
|
"created_at": "0001-01-01T00:00:00Z",
|
||||||
"text": "Answer 3"
|
"updated_at": "0001-01-01T00:00:00Z",
|
||||||
},
|
"text": "Question text with #tag1."
|
||||||
{
|
},
|
||||||
"id": "16c4b95e-64ce-4666-8cbe-b66fa59eb23b",
|
"answers": [
|
||||||
"text": "Answer 4"
|
{
|
||||||
}
|
"id": "1ab5ff1f-74c8-4efc-abdc-d03a3640f3bc",
|
||||||
],
|
"text": "Answer 1"
|
||||||
"tags": [
|
},
|
||||||
{
|
{
|
||||||
"CreatedAt": "0001-01-01T00:00:00Z",
|
"id": "74547724-b905-476f-8cfc-6ee633f92ef3",
|
||||||
"UpdatedAt": "0001-01-01T00:00:00Z",
|
"text": "Answer 2"
|
||||||
"DeletedAt": null,
|
},
|
||||||
"name": "#tag1"
|
{
|
||||||
}
|
"id": "96c1a8ee-c50c-4ebc-89e4-9f3ca356adbd",
|
||||||
],
|
"text": "Answer 3"
|
||||||
"correct": {
|
},
|
||||||
"id": "1ab5ff1f-74c8-4efc-abdc-d03a3640f3bc",
|
{
|
||||||
"text": "Answer 1"
|
"id": "16c4b95e-64ce-4666-8cbe-b66fa59eb23b",
|
||||||
},
|
"text": "Answer 4"
|
||||||
"CorrectPos": 0,
|
}
|
||||||
"type": 0
|
],
|
||||||
},
|
"tags": [
|
||||||
{
|
{
|
||||||
"id": "915818c4-b0ce-4efc-8def-7fc8d5fa7454",
|
"CreatedAt": "0001-01-01T00:00:00Z",
|
||||||
"created_at": "2023-11-27T17:51:53.91077796+01:00",
|
"UpdatedAt": "0001-01-01T00:00:00Z",
|
||||||
"updated_at": "0001-01-01T00:00:00Z",
|
"DeletedAt": null,
|
||||||
"hash": "",
|
"name": "#tag1"
|
||||||
"question": {
|
}
|
||||||
"id": "70793f0d-2855-4140-814e-40167464424b",
|
],
|
||||||
"created_at": "0001-01-01T00:00:00Z",
|
"correct": {
|
||||||
"updated_at": "0001-01-01T00:00:00Z",
|
"id": "1ab5ff1f-74c8-4efc-abdc-d03a3640f3bc",
|
||||||
"text": "Another question text with #tag1."
|
"text": "Answer 1"
|
||||||
},
|
},
|
||||||
"answers": [
|
"CorrectPos": 0,
|
||||||
{
|
"type": 0
|
||||||
"id": "1ab5ff1f-74c8-4efc-abdc-d03a3640f3bc",
|
},
|
||||||
"text": "Answer 1"
|
{
|
||||||
},
|
"id": "915818c4-b0ce-4efc-8def-7fc8d5fa7454",
|
||||||
{
|
"created_at": "2023-11-27T17:51:53.91077796+01:00",
|
||||||
"id": "74547724-b905-476f-8cfc-6ee633f92ef3",
|
"updated_at": "0001-01-01T00:00:00Z",
|
||||||
"text": "Answer 2"
|
"hash": "",
|
||||||
},
|
"question": {
|
||||||
{
|
"id": "70793f0d-2855-4140-814e-40167464424b",
|
||||||
"id": "96c1a8ee-c50c-4ebc-89e4-9f3ca356adbd",
|
"created_at": "0001-01-01T00:00:00Z",
|
||||||
"text": "Answer 3"
|
"updated_at": "0001-01-01T00:00:00Z",
|
||||||
},
|
"text": "Another question text with #tag1."
|
||||||
{
|
},
|
||||||
"id": "16c4b95e-64ce-4666-8cbe-b66fa59eb23b",
|
"answers": [
|
||||||
"text": "Answer 4"
|
{
|
||||||
}
|
"id": "1ab5ff1f-74c8-4efc-abdc-d03a3640f3bc",
|
||||||
],
|
"text": "Answer 1"
|
||||||
"tags": [
|
},
|
||||||
{
|
{
|
||||||
"CreatedAt": "0001-01-01T00:00:00Z",
|
"id": "74547724-b905-476f-8cfc-6ee633f92ef3",
|
||||||
"UpdatedAt": "0001-01-01T00:00:00Z",
|
"text": "Answer 2"
|
||||||
"DeletedAt": null,
|
},
|
||||||
"name": "#tag1"
|
{
|
||||||
}
|
"id": "96c1a8ee-c50c-4ebc-89e4-9f3ca356adbd",
|
||||||
],
|
"text": "Answer 3"
|
||||||
"correct": {
|
},
|
||||||
"id": "1ab5ff1f-74c8-4efc-abdc-d03a3640f3bc",
|
{
|
||||||
"text": "Answer 1"
|
"id": "16c4b95e-64ce-4666-8cbe-b66fa59eb23b",
|
||||||
},
|
"text": "Answer 4"
|
||||||
"CorrectPos": 0,
|
}
|
||||||
"type": 0
|
],
|
||||||
}
|
"tags": [
|
||||||
]
|
{
|
||||||
},
|
"CreatedAt": "0001-01-01T00:00:00Z",
|
||||||
{
|
"UpdatedAt": "0001-01-01T00:00:00Z",
|
||||||
"id": "12345678-abcd-efgh-ijkl-9876543210ef",
|
"DeletedAt": null,
|
||||||
"created_at": "2023-12-01T12:00:00Z",
|
"name": "#tag1"
|
||||||
"updated_at": "2023-12-01T12:00:00Z",
|
}
|
||||||
"Participant": {
|
],
|
||||||
"ID": "5678",
|
"correct": {
|
||||||
"Firstname": "Jane",
|
"id": "1ab5ff1f-74c8-4efc-abdc-d03a3640f3bc",
|
||||||
"Lastname": "Doe",
|
"text": "Answer 1"
|
||||||
"Token": 333444,
|
},
|
||||||
"Attributes": {
|
"CorrectPos": 0,
|
||||||
"class": "2 A SCI"
|
"type": 0
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"Name": "Test session",
|
"333444": {
|
||||||
"Quizzes": [
|
"id": "12345678-abcd-efgh-ijkl-9876543210ef",
|
||||||
{
|
"created_at": "2023-12-01T12:00:00Z",
|
||||||
"id": "22222222-abcd-efgh-ijkl-9876543210ef",
|
"updated_at": "2023-12-01T12:00:00Z",
|
||||||
"created_at": "2023-12-01T12:00:00Z",
|
"Participant": {
|
||||||
"updated_at": "2023-12-01T12:00:00Z",
|
"ID": "5678",
|
||||||
"hash": "",
|
"Firstname": "Jane",
|
||||||
"question": {
|
"Lastname": "Doe",
|
||||||
"id": "33333333-abcd-efgh-ijkl-9876543210ef",
|
"Token": "333444",
|
||||||
"created_at": "2023-12-01T12:00:00Z",
|
"Attributes": {
|
||||||
"updated_at": "2023-12-01T12:00:00Z",
|
"class": "2 A SCI"
|
||||||
"text": "Sample question text."
|
}
|
||||||
},
|
},
|
||||||
"answers": [
|
"Quizzes": [
|
||||||
{
|
{
|
||||||
"id": "44444444-abcd-efgh-ijkl-9876543210ef",
|
"id": "22222222-abcd-efgh-ijkl-9876543210ef",
|
||||||
"text": "Option 1"
|
"created_at": "2023-12-01T12:00:00Z",
|
||||||
},
|
"updated_at": "2023-12-01T12:00:00Z",
|
||||||
{
|
"hash": "",
|
||||||
"id": "55555555-abcd-efgh-ijkl-9876543210ef",
|
"question": {
|
||||||
"text": "Option 2"
|
"id": "33333333-abcd-efgh-ijkl-9876543210ef",
|
||||||
},
|
"created_at": "2023-12-01T12:00:00Z",
|
||||||
{
|
"updated_at": "2023-12-01T12:00:00Z",
|
||||||
"id": "66666666-abcd-efgh-ijkl-9876543210ef",
|
"text": "Sample question text."
|
||||||
"text": "Option 3"
|
},
|
||||||
},
|
"answers": [
|
||||||
{
|
{
|
||||||
"id": "77777777-abcd-efgh-ijkl-9876543210ef",
|
"id": "44444444-abcd-efgh-ijkl-9876543210ef",
|
||||||
"text": "Option 4"
|
"text": "Option 1"
|
||||||
}
|
},
|
||||||
],
|
{
|
||||||
"tags": [
|
"id": "55555555-abcd-efgh-ijkl-9876543210ef",
|
||||||
{
|
"text": "Option 2"
|
||||||
"CreatedAt": "2023-12-01T12:00:00Z",
|
},
|
||||||
"UpdatedAt": "2023-12-01T12:00:00Z",
|
{
|
||||||
"DeletedAt": null,
|
"id": "66666666-abcd-efgh-ijkl-9876543210ef",
|
||||||
"name": "#tag2"
|
"text": "Option 3"
|
||||||
}
|
},
|
||||||
],
|
{
|
||||||
"correct": {
|
"id": "77777777-abcd-efgh-ijkl-9876543210ef",
|
||||||
"id": "44444444-abcd-efgh-ijkl-9876543210ef",
|
"text": "Option 4"
|
||||||
"text": "Option 1"
|
}
|
||||||
},
|
],
|
||||||
"CorrectPos": 0,
|
"tags": [
|
||||||
"type": 0
|
{
|
||||||
}
|
"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 {
|
type serverTestSuite struct {
|
||||||
|
@ -218,16 +220,15 @@ func (t *serverTestSuite) TestCreate() {
|
||||||
err := json.Unmarshal(response.Body.Bytes(), &result)
|
err := json.Unmarshal(response.Body.Bytes(), &result)
|
||||||
t.Nil(err)
|
t.Nil(err)
|
||||||
|
|
||||||
path := filepath.Join(GetDefaultSessionDir(), result["id"])
|
path := filepath.Join(GetDefaultSessionDir(), "session_fe0a7ee0-f31a-413d-f123-ab5068bcaaaa.json")
|
||||||
|
|
||||||
_, err = os.Stat(path)
|
_, err = os.Stat(path)
|
||||||
defer os.RemoveAll(path)
|
|
||||||
|
|
||||||
files, err := os.ReadDir(path)
|
|
||||||
t.Nil(err)
|
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)
|
err := json.Unmarshal(response.Body.Bytes(), &result)
|
||||||
t.Nil(err)
|
t.Nil(err)
|
||||||
|
|
||||||
path := filepath.Join(GetDefaultSessionDir(), result["id"])
|
path := filepath.Join(GetDefaultSessionDir(), "session_fe0a7ee0-f31a-413d-f123-ab5068bcaaaa.json")
|
||||||
_, err = os.Stat(path)
|
_, err = os.Stat(path)
|
||||||
t.Nil(err)
|
t.Nil(err)
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<link rel="stylesheet" type="text/css" href="/static/css/neat.css">
|
<link rel="stylesheet" type="text/css" href="/static/css/neat.css">
|
||||||
<title>{{.Name}}</title>
|
<title>Exam</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>{{.Name}}</h1>
|
<h1>Exam</h1>
|
||||||
<h2>{{.Participant.Firstname}} {{.Participant.Lastname}}</h2>
|
<h2>{{.Participant.Firstname}} {{.Participant.Lastname}}</h2>
|
||||||
<form action="/{{.SessionID}}/{{.Participant.Token}}" method="post">
|
<form action="/{{.SessionID}}/{{.Participant.Token}}" method="post">
|
||||||
{{range $index, $quiz := .Quizzes}}
|
{{range $index, $quiz := .Quizzes}}
|
||||||
|
@ -15,7 +15,7 @@
|
||||||
{{range $answer := $quiz.Answers}}
|
{{range $answer := $quiz.Answers}}
|
||||||
<input type="radio"
|
<input type="radio"
|
||||||
id="{{$answer.ID}}" name="answer"
|
id="{{$answer.ID}}" name="answer"
|
||||||
value="{{$answer.ID}}">
|
value="{{$quiz.Question.ID}}_{{$answer.ID}}">
|
||||||
<label
|
<label
|
||||||
for="{{$answer.ID}}">{{$answer.Text}}</label><br>
|
for="{{$answer.ID}}">{{$answer.Text}}</label><br>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -10,6 +10,7 @@ var (
|
||||||
DefaultGroupsSubdir = "groups"
|
DefaultGroupsSubdir = "groups"
|
||||||
DefaultExamsSubdir = "exams"
|
DefaultExamsSubdir = "exams"
|
||||||
DefaultResponsesSubdir = "responses"
|
DefaultResponsesSubdir = "responses"
|
||||||
|
DefaultSessionSubdir = "sessions"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetDefaultQuizzesDir() string {
|
func GetDefaultQuizzesDir() string {
|
||||||
|
@ -32,6 +33,10 @@ func GetDefaultExamsDir() string {
|
||||||
return filepath.Join(DefaultBaseDir, DefaultExamsSubdir)
|
return filepath.Join(DefaultBaseDir, DefaultExamsSubdir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetDefaultSessionDir() string {
|
||||||
|
return filepath.Join(DefaultBaseDir, DefaultSessionSubdir)
|
||||||
|
}
|
||||||
|
|
||||||
func GetDefaultResponsesDir() string {
|
func GetDefaultResponsesDir() string {
|
||||||
return filepath.Join(DefaultBaseDir, DefaultResponsesSubdir)
|
return filepath.Join(DefaultBaseDir, DefaultResponsesSubdir)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 @@
|
||||||
{"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 @@
|
||||||
{"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"}}
|
Loading…
Reference in a new issue