package main import ( "encoding/json" "io" "log" "math/rand" "net/http" "os" "path/filepath" "strconv" "strings" "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 ExamTemplateData struct { *models.Exam SessionID string } type Server struct { config *Config mux *http.ServeMux sessionFileStore *file.SessionFileStore responseFileStore *file.ResponseFileStore } func GetDefaultTemplateDir() string { return DefaultTemplateDir } func GetDefaultStaticDir() string { return DefaultStaticDir } 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 } _, err = os.Stat(config.TemplateDir) if err != nil { return nil, err } _, err = os.Stat(config.StaticDir) if err != nil { 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(), sStore, rStore, } 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(), ResponseDir: GetDefaultResponseDir(), TemplateDir: GetDefaultTemplateDir(), StaticDir: GetDefaultStaticDir(), }) } func (s *Server) createExamSessionHandler(w http.ResponseWriter, r *http.Request) { session := new(models.Session) data, err := io.ReadAll(r.Body) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } err = session.Unmarshal(data) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } memorySession, err := s.sessionFileStore.Create(session) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } 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, "/") sessionID := urlParts[1] token := urlParts[2] session, err := s.sessionFileStore.Read(sessionID) 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")) 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, session.ID}) 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 } 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 parts[1] == exam.Quizzes[0].Correct.ID { w.Write([]byte("

Corretto!

")) return } w.Write([]byte("

Errato!

")) return } } 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) }