From 1a5eea3bb2fd8b3f1f0b3ec549e6784c9b65c0c6 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Mon, 28 Mar 2022 10:53:45 +0200 Subject: [PATCH] Add button to report bad correction --- db.go | 1 + responses.go | 62 ++++++++++++---- ui/src/components/CorrectionResponses.svelte | 2 +- ui/src/components/ResponseCorrected.svelte | 73 +++++++++++++++++++ ui/src/lib/response.js | 17 ++++- ui/src/lib/surveys.js | 3 +- .../surveys/[sid]/responses/index.svelte | 2 +- 7 files changed, 143 insertions(+), 17 deletions(-) diff --git a/db.go b/db.go index d9c7194..79e9349 100644 --- a/db.go +++ b/db.go @@ -126,6 +126,7 @@ CREATE TABLE IF NOT EXISTS survey_responses( score_explanation TEXT, id_corrector INTEGER, time_scored TIMESTAMP NULL DEFAULT NULL, + time_reported TIMESTAMP NULL DEFAULT NULL, FOREIGN KEY(id_question) REFERENCES survey_quests(id_question), FOREIGN KEY(id_user) REFERENCES users(id_user), FOREIGN KEY(id_corrector) REFERENCES users(id_user) diff --git a/responses.go b/responses.go index 072b9e2..b230206 100644 --- a/responses.go +++ b/responses.go @@ -3,6 +3,7 @@ package main import ( "encoding/json" "fmt" + "net/http" "strconv" "time" @@ -97,6 +98,24 @@ func init() { func(r Response, _ *User, _ []byte) HTTPResponse { return APIResponse{r} }), adminRestricted)) + router.POST("/api/surveys/:sid/responses/:rid/report", apiAuthHandler(surveyResponseAuthHandler( + func(s *Survey, r Response, u *User, _ []byte) HTTPResponse { + if s == nil || !s.Corrected || r.IdUser != u.Id { + return APIErrorResponse{err: fmt.Errorf("Cette action est impossible pour l'instant"), status: http.StatusForbidden} + } + + if r.TimeScored == nil || r.TimeReported == nil || r.TimeReported.Before(*r.TimeScored) { + now := time.Now() + r.TimeReported = &now + } else { + r.TimeReported = nil + } + if _, err := r.Update(); err != nil { + return APIErrorResponse{err: err} + } + + return APIResponse{r} + }), loggedUser)) router.GET("/api/surveys/:sid/questions/:qid/responses", apiAuthHandler(questionAuthHandler( func(q Question, u *User, _ []byte) HTTPResponse { return formatApiResponse(q.GetResponses()) @@ -144,6 +163,14 @@ func init() { } func responseHandler(f func(Response, []byte) HTTPResponse) func(httprouter.Params, []byte) HTTPResponse { + return func(ps httprouter.Params, body []byte) HTTPResponse { + return surveyResponseHandler(func(s *Survey, r Response, b []byte) HTTPResponse { + return f(r, b) + })(ps, body) + } +} + +func surveyResponseHandler(f func(*Survey, Response, []byte) HTTPResponse) func(httprouter.Params, []byte) HTTPResponse { return func(ps httprouter.Params, body []byte) HTTPResponse { var survey *Survey = nil @@ -159,18 +186,26 @@ func responseHandler(f func(Response, []byte) HTTPResponse) func(httprouter.Para if response, err := getResponse(rid); err != nil { return APIErrorResponse{err: err} } else { - return f(response, body) + return f(survey, response, body) } } else { if response, err := survey.GetResponse(rid); err != nil { return APIErrorResponse{err: err} } else { - return f(response, body) + return f(survey, response, body) } } } } +func surveyResponseAuthHandler(f func(*Survey, Response, *User, []byte) HTTPResponse) func(*User, httprouter.Params, []byte) HTTPResponse { + return func(u *User, ps httprouter.Params, body []byte) HTTPResponse { + return surveyResponseHandler(func(s *Survey, r Response, body []byte) HTTPResponse { + return f(s, r, u, body) + })(ps, body) + } +} + func responseAuthHandler(f func(Response, *User, []byte) HTTPResponse) func(*User, httprouter.Params, []byte) HTTPResponse { return func(u *User, ps httprouter.Params, body []byte) HTTPResponse { return responseHandler(func(r Response, body []byte) HTTPResponse { @@ -189,17 +224,18 @@ type Response struct { ScoreExplaination *string `json:"score_explaination,omitempty"` IdCorrector *int64 `json:"id_corrector,omitempty"` TimeScored *time.Time `json:"time_scored,omitempty"` + TimeReported *time.Time `json:"time_reported,omitempty"` } func (s *Survey) GetResponses() (responses []Response, err error) { - if rows, errr := DBQuery("SELECT R.id_response, R.id_question, R.id_user, R.answer, R.time_submit, R.score, R.score_explanation, R.id_corrector, R.time_scored FROM survey_responses R INNER JOIN survey_quests Q ON Q.id_question = R.id_question WHERE Q.id_survey=?", s.Id); errr != nil { + if rows, errr := DBQuery("SELECT R.id_response, R.id_question, R.id_user, R.answer, R.time_submit, R.score, R.score_explanation, R.id_corrector, R.time_scored, R.time_reported FROM survey_responses R INNER JOIN survey_quests Q ON Q.id_question = R.id_question WHERE Q.id_survey=?", s.Id); errr != nil { return nil, errr } else { defer rows.Close() for rows.Next() { var r Response - if err = rows.Scan(&r.Id, &r.IdQuestion, &r.IdUser, &r.Answer, &r.TimeSubmit, &r.Score, &r.ScoreExplaination, &r.IdCorrector, &r.TimeScored); err != nil { + if err = rows.Scan(&r.Id, &r.IdQuestion, &r.IdUser, &r.Answer, &r.TimeSubmit, &r.Score, &r.ScoreExplaination, &r.IdCorrector, &r.TimeScored, &r.TimeReported); err != nil { return } responses = append(responses, r) @@ -213,14 +249,14 @@ func (s *Survey) GetResponses() (responses []Response, err error) { } func (s *Survey) GetMyResponses(u *User, showScore bool) (responses []Response, err error) { - if rows, errr := DBQuery("SELECT R.id_response, R.id_question, R.id_user, R.answer, R.time_submit, R.score, R.score_explanation, R.id_corrector, R.time_scored FROM survey_responses R INNER JOIN survey_quests Q ON Q.id_question = R.id_question WHERE Q.id_survey=? AND R.id_user=? ORDER BY time_submit DESC", s.Id, u.Id); errr != nil { + if rows, errr := DBQuery("SELECT R.id_response, R.id_question, R.id_user, R.answer, R.time_submit, R.score, R.score_explanation, R.id_corrector, R.time_scored, R.time_reported FROM survey_responses R INNER JOIN survey_quests Q ON Q.id_question = R.id_question WHERE Q.id_survey=? AND R.id_user=? ORDER BY time_submit DESC", s.Id, u.Id); errr != nil { return nil, errr } else { defer rows.Close() for rows.Next() { var r Response - if err = rows.Scan(&r.Id, &r.IdQuestion, &r.IdUser, &r.Answer, &r.TimeSubmit, &r.Score, &r.ScoreExplaination, &r.IdCorrector, &r.TimeScored); err != nil { + if err = rows.Scan(&r.Id, &r.IdQuestion, &r.IdUser, &r.Answer, &r.TimeSubmit, &r.Score, &r.ScoreExplaination, &r.IdCorrector, &r.TimeScored, &r.TimeReported); err != nil { return } if !showScore { @@ -238,7 +274,7 @@ func (s *Survey) GetMyResponses(u *User, showScore bool) (responses []Response, } func (q *Question) GetMyResponse(u *User, showScore bool) (r Response, err error) { - err = DBQueryRow("SELECT R.id_response, R.id_question, R.id_user, R.answer, R.time_submit, R.score, R.score_explanation, R.id_corrector, R.time_scored FROM survey_responses R WHERE R.id_question=? AND R.id_user=? ORDER BY time_submit DESC LIMIT 1", q.Id, u.Id).Scan(&r.Id, &r.IdQuestion, &r.IdUser, &r.Answer, &r.TimeSubmit, &r.Score, &r.ScoreExplaination, &r.IdCorrector, &r.TimeScored) + err = DBQueryRow("SELECT R.id_response, R.id_question, R.id_user, R.answer, R.time_submit, R.score, R.score_explanation, R.id_corrector, R.time_scored, R.time_reported FROM survey_responses R WHERE R.id_question=? AND R.id_user=? ORDER BY time_submit DESC LIMIT 1", q.Id, u.Id).Scan(&r.Id, &r.IdQuestion, &r.IdUser, &r.Answer, &r.TimeSubmit, &r.Score, &r.ScoreExplaination, &r.IdCorrector, &r.TimeScored, &r.TimeReported) if !showScore { r.Score = nil r.ScoreExplaination = nil @@ -247,14 +283,14 @@ func (q *Question) GetMyResponse(u *User, showScore bool) (r Response, err error } func (q *Question) GetResponses() (responses []Response, err error) { - if rows, errr := DBQuery("SELECT id_response, id_question, S.id_user, answer, S.time_submit, score, score_explanation, id_corrector, time_scored FROM (SELECT id_user, MAX(time_submit) AS time_submit FROM survey_responses WHERE id_question=? GROUP BY id_user) R INNER JOIN survey_responses S ON S.id_user = R.id_user AND S.time_submit = R.time_submit AND S.id_question=?", q.Id, q.Id); errr != nil { + if rows, errr := DBQuery("SELECT id_response, id_question, S.id_user, answer, S.time_submit, score, score_explanation, id_corrector, time_scored, time_reported FROM (SELECT id_user, MAX(time_submit) AS time_submit FROM survey_responses WHERE id_question=? GROUP BY id_user) R INNER JOIN survey_responses S ON S.id_user = R.id_user AND S.time_submit = R.time_submit AND S.id_question=?", q.Id, q.Id); errr != nil { return nil, errr } else { defer rows.Close() for rows.Next() { var r Response - if err = rows.Scan(&r.Id, &r.IdQuestion, &r.IdUser, &r.Answer, &r.TimeSubmit, &r.Score, &r.ScoreExplaination, &r.IdCorrector, &r.TimeScored); err != nil { + if err = rows.Scan(&r.Id, &r.IdQuestion, &r.IdUser, &r.Answer, &r.TimeSubmit, &r.Score, &r.ScoreExplaination, &r.IdCorrector, &r.TimeScored, &r.TimeReported); err != nil { return } responses = append(responses, r) @@ -268,12 +304,12 @@ func (q *Question) GetResponses() (responses []Response, err error) { } func getResponse(id int) (r Response, err error) { - err = DBQueryRow("SELECT id_response, id_question, id_user, answer, time_submit, score, score_explanation, id_corrector, time_scored FROM survey_responses WHERE id_response=?", id).Scan(&r.Id, &r.IdQuestion, &r.IdUser, &r.Answer, &r.TimeSubmit, &r.Score, &r.ScoreExplaination, &r.IdCorrector, &r.TimeScored) + err = DBQueryRow("SELECT id_response, id_question, id_user, answer, time_submit, score, score_explanation, id_corrector, time_scored, time_reported FROM survey_responses WHERE id_response=?", id).Scan(&r.Id, &r.IdQuestion, &r.IdUser, &r.Answer, &r.TimeSubmit, &r.Score, &r.ScoreExplaination, &r.IdCorrector, &r.TimeScored, &r.TimeReported) return } func (s *Survey) GetResponse(id int) (r Response, err error) { - err = DBQueryRow("SELECT R.id_response, R.id_question, R.id_user, R.answer, R.time_submit, R.score, R.score_explanation, R.id_corrector, R.time_scored FROM survey_responses R INNER JOIN survey_quests Q ON Q.id_question = R.id_question WHERE R.id_response=? AND Q.id_survey=?", id, s.Id).Scan(&r.Id, &r.IdQuestion, &r.IdUser, &r.Answer, &r.TimeSubmit, &r.Score, &r.ScoreExplaination, &r.IdCorrector, &r.TimeScored) + err = DBQueryRow("SELECT R.id_response, R.id_question, R.id_user, R.answer, R.time_submit, R.score, R.score_explanation, R.id_corrector, R.time_scored, R.time_reported FROM survey_responses R INNER JOIN survey_quests Q ON Q.id_question = R.id_question WHERE R.id_response=? AND Q.id_survey=?", id, s.Id).Scan(&r.Id, &r.IdQuestion, &r.IdUser, &r.Answer, &r.TimeSubmit, &r.Score, &r.ScoreExplaination, &r.IdCorrector, &r.TimeScored, &r.TimeReported) return } @@ -283,7 +319,7 @@ func (s *Survey) NewResponse(id_question int64, id_user int64, response string) } else if rid, err := res.LastInsertId(); err != nil { return Response{}, err } else { - return Response{rid, id_question, id_user, response, time.Now(), nil, nil, nil, nil}, nil + return Response{rid, id_question, id_user, response, time.Now(), nil, nil, nil, nil, nil}, nil } } @@ -296,7 +332,7 @@ func (r *Response) GetSurveyId() (int64, error) { } func (r Response) Update() (Response, error) { - _, err := DBExec("UPDATE survey_responses SET id_question = ?, id_user = ?, answer = ?, time_submit = ?, score = ?, score_explanation = ?, id_corrector = ?, time_scored = ? WHERE id_response = ?", r.IdQuestion, r.IdUser, r.Answer, r.TimeSubmit, r.Score, r.ScoreExplaination, r.IdCorrector, r.TimeScored, r.Id) + _, err := DBExec("UPDATE survey_responses SET id_question = ?, id_user = ?, answer = ?, time_submit = ?, score = ?, score_explanation = ?, id_corrector = ?, time_scored = ?, time_reported = ? WHERE id_response = ?", r.IdQuestion, r.IdUser, r.Answer, r.TimeSubmit, r.Score, r.ScoreExplaination, r.IdCorrector, r.TimeScored, r.TimeReported, r.Id) return r, err } diff --git a/ui/src/components/CorrectionResponses.svelte b/ui/src/components/CorrectionResponses.svelte index 90ef5e9..7ed7042 100644 --- a/ui/src/components/CorrectionResponses.svelte +++ b/ui/src/components/CorrectionResponses.svelte @@ -32,7 +32,7 @@ let filteredResponses = []; $:{ - filteredResponses = responses.filter((r) => (notCorrected || !r.time_scored) && (!filter || ((filter[0] == '!' && !r.value.match(filter.substring(1))) || r.value.match(filter)))); + filteredResponses = responses.filter((r) => (notCorrected || r.time_scored <= r.time_reported || !r.time_scored) && (!filter || ((filter[0] == '!' && !r.value.match(filter.substring(1))) || r.value.match(filter)))); } export async function applyCorrections() { diff --git a/ui/src/components/ResponseCorrected.svelte b/ui/src/components/ResponseCorrected.svelte index 54f3eea..e83d195 100644 --- a/ui/src/components/ResponseCorrected.svelte +++ b/ui/src/components/ResponseCorrected.svelte @@ -1,6 +1,40 @@ {#if response.score !== undefined} @@ -19,6 +53,45 @@
+ {#if response.id_user == $user.id} + + {:else if $user.is_admin && response.time_reported} + {#if response.time_reported > response.time_scored} + = 45} + > + {:else} + = 45} + > + {/if} + {/if} {#if response.score_explaination} {response.score_explaination} {:else if response.score === 100} diff --git a/ui/src/lib/response.js b/ui/src/lib/response.js index f56a9ee..1f38d80 100644 --- a/ui/src/lib/response.js +++ b/ui/src/lib/response.js @@ -5,7 +5,7 @@ export class Response { } } - update({ id, id_question, id_user, value, time_submit, score, score_explaination, id_corrector, time_scored }) { + update({ id, id_question, id_user, value, time_submit, score, score_explaination, id_corrector, time_scored, time_reported }) { this.id = id; this.id_question = id_question; this.id_user = id_user; @@ -15,6 +15,21 @@ export class Response { this.score_explaination = score_explaination; this.id_corrector = id_corrector; this.time_scored = time_scored; + this.time_reported = time_reported; + } + + async report(survey) { + const res = await fetch(`api/surveys/${survey.id}/responses/${this.id}/report`, { + method: 'POST', + headers: {'Accept': 'application/json'}, + }); + if (res.status == 200) { + const data = await res.json(); + this.update(data); + return data; + } else { + throw new Error((await res.json()).errmsg); + } } async save() { diff --git a/ui/src/lib/surveys.js b/ui/src/lib/surveys.js index 5c1859e..27b5edd 100644 --- a/ui/src/lib/surveys.js +++ b/ui/src/lib/surveys.js @@ -1,4 +1,5 @@ import { getQuestions } from './questions'; +import { Response } from './response'; export class Survey { constructor(res) { @@ -49,7 +50,7 @@ export class Survey { headers: {'Accept': 'application/json'}, }); if (res.status == 200) { - return await res.json(); + return (await res.json()).map((r) => new Response(r)); } else { throw new Error((await res.json()).errmsg); } diff --git a/ui/src/routes/surveys/[sid]/responses/index.svelte b/ui/src/routes/surveys/[sid]/responses/index.svelte index 0c344ba..a45583d 100644 --- a/ui/src/routes/surveys/[sid]/responses/index.svelte +++ b/ui/src/routes/surveys/[sid]/responses/index.svelte @@ -56,7 +56,7 @@ {:then responses} {#if responses} - {responses.filter((r) => !r.time_scored).length} / + {responses.filter((r) => !r.time_scored || (r.time_reported && r.time_reported >= r.time_scored)).length} / {responses.length} {:else} 0