Working on server and cli
This commit is contained in:
parent
1c0119b342
commit
142741ab5f
16 changed files with 528 additions and 118 deletions
25
cli/main.go
25
cli/main.go
|
@ -4,7 +4,7 @@ import (
|
|||
"log"
|
||||
"os"
|
||||
|
||||
"git.andreafazzi.eu/andrea/probo/models"
|
||||
"git.andreafazzi.eu/andrea/probo/session"
|
||||
"git.andreafazzi.eu/andrea/probo/store/file"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
@ -36,21 +36,28 @@ func main() {
|
|||
if err != nil {
|
||||
log.Fatalf("An error occurred: %v", err)
|
||||
}
|
||||
eStore, err := file.NewDefaultExamFileStore()
|
||||
|
||||
session, err := session.NewSession(
|
||||
"http://localhost:8080/create",
|
||||
cCtx.Args().First(),
|
||||
pStore.Storer,
|
||||
qStore.Storer,
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("An error occurred: %v", err)
|
||||
}
|
||||
|
||||
for _, p := range pStore.ReadAll() {
|
||||
e, err := eStore.Create(&models.Exam{
|
||||
Name: cCtx.Args().First(),
|
||||
Participant: p,
|
||||
Quizzes: qStore.ReadAll(),
|
||||
})
|
||||
id, err := session.Push()
|
||||
if err != nil {
|
||||
log.Fatalf("An error occurred: %v", err)
|
||||
}
|
||||
log.Printf("Created exam %v... in %v", e.ID[:8], eStore.GetPath(e))
|
||||
|
||||
log.Printf("Session upload completed with success. URL: https://localhost:8080/%v", id)
|
||||
|
||||
for _, p := range pStore.ReadAll() {
|
||||
log.Printf("http://localhost:8080/%v/%v", id, p.Token)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
2
go.mod
2
go.mod
|
@ -3,6 +3,7 @@ module git.andreafazzi.eu/andrea/probo
|
|||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/gocarina/gocsv v0.0.0-20230616125104-99d496ca653d
|
||||
github.com/google/uuid v1.3.1
|
||||
github.com/julienschmidt/httprouter v1.3.0
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
|
@ -16,7 +17,6 @@ require (
|
|||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||
github.com/glebarez/sqlite v1.9.0 // indirect
|
||||
github.com/go-yaml/yaml v2.1.0+incompatible // indirect
|
||||
github.com/gocarina/gocsv v0.0.0-20230616125104-99d496ca653d // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/kr/pretty v0.2.1 // indirect
|
||||
|
|
29
models/response.go
Normal file
29
models/response.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
Meta
|
||||
QuestionID string
|
||||
AnswerID string
|
||||
}
|
||||
|
||||
func (r *Response) String() string {
|
||||
return fmt.Sprintf("QID: %v, AID:%v", r.QuestionID, r.AnswerID)
|
||||
}
|
||||
|
||||
func (r *Response) GetHash() string {
|
||||
return fmt.Sprintf("%x", sha256.Sum256([]byte(r.QuestionID+r.AnswerID)))
|
||||
}
|
||||
|
||||
func (r *Response) Marshal() ([]byte, error) {
|
||||
return json.Marshal(r)
|
||||
}
|
||||
|
||||
func (r *Response) Unmarshal(data []byte) error {
|
||||
return json.Unmarshal(data, r)
|
||||
}
|
1
models/session.go
Normal file
1
models/session.go
Normal file
|
@ -0,0 +1 @@
|
|||
package models
|
176
server/main.go
176
server/main.go
|
@ -2,7 +2,7 @@ package main
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
|
@ -10,24 +10,89 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"git.andreafazzi.eu/andrea/probo/models"
|
||||
"git.andreafazzi.eu/andrea/probo/store/file"
|
||||
)
|
||||
|
||||
var Dir = "data"
|
||||
var (
|
||||
DefaultDataDir = "data"
|
||||
DefaultSessionDir = "sessions"
|
||||
DefaultTemplateDir = "templates"
|
||||
DefaultStaticDir = "static"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
SessionDir string
|
||||
TemplateDir string
|
||||
StaticDir string
|
||||
}
|
||||
|
||||
type ExamSession []*models.Exam
|
||||
|
||||
func generateRandomID() string {
|
||||
id := ""
|
||||
for i := 0; i < 6; i++ {
|
||||
id += strconv.Itoa(rand.Intn(9) + 1)
|
||||
}
|
||||
return id
|
||||
type ExamTemplateData struct {
|
||||
*models.Exam
|
||||
|
||||
SessionID string
|
||||
}
|
||||
|
||||
func createExamSessionHandler(w http.ResponseWriter, r *http.Request) {
|
||||
type Server struct {
|
||||
config *Config
|
||||
mux *http.ServeMux
|
||||
responseStore *file.ResponseFileStore
|
||||
}
|
||||
|
||||
func GetDefaultSessionDir() string {
|
||||
return filepath.Join(DefaultDataDir, DefaultSessionDir)
|
||||
}
|
||||
|
||||
func GetDefaultTemplateDir() string {
|
||||
return DefaultTemplateDir
|
||||
}
|
||||
|
||||
func GetDefaultStaticDir() string {
|
||||
return DefaultStaticDir
|
||||
}
|
||||
|
||||
func NewServer(config *Config) (*Server, error) {
|
||||
|
||||
_, err := os.Stat(config.SessionDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = os.Stat(config.TemplateDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = os.Stat(config.StaticDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := &Server{
|
||||
config,
|
||||
http.NewServeMux(),
|
||||
nil,
|
||||
}
|
||||
|
||||
s.mux.Handle("/static/", http.StripPrefix("/static", http.FileServer(http.Dir(config.StaticDir))))
|
||||
s.mux.HandleFunc("/create", s.createExamSessionHandler)
|
||||
s.mux.HandleFunc("/", s.getExamHandler)
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func NewDefaultServer() (*Server, error) {
|
||||
return NewServer(&Config{
|
||||
SessionDir: GetDefaultSessionDir(),
|
||||
TemplateDir: GetDefaultTemplateDir(),
|
||||
StaticDir: GetDefaultStaticDir(),
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) createExamSessionHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var p ExamSession
|
||||
err := json.NewDecoder(r.Body).Decode(&p)
|
||||
if err != nil {
|
||||
|
@ -36,7 +101,7 @@ func createExamSessionHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
id := generateRandomID()
|
||||
path := filepath.Join(Dir, id)
|
||||
path := filepath.Join(s.config.SessionDir, id)
|
||||
|
||||
err = os.MkdirAll(path, os.ModePerm)
|
||||
if err != nil {
|
||||
|
@ -62,13 +127,13 @@ func createExamSessionHandler(w http.ResponseWriter, r *http.Request) {
|
|||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
func getExamHandler(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *Server) getExamHandler(w http.ResponseWriter, r *http.Request) {
|
||||
urlParts := strings.Split(r.URL.Path, "/")
|
||||
|
||||
examID := urlParts[1]
|
||||
token := urlParts[2]
|
||||
|
||||
filePath := filepath.Join(Dir, examID, token+".json")
|
||||
filePath := filepath.Join(s.config.SessionDir, examID, token+".json")
|
||||
|
||||
data, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
|
@ -83,48 +148,61 @@ func getExamHandler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
tmpl := template.Must(template.New("exam").Parse(`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{.Name}}</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>{{.Name}}</h1>
|
||||
<h2>{{.Participant.Firstname}} {{.Participant.Lastname}}</h2>
|
||||
<form>
|
||||
{{range $index, $quiz := .Quizzes}}
|
||||
<h3>Question {{$index}}:</h3>
|
||||
<p>{{$quiz.Question.Text}}</p>
|
||||
{{range $answer := $quiz.Answers}}
|
||||
<input type="radio"
|
||||
id="{{$answer.ID}}" name="$answer.ID"
|
||||
value="{{$answer.Text}}">
|
||||
<label
|
||||
for="{{$answer.ID}}">{{$answer.Text}}</label><br>
|
||||
{{end}}
|
||||
<br>
|
||||
{{end}}
|
||||
<input type="submit" value="Invia">
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
`))
|
||||
if r.Method == "GET" {
|
||||
|
||||
err = tmpl.Execute(w, exam)
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
|
||||
tplData, err := os.ReadFile(filepath.Join(GetDefaultTemplateDir(), "exam.tpl"))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
|
||||
}
|
||||
tmpl := template.Must(template.New("exam").Parse(string(tplData)))
|
||||
|
||||
err = tmpl.Execute(w, ExamTemplateData{exam, examID})
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if r.Method == "POST" {
|
||||
err := r.ParseForm()
|
||||
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 {
|
||||
w.Write([]byte("<p>Corretto!</p>"))
|
||||
return
|
||||
}
|
||||
w.Write([]byte("<p>Errato!</p>"))
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func main() {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/create", createExamSessionHandler)
|
||||
mux.HandleFunc("/", getExamHandler)
|
||||
|
||||
slog.Info("Probo server started", "at", time.Now())
|
||||
http.ListenAndServe(":8080", mux)
|
||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
s.mux.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func generateRandomID() string {
|
||||
id := ""
|
||||
for i := 0; i < 6; i++ {
|
||||
id += strconv.Itoa(rand.Intn(9) + 1)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
func main() {
|
||||
server, err := NewDefaultServer()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
log.Println("Probo server started.", "Config", server.config)
|
||||
http.ListenAndServe(":8080", server)
|
||||
}
|
||||
|
|
|
@ -197,12 +197,16 @@ func TestRunner(t *testing.T) {
|
|||
|
||||
func (t *serverTestSuite) TestCreate() {
|
||||
|
||||
Dir = "testdata"
|
||||
DefaultDataDir = "testdata"
|
||||
|
||||
s, err := NewDefaultServer()
|
||||
t.Nil(err)
|
||||
|
||||
if !t.Failed() {
|
||||
request, _ := http.NewRequest(http.MethodPost, "/create", strings.NewReader(examPayload))
|
||||
response := httptest.NewRecorder()
|
||||
|
||||
handler := http.HandlerFunc(createExamSessionHandler)
|
||||
handler := http.HandlerFunc(s.createExamSessionHandler)
|
||||
|
||||
handler.ServeHTTP(response, request)
|
||||
|
||||
|
@ -214,7 +218,7 @@ func (t *serverTestSuite) TestCreate() {
|
|||
err := json.Unmarshal(response.Body.Bytes(), &result)
|
||||
t.Nil(err)
|
||||
|
||||
path := filepath.Join(Dir, result["id"])
|
||||
path := filepath.Join(GetDefaultSessionDir(), result["id"])
|
||||
_, err = os.Stat(path)
|
||||
defer os.RemoveAll(path)
|
||||
|
||||
|
@ -225,16 +229,21 @@ func (t *serverTestSuite) TestCreate() {
|
|||
|
||||
t.Nil(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *serverTestSuite) TestRead() {
|
||||
|
||||
Dir = "testdata"
|
||||
DefaultDataDir = "testdata"
|
||||
|
||||
s, err := NewDefaultServer()
|
||||
t.Nil(err)
|
||||
|
||||
if !t.Failed() {
|
||||
request, _ := http.NewRequest(http.MethodPost, "/create", strings.NewReader(examPayload))
|
||||
response := httptest.NewRecorder()
|
||||
|
||||
handler := http.HandlerFunc(createExamSessionHandler)
|
||||
handler := http.HandlerFunc(s.createExamSessionHandler)
|
||||
|
||||
handler.ServeHTTP(response, request)
|
||||
|
||||
|
@ -246,18 +255,23 @@ func (t *serverTestSuite) TestRead() {
|
|||
err := json.Unmarshal(response.Body.Bytes(), &result)
|
||||
t.Nil(err)
|
||||
|
||||
path := filepath.Join(Dir, result["id"])
|
||||
path := filepath.Join(GetDefaultSessionDir(), result["id"])
|
||||
_, err = os.Stat(path)
|
||||
t.Nil(err)
|
||||
|
||||
if !t.Failed() {
|
||||
defer os.RemoveAll(path)
|
||||
|
||||
request, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("/%s/%s", result["id"], "111222"), nil)
|
||||
response := httptest.NewRecorder()
|
||||
|
||||
handler := http.HandlerFunc(getExamHandler)
|
||||
handler := http.HandlerFunc(s.getExamHandler)
|
||||
|
||||
handler.ServeHTTP(response, request)
|
||||
|
||||
t.Equal(http.StatusOK, response.Code)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
144
server/static/css/neat.css
Normal file
144
server/static/css/neat.css
Normal file
|
@ -0,0 +1,144 @@
|
|||
:root {
|
||||
color-scheme: light dark;
|
||||
--light: #fff;
|
||||
--lesslight: #efefef;
|
||||
--dark: #404040;
|
||||
--moredark: #000;
|
||||
border-top: 5px solid var(--dark);
|
||||
line-height: 1.5em; /* This causes wrapping h1's to collapse too small */
|
||||
font-family: sans-serif;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
color: var(--dark);
|
||||
}
|
||||
|
||||
button, input {
|
||||
font-size: 1em; /* Override browser default font shrinking*/
|
||||
}
|
||||
|
||||
input {
|
||||
border: 1px solid var(--dark);
|
||||
background-color: var(--lesslight);
|
||||
border-radius: .25em;
|
||||
padding: .5em;
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: var(--lesslight);
|
||||
margin: 0.5em 0 0.5em 0;
|
||||
padding: 0.5em;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: var(--lesslight);
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--light);
|
||||
margin: 0;
|
||||
max-width: 800px;
|
||||
padding: 0 20px 20px 20px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
button, .button, input[type=submit] {
|
||||
display: inline-block;
|
||||
background-color: var(--dark);
|
||||
color: var(--light);
|
||||
text-align: center;
|
||||
padding: .5em;
|
||||
border-radius: .25em;
|
||||
text-decoration: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover, .button:hover, input[type=submit]:hover {
|
||||
color: var(--lesslight);
|
||||
background-color: var(--moredark);
|
||||
}
|
||||
|
||||
/* Add a margin between side-by-side buttons */
|
||||
button + button, .button + .button, input[type=submit] + input[type=submit] {
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.center {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.bordered {
|
||||
border: 3px solid;
|
||||
}
|
||||
|
||||
.home {
|
||||
display: inline-block;
|
||||
background-color: var(--dark);
|
||||
color: var(--light);
|
||||
margin-top: 20px;
|
||||
padding: 5px 10px 5px 10px;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
/* Desktop sizes */
|
||||
@media only screen and (min-width: 600px) {
|
||||
ol.twocol {
|
||||
column-count: 2;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Make everything in a row a column */
|
||||
.row > * {
|
||||
display: block;
|
||||
flex: 1 1 auto;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.row > *:not(:last-child) {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Dark mode overrides (confusingly inverse) */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--light: #222;
|
||||
--lesslight: #333;
|
||||
--dark: #eee;
|
||||
--moredark: #fefefe;
|
||||
}
|
||||
/* This fixes an odd blue then white shadow on FF in dark mode */
|
||||
*:focus {
|
||||
outline: var(--light);
|
||||
box-shadow: 0 0 0 .25em royalblue;
|
||||
}
|
||||
}
|
||||
|
||||
/* Printing */
|
||||
@media print {
|
||||
.home {
|
||||
display: none;
|
||||
}
|
||||
}
|
27
server/templates/exam.tpl
Normal file
27
server/templates/exam.tpl
Normal file
|
@ -0,0 +1,27 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" type="text/css" href="/static/css/neat.css">
|
||||
<title>{{.Name}}</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>{{.Name}}</h1>
|
||||
<h2>{{.Participant.Firstname}} {{.Participant.Lastname}}</h2>
|
||||
<form action="/{{.SessionID}}/{{.Participant.Token}}" method="post">
|
||||
{{range $index, $quiz := .Quizzes}}
|
||||
<h3>Question {{$index}}:</h3>
|
||||
<p>{{$quiz.Question.Text}}</p>
|
||||
{{range $answer := $quiz.Answers}}
|
||||
<input type="radio"
|
||||
id="{{$answer.ID}}" name="answer"
|
||||
value="{{$answer.ID}}">
|
||||
<label
|
||||
for="{{$answer.ID}}">{{$answer.Text}}</label><br>
|
||||
{{end}}
|
||||
<br>
|
||||
{{end}}
|
||||
<button type="submit">Invia</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
1
server/testdata/sessions/README
vendored
Normal file
1
server/testdata/sessions/README
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Please keep this file in the git tree in order to add the testdata/sessions folder.
|
74
session/session.go
Normal file
74
session/session.go
Normal file
|
@ -0,0 +1,74 @@
|
|||
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
|
||||
}
|
|
@ -9,6 +9,7 @@ var (
|
|||
DefaultParticipantsSubdir = "participants"
|
||||
DefaultGroupsSubdir = "groups"
|
||||
DefaultExamsSubdir = "exams"
|
||||
DefaultResponsesSubdir = "responses"
|
||||
)
|
||||
|
||||
func GetDefaultQuizzesDir() string {
|
||||
|
@ -30,3 +31,7 @@ func GetDefaultGroupsDir() string {
|
|||
func GetDefaultExamsDir() string {
|
||||
return filepath.Join(DefaultBaseDir, DefaultExamsSubdir)
|
||||
}
|
||||
|
||||
func GetDefaultResponsesDir() string {
|
||||
return filepath.Join(DefaultBaseDir, DefaultResponsesSubdir)
|
||||
}
|
||||
|
|
25
store/file/response.go
Normal file
25
store/file/response.go
Normal file
|
@ -0,0 +1,25 @@
|
|||
package file
|
||||
|
||||
import (
|
||||
"git.andreafazzi.eu/andrea/probo/models"
|
||||
"git.andreafazzi.eu/andrea/probo/store"
|
||||
)
|
||||
|
||||
type ResponseFileStore = FileStore[*models.Response, *store.Store[*models.Response]]
|
||||
|
||||
func NewResponseFileStore(config *FileStoreConfig[*models.Response, *store.ResponseStore]) (*ResponseFileStore, error) {
|
||||
return NewFileStore[*models.Response](config, store.NewStore[*models.Response]())
|
||||
}
|
||||
|
||||
func NewDefaultResponseFileStore() (*ResponseFileStore, error) {
|
||||
return NewResponseFileStore(
|
||||
&FileStoreConfig[*models.Response, *store.ResponseStore]{
|
||||
FilePathConfig: FilePathConfig{GetDefaultResponsesDir(), "response", ".json"},
|
||||
IndexDirFunc: DefaultIndexDirFunc[*models.Response, *store.ResponseStore],
|
||||
CreateEntityFunc: func() *models.Response {
|
||||
return &models.Response{}
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
}
|
|
@ -1 +1 @@
|
|||
{"id":"5467","created_at":"2023-12-05T22:00:51.525533451+01:00","updated_at":"2023-12-05T22:00:58.859239024+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-09T19:59:19.383186974+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-05T22:00:58.859318678+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-09T19:59:19.383367508+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-05T22:00:58.859375108+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-09T19:59:19.383472724+01:00","Firstname":"Wendy","Lastname":"Darling","Token":333444,"Attributes":{"class":"2 D LIN"}}
|
5
store/response.go
Normal file
5
store/response.go
Normal file
|
@ -0,0 +1,5 @@
|
|||
package store
|
||||
|
||||
import "git.andreafazzi.eu/andrea/probo/models"
|
||||
|
||||
type ResponseStore = Store[*models.Response]
|
Loading…
Reference in a new issue