From fe79033d51bef89612df3709f5d7d1a81e883f1a Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Tue, 1 Mar 2022 15:26:30 +0100 Subject: [PATCH] Can ask questions during live sessions --- asks.go | 105 +++++++++++++++++++++++ db.go | 14 +++ direct.go | 20 +++++ ui/src/routes/surveys/[sid]/admin.svelte | 66 ++++++++++++++ ui/src/routes/surveys/[sid]/live.svelte | 61 ++++++++++++- 5 files changed, 264 insertions(+), 2 deletions(-) create mode 100644 asks.go diff --git a/asks.go b/asks.go new file mode 100644 index 0000000..d1faff2 --- /dev/null +++ b/asks.go @@ -0,0 +1,105 @@ +package main + +import ( + "encoding/json" + "time" +) + +func init() { + router.POST("/api/surveys/:sid/ask", apiAuthHandler(surveyAuthHandler(func(s Survey, u *User, body []byte) HTTPResponse { + var ask Ask + if err := json.Unmarshal(body, &ask); err != nil { + return APIErrorResponse{err: err} + } + + a, err := s.NewAsk(u.Id, ask.Content) + if err != nil { + return APIErrorResponse{err: err} + } + + if s.Direct != nil { + s.WSAdminWriteAll(WSMessage{Action: "new_ask", UserId: &u.Id, QuestionId: &a.Id, Response: ask.Content}) + } + + return formatApiResponse(a, err) + }), loggedUser)) + router.GET("/api/surveys/:sid/ask", apiHandler(surveyHandler( + func(s Survey, _ []byte) HTTPResponse { + return formatApiResponse(s.GetAsks(true)) + }), adminRestricted)) +} + +type Ask struct { + Id int64 `json:"id"` + IdSurvey int64 `json:"id_survey"` + IdUser int64 `json:"id_user"` + Date time.Time `json:"date"` + Content string `json:"content"` + Answered bool `json:"answered,omitempty"` +} + +func (s *Survey) GetAsks(unansweredonly bool) (asks []Ask, err error) { + cmp := "" + if unansweredonly { + cmp = " AND answered = 0" + } + + if rows, errr := DBQuery("SELECT id_ask, id_survey, id_user, date, content, answered FROM survey_asks WHERE id_survey=?"+cmp, s.Id); errr != nil { + return nil, errr + } else { + defer rows.Close() + + for rows.Next() { + var a Ask + if err = rows.Scan(&a.Id, &a.IdSurvey, &a.IdUser, &a.Date, &a.Content, &a.Answered); err != nil { + return + } + asks = append(asks, a) + } + if err = rows.Err(); err != nil { + return + } + + return + } +} + +func GetAsk(id int) (a Ask, err error) { + err = DBQueryRow("SELECT id_ask, id_survey, id_user, date, content, answered FROM survey_asks WHERE id_ask = ?", id).Scan(&a.Id, &a.IdSurvey, &a.IdUser, &a.Date, &a.Content, &a.Answered) + return +} + +func (s *Survey) NewAsk(id_user int64, content string) (Ask, error) { + if res, err := DBExec("INSERT INTO survey_asks (id_survey, id_user, date, content) VALUES (?, ?, ?, ?)", s.Id, id_user, time.Now(), content); err != nil { + return Ask{}, err + } else if aid, err := res.LastInsertId(); err != nil { + return Ask{}, err + } else { + return Ask{aid, s.Id, id_user, time.Now(), content, false}, nil + } +} + +func (a *Ask) Update() error { + _, err := DBExec("UPDATE survey_asks SET id_survey = ?, id_user = ?, date = ?, content = ?, answered = ? WHERE id_ask = ?", a.IdSurvey, a.IdUser, a.Date, a.Content, a.Answered, a.Id) + return err +} + +func (a *Ask) Delete() (int64, error) { + if res, err := DBExec("DELETE FROM survey_asks WHERE id_ask = ?", a.Id); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} + +func ClearAsks() (int64, error) { + if res, err := DBExec("DELETE FROM survey_asks"); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} diff --git a/db.go b/db.go index 4aee092..a8aafb9 100644 --- a/db.go +++ b/db.go @@ -154,6 +154,20 @@ CREATE TABLE IF NOT EXISTS student_corrected( FOREIGN KEY(id_user) REFERENCES users(id_user), FOREIGN KEY(id_template) REFERENCES correction_templates(id_template) ) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin; +`); err != nil { + return err + } + if _, err := db.Exec(` +CREATE TABLE IF NOT EXISTS survey_asks( + id_ask INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, + id_survey INTEGER NOT NULL, + id_user INTEGER NOT NULL, + date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + content TEXT NOT NULL, + answered BOOLEAN NOT NULL DEFAULT FALSE, + FOREIGN KEY(id_user) REFERENCES users(id_user), + FOREIGN KEY(id_survey) REFERENCES surveys(id_survey) +) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin; `); err != nil { return err } diff --git a/direct.go b/direct.go index 2e45835..b833fe9 100644 --- a/direct.go +++ b/direct.go @@ -328,6 +328,26 @@ func SurveyWSAdmin(w http.ResponseWriter, r *http.Request, ps httprouter.Params, } } } + } else if v.Action == "get_asks" { + if survey, err := getSurvey(sid); err != nil { + log.Println("Unable to retrieve survey:", err) + } else if asks, err := survey.GetAsks(v.Response == ""); err != nil { + log.Println("Unable to retrieve asks:", err) + } else { + for _, a := range asks { + wsjson.Write(context.Background(), ws, WSMessage{Action: "new_ask", UserId: &a.IdUser, QuestionId: &a.Id, Response: a.Content}) + } + } + } else if v.Action == "mark_answered" && v.QuestionId != nil { + if asks, err := GetAsk(int(*v.QuestionId)); err != nil { + log.Println("Unable to retrieve ask:", err) + } else { + asks.Answered = true + err = asks.Update() + if err != nil { + log.Println("Unable to update:", err) + } + } } else { log.Println("Unknown admin action:", v.Action) } diff --git a/ui/src/routes/surveys/[sid]/admin.svelte b/ui/src/routes/surveys/[sid]/admin.svelte index 8978448..2ec87b1 100644 --- a/ui/src/routes/surveys/[sid]/admin.svelte +++ b/ui/src/routes/surveys/[sid]/admin.svelte @@ -63,6 +63,7 @@ responsesbyid = tmp; } + let asks = []; function wsconnect() { if (ws !== null) return; @@ -72,6 +73,7 @@ ws_up = true; ws.send('{"action":"get_responses"}'); ws.send('{"action":"get_stats"}'); + ws.send('{"action":"get_asks"}'); }); ws.addEventListener("close", (e) => { @@ -99,6 +101,9 @@ } else if (data.action && data.action == "new_response") { if (!responses[data.question]) responses[data.question] = {}; responses[data.question][data.user] = data.value; + } else if (data.action && data.action == "new_ask") { + asks.push({"id": data.question, "content": data.value, "userid": data.user}); + asks = asks; } else { current_question = null; } @@ -224,6 +229,67 @@ {/await} {/if} +
+ + +

+ Questions + {#if asks.length} + + {asks.length} question{#if asks.length > 1}s{/if} + + {/if} +

+ {#if asks.length} + {#each asks as ask (ask.id)} +
+
+

+ {ask.content} +

+
+ +
+ {/each} + {:else} +
+ Pas de question pour l'instant. +
+ {/if} +
+ + {/if} + {:else}

La session est terminée. On se retrouve une prochaine fois…