From 6127260b912a29c6a024d722429be2a090c3e289 Mon Sep 17 00:00:00 2001 From: Andrea Fazzi Date: Wed, 22 May 2024 20:37:50 +0200 Subject: [PATCH] Use templates for rendering long CLI description --- cmd/filter.go | 45 ++-------- cmd/init.go | 38 +++++++++ cmd/root.go | 16 ++-- cmd/serve.go | 82 +++++++++++++++++++ cmd/serve/controller.go | 64 +++++++++++++++ cmd/serve/exam.go | 78 ++++++++++++++++++ cmd/serve/jwt.go | 36 ++++++++ cmd/serve/login.go | 76 +++++++++++++++++ cmd/serve/recover.go | 22 +++++ cmd/serve/sessions.go | 39 +++++++++ cmd/session/session.go | 4 +- cmd/templates/filter.tmpl | 3 - cmd/update.go | 13 +-- cmd/util/util.go | 25 ++++++ embed/cli/filter/description.tmpl | 33 ++++++++ embed/cli/init/description.tmpl | 27 ++++++ {cmd/templates => embed/cli}/layout.tmpl | 2 +- embed/cli/logo.tmpl | 10 +++ embed/cli/root/description.tmpl | 50 +++++++++++ embed/embed.go | 71 ++++++++++++++++ embed/public/css/login.css | 40 +++++++++ embed/templates/exam/exam.html.tmpl | 23 ++++++ embed/templates/exam/layout-exam.html.tmpl | 42 ++++++++++ embed/templates/login/layout-login.html.tmpl | 29 +++++++ embed/templates/login/login.html.tmpl | 25 ++++++ .../sessions/layout-sessions.html.tmpl | 55 +++++++++++++ embed/templates/sessions/sessions.html.tmpl | 18 ++++ go.mod | 13 ++- go.sum | 36 ++++++++ go.work | 4 +- go.work.sum | 5 +- main.go | 4 +- pkg/store/file/file.go | 2 +- pkg/store/file/quiz.go | 3 +- 34 files changed, 962 insertions(+), 71 deletions(-) create mode 100644 cmd/init.go create mode 100644 cmd/serve.go create mode 100644 cmd/serve/controller.go create mode 100644 cmd/serve/exam.go create mode 100644 cmd/serve/jwt.go create mode 100644 cmd/serve/login.go create mode 100644 cmd/serve/recover.go create mode 100644 cmd/serve/sessions.go delete mode 100644 cmd/templates/filter.tmpl create mode 100644 embed/cli/filter/description.tmpl create mode 100644 embed/cli/init/description.tmpl rename {cmd/templates => embed/cli}/layout.tmpl (56%) create mode 100644 embed/cli/logo.tmpl create mode 100644 embed/cli/root/description.tmpl create mode 100644 embed/embed.go create mode 100644 embed/public/css/login.css create mode 100644 embed/templates/exam/exam.html.tmpl create mode 100644 embed/templates/exam/layout-exam.html.tmpl create mode 100644 embed/templates/login/layout-login.html.tmpl create mode 100644 embed/templates/login/login.html.tmpl create mode 100644 embed/templates/sessions/layout-sessions.html.tmpl create mode 100644 embed/templates/sessions/sessions.html.tmpl diff --git a/cmd/filter.go b/cmd/filter.go index 9958d1a..1508240 100644 --- a/cmd/filter.go +++ b/cmd/filter.go @@ -11,53 +11,20 @@ import ( "git.andreafazzi.eu/andrea/probo/cmd/filter" "git.andreafazzi.eu/andrea/probo/cmd/util" tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/glamour" "github.com/charmbracelet/huh" "github.com/charmbracelet/lipgloss" "github.com/muesli/termenv" "github.com/spf13/cobra" ) -var longDescription string = ` -# Filters - -**Filters can made selection over stores.** - -Filters allow you to narrow down selections across various stores. By -using filters, you can select participants, quizzes, and -responses. The command triggers a Text User Interface (TUI) that runs -a jq filter, displaying the outcome in real-time. After you're content -with the filtered JSON, pressing ⏎ will present the result on -stdout, enabling you to further process it by piping it forward. - -## Examples - -Filter over participants store. - -**probo filter participants** - -Filter over quizzes store using the jq filter in tags.jq file - -**probo filter quizzes -i data/filters/tags.jq** - -Filter over participants and pipe the result on the next filter. The result is then stored in a JSON file. - -**probo filter participants | probo filter quizzes > data/json/selection.json** -` +var filterCmd = &cobra.Command{ + Use: "filter {participants,quizzes,responses}", + Short: "Filter the given store", + Long: util.RenderMarkdownTemplates("cli/*.tmpl", "cli/filter/*.tmpl"), + Run: runFilter, +} func init() { - desc, err := glamour.Render(fmt.Sprintf("```\n%s```\n%s", logo, longDescription), "dark") - if err != nil { - panic(err) - } - - filterCmd := &cobra.Command{ - Use: "filter {participants,quizzes,responses}", - Short: "Filter the given store", - Long: desc, - Run: runFilter, - } - rootCmd.AddCommand(filterCmd) filterCmd.PersistentFlags().StringP("input", "i", "", "Specify an input file") } diff --git a/cmd/init.go b/cmd/init.go new file mode 100644 index 0000000..3818c63 --- /dev/null +++ b/cmd/init.go @@ -0,0 +1,38 @@ +/* +Copyright © 2024 NAME HERE +*/ +package cmd + +import ( + "git.andreafazzi.eu/andrea/probo/cmd/util" + "git.andreafazzi.eu/andrea/probo/embed" + "github.com/spf13/cobra" +) + +// initCmd represents the init command +var initCmd = &cobra.Command{ + Use: "init", + Short: "Initialize a working directory", + Long: util.RenderMarkdownTemplates("cli/*.tmpl", "cli/init/*.tmpl"), + Run: runInit, +} + +func init() { + rootCmd.AddCommand(initCmd) +} + +func runInit(cmd *cobra.Command, args []string) { + err := embed.CopyToWorkingDirectory(embed.Data) + if err != nil { + panic(err) + } + err = embed.CopyToWorkingDirectory(embed.Templates) + if err != nil { + panic(err) + } + err = embed.CopyToWorkingDirectory(embed.Public) + if err != nil { + panic(err) + } + +} diff --git a/cmd/root.go b/cmd/root.go index 922c9d9..0a9e37a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -25,20 +25,24 @@ import ( "fmt" "os" + "git.andreafazzi.eu/andrea/probo/cmd/util" "github.com/spf13/cobra" "github.com/spf13/viper" ) -var cfgFile string +var ( + cfgFile string +) // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "probo", - Short: "A Quiz Management System for Hackers!", - Long: ` -Probo is a CLI/TUI application that allows for the quick -creation of quizzes from markdown files and their distribution -as web pages.`, + Short: "A Quiz Management System", + /*Long: ` + Probo is a CLI/TUI application that allows for the quick + creation of quizzes from markdown files and their distribution + as web pages.`,*/ + Long: util.RenderMarkdownTemplates("cli/*.tmpl", "cli/root/*.tmpl"), // Uncomment the following line if your bare application // has an action associated with it: diff --git a/cmd/serve.go b/cmd/serve.go new file mode 100644 index 0000000..5afc309 --- /dev/null +++ b/cmd/serve.go @@ -0,0 +1,82 @@ +/* +Copyright © 2024 NAME HERE +*/ +package cmd + +import ( + "net/http" + + "git.andreafazzi.eu/andrea/probo/cmd/serve" + "git.andreafazzi.eu/andrea/probo/pkg/store/file" + "github.com/charmbracelet/log" + "github.com/spf13/cobra" +) + +// serveCmd represents the serve command +var serveCmd = &cobra.Command{ + Use: "serve", + Short: "Launch a web server to adminster exam sessions", + Long: `A longer description that spans multiple lines and likely contains examples +and usage of using your command. For example: + +Cobra is a CLI library for Go that empowers applications. +This application is a tool to generate the needed files +to quickly create a Cobra application.`, + Run: runServer, +} + +func init() { + rootCmd.AddCommand(serveCmd) +} + +func runServer(cmd *cobra.Command, args []string) { + sStore, err := file.NewDefaultSessionFileStore() + if err != nil { + log.Fatal("Session store loading", "err", err) + } + + rStore, err := file.NewDefaultResponseFileStore() + if err != nil { + log.Fatal("Session store loading", "err", err) + } + + mux := http.NewServeMux() + + loginController := serve.NewController(sStore, rStore). + WithTemplates( + "templates/login/layout-login.html.tmpl", + "templates/login/login.html.tmpl", + ). + WithHandlerFunc(serve.LoginHandler) + + sessionsController := serve.NewController(sStore, rStore). + WithTemplates( + "templates/sessions/layout-sessions.html.tmpl", + "templates/sessions/sessions.html.tmpl", + ). + WithHandlerFunc(serve.SessionsHandler) + + examController := serve.NewController(sStore, rStore). + WithTemplates( + "templates/exam/layout-exam.html.tmpl", + "templates/exam/exam.html.tmpl", + ). + WithHandlerFunc(serve.ExamHandler) + + mux.Handle("GET /login", serve.Recover(loginController)) + mux.Handle("POST /login", serve.Recover(loginController)) + + mux.Handle("GET /sessions", serve.Recover(sessionsController)) + mux.Handle("GET /sessions/{uuid}/exams/{token}", serve.Recover(examController)) + mux.Handle("POST /sessions/{uuid}/exams/{token}", serve.Recover(examController)) + + mux.Handle("GET /public/", http.StripPrefix("/public", http.FileServer(http.Dir("public")))) + + log.Info("Listening...") + + err = http.ListenAndServe(":3000", mux) + if err != nil { + panic(err) + } + +} diff --git a/cmd/serve/controller.go b/cmd/serve/controller.go new file mode 100644 index 0000000..46e63e5 --- /dev/null +++ b/cmd/serve/controller.go @@ -0,0 +1,64 @@ +package serve + +import ( + "bytes" + "html/template" + "net/http" + + "git.andreafazzi.eu/andrea/probo/pkg/store/file" +) + +type Controller struct { + handlerFunc http.HandlerFunc + + sStore *file.SessionFileStore + rStore *file.ResponseFileStore + + template *template.Template +} + +func NewController(sStore *file.SessionFileStore, rStore *file.ResponseFileStore) *Controller { + return &Controller{ + sStore: sStore, + rStore: rStore, + } +} + +func (c *Controller) WithHandlerFunc(f func(c *Controller, w http.ResponseWriter, r *http.Request)) *Controller { + hf := func(w http.ResponseWriter, r *http.Request) { + f(c, w, r) + } + + c.handlerFunc = hf + + return c +} + +func (c *Controller) WithTemplates(paths ...string) *Controller { + tmpl, err := template.ParseFiles(paths...) + if err != nil { + panic(err) + } + + c.template = tmpl + return c +} + +func (c *Controller) ExecuteTemplate(w http.ResponseWriter, data any) error { + var buf bytes.Buffer + + err := c.template.Execute(&buf, data) + if err != nil { + return err + } + + w.Header().Set("Content-Type", "text/html; charset=UTF-8") + + buf.WriteTo(w) + + return nil +} + +func (c *Controller) ServeHTTP(w http.ResponseWriter, r *http.Request) { + c.handlerFunc.ServeHTTP(w, r) +} diff --git a/cmd/serve/exam.go b/cmd/serve/exam.go new file mode 100644 index 0000000..5e7b2e9 --- /dev/null +++ b/cmd/serve/exam.go @@ -0,0 +1,78 @@ +package serve + +import ( + "errors" + "net/http" + + "git.andreafazzi.eu/andrea/probo/pkg/models" + "github.com/charmbracelet/log" +) + +var ExamHandler = func(c *Controller, w http.ResponseWriter, r *http.Request) { + _, err := ValidateJwtCookie(r) + if err != nil { + panic(err) + } + + participantToken := r.PathValue("token") + + session, err := c.sStore.Read(r.PathValue("uuid")) + if err != nil { + panic(err) + } + + exam, ok := session.Exams[participantToken] + if !ok { + panic(errors.New("Exam not found in the store!")) + } + + examWithSession := struct { + *models.Exam + SessionID string + }{exam, session.ID} + + switch r.Method { + + case http.MethodGet: + log.Info("Sending exam to", "participant", exam.Participant, "exam", exam) + + err = c.ExecuteTemplate(w, examWithSession) + if err != nil { + panic(err) + } + + case http.MethodPost: + err := r.ParseForm() + if err != nil { + panic(err) + } + + answers := make([]*models.ParticipantAnswer, 0) + + for quizID, values := range r.Form { + correct := false + quiz := session.Quizzes[quizID] + + for _, answerID := range values { + log.Info(answerID) + if quiz.Correct.ID == answerID { + correct = true + } + answers = append(answers, &models.ParticipantAnswer{Quiz: quiz, Answer: session.Answers[answerID], Correct: correct}) + } + } + + response, err := c.rStore.Create( + &models.Response{ + SessionID: session.ID, + Answers: answers, + }) + if err != nil { + panic(err) + } + + log.Info("Saving response", "response", response) + + } + +} diff --git a/cmd/serve/jwt.go b/cmd/serve/jwt.go new file mode 100644 index 0000000..195552f --- /dev/null +++ b/cmd/serve/jwt.go @@ -0,0 +1,36 @@ +package serve + +import ( + "fmt" + "net/http" + "time" + + "github.com/golang-jwt/jwt" +) + +const jwtExpiresAt = time.Hour + +type Claims struct { + Token string `json:"token"` + jwt.StandardClaims +} + +var ( + jwtKey = []byte("my-secret") +) + +func ValidateJwtCookie(r *http.Request) (*jwt.Token, error) { + cookie, err := r.Cookie("Authorize") + if err != nil { + return nil, err + } + + token, err := jwt.Parse(cookie.Value, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) + } + return jwtKey, nil + }) + + return token, err +} diff --git a/cmd/serve/login.go b/cmd/serve/login.go new file mode 100644 index 0000000..fe87ada --- /dev/null +++ b/cmd/serve/login.go @@ -0,0 +1,76 @@ +package serve + +import ( + "errors" + "net/http" + "time" + + "git.andreafazzi.eu/andrea/probo/pkg/models" + "github.com/charmbracelet/log" + "github.com/golang-jwt/jwt" +) + +var LoginHandler = func(c *Controller, w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodPost { + err := r.ParseForm() + if err != nil { + panic(err) + } + + pToken := r.FormValue("participantToken") + if pToken == "" { + panic(errors.New("Token not found parsing the request!")) + } + + log.Info("Received", "participantToken", pToken) + + var loggedParticipant *models.Participant + + done := false + for _, session := range c.sStore.ReadAll() { + if done { + break + } + for _, exam := range session.Exams { + if pToken == exam.Participant.Token { + loggedParticipant = exam.Participant + done = true + } + } + } + + log.Info("Participant logged in as", "participant", loggedParticipant) + + if loggedParticipant == nil { + panic(errors.New("Participant not found!")) + } + + claims := &Claims{ + Token: pToken, + StandardClaims: jwt.StandardClaims{ + ExpiresAt: time.Now().Add(jwtExpiresAt).Unix(), + }, + } + + tokenString, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(jwtKey)) + if err != nil { + panic(err) + } + + http.SetCookie(w, &http.Cookie{ + Name: "Authorize", + Value: tokenString, + Expires: time.Now().Add(jwtExpiresAt), + }) + + log.Info("Released", "jwt", tokenString) + log.Info("Redirect to", "url", "/sessions") + + http.Redirect(w, r, "/sessions", http.StatusSeeOther) + } + + err := c.ExecuteTemplate(w, nil) + if err != nil { + panic(err) + } +} diff --git a/cmd/serve/recover.go b/cmd/serve/recover.go new file mode 100644 index 0000000..4d9203e --- /dev/null +++ b/cmd/serve/recover.go @@ -0,0 +1,22 @@ +package serve + +import ( + "net/http" + + "github.com/charmbracelet/log" +) + +func Recover(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + defer func() { + err := recover() + if err != nil { + log.Error("Recovering from", "err", err) + http.Error(w, err.(error).Error(), http.StatusInternalServerError) + } + + }() + + next.ServeHTTP(w, r) + }) +} diff --git a/cmd/serve/sessions.go b/cmd/serve/sessions.go new file mode 100644 index 0000000..66156bc --- /dev/null +++ b/cmd/serve/sessions.go @@ -0,0 +1,39 @@ +package serve + +import ( + "net/http" + + "git.andreafazzi.eu/andrea/probo/pkg/models" + "github.com/golang-jwt/jwt" +) + +var SessionsHandler = func(c *Controller, w http.ResponseWriter, r *http.Request) { + token, err := ValidateJwtCookie(r) + if err != nil { + panic(err) + } + claims := token.Claims.(jwt.MapClaims) + + var participantSessions []struct { + Token string + *models.Session + } + + for _, session := range c.sStore.ReadAll() { + for _, exam := range session.Exams { + if exam.Participant.Token == claims["token"] { + s := struct { + Token string + *models.Session + }{exam.Participant.Token, session} + participantSessions = append(participantSessions, s) + break + } + } + } + + err = c.ExecuteTemplate(w, participantSessions) + if err != nil { + panic(err) + } +} diff --git a/cmd/session/session.go b/cmd/session/session.go index 947a20f..4179e9d 100644 --- a/cmd/session/session.go +++ b/cmd/session/session.go @@ -213,7 +213,7 @@ func (m *SessionModel) View() string { return m.document.View() } -func (m *SessionModel) executeScript(path string) tea.Cmd { +func (m *SessionModel) executeScript() tea.Cmd { return func() tea.Msg { if m.scriptFilePath == "" { return nil @@ -302,7 +302,7 @@ func (m *SessionModel) updateViewportContent(session *models.Session) { m.showErrorOnStatusBar(err) } - m.viewport.SetContent(result) + m.viewport.SetContent(sanitize(result)) } diff --git a/cmd/templates/filter.tmpl b/cmd/templates/filter.tmpl deleted file mode 100644 index 270e699..0000000 --- a/cmd/templates/filter.tmpl +++ /dev/null @@ -1,3 +0,0 @@ -{{define "description"}} -{{.Description}} -{{end}} \ No newline at end of file diff --git a/cmd/update.go b/cmd/update.go index c0bf16e..1465ceb 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -1,6 +1,5 @@ /* Copyright © 2024 NAME HERE - */ package cmd @@ -13,7 +12,7 @@ import ( // updateCmd represents the update command var updateCmd = &cobra.Command{ Use: "update", - Short: "A brief description of your command", + Short: "Create or update an entity", Long: `A longer description that spans multiple lines and likely contains examples and usage of using your command. For example: @@ -27,14 +26,4 @@ to quickly create a Cobra application.`, func init() { rootCmd.AddCommand(updateCmd) - - // Here you will define your flags and configuration settings. - - // Cobra supports Persistent Flags which will work for this command - // and all subcommands, e.g.: - // updateCmd.PersistentFlags().String("foo", "", "A help for foo") - - // Cobra supports local flags which will only run when this command - // is called directly, e.g.: - // updateCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } diff --git a/cmd/util/util.go b/cmd/util/util.go index b503c6f..a96b89f 100644 --- a/cmd/util/util.go +++ b/cmd/util/util.go @@ -2,14 +2,39 @@ package util import ( "bufio" + "bytes" "fmt" + "html/template" "io" "os" "strings" + "git.andreafazzi.eu/andrea/probo/embed" tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/glamour" ) +func RenderMarkdownTemplates(paths ...string) string { + tmpl, err := template.ParseFS(embed.CLI, paths...) + if err != nil { + panic(err) + } + + var buf bytes.Buffer + + err = tmpl.Execute(&buf, nil) + if err != nil { + panic(err) + } + + result, err := glamour.Render(buf.String(), "dark") + if err != nil { + panic(err) + } + + return result +} + func LogToFile() *os.File { if len(os.Getenv("DEBUG")) > 0 { f, err := tea.LogToFile("probo-debug.log", "[DEBUG]") diff --git a/embed/cli/filter/description.tmpl b/embed/cli/filter/description.tmpl new file mode 100644 index 0000000..8e4b590 --- /dev/null +++ b/embed/cli/filter/description.tmpl @@ -0,0 +1,33 @@ +{{define "description"}} +# Filters + +**Filters can be used to make selections among stores.** + +Filters allow you to narrow down selections across various stores. By +using filters, you can select participants, quizzes, and +responses. The command triggers a Text User Interface (TUI) that runs +a `jq` filter, displaying the outcome in real-time. After you're content +with the filtered JSON, pressing ⏎ will present the result on +stdout, enabling you to further process it by piping it forward. + +## Examples + +1. Apply a filter to participants: + +``` +probo filter participants +``` + +2. Apply a filter to quizzes using the `jq` filter in `tags.jq` file: + +``` +probo filter quizzes -i data/filters/tags.jq +``` + +3. Apply a filter to participants, then pass the output through +another filter. The final result is saved in a JSON file: + +``` +probo filter participants | probo filter quizzes > data/json/selection.json +``` +{{end}} \ No newline at end of file diff --git a/embed/cli/init/description.tmpl b/embed/cli/init/description.tmpl new file mode 100644 index 0000000..c4208cd --- /dev/null +++ b/embed/cli/init/description.tmpl @@ -0,0 +1,27 @@ +{{define "description"}} +# Init + +**Init initializes the current working directory.** + +The `init` command creates, within the current directory, a file and +folder structure prepared for immediate use of `probo`. The filesystem +structure is as follows: +``` + +├── filters +├── json +├── participants +├── quizzes +├── responses +├── sessions +└── templates +``` + +## Examples + +1. Initialize the current directory: + +``` +probo init +``` +{{end}} \ No newline at end of file diff --git a/cmd/templates/layout.tmpl b/embed/cli/layout.tmpl similarity index 56% rename from cmd/templates/layout.tmpl rename to embed/cli/layout.tmpl index 9fd256c..ca24a2c 100644 --- a/cmd/templates/layout.tmpl +++ b/embed/cli/layout.tmpl @@ -1,2 +1,2 @@ -{{.Logo}} +{{template "logo" .}} {{template "description" .}} \ No newline at end of file diff --git a/embed/cli/logo.tmpl b/embed/cli/logo.tmpl new file mode 100644 index 0000000..70c259d --- /dev/null +++ b/embed/cli/logo.tmpl @@ -0,0 +1,10 @@ +{{define "logo"}} +``` + ____ _ +| _ \ _ __ ___ | |__ ___ +| |_) | '__/ _ \| '_ \ / _ \ +| __/| | | (_) | |_) | (_) | +|_| |_| \___/|_.__/ \___/____ + |_____| +``` +{{end}} diff --git a/embed/cli/root/description.tmpl b/embed/cli/root/description.tmpl new file mode 100644 index 0000000..5000887 --- /dev/null +++ b/embed/cli/root/description.tmpl @@ -0,0 +1,50 @@ +{{define "description"}} +# Probo + +`probo` is a quiz management system designed for command line +enthusiasts. `probo` aims to highlight themes such as privacy, +interoperability, accessibility, decentralization, and quick usage +speed. + +`probo` organizes information in plain text files and folders so that +data can be easily revised through version control systems. + +`probo` contains a built-in web server that allows quizzes to be +administered to participants and their answers received. + +`probo` is a self-contained application that can be distributed through +a simple executable containing all necessary assets for its operation. + +# Quickstart + +1. Initialize the working directory. The command will build a scaffold +containing example participants, quizzes, and filters + +``` +probo init +``` + +2. Filter participants and quizzes and create an exam +session. + +``` +probo filter participant -i data/filters/9th_grade.jq \ +| probo filter quizzes -i data/filters/difficult_easy.jq \ +| probo create session --name="My First Session" > data/sessions/my_session.json +``` + +3. Run the web server to allow participants to respond. + +``` +probo serve +``` + +4. Share the *qrcode* generated from the previous step with the +participants and wait for them to finish responding. + +5. Explore the results. + +``` +probo filter responses -f '.' | probo rank +``` +{{end}} \ No newline at end of file diff --git a/embed/embed.go b/embed/embed.go new file mode 100644 index 0000000..32edffa --- /dev/null +++ b/embed/embed.go @@ -0,0 +1,71 @@ +package embed + +import ( + "embed" + "io" + "io/fs" + "os" + "path/filepath" + + "github.com/charmbracelet/log" +) + +var ( + //go:embed cli/* + CLI embed.FS + + //go:embed templates/* + Templates embed.FS + + //go:embed public/* + Public embed.FS + + //go:embed data/* + Data embed.FS +) + +func CopyToWorkingDirectory(data embed.FS) error { + currentDir, err := os.Getwd() + if err != nil { + return err + } + + if err := fs.WalkDir(data, ".", func(path string, info fs.DirEntry, err error) error { + if err != nil { + log.Info(err) + return err + } + fullDstPath := filepath.Join(currentDir, path) + + if info.IsDir() { + log.Info("Creating folder", "path", path) + if err := os.MkdirAll(fullDstPath, 0755); err != nil { + return err + } + } else { + srcFile, err := data.Open(path) + if err != nil { + return err + } + defer srcFile.Close() + + dstFile, err := os.Create(fullDstPath) + if err != nil { + return err + } + defer dstFile.Close() + + log.Info("Copying file", "path", path) + _, err = io.Copy(dstFile, srcFile) + if err != nil { + return err + } + } + + return nil + }); err != nil { + return err + } + + return nil +} diff --git a/embed/public/css/login.css b/embed/public/css/login.css new file mode 100644 index 0000000..755eb58 --- /dev/null +++ b/embed/public/css/login.css @@ -0,0 +1,40 @@ +.container { + display: grid; + width: 300px; + margin: 0 auto; + padding: 20px; + justify-content: center; + align-items: center; + height: 100vh; + background-color: #f0f0f0; + border-radius: 8px; + box-shadow: 0 0 10px rgba(0,0,0,0.1); +} + +.container label { + display: block; + margin-bottom: 5px; +} + +.container input[type="text"], +.container input[type="password"] { + width: 100%; + padding: 10px; + margin-bottom: 20px; + border: 1px solid #ccc; + border-radius: 4px; +} + +.container button { + width: 100%; + padding: 10px; + background-color: #007bff; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; +} + +.container button:hover { + background-color: #0056b3; +} diff --git a/embed/templates/exam/exam.html.tmpl b/embed/templates/exam/exam.html.tmpl new file mode 100644 index 0000000..d7d2bf3 --- /dev/null +++ b/embed/templates/exam/exam.html.tmpl @@ -0,0 +1,23 @@ +{{ define "content" }} +
+ {{range $quiz := .Quizzes }} +
+
+
+
+
{{$quiz.Question}}
+
Una sola scelta possibile
+ {{range $answer := $quiz.Answers}} + +
+ {{end}} +
+
+
+
+ {{ end }} +
+{{ end }} diff --git a/embed/templates/exam/layout-exam.html.tmpl b/embed/templates/exam/layout-exam.html.tmpl new file mode 100644 index 0000000..994d78b --- /dev/null +++ b/embed/templates/exam/layout-exam.html.tmpl @@ -0,0 +1,42 @@ + + + + + + Test di {{.Participant}} + + + + +
+ + Probo_ + + +
+ +
+
+ +
+ + {{template "content" .}} + +
+
+ + + + © 2024 Andrea Fazzi +
+ + +
+ + + + diff --git a/embed/templates/login/layout-login.html.tmpl b/embed/templates/login/layout-login.html.tmpl new file mode 100644 index 0000000..bfec268 --- /dev/null +++ b/embed/templates/login/layout-login.html.tmpl @@ -0,0 +1,29 @@ + + + + + + Probo login + + + + {{template "content" .}} + +
+
+ + + + © 2024 Andrea Fazzi +
+ + +
+ + + + diff --git a/embed/templates/login/login.html.tmpl b/embed/templates/login/login.html.tmpl new file mode 100644 index 0000000..ec11478 --- /dev/null +++ b/embed/templates/login/login.html.tmpl @@ -0,0 +1,25 @@ +{{ define "content" }} +
+
+
+

🎓 Probo_

+

+ Una piattaforma per la creazione, la gestione e la + somministrazione di test the hacker way. Inserisci il + tuo token personale per iniziare. +

+
+
+ +
+ + +
+ +
+ Fai un bel respiro e non aver paura: non è un test su di te ma su quello che sai! + +
+
+
+{{ end }} diff --git a/embed/templates/sessions/layout-sessions.html.tmpl b/embed/templates/sessions/layout-sessions.html.tmpl new file mode 100644 index 0000000..8d538de --- /dev/null +++ b/embed/templates/sessions/layout-sessions.html.tmpl @@ -0,0 +1,55 @@ + + + + + + Probo login + + + + +
+
+ +
+ + Probo_ + + + + + + +
+ + +
+
+ +
+
+ + {{template "content" .}} + +
+
+ + + + © 2024 Andrea Fazzi +
+ + +
+ + + + diff --git a/embed/templates/sessions/sessions.html.tmpl b/embed/templates/sessions/sessions.html.tmpl new file mode 100644 index 0000000..e5b04ad --- /dev/null +++ b/embed/templates/sessions/sessions.html.tmpl @@ -0,0 +1,18 @@ +{{ define "content" }} +
+ {{range $session := . }} +
+
+
+
+
{{$session.Title}}
+
{{$session.CreatedAt}}
+

{{$session.Description}}

+ Inizia! +
+
+
+
+ {{ end }} +
+{{ end }} diff --git a/go.mod b/go.mod index 6727a60..b8259d2 100644 --- a/go.mod +++ b/go.mod @@ -1,15 +1,18 @@ module git.andreafazzi.eu/andrea/probo -go 1.21.6 +go 1.22.2 require ( github.com/alecthomas/chroma v0.10.0 github.com/charmbracelet/bubbles v0.18.1-0.20240309002305-b9e62cbfe181 github.com/charmbracelet/bubbletea v0.25.0 + github.com/charmbracelet/glamour v0.6.0 github.com/charmbracelet/huh v0.3.0 github.com/charmbracelet/lipgloss v0.10.0 + github.com/charmbracelet/log v0.4.0 github.com/d5/tengo/v2 v2.17.0 github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a + github.com/golang-jwt/jwt v3.2.2+incompatible github.com/google/uuid v1.6.0 github.com/itchyny/gojq v0.12.14 github.com/lmittmann/tint v1.0.4 @@ -24,10 +27,13 @@ require ( require ( github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/aymerick/douceur v0.2.0 // indirect github.com/catppuccin/go v0.2.0 // indirect github.com/containerd/console v1.0.4 // indirect github.com/dlclark/regexp2 v1.4.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/gorilla/css v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/itchyny/timefmt-go v0.1.5 // indirect @@ -38,10 +44,12 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/microcosm-cc/bluemonday v1.0.21 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/reflow v0.3.0 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect @@ -52,9 +60,12 @@ require ( github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/yuin/goldmark v1.5.2 // indirect + github.com/yuin/goldmark-emoji v1.0.1 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect + golang.org/x/net v0.19.0 // indirect golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.18.0 // indirect golang.org/x/term v0.18.0 // indirect diff --git a/go.sum b/go.sum index b53536e..56839e1 100644 --- a/go.sum +++ b/go.sum @@ -6,18 +6,25 @@ github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbf github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= +github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/catppuccin/go v0.2.0 h1:ktBeIrIP42b/8FGiScP9sgrWOss3lw0Z5SktRoithGA= github.com/catppuccin/go v0.2.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc= github.com/charmbracelet/bubbles v0.18.1-0.20240309002305-b9e62cbfe181 h1:ntdtXC9+kcgQYvqa6nyLZLniCEUOhWQknLlz38JpDpM= github.com/charmbracelet/bubbles v0.18.1-0.20240309002305-b9e62cbfe181/go.mod h1:Zlzkn8WOd6QS7RC1BXAY1iw1VLq+xT70UZ1vkEtnrvo= github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM= github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg= +github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM273bISc= +github.com/charmbracelet/glamour v0.6.0/go.mod h1:taqWV4swIMMbWALc0m7AfE9JkPSU8om2538k9ITBxOc= github.com/charmbracelet/huh v0.3.0 h1:CxPplWkgW2yUTDDG0Z4S5HH8SJOosWHd4LxCvi0XsKE= github.com/charmbracelet/huh v0.3.0/go.mod h1:fujUdKX8tC45CCSaRQdw789O6uaCRwx8l2NDyKfC4jA= github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s= github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE= +github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM= +github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM= github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro= github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -34,12 +41,18 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a h1:RYfmiM0zluBJOiPDJseKLEN4BapJ42uSi9SZBQ2YyiA= github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= +github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -58,13 +71,18 @@ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69 github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/microcosm-cc/bluemonday v1.0.21 h1:dNH3e4PSyE4vNX+KlRGHT5KrSvjeUkoNPwEORjffHJg= +github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= @@ -73,8 +91,11 @@ github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELU github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -117,24 +138,39 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.5.2 h1:ALmeCk/px5FSm1MAcFBAsVKZjDuMVj8Tm7FFIlMJnqU= +github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os= +github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o= golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= +golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/go.work b/go.work index d959651..0f3acfc 100644 --- a/go.work +++ b/go.work @@ -1,4 +1,6 @@ -go 1.21.6 +go 1.22.2 + +toolchain go1.22.3 use ( . diff --git a/go.work.sum b/go.work.sum index c25bbcf..1da348c 100644 --- a/go.work.sum +++ b/go.work.sum @@ -5,8 +5,6 @@ cloud.google.com/go/firestore v1.14.0/go.mod h1:96MVaHLsEhbvkBEdZgfN+AS/GIkco1LR cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= cloud.google.com/go/longrunning v0.5.4/go.mod h1:zqNVncI0BOP8ST6XQD1+VcvuShMmq7+xFSzOL++V0dI= cloud.google.com/go/storage v1.35.1/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= -github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= -github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/aymanbagabas/go-osc52 v1.0.3 h1:DTwqENW7X9arYimJrPeGZcV0ln14sGMt3pHZspWD+Mg= github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= @@ -51,6 +49,8 @@ github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/srwiley/oksvg v0.0.0-20220128195007-1f435e4c2b44/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q= github.com/srwiley/rasterx v0.0.0-20220128185129-2efea2b9ea41/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/etcd/api/v3 v3.5.10/go.mod h1:TidfmT4Uycad3NM/o25fG3J07odo4GBB9hoxaodFCtI= go.etcd.io/etcd/client/pkg/v3 v3.5.10/go.mod h1:DYivfIviIuQ8+/lCq4vcxuseg2P2XbHygkKwFo9fc8U= @@ -65,7 +65,6 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= diff --git a/main.go b/main.go index c11a61b..c321102 100644 --- a/main.go +++ b/main.go @@ -21,7 +21,9 @@ THE SOFTWARE. */ package main -import "git.andreafazzi.eu/andrea/probo/cmd" +import ( + "git.andreafazzi.eu/andrea/probo/cmd" +) func main() { cmd.Execute() diff --git a/pkg/store/file/file.go b/pkg/store/file/file.go index 728cffd..76cdca1 100644 --- a/pkg/store/file/file.go +++ b/pkg/store/file/file.go @@ -88,7 +88,7 @@ func DefaultIndexDirFunc[T FileStorable, K store.Storer[T]](s *FileStore[T, K]) err = entity.Unmarshal(content) if err != nil { - return err + return fmt.Errorf("An error occurred unmarshalling %v: %v", filename, err) } mEntity, err := s.Create(entity, fullPath) diff --git a/pkg/store/file/quiz.go b/pkg/store/file/quiz.go index de05767..4966429 100644 --- a/pkg/store/file/quiz.go +++ b/pkg/store/file/quiz.go @@ -4,6 +4,7 @@ import ( "bufio" "bytes" "errors" + "fmt" "io" "io/fs" "os" @@ -60,7 +61,7 @@ func DefaultQuizIndexDirFunc(s *QuizFileStore) error { err = entity.Unmarshal(content) if err != nil { - return err + return fmt.Errorf("An error occurred unmarshalling %v: %v", filename, err) } var errQuizAlreadyPresent *store.ErrQuizAlreadyPresent