From c129b2e477773999f826d3dc248ef662bda9cd1d Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Fri, 25 Jan 2019 07:26:52 +0100 Subject: [PATCH 01/17] frontend: polish public version checks --- frontend/static/js/challenge.js | 31 +++++++++++++++++++++++++------ frontend/static/views/defi.html | 5 ++++- libfic/team_my.go | 4 ++++ 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/frontend/static/js/challenge.js b/frontend/static/js/challenge.js index bbf92bff..d80e96ff 100644 --- a/frontend/static/js/challenge.js +++ b/frontend/static/js/challenge.js @@ -19,12 +19,21 @@ function treatFlagKey(flag) { } if (flag.found == null && flag.soluce !== undefined) { - if (check === undefined) check = true; + if (flag.value && flag.soluce) { + if (flag.ignore_case) + flag.value = flag.value.toLowerCase(); + if (flag.validator_regexp) { + var re = new RegExp(flag.validator_regexp, flag.ignore_case?'ui':'u'); + var match = re.exec(flag.value); + match.shift(); + flag.value = match.join("+"); + } - if (flag.value && flag.soluce == b2sum(flag.value)) - flag.found = new Date(); - check &= flag.found; + if (flag.soluce == b2sum(flag.value)) + flag.found = new Date(); + } } + return flag.found !== undefined && flag.found !== false; } angular.module("FICApp", ["ngRoute", "ngSanitize"]) @@ -329,7 +338,11 @@ angular.module("FICApp", ["ngRoute", "ngSanitize"]) angular.forEach(data.exercices, function(exercice, eid) { if ($scope.my && $scope.my.exercices[eid] && $scope.my.exercices[eid].submitted) data.exercices[eid].timeouted = true; + if ($scope.my && $scope.my.exercices[eid] && $scope.my.exercices[eid].solved !== undefined) + data.exercices[eid].solved = $scope.my.exercices[eid].solved; angular.forEach(exercice.flags, function(flag, fid) { + if ($scope.my && $scope.my.exercices[eid] && $scope.my.exercices[eid].flags && $scope.my.exercices[eid].flags[fid] && $scope.my.exercices[eid].flags[fid].found !== undefined) + data.exercices[eid].flags[fid].found = $scope.my.exercices[eid].flags[fid].found; if ($scope.my && $scope.my.exercices[eid] && $scope.my.exercices[eid].flags && $scope.my.exercices[eid].flags[fid] && $scope.my.exercices[eid].flags[fid].value !== undefined) data.exercices[eid].flags[fid].value = $scope.my.exercices[eid].flags[fid].value; if ($scope.my && $scope.my.exercices[eid] && $scope.my.exercices[eid].flags && $scope.my.exercices[eid].flags[fid] && $scope.my.exercices[eid].flags[fid].values !== undefined) @@ -337,6 +350,10 @@ angular.module("FICApp", ["ngRoute", "ngSanitize"]) else data.exercices[eid].flags[fid].values = [""]; }); + angular.forEach(exercice.mcqs, function(mcq, mid) { + if ($scope.my && $scope.my.exercices[eid] && $scope.my.exercices[eid].mcqs && $scope.my.exercices[eid].mcqs[mid] && $scope.my.exercices[eid].mcqs[mid].solved !== undefined) + data.exercices[eid].mcqs[mid].solved = $scope.my.exercices[eid].mcqs[mid].solved; + }); }); angular.forEach(data.exercices, function(exercice, eid) { angular.forEach(exercice.mcqs, function(mcq, mid) { @@ -474,7 +491,9 @@ angular.module("FICApp", ["ngRoute", "ngSanitize"]) { resp["flags"] = {}; angular.forEach($scope.my.exercices[$rootScope.current_exercice].flags, function(flag,kid) { - treatFlagKey(flag); + if (check === undefined) check = true; + + check &= treatFlagKey(flag) || flag.found; if (flag.found == null && flag.soluce === undefined) { resp["flags"][kid] = flag.value; } @@ -483,9 +502,9 @@ angular.module("FICApp", ["ngRoute", "ngSanitize"]) if ($scope.my.exercices[$rootScope.current_exercice].mcqs && Object.keys($scope.my.exercices[$rootScope.current_exercice].mcqs).length) { - var soluce = ""; resp["mcqs"] = {}; angular.forEach($scope.my.exercices[$rootScope.current_exercice].mcqs, function(mcq) { + var soluce = ""; if (mcq.solved == null) { angular.forEach(mcq.choices, function(choice, cid) { if (mcq.soluce !== undefined) { diff --git a/frontend/static/views/defi.html b/frontend/static/views/defi.html index 48c88b7a..64e93bc7 100644 --- a/frontend/static/views/defi.html +++ b/frontend/static/views/defi.html @@ -119,9 +119,12 @@ Défi réussi !
-

+

Vous êtes la {{ my.exercices[current_exercice].solved_rank }} équipe à avoir résolu ce défi à {{ my.exercices[current_exercice].solved_time | date:"mediumTime" }}. Vous avez marqué !

+

+ Bravo, vous avez résolu ce défi à {{ my.exercices[current_exercice].solved_time | date:"mediumTime" }}. Vous marquez ! +



diff --git a/libfic/team_my.go b/libfic/team_my.go index a471652b..0f74415f 100644 --- a/libfic/team_my.go +++ b/libfic/team_my.go @@ -30,6 +30,8 @@ type myTeamFlag struct { Help string `json:"help,omitempty"` Separator string `json:"separator,omitempty"` IgnoreOrder bool `json:"ignore_order,omitempty"` + IgnoreCase bool `json:"ignore_case,omitempty"` + ValidatorRe *string `json:"validator_regexp,omitempty"` Solved *time.Time `json:"found,omitempty"` Soluce string `json:"soluce,omitempty"` Choices map[string]string `json:"choices,omitempty"` @@ -189,6 +191,8 @@ func MyJSONTeam(t *Team, started bool) (interface{}, error) { // Retrieve solved state or solution for public iface if t == nil { + flag.IgnoreCase = k.IgnoreCase + flag.ValidatorRe = k.ValidatorRegexp flag.Soluce = hex.EncodeToString(k.Checksum) } else if PartialValidation { flag.Solved = t.HasPartiallySolved(k) From 04b42de06115f6f35c4e6762d541b1fd7744dee8 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Fri, 25 Jan 2019 07:54:27 +0100 Subject: [PATCH 02/17] libfic: add igncorecase flag to regexp related to ignorecase flag --- libfic/flag_key.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/libfic/flag_key.go b/libfic/flag_key.go index c31c8ac7..065cb89f 100644 --- a/libfic/flag_key.go +++ b/libfic/flag_key.go @@ -73,7 +73,10 @@ func getHashedFlag(raw_value []byte) [blake2b.Size]byte { return hash } -func ExecValidatorRegexp(vre string, val []byte) ([]byte, error) { +func ExecValidatorRegexp(vre string, val []byte, ignorecase bool) ([]byte, error) { + if (ignorecase) { + vre = "(?i)" + vre + } if re, err := regexp.Compile(vre); err != nil { return val, err } else if res := re.FindSubmatch(val); res == nil { @@ -92,7 +95,7 @@ func (e Exercice) AddRawFlagKey(name string, help string, ignorecase bool, valid // Check that raw value passes through the regexp if validator_regexp != nil { var err error - if raw_value, err = ExecValidatorRegexp(*validator_regexp, raw_value); err != nil { + if raw_value, err = ExecValidatorRegexp(*validator_regexp, raw_value, ignorecase); err != nil { return FlagKey{}, err } } @@ -127,7 +130,7 @@ func (k FlagKey) ComputeChecksum(val []byte) (cksum []byte, err error) { // Check that raw value passes through the regexp if k.ValidatorRegexp != nil { - if val, err = ExecValidatorRegexp(*k.ValidatorRegexp, val); err != nil { + if val, err = ExecValidatorRegexp(*k.ValidatorRegexp, val, k.IgnoreCase); err != nil { return } } From 2b106df669362f6294ed2570c7082952c0327e3c Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Fri, 25 Jan 2019 08:29:39 +0100 Subject: [PATCH 03/17] frontend: avoid fetching events.json on public interface --- frontend/static/js/challenge.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/static/js/challenge.js b/frontend/static/js/challenge.js index d80e96ff..d56ce50b 100644 --- a/frontend/static/js/challenge.js +++ b/frontend/static/js/challenge.js @@ -250,6 +250,8 @@ angular.module("FICApp", ["ngRoute", "ngSanitize"]) var refreshRate = 1200; if ($rootScope.notify_field == 0 && eventsLastTreated) refreshRate = 30000; + if ($scope.my && !$scope.my.team_id) + return; refreshEventsInterval = $interval(refreshEvents, Math.floor(Math.random() * refreshRate * 2) + refreshRate); if (!eventsLastTreated) { From 7587cd9140aa6263e5a8863a19a9729123cd0fdc Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Fri, 25 Jan 2019 12:39:27 +0100 Subject: [PATCH 04/17] Remove old unused files --- admin/fill_exercices.sh | 262 ---------------------------------------- admin/get_files.sh | 23 ---- 2 files changed, 285 deletions(-) delete mode 100755 admin/fill_exercices.sh delete mode 100755 admin/get_files.sh diff --git a/admin/fill_exercices.sh b/admin/fill_exercices.sh deleted file mode 100755 index e2ce2141..00000000 --- a/admin/fill_exercices.sh +++ /dev/null @@ -1,262 +0,0 @@ -#!/bin/bash - -BASEURL="http://localhost:8081" -BASEURI="https://owncloud.srs.epita.fr/remote.php/webdav/FIC 2018" -BASEFILE="/mnt/fic/" -CLOUDPASS="$CLOUD_USER:$CLOUD_PASS" - -new_theme() { - NAME=`echo $1 | sed 's/"/\\\\"/g'` - AUTHORS=`echo $2 | sed 's/"/\\\\"/g'` - curl -f -s -d "{\"name\": \"$NAME\", \"authors\": \"$AUTHORS\"}" "${BASEURL}/api/themes" | - grep -Eo '"id":[0-9]+,' | grep -Eo "[0-9]+" -} - -new_exercice() { - THEME="$1" - TITLE=`echo "$2" | sed 's/"/\\\\"/g'` - STATEMENT=`echo "$3" | sed 's/"/\\\\"/g' | sed ':a;N;$!ba;s/\n/
/g'` - DEPEND="$4" - GAIN="$5" - VIDEO="$6" - - curl -f -s -d "{\"title\": \"$TITLE\", \"statement\": \"$STATEMENT\", \"depend\": $DEPEND, \"gain\": $GAIN, \"videoURI\": \"$VIDEO\"}" "${BASEURL}/api/themes/$THEME/exercices" | - grep -Eo '"id":[0-9]+,' | grep -Eo "[0-9]+" -} - -new_file() ( - THEME="$1" - EXERCICE="$2" - URI="$3" - DIGEST="$4" - ARGS="$5" - - FIRST= - PARTS=$(echo "$ARGS" | while read arg - do - [ -n "$arg" ] && { - [ -z "${FIRST}" ] || echo -n "," - echo "\"$arg\"" - } - FIRST=1 - done) - - [ -n "${DIGEST}" ] && DIGEST=", \"digest\": \"${DIGEST}\"" - - cat <&2 -{"path": "${BASEFILE}${URI}"${DIGEST}, "parts": [${PARTS}]} -EOF - -# curl -f -s -d "{\"URI\": \"${BASEFILE}${URI}\"}" "${BASEURL}/api/themes/$THEME/$EXERCICE/files" | - curl -f -s -d @- "${BASEURL}/api/themes/$THEME/exercices/$EXERCICE/files" </g'` - COST="$5" - URI="$6" - - [ -n "${CONTENT}" ] && CONTENT=", \"content\": \"${CONTENT}\"" - [ -n "${URI}" ] && URI=", \"path\": \"${BASEFILE}${URI}\"" - - curl -f -s -d "{\"title\": \"$TITLE\"$CONTENT$URI, \"cost\": $COST}" "${BASEURL}/api/themes/$THEME/exercices/$EXERCICE/hints" | - grep -Eo '"id":[0-9]+,' | grep -Eo "[0-9]+" -} - -new_key() { - THEME="$1" - EXERCICE="$2" - TYPE="$3" - KEY=`echo $4 | sed 's#\\\\#\\\\\\\\#g' | sed 's/"/\\\\"/g'` - - curl -f -s -d "{\"type\": \"$TYPE\", \"key\": \"$KEY\"}" "${BASEURL}/api/themes/$THEME/exercices/$EXERCICE/keys" | - grep -Eo '"id":[0-9]+,' | grep -Eo "[0-9]+" -} - -get_dir_from_cloud() { - curl -f -s -X PROPFIND -u "${CLOUDPASS}" "${BASEURI}$1" | xmllint --format - | grep 'd:href' | sed -E 's/^.*>(.*)<.*$/\1/' -} -get_dir() { - ls "${BASEFILE}$1" 2> /dev/null -} -#alias get_dir=get_dir_from_cloud - -get_file_from_cloud() { - curl -f -s -u "${CLOUDPASS}" "${BASEURI}$1" | tr -d '\r' -} -get_file() { - cat "${BASEFILE}$1" 2> /dev/null | tr -d '\r' - echo -} -#alias get_file=get_file_from_cloud - -unhtmlentities() { - cat | sed -E 's/%20/ /g' | sed -E "s/%27/'/g" | sed -E 's/%c3%a9/é/g' | sed -E 's/%c3%a8/è/g' -} - -# Theme -{ - if [ $# -ge 1 ]; then - echo $1 - else - get_dir "" - fi -} | while read f; do basename "$f"; done | while read THEME_URI -do - THM_BASEURI="/${THEME_URI}/" - THEME_NAME=$(echo "${THEME_URI#*-}" | unhtmlentities) - THEME_AUTHORS=$(get_file "${THM_BASEURI}/AUTHORS.txt" | sed '/^$/d;s/$/, /' | tr -d '\n' | sed 's/, $//') - THEME_ID=`new_theme "$THEME_NAME" "$THEME_AUTHORS"` - if [ -z "$THEME_ID" ]; then - echo -e "\e[31;01m!!! An error occured during theme add\e[00m" - continue - else - echo -e "\e[33m>>> New theme created:\e[00m $THEME_ID - $THEME_NAME" - fi - - LAST=null - EXO_NUM=0 - { - if [ $# -ge 2 ]; then - echo "$2" - else - get_dir "${THM_BASEURI}" - fi - } | while read f; do basename "$f"; done | while read EXO_URI - do - case ${EXO_URI} in - [0-9]-*) - ;; - *) - continue;; - esac - - #EXO_NUM=$((EXO_NUM + 1)) - EXO_NUM=${EXO_URI%-*} - EXO_NAME=$(echo "${EXO_URI#*-}" | unhtmlentities) - echo - echo -e "\e[36m--- Filling exercice ${EXO_NUM} in theme ${THEME_NAME}\e[00m" - - EXO_BASEURI="${EXO_URI}/" - - EXO_VIDEO=$(get_dir "${THM_BASEURI}${EXO_BASEURI}/resolution/" | grep -E "\.(mov|mkv|mp4|avi|flv|ogv|webm)$" | while read f; do basename "$f"; done | tail -1) - [ -n "$EXO_VIDEO" ] && EXO_VIDEO="/resolution${THM_BASEURI}${EXO_BASEURI}resolution/${EXO_VIDEO}" - - if [ "${LAST}" = "null" ]; then - echo ">>> Assuming this exercice has no dependency" - else - echo ">>> Assuming this exercice depends on the last entry (id=${LAST})" - fi - - EXO_GAIN=$((3 * (2 ** $EXO_NUM) - 1)) - HINT_COST=$(($EXO_GAIN / 4)) - echo ">>> Using default gain: ${EXO_GAIN} points" - - EXO_SCENARIO=$(get_file "${THM_BASEURI}${EXO_BASEURI}/scenario.txt") - - EXO_ID=`new_exercice "${THEME_ID}" "${EXO_NAME}" "${EXO_SCENARIO}" "${LAST}" "${EXO_GAIN}" "${EXO_VIDEO}"` - if [ -z "$EXO_ID" ]; then - echo -e "\e[31;01m!!! An error occured during exercice add.\e[00m" - continue - else - echo -e "\e[32m>>> New exercice created:\e[00m $EXO_ID - $EXO_NAME" - fi - - - # Keys - get_file "${THM_BASEURI}${EXO_BASEURI}/flags.txt" | while read KEYLINE - do - [ -z "${KEYLINE}" ] && continue - - KEY_NAME=$(echo "$KEYLINE" | cut -d$'\t' -f 1) - KEY_RAW=$(echo "$KEYLINE" | cut -d$'\t' -f 2-) - - if [ -z "${KEY_RAW}" ] || [ "${KEY_NAME}" = "${KEY_RAW}" ]; then - KEY_NAME=$(echo "$KEYLINE" | cut -d : -f 1) - KEY_RAW=$(echo "$KEYLINE" | cut -d : -f 2-) - fi - - if [ -z "${KEY_NAME}" ]; then - KEY_NAME="Flag" - fi - - KEY_ID=`new_key "${THEME_ID}" "${EXO_ID}" "${KEY_NAME}" "${KEY_RAW}"` - if [ -z "$KEY_ID" ]; then - echo -e "\e[31;01m!!! An error occured during key import!\e[00m (name=${KEYNAME};raw=${KEY_RAW})" - else - echo -e "\e[32m>>> New key added:\e[00m $KEY_ID - $KEY_NAME" - fi - done - - - # Hints - HINTS=$(get_dir "${THM_BASEURI}${EXO_BASEURI}/hints/" | sed -E 's#(.*)#hints/\1#') - [ -z "${HINTS}" ] && HINTS=$(get_dir "${THM_BASEURI}${EXO_BASEURI}/" | grep ^hint.) - [ -z "${HINTS}" ] && HINTS="hint.txt" - HINT_COUNT=1 - echo "${HINTS}" | while read HINT - do - EXO_HINT=$(get_file "${THM_BASEURI}${EXO_BASEURI}/${HINT}") - if [ -n "$EXO_HINT" ]; then - EXO_HINT_TYPE=$(echo "${EXO_HINT}" | file --mime-type -b -) - if echo "${EXO_HINT_TYPE}" | grep text/ && [ $(echo "${EXO_HINT}" | wc -l) -lt 25 ]; then - HINT_ID=`new_hint "${THEME_ID}" "${EXO_ID}" "Astuce #${HINT_COUNT}" "${EXO_HINT}" "${HINT_COST}"` - else - HINT_ID=`new_hint "${THEME_ID}" "${EXO_ID}" "Astuce #${HINT_COUNT}" "" "${HINT_COST}" "${THM_BASEURI}${EXO_BASEURI}/${HINT}"` - fi - - if [ -z "$HINT_ID" ]; then - echo -e "\e[31;01m!!! An error occured during hint import!\e[00m (title=Astuce #${HINT_COUNT};content::${EXO_HINT_TYPE};cost=${HINT_COST})" - else - echo -e "\e[32m>>> New hint added:\e[00m $HINT_ID - Astuce #${HINT_COUNT}" - fi - fi - HINT_COUNT=$(($HINT_COUNT + 1)) - done - - - # Files: splited - get_dir "${THM_BASEURI}${EXO_BASEURI}files/" | grep -v DIGESTS.txt | grep '[0-9][0-9]$' | sed -E 's/\.?([0-9][0-9])$//' | sort | uniq | while read f; do basename "$f"; done | while read FILE_URI - do - DIGEST=$(get_file "${THM_BASEURI}${EXO_BASEURI}files/DIGESTS.txt" | grep "${FILE_URI}\$" | awk '{ print $1; }') - - PARTS= - for part in $(get_dir "${THM_BASEURI}${EXO_BASEURI}files/" | grep "${FILE_URI}" | sort) - do - PARTS="${PARTS}${BASEFILE}${THM_BASEURI}${EXO_BASEURI}files/${part} -" - done - echo -e "\e[35mImport splited file ${THM_BASEURI}${EXO_BASEURI}files/${FILE_URI} from\e[00m `echo ${PARTS} | tr '\n' ' '`" - - FILE_ID=`new_file "${THEME_ID}" "${EXO_ID}" "${THM_BASEURI}${EXO_BASEURI}files/${FILE_URI}" "${DIGEST}" "${PARTS}"` - if [ -z "$FILE_ID" ]; then - echo -e "\e[31;01m!!! An error occured during file import! Please check path.\e[00m" - else - echo -e "\e[32m>>> New file added:\e[00m $FILE_ID - $FILE_URI" - fi - done - - # Files: entire - get_dir "${THM_BASEURI}${EXO_BASEURI}files/" | grep -v DIGESTS.txt | grep -v '[0-9][0-9]$' | while read f; do basename "$f"; done | while read FILE_URI - do - DIGEST=$(get_file "${THM_BASEURI}${EXO_BASEURI}files/DIGESTS.txt" | grep "${FILE_URI}\$" | awk '{ print $1; }') - - echo "Import file ${THM_BASEURI}${EXO_BASEURI}files/${FILE_URI}" - FILE_ID=`new_file "${THEME_ID}" "${EXO_ID}" "${THM_BASEURI}${EXO_BASEURI}files/${FILE_URI}" "${DIGEST}"` - if [ -z "$FILE_ID" ]; then - echo -e "\e[31;01m!!! An error occured during file import! Please check path.\e[00m" - else - echo -e "\e[32m>>> New file added:\e[00m $FILE_ID - $FILE_URI" - fi - done - - - LAST=$EXO_ID - done - echo -done diff --git a/admin/get_files.sh b/admin/get_files.sh deleted file mode 100755 index 68c032cb..00000000 --- a/admin/get_files.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/sh - -BASEURL="http://localhost:8081" -BASEURI="https://srs.epita.fr/owncloud/remote.php/webdav/FIC 2016" -CLOUDUSER='fic' -CLOUDPASS='f>t\nV33R|(+?$i*' - -if [ $# -gt 0 ] -then - WHERE=$1 -else - WHERE="files" -fi - -curl -q -f ${BASEURL}/api/themes/files-bindings | while read l -do - FROM=$(echo "$l" | cut -d ";" -f 1) - DEST=$(echo "$l" | cut -d ";" -f 2) - - mkdir -p $(dirname "${WHERE}${DEST}") - - wget -O "${WHERE}${DEST}" --user "${CLOUDUSER}" --password "${CLOUDPASS}" "${BASEURI}${FROM}" -done From 81502ce2382f6d1fdcb95f83a6f03def3a8324c3 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Fri, 25 Jan 2019 13:29:51 +0100 Subject: [PATCH 05/17] frontend: prefer default border color in home public screen --- frontend/static/views/home.html | 2 +- frontend/static/views/tag.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/static/views/home.html b/frontend/static/views/home.html index 134052b3..ad65dc54 100644 --- a/frontend/static/views/home.html +++ b/frontend/static/views/home.html @@ -15,7 +15,7 @@
-
+
diff --git a/frontend/static/views/tag.html b/frontend/static/views/tag.html index 6f606d28..7dc8910a 100644 --- a/frontend/static/views/tag.html +++ b/frontend/static/views/tag.html @@ -1,5 +1,5 @@
-
+
From caa05110234fa24b8e9938799e75c44b30974860 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Fri, 25 Jan 2019 16:31:48 +0100 Subject: [PATCH 06/17] admin: add a new route to generate a file for movie links --- admin/api/exercice.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/admin/api/exercice.go b/admin/api/exercice.go index a1ab5153..6d698641 100644 --- a/admin/api/exercice.go +++ b/admin/api/exercice.go @@ -14,6 +14,7 @@ import ( func init() { router.GET("/api/exercices/", apiHandler(listExercices)) + router.GET("/api/resolutions.json", apiHandler(exportResolutionMovies)) router.GET("/api/exercices/:eid", apiHandler(exerciceHandler(showExercice))) router.PUT("/api/exercices/:eid", apiHandler(exerciceHandler(updateExercice))) @@ -85,6 +86,28 @@ func listExercices(_ httprouter.Params, body []byte) (interface{}, error) { return fic.GetExercices() } +// Generate the csv to export with: +// curl -s http://127.0.0.1:8081/api/resolutions.json | jq -r ".[] | [ .theme,.title, @uri \"https://fic.srs.epita.fr/resolution/\\(.videoURI)\" ] | join(\";\")" +func exportResolutionMovies(_ httprouter.Params, body []byte) (interface{}, error) { + if exercices, err := fic.GetExercices(); err != nil { + return nil, err + } else { + export := []map[string]string{} + for _, exercice := range exercices { + if theme, err := fic.GetTheme(exercice.IdTheme); err != nil { + return nil, err + } else { + export = append(export, map[string]string{ + "videoURI": exercice.VideoURI, + "theme": theme.Name, + "title": exercice.Title, + }) + } + } + return export, nil + } +} + func listExerciceFiles(exercice fic.Exercice, body []byte) (interface{}, error) { return exercice.GetFiles() } From 0b0a9789644dc9fc7db2c5831cf1e569cdc8fa4c Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Fri, 1 Feb 2019 20:50:58 +0100 Subject: [PATCH 07/17] admin: thanks to ng-base, don't need other modifications --- admin/index.go | 30 +++++++++++++++--------------- admin/static/index.html | 30 +++++++++++++++--------------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/admin/index.go b/admin/index.go index 0461becf..595d0b2f 100644 --- a/admin/index.go +++ b/admin/index.go @@ -32,8 +32,8 @@ const indextpl = `
diff --git a/libfic/exercice_history.go b/libfic/exercice_history.go new file mode 100644 index 00000000..5e056311 --- /dev/null +++ b/libfic/exercice_history.go @@ -0,0 +1,110 @@ +package fic + +import ( + "fmt" + "time" +) + +// GetHistory aggregates all sources of events or actions for an Exercice +func (e Exercice) GetHistory() ([]map[string]interface{}, error) { + hist := make([]map[string]interface{}, 0) + + if rows, err := DBQuery(`SELECT id_team, U.name, U.color, "tries" AS kind, time, id_exercice, NULL, NULL FROM exercice_tries NATURAL JOIN teams U WHERE id_exercice = ? UNION + SELECT id_team, U.name, U.color, "solved" AS kind, time, id_exercice, coefficient, NULL FROM exercice_solved S NATURAL JOIN teams U WHERE id_exercice = ? UNION + SELECT id_team, U.name, U.color, "hint" AS kind, time, id_exercice, H.id_hint, H.title FROM team_hints T INNER JOIN exercice_hints H ON H.id_hint = T.id_hint NATURAL JOIN teams U WHERE id_exercice = ? UNION + SELECT id_team, U.name, U.color, "wchoices" AS kind, time, id_exercice, F.id_flag, F.type FROM team_wchoices W INNER JOIN exercice_flags F ON F.id_flag = W.id_flag NATURAL JOIN teams U WHERE id_exercice = ? UNION + SELECT id_team, U.name, U.color, "flag_found" AS kind, time, id_exercice, K.id_flag, K.type FROM flag_found F INNER JOIN exercice_flags K ON K.id_flag = F.id_flag NATURAL JOIN teams U WHERE id_exercice = ? UNION + SELECT id_team, U.name, U.color, "mcq_found" AS kind, time, id_exercice, Q.id_mcq, Q.title FROM mcq_found F INNER JOIN exercice_mcq Q ON Q.id_mcq = F.id_mcq NATURAL JOIN teams U WHERE id_exercice = ? + ORDER BY time DESC`, e.Id, e.Id, e.Id, e.Id, e.Id, e.Id); err != nil { + return nil, err + } else { + defer rows.Close() + + for rows.Next() { + var id_team int64 + var team_name string + var team_color uint32 + var kind string + var time time.Time + var exercice int64 + var secondary *int64 + var secondary_title *string + + if err := rows.Scan(&id_team, &team_name, &team_color, &kind, &time, &exercice, &secondary, &secondary_title); err != nil { + return nil, err + } + + h := map[string]interface{}{} + + h["team_id"] = id_team + h["team_name"] = team_name + h["team_color"] = fmt.Sprintf("#%x", team_color) + h["kind"] = kind + h["time"] = time + h["primary"] = e.Id + if secondary != nil { + h["secondary"] = secondary + h["secondary_title"] = secondary_title + } + + hist = append(hist, h) + } + } + + return hist, nil +} + +// DelHistoryItem removes from the database an entry from the history. +func (e Exercice) DelHistoryItem(tId int64, kind string, h time.Time, secondary *int64) (interface{}, error) { + if kind == "tries" { + if res, err := DBExec("DELETE FROM exercice_tries WHERE id_team = ? AND time = ? AND id_exercice = ?", tId, h, e.Id); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } + } else if kind == "hint" && secondary != nil { + if res, err := DBExec("DELETE FROM team_hints WHERE id_team = ? AND time = ? AND id_hint = ?", tId, h, *secondary); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } + } else if kind == "wchoices" && secondary != nil { + if res, err := DBExec("DELETE FROM team_wchoices WHERE id_team = ? AND time = ? AND id_flag = ?", tId, h, *secondary); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } + } else if kind == "flag_found" && secondary != nil { + if res, err := DBExec("DELETE FROM flag_found WHERE id_team = ? AND time = ? AND id_flag = ?", tId, h, *secondary); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } + } else if kind == "mcq_found" && secondary != nil { + if res, err := DBExec("DELETE FROM mcq_found WHERE id_team = ? AND time = ? AND id_mcq = ?", tId, h, *secondary); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } + } else if kind == "solved" { + if res, err := DBExec("DELETE FROM exercice_solved WHERE id_team = ? AND time = ? AND id_exercice = ?", tId, h, e.Id); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } + } else { + return nil, nil + } +} From 4125c7b1615c5241fee659607009742176ed9017 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Sun, 3 Feb 2019 22:40:44 +0100 Subject: [PATCH 15/17] admin: include coefficient in history.json --- admin/static/views/exercice.html | 3 +-- admin/static/views/team-edit.html | 2 +- libfic/exercice_history.go | 16 +++++++++------- libfic/team_history.go | 16 +++++++++------- 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/admin/static/views/exercice.html b/admin/static/views/exercice.html index 9bfa0547..7c72891a 100644 --- a/admin/static/views/exercice.html +++ b/admin/static/views/exercice.html @@ -227,7 +227,7 @@ - {{ row.time | date:"mediumTime" }}
{{ row.kind }} + {{ row.time | date:"mediumTime" }}
{{ row.kind }} x{{ row.coefficient }} @@ -239,7 +239,6 @@ {{ row.secondary_title }} {{ row.secondary_title }} - (coeff x{{ row.secondary }}) : {{ row.secondary }} diff --git a/admin/static/views/team-edit.html b/admin/static/views/team-edit.html index 9053bdf3..0be2b267 100644 --- a/admin/static/views/team-edit.html +++ b/admin/static/views/team-edit.html @@ -109,7 +109,7 @@ - {{ row.time | date:"mediumTime" }}
{{ row.kind }} + {{ row.time | date:"mediumTime" }}
{{ row.kind }} x{{ row.coefficient }} diff --git a/libfic/exercice_history.go b/libfic/exercice_history.go index 5e056311..29873345 100644 --- a/libfic/exercice_history.go +++ b/libfic/exercice_history.go @@ -9,12 +9,12 @@ import ( func (e Exercice) GetHistory() ([]map[string]interface{}, error) { hist := make([]map[string]interface{}, 0) - if rows, err := DBQuery(`SELECT id_team, U.name, U.color, "tries" AS kind, time, id_exercice, NULL, NULL FROM exercice_tries NATURAL JOIN teams U WHERE id_exercice = ? UNION - SELECT id_team, U.name, U.color, "solved" AS kind, time, id_exercice, coefficient, NULL FROM exercice_solved S NATURAL JOIN teams U WHERE id_exercice = ? UNION - SELECT id_team, U.name, U.color, "hint" AS kind, time, id_exercice, H.id_hint, H.title FROM team_hints T INNER JOIN exercice_hints H ON H.id_hint = T.id_hint NATURAL JOIN teams U WHERE id_exercice = ? UNION - SELECT id_team, U.name, U.color, "wchoices" AS kind, time, id_exercice, F.id_flag, F.type FROM team_wchoices W INNER JOIN exercice_flags F ON F.id_flag = W.id_flag NATURAL JOIN teams U WHERE id_exercice = ? UNION - SELECT id_team, U.name, U.color, "flag_found" AS kind, time, id_exercice, K.id_flag, K.type FROM flag_found F INNER JOIN exercice_flags K ON K.id_flag = F.id_flag NATURAL JOIN teams U WHERE id_exercice = ? UNION - SELECT id_team, U.name, U.color, "mcq_found" AS kind, time, id_exercice, Q.id_mcq, Q.title FROM mcq_found F INNER JOIN exercice_mcq Q ON Q.id_mcq = F.id_mcq NATURAL JOIN teams U WHERE id_exercice = ? + if rows, err := DBQuery(`SELECT id_team, U.name, U.color, "tries" AS kind, time, 0, id_exercice, NULL, NULL FROM exercice_tries NATURAL JOIN teams U WHERE id_exercice = ? UNION + SELECT id_team, U.name, U.color, "solved" AS kind, time, coefficient, id_exercice, NULL, NULL FROM exercice_solved S NATURAL JOIN teams U WHERE id_exercice = ? UNION + SELECT id_team, U.name, U.color, "hint" AS kind, time, coefficient, id_exercice, H.id_hint, H.title FROM team_hints T INNER JOIN exercice_hints H ON H.id_hint = T.id_hint NATURAL JOIN teams U WHERE id_exercice = ? UNION + SELECT id_team, U.name, U.color, "wchoices" AS kind, time, coefficient, id_exercice, F.id_flag, F.type FROM team_wchoices W INNER JOIN exercice_flags F ON F.id_flag = W.id_flag NATURAL JOIN teams U WHERE id_exercice = ? UNION + SELECT id_team, U.name, U.color, "flag_found" AS kind, time, 0, id_exercice, K.id_flag, K.type FROM flag_found F INNER JOIN exercice_flags K ON K.id_flag = F.id_flag NATURAL JOIN teams U WHERE id_exercice = ? UNION + SELECT id_team, U.name, U.color, "mcq_found" AS kind, time, 0, id_exercice, Q.id_mcq, Q.title FROM mcq_found F INNER JOIN exercice_mcq Q ON Q.id_mcq = F.id_mcq NATURAL JOIN teams U WHERE id_exercice = ? ORDER BY time DESC`, e.Id, e.Id, e.Id, e.Id, e.Id, e.Id); err != nil { return nil, err } else { @@ -26,11 +26,12 @@ func (e Exercice) GetHistory() ([]map[string]interface{}, error) { var team_color uint32 var kind string var time time.Time + var coeff float32 var exercice int64 var secondary *int64 var secondary_title *string - if err := rows.Scan(&id_team, &team_name, &team_color, &kind, &time, &exercice, &secondary, &secondary_title); err != nil { + if err := rows.Scan(&id_team, &team_name, &team_color, &kind, &time, &coeff, &exercice, &secondary, &secondary_title); err != nil { return nil, err } @@ -41,6 +42,7 @@ func (e Exercice) GetHistory() ([]map[string]interface{}, error) { h["team_color"] = fmt.Sprintf("#%x", team_color) h["kind"] = kind h["time"] = time + h["coefficient"] = coeff h["primary"] = e.Id if secondary != nil { h["secondary"] = secondary diff --git a/libfic/team_history.go b/libfic/team_history.go index 8a2cb581..b73656d3 100644 --- a/libfic/team_history.go +++ b/libfic/team_history.go @@ -8,12 +8,12 @@ import ( func (t Team) GetHistory() ([]map[string]interface{}, error) { hist := make([]map[string]interface{}, 0) - if rows, err := DBQuery(`SELECT id_team, "tries" AS kind, time, E.id_exercice, E.title, NULL, NULL FROM exercice_tries T INNER JOIN exercices E ON E.id_exercice = T.id_exercice WHERE id_team = ? UNION - SELECT id_team, "solved" AS kind, time, E.id_exercice, E.title, coefficient, NULL FROM exercice_solved S INNER JOIN exercices E ON E.id_exercice = S.id_exercice WHERE id_team = ? UNION - SELECT id_team, "hint" AS kind, time, E.id_exercice, E.title, H.id_hint, H.title FROM team_hints T INNER JOIN exercice_hints H ON H.id_hint = T.id_hint INNER JOIN exercices E ON E.id_exercice = H.id_exercice WHERE id_team = ? UNION - SELECT id_team, "wchoices" AS kind, time, E.id_exercice, E.title, F.id_flag, F.type FROM team_wchoices W INNER JOIN exercice_flags F ON F.id_flag = W.id_flag INNER JOIN exercices E ON E.id_exercice = F.id_exercice WHERE id_team = ? UNION - SELECT id_team, "flag_found" AS kind, time, E.id_exercice, E.title, K.id_flag, K.type FROM flag_found F INNER JOIN exercice_flags K ON K.id_flag = F.id_flag INNER JOIN exercices E ON K.id_exercice = E.id_exercice WHERE id_team = ? UNION - SELECT id_team, "mcq_found" AS kind, time, E.id_exercice, E.title, Q.id_mcq, Q.title FROM mcq_found F INNER JOIN exercice_mcq Q ON Q.id_mcq = F.id_mcq INNER JOIN exercices E ON Q.id_exercice = E.id_exercice WHERE id_team = ? + if rows, err := DBQuery(`SELECT id_team, "tries" AS kind, time, 0, E.id_exercice, E.title, NULL, NULL FROM exercice_tries T INNER JOIN exercices E ON E.id_exercice = T.id_exercice WHERE id_team = ? UNION + SELECT id_team, "solved" AS kind, time, coefficient, E.id_exercice, E.title, NULL, NULL FROM exercice_solved S INNER JOIN exercices E ON E.id_exercice = S.id_exercice WHERE id_team = ? UNION + SELECT id_team, "hint" AS kind, time, coefficient, E.id_exercice, E.title, H.id_hint, H.title FROM team_hints T INNER JOIN exercice_hints H ON H.id_hint = T.id_hint INNER JOIN exercices E ON E.id_exercice = H.id_exercice WHERE id_team = ? UNION + SELECT id_team, "wchoices" AS kind, time, coefficient, E.id_exercice, E.title, F.id_flag, F.type FROM team_wchoices W INNER JOIN exercice_flags F ON F.id_flag = W.id_flag INNER JOIN exercices E ON E.id_exercice = F.id_exercice WHERE id_team = ? UNION + SELECT id_team, "flag_found" AS kind, time, 0, E.id_exercice, E.title, K.id_flag, K.type FROM flag_found F INNER JOIN exercice_flags K ON K.id_flag = F.id_flag INNER JOIN exercices E ON K.id_exercice = E.id_exercice WHERE id_team = ? UNION + SELECT id_team, "mcq_found" AS kind, time, 0, E.id_exercice, E.title, Q.id_mcq, Q.title FROM mcq_found F INNER JOIN exercice_mcq Q ON Q.id_mcq = F.id_mcq INNER JOIN exercices E ON Q.id_exercice = E.id_exercice WHERE id_team = ? ORDER BY time DESC`, t.Id, t.Id, t.Id, t.Id, t.Id, t.Id); err != nil { return nil, err } else { @@ -23,12 +23,13 @@ func (t Team) GetHistory() ([]map[string]interface{}, error) { var id_team int64 var kind string var time time.Time + var coefficient float32 var primary *int64 var primary_title *string var secondary *int64 var secondary_title *string - if err := rows.Scan(&id_team, &kind, &time, &primary, &primary_title, &secondary, &secondary_title); err != nil { + if err := rows.Scan(&id_team, &kind, &time, &coefficient, &primary, &primary_title, &secondary, &secondary_title); err != nil { return nil, err } @@ -36,6 +37,7 @@ func (t Team) GetHistory() ([]map[string]interface{}, error) { h["kind"] = kind h["time"] = time + h["coefficient"] = coefficient if primary != nil { h["primary"] = primary h["primary_title"] = primary_title From c2887a1812c2c9b76e2d83aaa9828c22b5b63552 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Sun, 3 Feb 2019 22:49:03 +0100 Subject: [PATCH 16/17] admin: add a new route to update team history coefficient --- admin/api/team.go | 25 +++++++++++++++++++++---- libfic/team_history.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/admin/api/team.go b/admin/api/team.go index dcffb055..8c32692d 100644 --- a/admin/api/team.go +++ b/admin/api/team.go @@ -238,10 +238,27 @@ func setTeamMember(team fic.Team, body []byte) (interface{}, error) { } type uploadedHistory struct { - Kind string - Time time.Time - Primary *int64 - Secondary *int64 + Kind string + Time time.Time + Primary *int64 + Secondary *int64 + Coefficient float32 +} + +func updateHistory(team *fic.Team, body []byte) (interface{}, error) { + var uh uploadedHistory + if err := json.Unmarshal(body, &uh); err != nil { + return nil, err + } + + var givenId int64 + if uh.Secondary != nil { + givenId = *uh.Secondary + } else if uh.Primary != nil { + givenId = *uh.Primary + } + + return team.UpdateHistoryCoeff(uh.Kind, uh.Time, givenId, uh.Coefficient) } func delHistory(team *fic.Team, body []byte) (interface{}, error) { diff --git a/libfic/team_history.go b/libfic/team_history.go index b73656d3..0b21276a 100644 --- a/libfic/team_history.go +++ b/libfic/team_history.go @@ -54,6 +54,37 @@ func (t Team) GetHistory() ([]map[string]interface{}, error) { return hist, nil } +// UpdateHistoryCoeff updates the coefficient for a given entry. +func (t Team) UpdateHistoryCoeff(kind string, h time.Time, givenId int64, newCoeff float32) (interface{}, error) { + if kind == "hint" { + if res, err := DBExec("UPDATE team_hints SET coefficient = ? WHERE id_team = ? AND time = ? AND id_hint = ?", newCoeff, t.Id, h, givenId); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } + } else if kind == "wchoices" { + if res, err := DBExec("UPDATE team_wchoices SET coefficient = ? WHERE id_team = ? AND time = ? AND id_flag = ?", newCoeff, t.Id, h, givenId); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } + } else if kind == "solved" { + if res, err := DBExec("UPDATE exercice_solved SET coefficient = ? WHERE id_team = ? AND time = ? AND id_exercice = ?", newCoeff, t.Id, h, givenId); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } + } else { + return nil, nil + } +} + // DelHistoryItem removes from the database an entry from the history. func (t Team) DelHistoryItem(kind string, h time.Time, primary *int64, secondary *int64) (interface{}, error) { if kind == "tries" && primary != nil { From ee2f65aae74267decc5d33de3a4f485e92d1cfd8 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Sun, 3 Feb 2019 22:48:14 +0100 Subject: [PATCH 17/17] admin: new route to get exercice stats --- admin/api/exercice.go | 10 ++++++++++ admin/static/js/app.js | 6 ++++++ libfic/exercice.go | 41 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+) diff --git a/admin/api/exercice.go b/admin/api/exercice.go index 30b7aff6..d7c7ebd6 100644 --- a/admin/api/exercice.go +++ b/admin/api/exercice.go @@ -22,6 +22,7 @@ func init() { router.PATCH("/api/exercices/:eid", apiHandler(exerciceHandler(partUpdateExercice))) router.DELETE("/api/exercices/:eid", apiHandler(exerciceHandler(deleteExercice))) + router.GET("/api/exercices/:eid/stats", apiHandler(exerciceHandler(getExerciceStats))) router.GET("/api/exercices/:eid/history.json", apiHandler(exerciceHandler(getExerciceHistory))) router.DELETE("/api/exercices/:eid/history.json", apiHandler(exerciceHandler(delExerciceHistory))) @@ -136,6 +137,15 @@ func showExercice(exercice fic.Exercice, body []byte) (interface{}, error) { return exercice, nil } +func getExerciceStats(exercice fic.Exercice, body []byte) (interface{}, error) { + team_tries, tries := exercice.TriesByTeam() + return map[string]interface{}{ + "solved": exercice.SolvedTeams(), + "total_tried": tries, + "team_tries": team_tries, + }, nil +} + func getExerciceHistory(exercice fic.Exercice, body []byte) (interface{}, error) { return exercice.GetHistory() } diff --git a/admin/static/js/app.js b/admin/static/js/app.js index ebf32265..e5bea0ab 100644 --- a/admin/static/js/app.js +++ b/admin/static/js/app.js @@ -1197,6 +1197,12 @@ angular.module("FICApp") } else { $scope.exercice = Exercice.get({ exerciceId: $routeParams.exerciceId }); } + $http({ + url: "/api/exercices/" + $routeParams.exerciceId + "/stats", + method: "GET" + }).then(function(response) { + $scope.stats = response.data; + }); $http({ url: "/api/themes.json", method: "GET" diff --git a/libfic/exercice.go b/libfic/exercice.go index ea2a3795..efce52b1 100644 --- a/libfic/exercice.go +++ b/libfic/exercice.go @@ -330,6 +330,21 @@ func (e Exercice) SolvedCount() int64 { } } +// SolvedTeams returns the list of Team that already have solved the challenge. +func (e Exercice) SolvedTeams() (teams []int64) { + if rows, err := DBQuery("SELECT id_team FROM exercice_solved WHERE id_exercice = ?", e.Id); err == nil { + defer rows.Close() + + for rows.Next() { + var tid int64 + if err := rows.Scan(&tid); err == nil { + teams = append(teams, tid) + } + } + } + return +} + // TriedTeamCount returns the number of Team that attempted to solve the exercice. func (e Exercice) TriedTeamCount() int64 { tries_table := "exercice_tries" @@ -345,6 +360,32 @@ func (e Exercice) TriedTeamCount() int64 { } } +type TbT struct { + IdTeam int64 `json:"id_team"` + Tries int64 `json:"tries"` +} + +// TriesByTeam returns the number of tries by Team. +func (e Exercice) TriesByTeam() (ts []TbT, sum int64) { + tries_table := "exercice_tries" + if SubmissionUniqueness { + tries_table = "exercice_distinct_tries" + } + + if rows, err := DBQuery("SELECT id_team, COUNT(id_team) FROM " + tries_table + " WHERE id_exercice = ? GROUP BY id_team", e.Id); err == nil { + defer rows.Close() + + for rows.Next() { + var tbt TbT + if err := rows.Scan(&tbt.IdTeam, &tbt.Tries); err == nil { + sum += tbt.Tries + ts = append(ts, tbt) + } + } + } + return +} + // TriedCount returns the number of cumulative attempts, all Team combined, for the exercice. func (e Exercice) TriedCount() int64 { tries_table := "exercice_tries"