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"
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"srs.epita.fr/fic-server/admin/sync"
|
||||
"srs.epita.fr/fic-server/libfic"
|
||||
|
@ -14,12 +15,17 @@ 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)))
|
||||
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)))
|
||||
|
||||
router.GET("/api/exercices/:eid/files", apiHandler(exerciceHandler(listExerciceFiles)))
|
||||
router.POST("/api/exercices/:eid/files", apiHandler(exerciceHandler(createExerciceFile)))
|
||||
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()
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
@ -109,6 +137,35 @@ 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()
|
||||
}
|
||||
|
||||
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) {
|
||||
return exercice.Delete()
|
||||
}
|
||||
|
|
|
@ -242,6 +242,23 @@ type uploadedHistory struct {
|
|||
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) {
|
||||
|
|
|
@ -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>
|
||||
<body class="bg-light text-dark">
|
||||
<nav class="navbar sticky-top navbar-expand-lg navbar-dark bg-dark text-light">
|
||||
<a class="navbar-brand" href="{{.urlbase}}">
|
||||
<img alt="FIC" src="{{.urlbase}}img/fic.png" style="height: 30px">
|
||||
<a class="navbar-brand" href=".">
|
||||
<img alt="FIC" src="img/fic.png" style="height: 30px">
|
||||
</a>
|
||||
<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>
|
||||
|
@ -41,15 +41,15 @@ const indextpl = `<!DOCTYPE html>
|
|||
|
||||
<div class="collapse navbar-collapse" id="adminMenu">
|
||||
<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="{{.urlbase}}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="{{.urlbase}}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="{{.urlbase}}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="{{.urlbase}}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="teams">Équipes</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="pki">PKI</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="exercices">Exercices</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="files">Fichiers</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="events">Événements</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="settings">Paramètres</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
@ -87,14 +87,14 @@ const indextpl = `<!DOCTYPE html>
|
|||
<div class="container mt-1" ng-view></div>
|
||||
|
||||
<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/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-sanitize.min.js"></script>
|
||||
<script src="{{.urlbase}}js/app.js"></script>
|
||||
<script src="{{.urlbase}}js/common.js"></script>
|
||||
<script src="js/app.js"></script>
|
||||
<script src="js/common.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
|
|
|
@ -30,8 +30,8 @@
|
|||
</head>
|
||||
<body class="bg-light text-dark">
|
||||
<nav class="navbar sticky-top navbar-expand-lg navbar-dark bg-dark text-light">
|
||||
<a class="navbar-brand" href="/admin/">
|
||||
<img alt="FIC" src="/admin/img/fic.png" style="height: 30px">
|
||||
<a class="navbar-brand" href=".">
|
||||
<img alt="FIC" src="img/fic.png" style="height: 30px">
|
||||
</a>
|
||||
<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>
|
||||
|
@ -39,15 +39,15 @@
|
|||
|
||||
<div class="collapse navbar-collapse" id="adminMenu">
|
||||
<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="/admin/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="/admin/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="/admin/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="/admin/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="teams">Équipes</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="pki">PKI</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="exercices">Exercices</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="files">Fichiers</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="events">Événements</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="settings">Paramètres</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
@ -85,13 +85,13 @@
|
|||
<div class="container mt-1" ng-view></div>
|
||||
|
||||
<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/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-sanitize.min.js"></script>
|
||||
<script src="/admin/js/app.js"></script>
|
||||
<script src="/admin/js/common.js"></script>
|
||||
<script src="js/app.js"></script>
|
||||
<script src="js/common.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -228,6 +228,9 @@ angular.module("FICApp")
|
|||
update: {method: 'PUT'}
|
||||
})
|
||||
})
|
||||
.factory("ExerciceHistory", function($resource) {
|
||||
return $resource("/api/exercices/:exerciceId/history.json", { exerciceId: '@id' })
|
||||
})
|
||||
.factory("ExerciceFile", function($resource) {
|
||||
return $resource("/api/exercices/:exerciceId/files/:fileId", { exerciceId: '@idExercice', fileId: '@id' }, {
|
||||
update: {method: 'PUT'}
|
||||
|
@ -1194,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"
|
||||
|
@ -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) {
|
||||
$scope.files = ExerciceFile.query({ exerciceId: $routeParams.exerciceId });
|
||||
|
||||
|
|
|
@ -221,6 +221,34 @@
|
|||
</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>
|
||||
|
|
|
@ -104,12 +104,12 @@
|
|||
|
||||
</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">
|
||||
<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 }}
|
||||
<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.primary_title">
|
||||
|
@ -118,7 +118,7 @@
|
|||
<span ng-if="!row.primary_title">{{ row.primary }}</span>
|
||||
<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 }}#hint-{{ row.secondary }}" ng-if="row.kind == 'hint'">{{ row.secondary_title }}</a>
|
||||
</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-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>
|
||||
|
|
|
@ -59,5 +59,8 @@ func ProcessMarkdown(i Importer, input string, rootDir string) (output string, e
|
|||
}
|
||||
}
|
||||
|
||||
// Trim output
|
||||
output = strings.TrimSpace(output)
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -19,12 +19,21 @@ function treatFlagKey(flag) {
|
|||
}
|
||||
|
||||
if (flag.found == null && flag.soluce !== undefined) {
|
||||
if (check === undefined) check = true;
|
||||
|
||||
if (flag.value && flag.soluce == b2sum(flag.value))
|
||||
flag.found = new Date();
|
||||
check &= flag.found;
|
||||
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.soluce == b2sum(flag.value))
|
||||
flag.found = new Date();
|
||||
}
|
||||
}
|
||||
return flag.found !== undefined && flag.found !== false;
|
||||
}
|
||||
|
||||
angular.module("FICApp", ["ngRoute", "ngSanitize"])
|
||||
|
@ -241,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) {
|
||||
|
@ -329,7 +340,15 @@ 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;
|
||||
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) {
|
||||
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 +356,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 +497,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 +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)
|
||||
{
|
||||
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) {
|
||||
|
@ -515,6 +540,8 @@ angular.module("FICApp", ["ngRoute", "ngSanitize"])
|
|||
|
||||
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].solved_time = new Date();
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
<hr class="my-3">
|
||||
<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 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>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -73,7 +74,7 @@
|
|||
<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">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>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -119,9 +120,12 @@
|
|||
<span class="glyphicon glyphicon-flag" aria-hidden="true"></span> Défi réussi !
|
||||
</div>
|
||||
<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> !
|
||||
</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">
|
||||
<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">
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
</div>
|
||||
|
||||
<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-body text-indent">
|
||||
<h5 class="card-title">
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<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-body">
|
||||
<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.
|
||||
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"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
@ -52,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 {
|
||||
|
@ -62,7 +95,7 @@ func (t Team) DelHistoryItem(kind string, h time.Time, primary *int64, secondary
|
|||
} else {
|
||||
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 {
|
||||
return 0, err
|
||||
} 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 {
|
||||
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 {
|
||||
return 0, err
|
||||
} 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 {
|
||||
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 {
|
||||
return 0, err
|
||||
} 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 {
|
||||
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 {
|
||||
return 0, err
|
||||
} else if nb, err := res.RowsAffected(); err != nil {
|
||||
|
|
|
@ -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"`
|
||||
|
@ -62,6 +64,7 @@ type myTeamExercice struct {
|
|||
SolvedTime *time.Time `json:"solved_time,omitempty"`
|
||||
SolvedRank int64 `json:"solved_rank,omitempty"`
|
||||
Tries int64 `json:"tries,omitempty"`
|
||||
TotalTries int64 `json:"total_tries,omitempty"`
|
||||
VideoURI string `json:"video_uri,omitempty"`
|
||||
Issue string `json:"issue,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.Finished = strings.Replace(e.Finished, "$FILES$", FilesDir, -1)
|
||||
exercice.VideoURI = e.VideoURI
|
||||
exercice.Tries = e.TriedCount()
|
||||
exercice.TotalTries = e.TriedCount()
|
||||
exercice.Gain = int(float64(e.Gain) * e.Coefficient)
|
||||
} else {
|
||||
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
|
||||
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)
|
||||
|
|
Loading…
Reference in New Issue