probo/main.go

291 lines
6.3 KiB
Go
Raw Normal View History

2022-06-23 11:25:35 +02:00
package main
import (
2023-12-21 17:38:05 +01:00
"encoding/json"
"io"
"log/slog"
"math/rand"
2022-06-23 11:25:35 +02:00
"net/http"
2023-12-21 17:38:05 +01:00
"os"
"path/filepath"
"strconv"
"strings"
"text/template"
"time"
2022-06-23 11:25:35 +02:00
2023-12-21 17:38:05 +01:00
"git.andreafazzi.eu/andrea/probo/lib/models"
"git.andreafazzi.eu/andrea/probo/lib/store"
"git.andreafazzi.eu/andrea/probo/lib/store/file"
"github.com/lmittmann/tint"
2022-06-23 11:25:35 +02:00
)
2023-12-21 17:38:05 +01:00
var (
DefaultAssetDir = "assets"
DefaultDataDir = "data"
DefaultSessionDir = "sessions"
DefaultResponseDir = "responses"
DefaultTemplateDir = "templates"
DefaultStaticDir = "static"
)
2022-06-23 11:25:35 +02:00
2023-12-21 17:38:05 +01:00
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 filepath.Join(DefaultAssetDir, DefaultTemplateDir)
}
func GetDefaultStaticDir() string {
return filepath.Join(DefaultAssetDir, 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
}
2022-06-23 11:25:35 +02:00
2023-12-21 17:38:05 +01:00
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{}
},
},
2022-06-24 18:56:06 +02:00
)
2023-12-21 17:38:05 +01:00
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("/responses/", s.getResponsesHandler)
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) getResponsesHandler(w http.ResponseWriter, r *http.Request) {
result := make([]*models.Response, 0)
urlParts := strings.Split(r.URL.Path, "/")
sessionID := urlParts[2]
if r.Method == "GET" {
session, err := s.sessionFileStore.Read(sessionID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
for _, exam := range session.Exams {
responses := s.responseFileStore.ReadAll()
for _, r := range responses {
if r.ID == exam.ID {
result = append(result, r)
}
}
}
err = json.NewEncoder(w).Encode(result)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
}
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
}
err = json.NewEncoder(w).Encode(memorySession)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
slog.Info("Received a new session", "session", memorySession)
}
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
}
response := new(models.Response)
response.UniqueIDFunc = func() string {
return exam.GetID()
}
response.Questions = make(map[string]string)
for qID, values := range r.Form {
for _, aID := range values {
response.Questions[qID] = aID
}
}
_, err = s.responseFileStore.Create(response)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
w.Write([]byte("<p>Thank you for your response.</p>"))
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() {
slog.SetDefault(slog.New(
tint.NewHandler(os.Stdout, &tint.Options{
Level: slog.LevelInfo,
TimeFormat: time.Kitchen,
}),
))
2022-06-23 11:25:35 +02:00
2023-12-21 17:38:05 +01:00
server, err := NewDefaultServer()
if err != nil {
panic(err)
}
2022-06-23 11:25:35 +02:00
2023-12-21 17:38:05 +01:00
slog.Info("Probo server started.")
http.ListenAndServe(":8080", server)
2022-06-23 11:25:35 +02:00
}