New option to allow teams to self reset their progression

This commit is contained in:
nemunaire 2023-11-04 21:14:16 +01:00
parent a0c34018cf
commit d6ff46ca7f
9 changed files with 173 additions and 3 deletions

View File

@ -182,6 +182,13 @@
</label>
</div>
<div class="form-check">
<label class="custom-control custom-checkbox">
<input class="custom-control-input" type="checkbox" ng-model="config.canResetProgress" ng-disabled="!config.wip">
<span class="custom-control-label" ng-class="{'text-primary font-weight-bold': !config.canResetProgress != !dist_config.canResetProgress}">Autoriser les équipes à auto-effacer leur progression</span>
</label>
</div>
<div class="form-check">
<label class="custom-control custom-checkbox">
<input class="custom-control-input" type="checkbox" ng-model="config.enableExerciceDepend" ng-change="exerciceDependChange()">

View File

@ -71,6 +71,7 @@ func reloadSettings(config *settings.Settings) {
allowRegistration = config.AllowRegistration
canJoinTeam = config.CanJoinTeam
denyTeamCreation = config.DenyTeamCreation
canResetProgression = config.WorkInProgress && config.CanResetProgression
fic.HintCoefficient = config.HintCurCoefficient
fic.WChoiceCoefficient = config.WChoiceCurCoefficient
fic.ExerciceCurrentCoefficient = config.ExerciceCurCoefficient
@ -211,6 +212,8 @@ func treat(raw_path string) {
treatOpeningHint(raw_path, team)
case "choices":
treatWantChoices(raw_path, team)
case "reset_progress":
treatResetProgress(raw_path, team)
case ".locked":
treatLocked(raw_path, team)
default:

54
checker/reset_progress.go Normal file
View File

@ -0,0 +1,54 @@
package main
import (
"encoding/base64"
"encoding/binary"
"encoding/json"
"io/ioutil"
"log"
"math/rand"
"os"
"srs.epita.fr/fic-server/libfic"
)
var canResetProgression = false
type askResetProgress struct {
ExerciceId int64 `json:"eid"`
}
func treatResetProgress(pathname string, team *fic.Team) {
if !canResetProgression {
log.Printf("[!!!] Receive reset_progress, whereas desactivated in settings.\n")
return
}
// Generate a unique identifier to follow the request in logs
bid := make([]byte, 5)
binary.LittleEndian.PutUint32(bid, rand.Uint32())
id := "[" + base64.StdEncoding.EncodeToString(bid) + "]"
log.Println(id, "New ResetProgress receive", pathname)
var ask askResetProgress
if cnt_raw, err := ioutil.ReadFile(pathname); err != nil {
log.Printf("%s [ERR] %s\n", id, err)
} else if err = json.Unmarshal(cnt_raw, &ask); err != nil {
log.Printf("%s [ERR] %s\n", id, err)
} else if ask.ExerciceId == 0 {
log.Printf("%s [WRN] Invalid content in reset_progress file: %s\n", id, pathname)
os.Remove(pathname)
} else if exercice, err := fic.GetExercice(ask.ExerciceId); err != nil {
log.Printf("%s [ERR] Unable to retrieve the given exercice: %s\n", id, err)
} else if exercice.Disabled {
log.Println("[!!!] The team submits something for a disabled exercice")
} else if err := team.ResetProgressionOnExercice(exercice); err != nil {
log.Printf("%s [ERR] Unable to reset progression: %s\n", id, err)
} else {
appendGenQueue(fic.GenStruct{Id: id, Type: fic.GenTeam, TeamId: team.Id})
if err = os.Remove(pathname); err != nil {
log.Printf("%s [ERR] %s\n", id, err)
}
}
}

View File

@ -10,6 +10,7 @@
Col,
Icon,
Row,
Spinner,
} from 'sveltestrap';
import ExerciceDownloads from '$lib/components/ExerciceDownloads.svelte';
@ -24,8 +25,29 @@
import { current_theme } from '$lib/stores/themes';
import { settings } from '$lib/stores/settings';
import { waitDiff, waitInProgress } from '$lib/wait.js';
let solved = {};
let openResolution = false;
async function askProgressReset() {
waitInProgress.set(true);
const response = await fetch(
"reset_progress",
{
method: "POST",
headers: {'Accept': 'application/json'},
body: JSON.stringify({"eid": $current_exercice.id}),
}
);
if (response.status < 300) {
waitDiff(13, $my.exercices[$current_exercice.id]);
} else {
data = await response.json();
waitInProgress.set(false);
alert("Quelque chose s'est mal passé : " + data.errmsg);
}
}
</script>
{#if $current_exercice}
@ -162,19 +184,29 @@
</Row>
</Col>
{#if $my && $my.team_id}
<Col xs="auto">
<Col xs="auto" class="d-flex flex-column justify-content-between">
{#if $settings.acceptNewIssue}
<a href="issues/?eid={$current_exercice.id}" class="float-end btn btn-sm btn-warning">
<a href="issues/?eid={$current_exercice.id}" class="btn btn-sm btn-warning">
<Icon name="bug" />
Rapporter une anomalie sur ce défi
</a>
{/if}
{#if $settings.QAenabled}
<a href="qa/exercices/{$current_exercice.id}" class="float-end btn btn-sm btn-info" target="_self">
<a href="qa/exercices/{$current_exercice.id}" class="btn btn-sm btn-info" target="_self">
<Icon name="bug" />
Voir les éléments QA sur ce défi
</a>
{/if}
{#if $settings.wip && $settings.canResetProgress}
<Button size="sm" color="dark" on:click={askProgressReset} disabled={!$my.exercices[$current_exercice.id].solved_time || $waitInProgress}>
{#if $waitInProgress}
<Spinner size="sm" />
{:else}
<Icon name="skip-start-fill" />
{/if}
Effacer ma progression sur ce défi
</Button>
{/if}
</Col>
{/if}
</Row>

View File

@ -139,3 +139,47 @@ func (t *Team) DelHistoryItem(kind string, h time.Time, primary *int64, secondar
return nil, nil
}
}
// ResetProgressionOnExercice removes all tries and validations for a given Exercice.
func (t *Team) ResetProgressionOnExercice(exercice *Exercice) error {
hints, err := exercice.GetHints()
if err != nil {
return err
}
flags, err := exercice.GetFlags()
if err != nil {
return err
}
if _, err := DBExec("DELETE FROM exercice_tries WHERE id_team = ? AND id_exercice = ?", t.Id, exercice.Id); err != nil {
return err
}
if _, err := DBExec("DELETE FROM exercice_solved WHERE id_team = ? AND id_exercice = ?", t.Id, exercice.Id); err != nil {
return err
}
for _, hint := range hints {
if _, err := DBExec("DELETE FROM team_hints WHERE id_team = ? AND id_hint = ?", t.Id, hint.Id); err != nil {
return err
}
}
for _, flag := range flags {
if k, ok := flag.(*FlagKey); ok {
if _, err := DBExec("DELETE FROM team_wchoices WHERE id_team = ? AND id_flag = ?", t.Id, k.Id); err != nil {
return err
}
if _, err := DBExec("DELETE FROM flag_found WHERE id_team = ? AND id_flag = ?", t.Id, k.Id); err != nil {
return err
}
} else if mcq, ok := flag.(*MCQ); ok {
if _, err := DBExec("DELETE FROM mcq_found WHERE id_team = ? AND id_mcq = ?", t.Id, mcq.Id); err != nil {
return err
}
}
}
return nil
}

View File

@ -52,6 +52,7 @@ func main() {
http.Handle(fmt.Sprintf("%s/wantchoices/", *prefix), http.StripPrefix(fmt.Sprintf("%s/wantchoices/", *prefix), submissionTeamChecker{"wantint choices", WantChoicesHandler, *teamsDir, *simulator}))
http.Handle(fmt.Sprintf("%s/registration", *prefix), http.StripPrefix(fmt.Sprintf("%s/registration", *prefix), submissionChecker{"registration", RegistrationHandler}))
http.Handle(fmt.Sprintf("%s/resolution/", *prefix), http.StripPrefix(fmt.Sprintf("%s/resolution/", *prefix), ResolutionHandler{}))
http.Handle(fmt.Sprintf("%s/reset_progress", *prefix), http.StripPrefix(fmt.Sprintf("%s/reset_progress", *prefix), submissionTeamChecker{"reset_progress", ResetProgressHandler, *teamsDir, *simulator}))
http.Handle(fmt.Sprintf("%s/submission/", *prefix), http.StripPrefix(fmt.Sprintf("%s/submission/", *prefix), submissionTeamChecker{"submission", SubmissionHandler, *teamsDir, *simulator}))
if *simulator != "" {

26
receiver/reset.go Normal file
View File

@ -0,0 +1,26 @@
package main
import (
"net/http"
"path"
"time"
)
var enableResetProgression = false
func ResetProgressHandler(w http.ResponseWriter, r *http.Request, team string, sURL []string) {
if !enableResetProgression {
http.Error(w, "{\"errmsg\":\"Le challenge est terminé, trop tard !\"}", http.StatusForbidden)
return
}
if challengeEnd != nil && time.Now().After(*challengeEnd) {
http.Error(w, "{\"errmsg\":\"Le challenge est terminé, trop tard !\"}", http.StatusGone)
return
}
// Enqueue file for backend treatment
if saveTeamFile(path.Join(team, "reset_progress"), w, r) {
http.Error(w, "{\"errmsg\":\"Demande acceptée...\"}", http.StatusAccepted)
}
}

View File

@ -64,4 +64,5 @@ func reloadSettings(config *settings.Settings) {
denyNameChange = config.DenyNameChange
acceptNewIssues = config.AcceptNewIssue
allowRegistration = config.AllowRegistration
enableResetProgression = config.WorkInProgress && config.CanResetProgression
}

View File

@ -61,6 +61,8 @@ type Settings struct {
AcceptNewIssue bool `json:"acceptNewIssue,omitempty"`
// QAenabled enables links to QA interface.
QAenabled bool `json:"QAenabled,omitempty"`
// CanResetProgression allows a team to reset the progress it made on a given exercice.
CanResetProgression bool `json:"canResetProgress,omitempty"`
// EnableResolutionRoute activates the route displaying resolution movies.
EnableResolutionRoute bool `json:"enableResolutionRoute,omitempty"`
// PartialValidation validates each correct given answers, don't expect Team to give all correct answer in a try.