Compare commits
17 Commits
master
...
f/admin_ex
Author | SHA1 | Date |
---|---|---|
nemunaire | ee2f65aae7 | |
nemunaire | c2887a1812 | |
nemunaire | 4125c7b161 | |
nemunaire | b73eaa8b3f | |
nemunaire | 1dd55a0f73 | |
nemunaire | 8afc7b9488 | |
nemunaire | 2fcbc44c60 | |
nemunaire | 8803852d47 | |
nemunaire | 8790d6d678 | |
nemunaire | 6c03d04f36 | |
nemunaire | 0b0a978964 | |
nemunaire | caa0511023 | |
nemunaire | 81502ce238 | |
nemunaire | 7587cd9140 | |
nemunaire | 2b106df669 | |
nemunaire | 04b42de061 | |
nemunaire | c129b2e477 |
|
@ -5,6 +5,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"srs.epita.fr/fic-server/admin/sync"
|
"srs.epita.fr/fic-server/admin/sync"
|
||||||
"srs.epita.fr/fic-server/libfic"
|
"srs.epita.fr/fic-server/libfic"
|
||||||
|
@ -14,12 +15,17 @@ import (
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
router.GET("/api/exercices/", apiHandler(listExercices))
|
router.GET("/api/exercices/", apiHandler(listExercices))
|
||||||
|
router.GET("/api/resolutions.json", apiHandler(exportResolutionMovies))
|
||||||
|
|
||||||
router.GET("/api/exercices/:eid", apiHandler(exerciceHandler(showExercice)))
|
router.GET("/api/exercices/:eid", apiHandler(exerciceHandler(showExercice)))
|
||||||
router.PUT("/api/exercices/:eid", apiHandler(exerciceHandler(updateExercice)))
|
router.PUT("/api/exercices/:eid", apiHandler(exerciceHandler(updateExercice)))
|
||||||
router.PATCH("/api/exercices/:eid", apiHandler(exerciceHandler(partUpdateExercice)))
|
router.PATCH("/api/exercices/:eid", apiHandler(exerciceHandler(partUpdateExercice)))
|
||||||
router.DELETE("/api/exercices/:eid", apiHandler(exerciceHandler(deleteExercice)))
|
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)))
|
||||||
|
|
||||||
router.GET("/api/exercices/:eid/files", apiHandler(exerciceHandler(listExerciceFiles)))
|
router.GET("/api/exercices/:eid/files", apiHandler(exerciceHandler(listExerciceFiles)))
|
||||||
router.POST("/api/exercices/:eid/files", apiHandler(exerciceHandler(createExerciceFile)))
|
router.POST("/api/exercices/:eid/files", apiHandler(exerciceHandler(createExerciceFile)))
|
||||||
router.GET("/api/exercices/:eid/files/:fid", apiHandler(exerciceFileHandler(showExerciceFile)))
|
router.GET("/api/exercices/:eid/files/:fid", apiHandler(exerciceFileHandler(showExerciceFile)))
|
||||||
|
@ -85,6 +91,28 @@ func listExercices(_ httprouter.Params, body []byte) (interface{}, error) {
|
||||||
return fic.GetExercices()
|
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) {
|
func listExerciceFiles(exercice fic.Exercice, body []byte) (interface{}, error) {
|
||||||
return exercice.GetFiles()
|
return exercice.GetFiles()
|
||||||
}
|
}
|
||||||
|
@ -109,6 +137,35 @@ func showExercice(exercice fic.Exercice, body []byte) (interface{}, error) {
|
||||||
return exercice, nil
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
type uploadedExerciceHistory struct {
|
||||||
|
IdTeam int64 `json:"team_id"`
|
||||||
|
Kind string
|
||||||
|
Time time.Time
|
||||||
|
Secondary *int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func delExerciceHistory(exercice fic.Exercice, body []byte) (interface{}, error) {
|
||||||
|
var uh uploadedExerciceHistory
|
||||||
|
if err := json.Unmarshal(body, &uh); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return exercice.DelHistoryItem(uh.IdTeam, uh.Kind, uh.Time, uh.Secondary)
|
||||||
|
}
|
||||||
|
|
||||||
func deleteExercice(exercice fic.Exercice, _ []byte) (interface{}, error) {
|
func deleteExercice(exercice fic.Exercice, _ []byte) (interface{}, error) {
|
||||||
return exercice.Delete()
|
return exercice.Delete()
|
||||||
}
|
}
|
||||||
|
|
|
@ -238,10 +238,27 @@ func setTeamMember(team fic.Team, body []byte) (interface{}, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type uploadedHistory struct {
|
type uploadedHistory struct {
|
||||||
Kind string
|
Kind string
|
||||||
Time time.Time
|
Time time.Time
|
||||||
Primary *int64
|
Primary *int64
|
||||||
Secondary *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) {
|
func delHistory(team *fic.Team, body []byte) (interface{}, error) {
|
||||||
|
|
|
@ -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/<br>/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 <<EOF >&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" <<EOF | grep -Eo '"id":[0-9]+,' | grep -Eo "[0-9]+"
|
|
||||||
{"path": "${BASEFILE}${URI}"${DIGEST}, "parts": [${PARTS}]}
|
|
||||||
EOF
|
|
||||||
)
|
|
||||||
|
|
||||||
new_hint() {
|
|
||||||
THEME="$1"
|
|
||||||
EXERCICE="$2"
|
|
||||||
TITLE=`echo "$3" | sed 's/"/\\\\"/g'`
|
|
||||||
CONTENT=`echo "$4" | sed 's/"/\\\\"/g' | sed ':a;N;$!ba;s/\n/<br>/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
|
|
|
@ -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
|
|
|
@ -32,8 +32,8 @@ const indextpl = `<!DOCTYPE html>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-light text-dark">
|
<body class="bg-light text-dark">
|
||||||
<nav class="navbar sticky-top navbar-expand-lg navbar-dark bg-dark text-light">
|
<nav class="navbar sticky-top navbar-expand-lg navbar-dark bg-dark text-light">
|
||||||
<a class="navbar-brand" href="{{.urlbase}}">
|
<a class="navbar-brand" href=".">
|
||||||
<img alt="FIC" src="{{.urlbase}}img/fic.png" style="height: 30px">
|
<img alt="FIC" src="img/fic.png" style="height: 30px">
|
||||||
</a>
|
</a>
|
||||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#adminMenu" aria-controls="adminMenu" aria-expanded="false" aria-label="Toggle navigation">
|
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#adminMenu" aria-controls="adminMenu" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
<span class="navbar-toggler-icon"></span>
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
@ -41,15 +41,15 @@ const indextpl = `<!DOCTYPE html>
|
||||||
|
|
||||||
<div class="collapse navbar-collapse" id="adminMenu">
|
<div class="collapse navbar-collapse" id="adminMenu">
|
||||||
<ul class="navbar-nav mr-auto">
|
<ul class="navbar-nav mr-auto">
|
||||||
<li class="nav-item"><a class="nav-link" href="{{.urlbase}}teams">Équipes</a></li>
|
<li class="nav-item"><a class="nav-link" href="teams">Équipes</a></li>
|
||||||
<li class="nav-item"><a class="nav-link" href="{{.urlbase}}pki">PKI</a></li>
|
<li class="nav-item"><a class="nav-link" href="pki">PKI</a></li>
|
||||||
<li class="nav-item"><a class="nav-link" href="{{.urlbase}}themes">Thèmes</a></li>
|
<li class="nav-item"><a class="nav-link" href="themes">Thèmes</a></li>
|
||||||
<li class="nav-item"><a class="nav-link" href="{{.urlbase}}exercices">Exercices</a></li>
|
<li class="nav-item"><a class="nav-link" href="exercices">Exercices</a></li>
|
||||||
<li class="nav-item"><a class="nav-link" href="{{.urlbase}}files">Fichiers</a></li>
|
<li class="nav-item"><a class="nav-link" href="files">Fichiers</a></li>
|
||||||
<li class="nav-item"><a class="nav-link" href="{{.urlbase}}public/0">Public</a></li>
|
<li class="nav-item"><a class="nav-link" href="public/0">Public</a></li>
|
||||||
<li class="nav-item"><a class="nav-link" href="{{.urlbase}}events">Événements</a></li>
|
<li class="nav-item"><a class="nav-link" href="events">Événements</a></li>
|
||||||
<li class="nav-item"><a class="nav-link" href="{{.urlbase}}claims">Tâches</a></li>
|
<li class="nav-item"><a class="nav-link" href="claims">Tâches</a></li>
|
||||||
<li class="nav-item"><a class="nav-link" href="{{.urlbase}}settings">Paramètres</a></li>
|
<li class="nav-item"><a class="nav-link" href="settings">Paramètres</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -87,14 +87,14 @@ const indextpl = `<!DOCTYPE html>
|
||||||
<div class="container mt-1" ng-view></div>
|
<div class="container mt-1" ng-view></div>
|
||||||
|
|
||||||
<script src="/js/jquery.min.js"></script>
|
<script src="/js/jquery.min.js"></script>
|
||||||
<script src="{{.urlbase}}js/popper.min.js"></script>
|
<script src="js/popper.min.js"></script>
|
||||||
<script src="/js/bootstrap.min.js"></script>
|
<script src="/js/bootstrap.min.js"></script>
|
||||||
<script src="/js/angular.min.js"></script>
|
<script src="/js/angular.min.js"></script>
|
||||||
<script src="{{.urlbase}}js/angular-resource.min.js"></script>
|
<script src="js/angular-resource.min.js"></script>
|
||||||
<script src="/js/angular-route.min.js"></script>
|
<script src="/js/angular-route.min.js"></script>
|
||||||
<script src="/js/angular-sanitize.min.js"></script>
|
<script src="/js/angular-sanitize.min.js"></script>
|
||||||
<script src="{{.urlbase}}js/app.js"></script>
|
<script src="js/app.js"></script>
|
||||||
<script src="{{.urlbase}}js/common.js"></script>
|
<script src="js/common.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
`
|
`
|
||||||
|
|
|
@ -30,8 +30,8 @@
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-light text-dark">
|
<body class="bg-light text-dark">
|
||||||
<nav class="navbar sticky-top navbar-expand-lg navbar-dark bg-dark text-light">
|
<nav class="navbar sticky-top navbar-expand-lg navbar-dark bg-dark text-light">
|
||||||
<a class="navbar-brand" href="/admin/">
|
<a class="navbar-brand" href=".">
|
||||||
<img alt="FIC" src="/admin/img/fic.png" style="height: 30px">
|
<img alt="FIC" src="img/fic.png" style="height: 30px">
|
||||||
</a>
|
</a>
|
||||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#adminMenu" aria-controls="adminMenu" aria-expanded="false" aria-label="Toggle navigation">
|
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#adminMenu" aria-controls="adminMenu" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
<span class="navbar-toggler-icon"></span>
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
@ -39,15 +39,15 @@
|
||||||
|
|
||||||
<div class="collapse navbar-collapse" id="adminMenu">
|
<div class="collapse navbar-collapse" id="adminMenu">
|
||||||
<ul class="navbar-nav mr-auto">
|
<ul class="navbar-nav mr-auto">
|
||||||
<li class="nav-item"><a class="nav-link" href="/admin/teams">Équipes</a></li>
|
<li class="nav-item"><a class="nav-link" href="teams">Équipes</a></li>
|
||||||
<li class="nav-item"><a class="nav-link" href="/admin/pki">PKI</a></li>
|
<li class="nav-item"><a class="nav-link" href="pki">PKI</a></li>
|
||||||
<li class="nav-item"><a class="nav-link" href="/admin/themes">Thèmes</a></li>
|
<li class="nav-item"><a class="nav-link" href="themes">Thèmes</a></li>
|
||||||
<li class="nav-item"><a class="nav-link" href="/admin/exercices">Exercices</a></li>
|
<li class="nav-item"><a class="nav-link" href="exercices">Exercices</a></li>
|
||||||
<li class="nav-item"><a class="nav-link" href="/admin/files">Fichiers</a></li>
|
<li class="nav-item"><a class="nav-link" href="files">Fichiers</a></li>
|
||||||
<li class="nav-item"><a class="nav-link" href="/admin/public/0">Public</a></li>
|
<li class="nav-item"><a class="nav-link" href="public/0">Public</a></li>
|
||||||
<li class="nav-item"><a class="nav-link" href="/admin/events">Événements</a></li>
|
<li class="nav-item"><a class="nav-link" href="events">Événements</a></li>
|
||||||
<li class="nav-item"><a class="nav-link" href="/admin/claims">Tâches</a></li>
|
<li class="nav-item"><a class="nav-link" href="claims">Tâches</a></li>
|
||||||
<li class="nav-item"><a class="nav-link" href="/admin/settings">Paramètres</a></li>
|
<li class="nav-item"><a class="nav-link" href="settings">Paramètres</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -85,13 +85,13 @@
|
||||||
<div class="container mt-1" ng-view></div>
|
<div class="container mt-1" ng-view></div>
|
||||||
|
|
||||||
<script src="/js/jquery.min.js"></script>
|
<script src="/js/jquery.min.js"></script>
|
||||||
<script src="/admin/js/popper.min.js"></script>
|
<script src="js/popper.min.js"></script>
|
||||||
<script src="/js/bootstrap.min.js"></script>
|
<script src="/js/bootstrap.min.js"></script>
|
||||||
<script src="/js/angular.min.js"></script>
|
<script src="/js/angular.min.js"></script>
|
||||||
<script src="/admin/js/angular-resource.min.js"></script>
|
<script src="js/angular-resource.min.js"></script>
|
||||||
<script src="/js/angular-route.min.js"></script>
|
<script src="/js/angular-route.min.js"></script>
|
||||||
<script src="/js/angular-sanitize.min.js"></script>
|
<script src="/js/angular-sanitize.min.js"></script>
|
||||||
<script src="/admin/js/app.js"></script>
|
<script src="js/app.js"></script>
|
||||||
<script src="/admin/js/common.js"></script>
|
<script src="js/common.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -228,6 +228,9 @@ angular.module("FICApp")
|
||||||
update: {method: 'PUT'}
|
update: {method: 'PUT'}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
.factory("ExerciceHistory", function($resource) {
|
||||||
|
return $resource("/api/exercices/:exerciceId/history.json", { exerciceId: '@id' })
|
||||||
|
})
|
||||||
.factory("ExerciceFile", function($resource) {
|
.factory("ExerciceFile", function($resource) {
|
||||||
return $resource("/api/exercices/:exerciceId/files/:fileId", { exerciceId: '@idExercice', fileId: '@id' }, {
|
return $resource("/api/exercices/:exerciceId/files/:fileId", { exerciceId: '@idExercice', fileId: '@id' }, {
|
||||||
update: {method: 'PUT'}
|
update: {method: 'PUT'}
|
||||||
|
@ -1194,6 +1197,12 @@ angular.module("FICApp")
|
||||||
} else {
|
} else {
|
||||||
$scope.exercice = Exercice.get({ exerciceId: $routeParams.exerciceId });
|
$scope.exercice = Exercice.get({ exerciceId: $routeParams.exerciceId });
|
||||||
}
|
}
|
||||||
|
$http({
|
||||||
|
url: "/api/exercices/" + $routeParams.exerciceId + "/stats",
|
||||||
|
method: "GET"
|
||||||
|
}).then(function(response) {
|
||||||
|
$scope.stats = response.data;
|
||||||
|
});
|
||||||
$http({
|
$http({
|
||||||
url: "/api/themes.json",
|
url: "/api/themes.json",
|
||||||
method: "GET"
|
method: "GET"
|
||||||
|
@ -1272,6 +1281,22 @@ angular.module("FICApp")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
.controller("ExerciceHistoryController", function($scope, ExerciceHistory, $routeParams, $http, $rootScope) {
|
||||||
|
$scope.history = ExerciceHistory.query({ exerciceId: $routeParams.exerciceId });
|
||||||
|
$scope.delHistory = function(row) {
|
||||||
|
$http({
|
||||||
|
url: "/api/exercices/" + $routeParams.exerciceId + "/history.json",
|
||||||
|
method: "DELETE",
|
||||||
|
data: row
|
||||||
|
}).then(function(response) {
|
||||||
|
$rootScope.staticFilesNeedUpdate++;
|
||||||
|
$scope.history = ExerciceHistory.query({ exerciceId: $routeParams.exerciceId });
|
||||||
|
}, function(response) {
|
||||||
|
$rootScope.newBox('danger', 'An error occurs when removing history item: ', response.data.errmsg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
.controller("ExerciceFilesController", function($scope, ExerciceFile, $routeParams, $rootScope, $http) {
|
.controller("ExerciceFilesController", function($scope, ExerciceFile, $routeParams, $rootScope, $http) {
|
||||||
$scope.files = ExerciceFile.query({ exerciceId: $routeParams.exerciceId });
|
$scope.files = ExerciceFile.query({ exerciceId: $routeParams.exerciceId });
|
||||||
|
|
||||||
|
|
|
@ -221,6 +221,34 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-2" style="overflow-y: scroll; height: 450px">
|
||||||
|
<h3>Historique</h3>
|
||||||
|
<table ng-controller="ExerciceHistoryController" class="table table-hover table-striped table-bordered bg-primary text-light">
|
||||||
|
<tbody>
|
||||||
|
<tr ng-repeat="row in history" ng-class="{'bg-ffound': row.kind == 'flag_found', 'bg-mfound': row.kind == 'mcq_found', 'bg-wchoices': row.kind == 'wchoices', 'bg-success': row.kind == 'solved', 'bg-info': row.kind == 'hint', 'bg-warning': row.kind == 'tries'}">
|
||||||
|
<td>
|
||||||
|
<nobr title="{{ row.time }}">{{ row.time | date:"mediumTime" }}</nobr><br>{{ row.kind }} <span ng-if="row.kind != 'flag_found' && row.kind != 'tries' && row.kind != 'mcq_found'">x{{ row.coefficient }}</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span ng-if="row.team_id">
|
||||||
|
<a href="teams/{{ row.team_id }}">{{ row.team_name }}</a>
|
||||||
|
</span>
|
||||||
|
<span ng-if="row.secondary_title">
|
||||||
|
:
|
||||||
|
<a href="exercices/{{ row.primary }}#key-{{ row.secondary }}" ng-if="row.kind == 'flag_found' || row.kind == 'wchoices'">{{ row.secondary_title }}</a>
|
||||||
|
<a href="exercices/{{ row.primary }}#quizz-{{ row.secondary }}" ng-if="row.kind == 'mcq_found'">{{ row.secondary_title }}</a>
|
||||||
|
<a href="exercices/{{ row.primary }}#hint-{{ row.secondary }}" ng-if="row.kind == 'hint'">{{ row.secondary_title }}</a>
|
||||||
|
</span>
|
||||||
|
<span ng-if="!row.secondary_title && row.secondary && row.kind != 'solved'">: {{ row.secondary }}</span>
|
||||||
|
</td>
|
||||||
|
<td style="vertical-align: middle; padding: 0; background-color: {{ row.team_color }}">
|
||||||
|
<button type="button" ng-click="delHistory(row)" class="float-right btn btn-sm btn-danger"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -104,12 +104,12 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-lg-4">
|
<div class="col-lg-4" style="overflow-y: scroll; height: 95vh">
|
||||||
<table ng-controller="TeamHistoryController" class="table table-hover table-striped table-bordered bg-primary text-light">
|
<table ng-controller="TeamHistoryController" class="table table-hover table-striped table-bordered bg-primary text-light">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr ng-repeat="row in history" ng-class="{'bg-ffound': row.kind == 'flag_found', 'bg-mfound': row.kind == 'mcq_found', 'bg-wchoices': row.kind == 'wchoices', 'bg-success': row.kind == 'solved', 'bg-info': row.kind == 'hint', 'bg-warning': row.kind == 'tries'}">
|
<tr ng-repeat="row in history" ng-class="{'bg-ffound': row.kind == 'flag_found', 'bg-mfound': row.kind == 'mcq_found', 'bg-wchoices': row.kind == 'wchoices', 'bg-success': row.kind == 'solved', 'bg-info': row.kind == 'hint', 'bg-warning': row.kind == 'tries'}">
|
||||||
<td>
|
<td>
|
||||||
<nobr title="{{ row.time }}">{{ row.time | date:"mediumTime" }}</nobr><br>{{ row.kind }}
|
<nobr title="{{ row.time }}">{{ row.time | date:"mediumTime" }}</nobr><br>{{ row.kind }} <span ng-if="row.kind != 'flag_found' && row.kind != 'tries' && row.kind != 'mcq_found'">x{{ row.coefficient }}</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span ng-if="row.primary_title">
|
<span ng-if="row.primary_title">
|
||||||
|
@ -118,7 +118,7 @@
|
||||||
<span ng-if="!row.primary_title">{{ row.primary }}</span>
|
<span ng-if="!row.primary_title">{{ row.primary }}</span>
|
||||||
<span ng-if="row.secondary_title">
|
<span ng-if="row.secondary_title">
|
||||||
:
|
:
|
||||||
<a href="exercices/{{ row.primary }}#key-{{ row.secondary }}" ng-if="row.kind == 'key_found'">{{ row.secondary_title }}</a>
|
<a href="exercices/{{ row.primary }}#key-{{ row.secondary }}" ng-if="row.kind == 'flag_found' || row.kind == 'wchoices'">{{ row.secondary_title }}</a>
|
||||||
<a href="exercices/{{ row.primary }}#quizz-{{ row.secondary }}" ng-if="row.kind == 'mcq_found'">{{ row.secondary_title }}</a>
|
<a href="exercices/{{ row.primary }}#quizz-{{ row.secondary }}" ng-if="row.kind == 'mcq_found'">{{ row.secondary_title }}</a>
|
||||||
<a href="exercices/{{ row.primary }}#hint-{{ row.secondary }}" ng-if="row.kind == 'hint'">{{ row.secondary_title }}</a>
|
<a href="exercices/{{ row.primary }}#hint-{{ row.secondary }}" ng-if="row.kind == 'hint'">{{ row.secondary_title }}</a>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<div ng-repeat="team in teams" ng-click="show(team.id)" ng-controller="TeamController">
|
<div ng-repeat="team in teams" ng-click="show(team.id)" ng-controller="TeamController" ng-if="team.active">
|
||||||
<div ng-controller="TeamExercicesController">
|
<div ng-controller="TeamExercicesController">
|
||||||
<div ng-if="teams[my.team_id].rank">
|
<div ng-if="teams[my.team_id].rank">
|
||||||
<h2>{{ team.name }} <small>{{ teams[my.team_id].rank }}/{{ nb_teams }} – <ng-pluralize count="my.score" when="{'one': '{} point', 'other': '{} points'}"></ng-pluralize></small></h2>
|
<h2>{{ team.name }} <small>{{ teams[my.team_id].rank }}/{{ nb_teams }} – <ng-pluralize count="my.score" when="{'one': '{} point', 'other': '{} points'}"></ng-pluralize></small></h2>
|
||||||
|
|
|
@ -59,5 +59,8 @@ func ProcessMarkdown(i Importer, input string, rootDir string) (output string, e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Trim output
|
||||||
|
output = strings.TrimSpace(output)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,12 +19,21 @@ function treatFlagKey(flag) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flag.found == null && flag.soluce !== undefined) {
|
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))
|
if (flag.soluce == b2sum(flag.value))
|
||||||
flag.found = new Date();
|
flag.found = new Date();
|
||||||
check &= flag.found;
|
}
|
||||||
}
|
}
|
||||||
|
return flag.found !== undefined && flag.found !== false;
|
||||||
}
|
}
|
||||||
|
|
||||||
angular.module("FICApp", ["ngRoute", "ngSanitize"])
|
angular.module("FICApp", ["ngRoute", "ngSanitize"])
|
||||||
|
@ -241,6 +250,8 @@ angular.module("FICApp", ["ngRoute", "ngSanitize"])
|
||||||
var refreshRate = 1200;
|
var refreshRate = 1200;
|
||||||
if ($rootScope.notify_field == 0 && eventsLastTreated)
|
if ($rootScope.notify_field == 0 && eventsLastTreated)
|
||||||
refreshRate = 30000;
|
refreshRate = 30000;
|
||||||
|
if ($scope.my && !$scope.my.team_id)
|
||||||
|
return;
|
||||||
refreshEventsInterval = $interval(refreshEvents, Math.floor(Math.random() * refreshRate * 2) + refreshRate);
|
refreshEventsInterval = $interval(refreshEvents, Math.floor(Math.random() * refreshRate * 2) + refreshRate);
|
||||||
|
|
||||||
if (!eventsLastTreated) {
|
if (!eventsLastTreated) {
|
||||||
|
@ -329,7 +340,15 @@ angular.module("FICApp", ["ngRoute", "ngSanitize"])
|
||||||
angular.forEach(data.exercices, function(exercice, eid) {
|
angular.forEach(data.exercices, function(exercice, eid) {
|
||||||
if ($scope.my && $scope.my.exercices[eid] && $scope.my.exercices[eid].submitted)
|
if ($scope.my && $scope.my.exercices[eid] && $scope.my.exercices[eid].submitted)
|
||||||
data.exercices[eid].timeouted = true;
|
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;
|
||||||
|
if ($scope.my && $scope.my.exercices[eid] && $scope.my.exercices[eid].solved_time !== undefined && data.exercices[eid].solved_time === undefined)
|
||||||
|
data.exercices[eid].solved_time = $scope.my.exercices[eid].solved_time;
|
||||||
|
if ($scope.my && $scope.my.exercices[eid] && $scope.my.exercices[eid].tries !== undefined && data.exercices[eid].tries === undefined)
|
||||||
|
data.exercices[eid].tries = $scope.my.exercices[eid].tries;
|
||||||
angular.forEach(exercice.flags, function(flag, fid) {
|
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)
|
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;
|
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)
|
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 +356,10 @@ angular.module("FICApp", ["ngRoute", "ngSanitize"])
|
||||||
else
|
else
|
||||||
data.exercices[eid].flags[fid].values = [""];
|
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(data.exercices, function(exercice, eid) {
|
||||||
angular.forEach(exercice.mcqs, function(mcq, mid) {
|
angular.forEach(exercice.mcqs, function(mcq, mid) {
|
||||||
|
@ -474,7 +497,9 @@ angular.module("FICApp", ["ngRoute", "ngSanitize"])
|
||||||
{
|
{
|
||||||
resp["flags"] = {};
|
resp["flags"] = {};
|
||||||
angular.forEach($scope.my.exercices[$rootScope.current_exercice].flags, function(flag,kid) {
|
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) {
|
if (flag.found == null && flag.soluce === undefined) {
|
||||||
resp["flags"][kid] = flag.value;
|
resp["flags"][kid] = flag.value;
|
||||||
}
|
}
|
||||||
|
@ -483,9 +508,9 @@ angular.module("FICApp", ["ngRoute", "ngSanitize"])
|
||||||
|
|
||||||
if ($scope.my.exercices[$rootScope.current_exercice].mcqs && Object.keys($scope.my.exercices[$rootScope.current_exercice].mcqs).length)
|
if ($scope.my.exercices[$rootScope.current_exercice].mcqs && Object.keys($scope.my.exercices[$rootScope.current_exercice].mcqs).length)
|
||||||
{
|
{
|
||||||
var soluce = "";
|
|
||||||
resp["mcqs"] = {};
|
resp["mcqs"] = {};
|
||||||
angular.forEach($scope.my.exercices[$rootScope.current_exercice].mcqs, function(mcq) {
|
angular.forEach($scope.my.exercices[$rootScope.current_exercice].mcqs, function(mcq) {
|
||||||
|
var soluce = "";
|
||||||
if (mcq.solved == null) {
|
if (mcq.solved == null) {
|
||||||
angular.forEach(mcq.choices, function(choice, cid) {
|
angular.forEach(mcq.choices, function(choice, cid) {
|
||||||
if (mcq.soluce !== undefined) {
|
if (mcq.soluce !== undefined) {
|
||||||
|
@ -515,6 +540,8 @@ angular.module("FICApp", ["ngRoute", "ngSanitize"])
|
||||||
|
|
||||||
if (check !== undefined)
|
if (check !== undefined)
|
||||||
{
|
{
|
||||||
|
if (!$scope.my.exercices[$rootScope.current_exercice].tries)
|
||||||
|
$scope.my.exercices[$rootScope.current_exercice].tries = 0;
|
||||||
$scope.my.exercices[$rootScope.current_exercice].tries += 1;
|
$scope.my.exercices[$rootScope.current_exercice].tries += 1;
|
||||||
$scope.my.exercices[$rootScope.current_exercice].solved_time = new Date();
|
$scope.my.exercices[$rootScope.current_exercice].solved_time = new Date();
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
<hr class="my-3">
|
<hr class="my-3">
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>Gain :</strong> <ng-pluralize count="themes[current_theme].exercices[current_exercice].gain" when="{'one': '{} point', 'other': '{} points'}"></ng-pluralize> <em ng-if="settings.firstBlood && themes[current_theme].exercices[current_exercice].solved < 1">{{ 1 + settings.firstBlood | coeff }} prem's</em> <em ng-if="themes[current_theme].exercices[current_exercice].curcoeff != 1.0 || settings.exerciceCurrentCoefficient">{{ themes[current_theme].exercices[current_exercice].curcoeff * settings.exerciceCurrentCoefficient | coeff }} bonus</em></li>
|
<li><strong>Gain :</strong> <ng-pluralize count="themes[current_theme].exercices[current_exercice].gain" when="{'one': '{} point', 'other': '{} points'}"></ng-pluralize> <em ng-if="settings.firstBlood && themes[current_theme].exercices[current_exercice].solved < 1">{{ 1 + settings.firstBlood | coeff }} prem's</em> <em ng-if="themes[current_theme].exercices[current_exercice].curcoeff != 1.0 || settings.exerciceCurrentCoefficient">{{ themes[current_theme].exercices[current_exercice].curcoeff * settings.exerciceCurrentCoefficient | coeff }} bonus</em></li>
|
||||||
|
<li ng-if="themes[current_theme].exercices[current_exercice].tried"><strong>Tenté par :</strong> <ng-pluralize count="themes[current_theme].exercices[current_exercice].tried" when="{'0': 'aucune équipe', 'one': '{} équipe', 'other': '{} équipes'}"></ng-pluralize> <span ng-if="my.exercices[current_exercice].total_tries">(cumulant <ng-pluralize count="my.exercices[current_exercice].total_tries" when="{'one': '{} tentative', 'other': '{} tentatives'}"></ng-pluralize>)</span>.</li>
|
||||||
<li><strong>Résolu par :</strong> <ng-pluralize count="themes[current_theme].exercices[current_exercice].solved" when="{'0': 'aucune équipe', 'one': '{} équipe', 'other': '{} équipes'}"></ng-pluralize>.</li>
|
<li><strong>Résolu par :</strong> <ng-pluralize count="themes[current_theme].exercices[current_exercice].solved" when="{'0': 'aucune équipe', 'one': '{} équipe', 'other': '{} équipes'}"></ng-pluralize>.</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -73,7 +74,7 @@
|
||||||
<h4 class="list-group-item-heading">{{ hint.title }}</h4>
|
<h4 class="list-group-item-heading">{{ hint.title }}</h4>
|
||||||
<p class="list-group-item-text" ng-if="!hint.file && hint.content && !hint.hidden" ng-bind-html="hint.content"></p>
|
<p class="list-group-item-text" ng-if="!hint.file && hint.content && !hint.hidden" ng-bind-html="hint.content"></p>
|
||||||
<p class="list-group-item-text" ng-if="hint.file">Cliquez ici pour télécharger l'indice.<br>b2sum : <samp class="cksum" title="Somme de contrôle BLAKE2b : {{ hint.content }}">{{ hint.content }}</samp></p>
|
<p class="list-group-item-text" ng-if="hint.file">Cliquez ici pour télécharger l'indice.<br>b2sum : <samp class="cksum" title="Somme de contrôle BLAKE2b : {{ hint.content }}">{{ hint.content }}</samp></p>
|
||||||
<p class="list-group-item-text" ng-if="!hint.file && !hint.content && (hint.hidden === true || hint.hidden === undefined)">Débloquer cet indice vous fera perdre <ng-pluralize count="hint.cost * settings.hintCurrentCoefficient" when="{'one': '{} point', 'other': '{} points'}"></ng-pluralize>.</p>
|
<p class="list-group-item-text" ng-if="!hint.file && ((!hint.content && hint.hidden === undefined) || hint.hidden === true)">Débloquer cet indice vous fera perdre <ng-pluralize count="hint.cost * settings.hintCurrentCoefficient" when="{'one': '{} point', 'other': '{} points'}"></ng-pluralize>.</p>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -119,9 +120,12 @@
|
||||||
<span class="glyphicon glyphicon-flag" aria-hidden="true"></span> Défi réussi !
|
<span class="glyphicon glyphicon-flag" aria-hidden="true"></span> Défi réussi !
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p class="card-text">
|
<p class="card-text" ng-if="my.exercices[current_exercice].solved_rank">
|
||||||
Vous êtes la {{ my.exercices[current_exercice].solved_rank }}<sup><ng-pluralize count="my.exercices[current_exercice].solved_rank" when="{'one': 're', 'other': 'e'}"></ng-pluralize></sup> équipe à avoir résolu ce défi à {{ my.exercices[current_exercice].solved_time | date:"mediumTime" }}. Vous avez marqué <ng-pluralize count="my.exercices[current_exercice].gain" when="{'one': '{} point', 'other': '{} points'}"></ng-pluralize> !
|
Vous êtes la {{ my.exercices[current_exercice].solved_rank }}<sup><ng-pluralize count="my.exercices[current_exercice].solved_rank" when="{'one': 're', 'other': 'e'}"></ng-pluralize></sup> équipe à avoir résolu ce défi à {{ my.exercices[current_exercice].solved_time | date:"mediumTime" }}. Vous avez marqué <ng-pluralize count="my.exercices[current_exercice].gain" when="{'one': '{} point', 'other': '{} points'}"></ng-pluralize> !
|
||||||
</p>
|
</p>
|
||||||
|
<p class="card-text" ng-if="!my.exercices[current_exercice].solved_rank">
|
||||||
|
Bravo, vous avez résolu ce défi à {{ my.exercices[current_exercice].solved_time | date:"mediumTime" }}. Vous marquez <ng-pluralize count="my.exercices[current_exercice].gain" when="{'one': '{} point', 'other': '{} points'}"></ng-pluralize> !
|
||||||
|
</p>
|
||||||
<hr ng-if="my.exercices[current_exercice].finished">
|
<hr ng-if="my.exercices[current_exercice].finished">
|
||||||
<p class="card-text" ng-if="my.exercices[current_exercice].finished" ng-bind-html="my.exercices[current_exercice].finished"></p>
|
<p class="card-text" ng-if="my.exercices[current_exercice].finished" ng-bind-html="my.exercices[current_exercice].finished"></p>
|
||||||
<hr ng-if="my.exercices[current_exercice].finished && themes[current_theme].exercices[current_exercice].next">
|
<hr ng-if="my.exercices[current_exercice].finished && themes[current_theme].exercices[current_exercice].next">
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-columns">
|
<div class="card-columns">
|
||||||
<div class="card niceborder excard" ng-repeat="(k,theme) in themes" ng-click="goTheme()" ng-class="{'border-success': theme.solved > 0, 'border-warning': theme.exercice_coeff_max > 1}">
|
<div class="card niceborder excard" ng-repeat="(k,theme) in themes" ng-click="goTheme()" ng-class="{'border-success': my.team_id && theme.solved > 0, 'border-warning': theme.exercice_coeff_max > 1}">
|
||||||
<div class="card-img-top theme-card" ng-show="theme.image" style="background-image: url({{ theme.image }})"></div>
|
<div class="card-img-top theme-card" ng-show="theme.image" style="background-image: url({{ theme.image }})"></div>
|
||||||
<div class="card-body text-indent">
|
<div class="card-body text-indent">
|
||||||
<h5 class="card-title">
|
<h5 class="card-title">
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<div class="card-columns">
|
<div class="card-columns">
|
||||||
<div class="card niceborder excard" ng-repeat="ex in exercices" ng-click="goDefi()" ng-class="{'border-success': ex.exercice.solved, 'border-secondary': !my.exercices[ex.eid], 'border-warning': ex.exercice.curcoeff > 1.0}">
|
<div class="card niceborder excard" ng-repeat="ex in exercices" ng-click="goDefi()" ng-class="{'border-success': my.team_id && ex.exercice.solved, 'border-secondary': !my.exercices[ex.eid], 'border-warning': ex.exercice.curcoeff > 1.0}">
|
||||||
<div class="card-img-top theme-card" ng-show="ex.theme.image" style="background-image: url({{ ex.theme.image }})"></div>
|
<div class="card-img-top theme-card" ng-show="ex.theme.image" style="background-image: url({{ ex.theme.image }})"></div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h6 class="card-title">
|
<h6 class="card-title">
|
||||||
|
|
|
@ -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.
|
// TriedTeamCount returns the number of Team that attempted to solve the exercice.
|
||||||
func (e Exercice) TriedTeamCount() int64 {
|
func (e Exercice) TriedTeamCount() int64 {
|
||||||
tries_table := "exercice_tries"
|
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.
|
// TriedCount returns the number of cumulative attempts, all Team combined, for the exercice.
|
||||||
func (e Exercice) TriedCount() int64 {
|
func (e Exercice) TriedCount() int64 {
|
||||||
tries_table := "exercice_tries"
|
tries_table := "exercice_tries"
|
||||||
|
|
|
@ -0,0 +1,112 @@
|
||||||
|
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, 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 {
|
||||||
|
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 coeff float32
|
||||||
|
var exercice int64
|
||||||
|
var secondary *int64
|
||||||
|
var secondary_title *string
|
||||||
|
|
||||||
|
if err := rows.Scan(&id_team, &team_name, &team_color, &kind, &time, &coeff, &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["coefficient"] = coeff
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
|
@ -73,7 +73,10 @@ func getHashedFlag(raw_value []byte) [blake2b.Size]byte {
|
||||||
return hash
|
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 {
|
if re, err := regexp.Compile(vre); err != nil {
|
||||||
return val, err
|
return val, err
|
||||||
} else if res := re.FindSubmatch(val); res == nil {
|
} 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
|
// Check that raw value passes through the regexp
|
||||||
if validator_regexp != nil {
|
if validator_regexp != nil {
|
||||||
var err error
|
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
|
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
|
// Check that raw value passes through the regexp
|
||||||
if k.ValidatorRegexp != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,12 +8,12 @@ import (
|
||||||
func (t Team) GetHistory() ([]map[string]interface{}, error) {
|
func (t Team) GetHistory() ([]map[string]interface{}, error) {
|
||||||
hist := make([]map[string]interface{}, 0)
|
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
|
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, 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, "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, 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, "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, 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, "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, 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, "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, 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 = ?
|
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 {
|
ORDER BY time DESC`, t.Id, t.Id, t.Id, t.Id, t.Id, t.Id); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
|
@ -23,12 +23,13 @@ func (t Team) GetHistory() ([]map[string]interface{}, error) {
|
||||||
var id_team int64
|
var id_team int64
|
||||||
var kind string
|
var kind string
|
||||||
var time time.Time
|
var time time.Time
|
||||||
|
var coefficient float32
|
||||||
var primary *int64
|
var primary *int64
|
||||||
var primary_title *string
|
var primary_title *string
|
||||||
var secondary *int64
|
var secondary *int64
|
||||||
var secondary_title *string
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,6 +37,7 @@ func (t Team) GetHistory() ([]map[string]interface{}, error) {
|
||||||
|
|
||||||
h["kind"] = kind
|
h["kind"] = kind
|
||||||
h["time"] = time
|
h["time"] = time
|
||||||
|
h["coefficient"] = coefficient
|
||||||
if primary != nil {
|
if primary != nil {
|
||||||
h["primary"] = primary
|
h["primary"] = primary
|
||||||
h["primary_title"] = primary_title
|
h["primary_title"] = primary_title
|
||||||
|
@ -52,6 +54,37 @@ func (t Team) GetHistory() ([]map[string]interface{}, error) {
|
||||||
return hist, nil
|
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.
|
// 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) {
|
func (t Team) DelHistoryItem(kind string, h time.Time, primary *int64, secondary *int64) (interface{}, error) {
|
||||||
if kind == "tries" && primary != nil {
|
if kind == "tries" && primary != nil {
|
||||||
|
@ -62,7 +95,7 @@ func (t Team) DelHistoryItem(kind string, h time.Time, primary *int64, secondary
|
||||||
} else {
|
} else {
|
||||||
return nb, err
|
return nb, err
|
||||||
}
|
}
|
||||||
} else if kind == "hint" && primary != nil {
|
} else if kind == "hint" && primary != nil && secondary != nil {
|
||||||
if res, err := DBExec("DELETE FROM team_hints WHERE id_team = ? AND time = ? AND id_hint = ?", t.Id, h, *secondary); err != nil {
|
if res, err := DBExec("DELETE FROM team_hints WHERE id_team = ? AND time = ? AND id_hint = ?", t.Id, h, *secondary); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
} else if nb, err := res.RowsAffected(); err != nil {
|
} else if nb, err := res.RowsAffected(); err != nil {
|
||||||
|
@ -70,7 +103,7 @@ func (t Team) DelHistoryItem(kind string, h time.Time, primary *int64, secondary
|
||||||
} else {
|
} else {
|
||||||
return nb, err
|
return nb, err
|
||||||
}
|
}
|
||||||
} else if kind == "wchoices" && primary != nil {
|
} else if kind == "wchoices" && primary != nil && secondary != nil {
|
||||||
if res, err := DBExec("DELETE FROM team_wchoices WHERE id_team = ? AND time = ? AND id_flag = ?", t.Id, h, *secondary); err != nil {
|
if res, err := DBExec("DELETE FROM team_wchoices WHERE id_team = ? AND time = ? AND id_flag = ?", t.Id, h, *secondary); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
} else if nb, err := res.RowsAffected(); err != nil {
|
} else if nb, err := res.RowsAffected(); err != nil {
|
||||||
|
@ -78,7 +111,7 @@ func (t Team) DelHistoryItem(kind string, h time.Time, primary *int64, secondary
|
||||||
} else {
|
} else {
|
||||||
return nb, err
|
return nb, err
|
||||||
}
|
}
|
||||||
} else if kind == "flag_found" && secondary != nil {
|
} else if kind == "flag_found" && primary != nil && secondary != nil {
|
||||||
if res, err := DBExec("DELETE FROM flag_found WHERE id_team = ? AND time = ? AND id_flag = ?", t.Id, h, *secondary); err != nil {
|
if res, err := DBExec("DELETE FROM flag_found WHERE id_team = ? AND time = ? AND id_flag = ?", t.Id, h, *secondary); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
} else if nb, err := res.RowsAffected(); err != nil {
|
} else if nb, err := res.RowsAffected(); err != nil {
|
||||||
|
@ -86,7 +119,7 @@ func (t Team) DelHistoryItem(kind string, h time.Time, primary *int64, secondary
|
||||||
} else {
|
} else {
|
||||||
return nb, err
|
return nb, err
|
||||||
}
|
}
|
||||||
} else if kind == "mcq_found" && secondary != nil {
|
} else if kind == "mcq_found" && primary != nil && secondary != nil {
|
||||||
if res, err := DBExec("DELETE FROM mcq_found WHERE id_team = ? AND time = ? AND id_mcq = ?", t.Id, h, *secondary); err != nil {
|
if res, err := DBExec("DELETE FROM mcq_found WHERE id_team = ? AND time = ? AND id_mcq = ?", t.Id, h, *secondary); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
} else if nb, err := res.RowsAffected(); err != nil {
|
} else if nb, err := res.RowsAffected(); err != nil {
|
||||||
|
|
|
@ -30,6 +30,8 @@ type myTeamFlag struct {
|
||||||
Help string `json:"help,omitempty"`
|
Help string `json:"help,omitempty"`
|
||||||
Separator string `json:"separator,omitempty"`
|
Separator string `json:"separator,omitempty"`
|
||||||
IgnoreOrder bool `json:"ignore_order,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"`
|
Solved *time.Time `json:"found,omitempty"`
|
||||||
Soluce string `json:"soluce,omitempty"`
|
Soluce string `json:"soluce,omitempty"`
|
||||||
Choices map[string]string `json:"choices,omitempty"`
|
Choices map[string]string `json:"choices,omitempty"`
|
||||||
|
@ -62,6 +64,7 @@ type myTeamExercice struct {
|
||||||
SolvedTime *time.Time `json:"solved_time,omitempty"`
|
SolvedTime *time.Time `json:"solved_time,omitempty"`
|
||||||
SolvedRank int64 `json:"solved_rank,omitempty"`
|
SolvedRank int64 `json:"solved_rank,omitempty"`
|
||||||
Tries int64 `json:"tries,omitempty"`
|
Tries int64 `json:"tries,omitempty"`
|
||||||
|
TotalTries int64 `json:"total_tries,omitempty"`
|
||||||
VideoURI string `json:"video_uri,omitempty"`
|
VideoURI string `json:"video_uri,omitempty"`
|
||||||
Issue string `json:"issue,omitempty"`
|
Issue string `json:"issue,omitempty"`
|
||||||
IssueKind string `json:"issuekind,omitempty"`
|
IssueKind string `json:"issuekind,omitempty"`
|
||||||
|
@ -112,7 +115,7 @@ func MyJSONTeam(t *Team, started bool) (interface{}, error) {
|
||||||
exercice.Overview = strings.Replace(e.Overview, "$FILES$", FilesDir, -1)
|
exercice.Overview = strings.Replace(e.Overview, "$FILES$", FilesDir, -1)
|
||||||
exercice.Finished = strings.Replace(e.Finished, "$FILES$", FilesDir, -1)
|
exercice.Finished = strings.Replace(e.Finished, "$FILES$", FilesDir, -1)
|
||||||
exercice.VideoURI = e.VideoURI
|
exercice.VideoURI = e.VideoURI
|
||||||
exercice.Tries = e.TriedCount()
|
exercice.TotalTries = e.TriedCount()
|
||||||
exercice.Gain = int(float64(e.Gain) * e.Coefficient)
|
exercice.Gain = int(float64(e.Gain) * e.Coefficient)
|
||||||
} else {
|
} else {
|
||||||
solved, stime := t.HasSolved(e)
|
solved, stime := t.HasSolved(e)
|
||||||
|
@ -189,6 +192,8 @@ func MyJSONTeam(t *Team, started bool) (interface{}, error) {
|
||||||
|
|
||||||
// Retrieve solved state or solution for public iface
|
// Retrieve solved state or solution for public iface
|
||||||
if t == nil {
|
if t == nil {
|
||||||
|
flag.IgnoreCase = k.IgnoreCase
|
||||||
|
flag.ValidatorRe = k.ValidatorRegexp
|
||||||
flag.Soluce = hex.EncodeToString(k.Checksum)
|
flag.Soluce = hex.EncodeToString(k.Checksum)
|
||||||
} else if PartialValidation {
|
} else if PartialValidation {
|
||||||
flag.Solved = t.HasPartiallySolved(k)
|
flag.Solved = t.HasPartiallySolved(k)
|
||||||
|
|
Loading…
Reference in New Issue