Rename SessionManager

This commit is contained in:
andrea 2023-12-12 09:21:55 +01:00
parent 142741ab5f
commit 7ff0d348b8
12 changed files with 295 additions and 304 deletions

View file

@ -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)
}

View file

@ -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) {

View file

@ -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"`
}

View file

@ -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)
}

View file

@ -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
@ -39,11 +41,9 @@ type ExamTemplateData struct {
type Server struct {
config *Config
mux *http.ServeMux
responseStore *file.ResponseFileStore
}
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
}

View file

@ -14,8 +14,11 @@ import (
)
var examPayload = `
[
{
{
"ID": "fe0a7ee0-f31a-413d-f123-ab5068bcaaaa",
"Name": "Test session",
"Exams": {
"111222": {
"id": "fe0a7ee0-f31a-413d-ba81-ab5068bc4c73",
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z",
@ -23,12 +26,11 @@ var examPayload = `
"ID": "1234",
"Firstname": "John",
"Lastname": "Smith",
"Token": 111222,
"Token": "111222",
"Attributes": {
"class": "1 D LIN"
}
},
"Name": "Test session",
"Quizzes": [
{
"id": "0610939b-a1a3-4d0e-bbc4-2aae0e8ee4b9",
@ -119,8 +121,9 @@ var examPayload = `
"type": 0
}
]
}
},
{
"333444": {
"id": "12345678-abcd-efgh-ijkl-9876543210ef",
"created_at": "2023-12-01T12:00:00Z",
"updated_at": "2023-12-01T12:00:00Z",
@ -128,12 +131,11 @@ var examPayload = `
"ID": "5678",
"Firstname": "Jane",
"Lastname": "Doe",
"Token": 333444,
"Token": "333444",
"Attributes": {
"class": "2 A SCI"
}
},
"Name": "Test session",
"Quizzes": [
{
"id": "22222222-abcd-efgh-ijkl-9876543210ef",
@ -181,7 +183,7 @@ var examPayload = `
}
]
}
]
}
`
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)

View file

@ -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}}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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"}}

View file

@ -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"}}

View file

@ -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"}}