diff --git a/admin/api/exercice.go b/admin/api/exercice.go index 7469df09..c4eeba7d 100644 --- a/admin/api/exercice.go +++ b/admin/api/exercice.go @@ -115,3 +115,22 @@ func createExerciceKey(theme fic.Theme, exercice fic.Exercice, args map[string]s return exercice.AddRawKey(uk.Name, uk.Key) } + +type uploadedHint struct { + Title string + Content string + Cost int64 +} + +func createExerciceHint(theme fic.Theme, exercice fic.Exercice, args map[string]string, body []byte) (interface{}, error) { + var uh uploadedHint + if err := json.Unmarshal(body, &uh); err != nil { + return nil, err + } + + if len(uh.Content) == 0 { + return nil, errors.New("Hint's content not filled") + } + + return exercice.AddHint(uh.Title, uh.Content, uh.Cost) +} diff --git a/admin/api/theme.go b/admin/api/theme.go index dfa2b014..7223861f 100644 --- a/admin/api/theme.go +++ b/admin/api/theme.go @@ -33,6 +33,10 @@ func init() { rtef.Methods("GET").HandlerFunc(apiHandler(themeHandler(themedExerciceHandler(listThemedExerciceFiles)))) rtef.Methods("POST").HandlerFunc(apiHandler(themeHandler(themedExerciceHandler(createExerciceFile)))) + rteh := rte.Path("/hints").Subrouter() + rteh.Methods("GET").HandlerFunc(apiHandler(themeHandler(themedExerciceHandler(listThemedExerciceHints)))) + rteh.Methods("POST").HandlerFunc(apiHandler(themeHandler(themedExerciceHandler(createExerciceHint)))) + rtek := rte.Path("/keys").Subrouter() rtek.Methods("GET").HandlerFunc(apiHandler(themeHandler(themedExerciceHandler(listThemedExerciceKeys)))) rtek.Methods("POST").HandlerFunc(apiHandler(themeHandler(themedExerciceHandler(createExerciceKey)))) @@ -86,6 +90,10 @@ func listThemedExerciceFiles(theme fic.Theme, exercice fic.Exercice, args map[st return exercice.GetFiles() } +func listThemedExerciceHints(theme fic.Theme, exercice fic.Exercice, args map[string]string, body []byte) (interface{}, error) { + return exercice.GetHints() +} + func listThemedExerciceKeys(theme fic.Theme, exercice fic.Exercice, args map[string]string, body []byte) (interface{}, error) { return exercice.GetKeys() } diff --git a/admin/fill_exercices.sh b/admin/fill_exercices.sh index 797c1352..1ebbbae4 100755 --- a/admin/fill_exercices.sh +++ b/admin/fill_exercices.sh @@ -16,12 +16,11 @@ new_exercice() { THEME="$1" TITLE=`echo "$2" | sed 's/"/\\\\"/g'` STATEMENT=`echo "$3" | sed 's/"/\\\\"/g' | sed ':a;N;$!ba;s/\n/
/g'` - HINT=`echo "$4" | sed 's/"/\\\\"/g' | sed ':a;N;$!ba;s/\n/
/g'` - DEPEND="$5" - GAIN="$6" - VIDEO="$7" + DEPEND="$4" + GAIN="$5" + VIDEO="$6" - curl -f -s -d "{\"title\": \"$TITLE\", \"statement\": \"$STATEMENT\", \"hint\": \"$HINT\", \"depend\": $DEPEND, \"gain\": $GAIN, \"videoURI\": \"$VIDEO\"}" "${BASEURL}/api/themes/$THEME" | + curl -f -s -d "{\"title\": \"$TITLE\", \"statement\": \"$STATEMENT\", \"depend\": $DEPEND, \"gain\": $GAIN, \"videoURI\": \"$VIDEO\"}" "${BASEURL}/api/themes/$THEME" | grep -Eo '"id":[0-9]+,' | grep -Eo "[0-9]+" } @@ -35,6 +34,17 @@ new_file() { grep -Eo '"id":[0-9]+,' | grep -Eo "[0-9]+" } +new_hint() { + THEME="$1" + EXERCICE="$2" + TITLE=`echo "$3" | sed 's/"/\\\\"/g'` + CONTENT=`echo "$4" | sed 's/"/\\\\"/g' | sed ':a;N;$!ba;s/\n/
/g'` + COST="$5" + + curl -f -s -d "{\"title\": \"$TITLE\", \"content\": \"$CONTENT\", \"cost\": \"$COST\"}" "${BASEURL}/api/themes/$THEME/$EXERCICE/hints" | + grep -Eo '"id":[0-9]+,' | grep -Eo "[0-9]+" +} + new_key() { THEME="$1" EXERCICE="$2" @@ -110,9 +120,8 @@ do echo ">>> Using default gain: ${EXO_GAIN} points" EXO_SCENARIO=$(get_file "${THM_BASEURI}${EXO_BASEURI}/scenario.txt") - EXO_HINT=$(get_file "${THM_BASEURI}${EXO_BASEURI}/hint.txt") - EXO_ID=`new_exercice "${THEME_ID}" "${EXO_NAME}" "${EXO_SCENARIO}" "${EXO_HINT}" "${LAST}" "${EXO_GAIN}" "/resolution${THM_BASEURI}${EXO_BASEURI}resolution/${EXO_VIDEO}"` + EXO_ID=`new_exercice "${THEME_ID}" "${EXO_NAME}" "${EXO_SCENARIO}" "${LAST}" "${EXO_GAIN}" "/resolution${THM_BASEURI}${EXO_BASEURI}resolution/${EXO_VIDEO}"` if [ -z "$EXO_ID" ]; then echo -e "\e[31;01m!!! An error occured during exercice add.\e[00m" continue @@ -140,6 +149,16 @@ do done + # Hints + EXO_HINT=$(get_file "${THM_BASEURI}${EXO_BASEURI}/hint.txt") + HINT_ID=`new_hint "${THEME_ID}" "${EXO_ID}" "Astuce #1" "${EXO_HINT}" "1"` + if [ -z "$HINT_ID" ]; then + echo -e "\e[31;01m!!! An error occured during hint import!\e[00m (title=Astuce #1;content=${EXO_HINT};cost=1)" + else + echo -e "\e[32m>>> New hint added:\e[00m $HINT_ID - Astuce #1" + fi + + # Files get_dir "${THM_BASEURI}${EXO_BASEURI}files/" | grep -v DIGESTS.txt | while read f; do basename "$f"; done | while read FILE_URI do diff --git a/frontend/static/views/theme.html b/frontend/static/views/theme.html index 1e538078..8dc8074b 100644 --- a/frontend/static/views/theme.html +++ b/frontend/static/views/theme.html @@ -7,8 +7,6 @@

-
-
+
+
+
Indices
+
+ +
+
Soumettre une solution
diff --git a/libfic/db.go b/libfic/db.go index 2f2d6228..b3bb4e31 100644 --- a/libfic/db.go +++ b/libfic/db.go @@ -64,7 +64,6 @@ CREATE TABLE IF NOT EXISTS exercices( id_theme INTEGER NOT NULL, title VARCHAR(255) NOT NULL, statement TEXT NOT NULL, - hint TEXT NOT NULL, depend INTEGER, gain INTEGER NOT NULL, video_uri VARCHAR(255) NOT NULL, @@ -85,6 +84,18 @@ CREATE TABLE IF NOT EXISTS exercice_files( size INTEGER NOT NULL, FOREIGN KEY(id_exercice) REFERENCES exercices(id_exercice) ); +`); err != nil { + return err + } + if _, err := db.Exec(` +CREATE TABLE IF NOT EXISTS exercice_hints( + id_hint INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, + id_exercice INTEGER NOT NULL, + title VARCHAR(255) NOT NULL, + content TEXT NOT NULL, + cost INTEGER NOT NULL, + FOREIGN KEY(id_exercice) REFERENCES exercices(id_exercice) +); `); err != nil { return err } @@ -118,6 +129,17 @@ CREATE TABLE IF NOT EXISTS exercice_tries( FOREIGN KEY(id_exercice) REFERENCES exercices(id_exercice), FOREIGN KEY(id_team) REFERENCES teams(id_team) ); +`); err != nil { + return err + } + if _, err := db.Exec(` +CREATE TABLE IF NOT EXISTS team_hints( + id_team INTEGER, + id_hint INTEGER, + time TIMESTAMP NOT NULL, + FOREIGN KEY(id_hint) REFERENCES exercice_hints(id_hint), + FOREIGN KEY(id_team) REFERENCES teams(id_team) +); `); err != nil { return err } diff --git a/libfic/exercice.go b/libfic/exercice.go index a977d877..6af662d4 100644 --- a/libfic/exercice.go +++ b/libfic/exercice.go @@ -9,7 +9,6 @@ type Exercice struct { Id int64 `json:"id"` Title string `json:"title"` Statement string `json:"statement"` - Hint string `json:"hint"` Depend *int64 `json:"depend"` Gain int64 `json:"gain"` VideoURI string `json:"videoURI"` @@ -17,7 +16,7 @@ type Exercice struct { func GetExercice(id int64) (Exercice, error) { var e Exercice - if err := DBQueryRow("SELECT id_exercice, title, statement, hint, depend, gain, video_uri FROM exercices WHERE id_exercice = ?", id).Scan(&e.Id, &e.Title, &e.Statement, &e.Hint, &e.Depend, &e.Gain, &e.VideoURI); err != nil { + if err := DBQueryRow("SELECT id_exercice, title, statement, depend, gain, video_uri FROM exercices WHERE id_exercice = ?", id).Scan(&e.Id, &e.Title, &e.Statement, &e.Depend, &e.Gain, &e.VideoURI); err != nil { return Exercice{}, err } @@ -26,7 +25,7 @@ func GetExercice(id int64) (Exercice, error) { func (t Theme) GetExercice(id int) (Exercice, error) { var e Exercice - if err := DBQueryRow("SELECT id_exercice, title, statement, hint, depend, gain, video_uri FROM exercices WHERE id_theme = ? AND id_exercice = ?", t.Id, id).Scan(&e.Id, &e.Title, &e.Statement, &e.Hint, &e.Depend, &e.Gain, &e.VideoURI); err != nil { + if err := DBQueryRow("SELECT id_exercice, title, statement, depend, gain, video_uri FROM exercices WHERE id_theme = ? AND id_exercice = ?", t.Id, id).Scan(&e.Id, &e.Title, &e.Statement, &e.Depend, &e.Gain, &e.VideoURI); err != nil { return Exercice{}, err } @@ -34,7 +33,7 @@ func (t Theme) GetExercice(id int) (Exercice, error) { } func GetExercices() ([]Exercice, error) { - if rows, err := DBQuery("SELECT id_exercice, title, statement, hint, depend, gain, video_uri FROM exercices"); err != nil { + if rows, err := DBQuery("SELECT id_exercice, title, statement, depend, gain, video_uri FROM exercices"); err != nil { return nil, err } else { defer rows.Close() @@ -42,7 +41,7 @@ func GetExercices() ([]Exercice, error) { var exos = make([]Exercice, 0) for rows.Next() { var e Exercice - if err := rows.Scan(&e.Id, &e.Title, &e.Statement, &e.Hint, &e.Depend, &e.Gain, &e.VideoURI); err != nil { + if err := rows.Scan(&e.Id, &e.Title, &e.Statement, &e.Depend, &e.Gain, &e.VideoURI); err != nil { return nil, err } exos = append(exos, e) @@ -56,7 +55,7 @@ func GetExercices() ([]Exercice, error) { } func (t Theme) GetExercices() ([]Exercice, error) { - if rows, err := DBQuery("SELECT id_exercice, title, statement, hint, depend, gain, video_uri FROM exercices WHERE id_theme = ?", t.Id); err != nil { + if rows, err := DBQuery("SELECT id_exercice, title, statement, depend, gain, video_uri FROM exercices WHERE id_theme = ?", t.Id); err != nil { return nil, err } else { defer rows.Close() @@ -64,7 +63,7 @@ func (t Theme) GetExercices() ([]Exercice, error) { var exos = make([]Exercice, 0) for rows.Next() { var e Exercice - if err := rows.Scan(&e.Id, &e.Title, &e.Statement, &e.Hint, &e.Depend, &e.Gain, &e.VideoURI); err != nil { + if err := rows.Scan(&e.Id, &e.Title, &e.Statement, &e.Depend, &e.Gain, &e.VideoURI); err != nil { return nil, err } exos = append(exos, e) @@ -77,28 +76,28 @@ func (t Theme) GetExercices() ([]Exercice, error) { } } -func (t Theme) AddExercice(title string, statement string, hint string, depend *Exercice, gain int, videoURI string) (Exercice, error) { +func (t Theme) AddExercice(title string, statement string, depend *Exercice, gain int, videoURI string) (Exercice, error) { var dpd interface{} if depend == nil { dpd = nil } else { dpd = depend.Id } - if res, err := DBExec("INSERT INTO exercices (id_theme, title, statement, hint, depend, gain, video_uri) VALUES (?, ?, ?, ?, ?, ?, ?)", t.Id, title, statement, hint, dpd, gain, videoURI); err != nil { + if res, err := DBExec("INSERT INTO exercices (id_theme, title, statement, depend, gain, video_uri) VALUES (?, ?, ?, ?, ?, ?)", t.Id, title, statement, dpd, gain, videoURI); err != nil { return Exercice{}, err } else if eid, err := res.LastInsertId(); err != nil { return Exercice{}, err } else { if depend == nil { - return Exercice{eid, title, statement, hint, nil, int64(gain), videoURI}, nil + return Exercice{eid, title, statement, nil, int64(gain), videoURI}, nil } else { - return Exercice{eid, title, statement, hint, &depend.Id, int64(gain), videoURI}, nil + return Exercice{eid, title, statement, &depend.Id, int64(gain), videoURI}, nil } } } func (e Exercice) Update() (int64, error) { - if res, err := DBExec("UPDATE exercices SET title = ?, statement = ?, hint = ?, depend = ?, gain = ?, video_uri = ? WHERE id_exercice = ?", e.Title, e.Statement, e.Hint, e.Depend, e.Gain, e.VideoURI, e.Id); err != nil { + if res, err := DBExec("UPDATE exercices SET title = ?, statement = ?, depend = ?, gain = ?, video_uri = ? WHERE id_exercice = ?", e.Title, e.Statement, e.Depend, e.Gain, e.VideoURI, e.Id); err != nil { return 0, err } else if nb, err := res.RowsAffected(); err != nil { return 0, err diff --git a/libfic/hint.go b/libfic/hint.go new file mode 100644 index 00000000..46ae6a6f --- /dev/null +++ b/libfic/hint.go @@ -0,0 +1,65 @@ +package fic + +import ( +) + +type EHint struct { + Id int64 `json:"id"` + IdExercice int64 `json:"idExercice"` + Title string `json:"title"` + Content string `json:"content"` + Cost int64 `json:"cost"` +} + +func (e Exercice) GetHints() ([]EHint, error) { + if rows, err := DBQuery("SELECT id_hint, title, content, cost FROM exercice_hints WHERE id_exercice = ?", e.Id); err != nil { + return nil, err + } else { + defer rows.Close() + + var hints = make([]EHint, 0) + for rows.Next() { + var h EHint + h.IdExercice = e.Id + if err := rows.Scan(&h.Id, &h.Title, &h.Content, &h.Cost); err != nil { + return nil, err + } + hints = append(hints, h) + } + if err := rows.Err(); err != nil { + return nil, err + } + + return hints, nil + } +} + +func (e Exercice) AddHint(title string, content string, cost int64) (EHint, error) { + if res, err := DBExec("INSERT INTO exercice_hints (id_exercice, title, content, cost) VALUES (?, ?, ?, ?)", e.Id, title, content, cost); err != nil { + return EHint{}, err + } else if hid, err := res.LastInsertId(); err != nil { + return EHint{}, err + } else { + return EHint{hid, e.Id, title, content, cost}, nil + } +} + +func (h EHint) Update() (int64, error) { + if res, err := DBExec("UPDATE exercice_hints SET id_exercice = ?, title = ?, content = ?, cost = ? WHERE id_hint = ?", h.IdExercice, h.Title, h.Content, h.Cost, h.Id); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} + +func (h EHint) Delete() (int64, error) { + if res, err := DBExec("DELETE FROM exercice_hints WHERE id_hint = ?", h.Id); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} diff --git a/libfic/team.go b/libfic/team.go index ecd1a69d..920ab6fd 100644 --- a/libfic/team.go +++ b/libfic/team.go @@ -210,6 +210,12 @@ func GetTries(t *Team, e *Exercice) ([]time.Time, error) { } } +func (t Team) HasHint(h EHint) (bool) { + var tm *time.Time + DBQueryRow("SELECT MIN(time) FROM team_hints WHERE id_team = ? AND id_hint = ?", t.Id, h.Id).Scan(&tm) + return tm != nil +} + func (t Team) HasSolved(e Exercice) (bool, time.Time, int64) { var nb *int64 var tm *time.Time diff --git a/libfic/team_my.go b/libfic/team_my.go index 11e04c58..3626fcf1 100644 --- a/libfic/team_my.go +++ b/libfic/team_my.go @@ -13,10 +13,17 @@ type myTeamFile struct { Checksum string `json:"checksum"` Size int64 `json:"size"` } +type myTeamHint struct { + HintId int64 `json:"id"` + Title string `json:"title"` + Content string `json:"content"` + Cost int64 `json:"cost"` + Unlocked bool `json:"unlocked"` +} type myTeamExercice struct { ThemeId int `json:"theme_id"` Statement string `json:"statement"` - Hint string `json:"hint"` + Hints []myTeamHint `json:"hints"` Gain int64 `json:"gain"` Files []myTeamFile `json:"files"` Keys []string `json:"keys"` @@ -35,6 +42,8 @@ type myTeam struct { func MyJSONTeam(t *Team, started bool) (interface{}, error) { ret := myTeam{} + + // Fill information about the team if t == nil { ret.Id = 0 } else { @@ -46,8 +55,9 @@ func MyJSONTeam(t *Team, started bool) (interface{}, error) { } } - ret.Exercices = map[string]myTeamExercice{} + // Fill exercices, only if the challenge is started + ret.Exercices = map[string]myTeamExercice{} if exos, err := GetExercices(); err != nil { return ret, err } else if started { @@ -58,7 +68,6 @@ func MyJSONTeam(t *Team, started bool) (interface{}, error) { exercice.ThemeId = tid } exercice.Statement = e.Statement - exercice.Hint = e.Hint if t == nil { exercice.VideoURI = e.VideoURI exercice.Solved = true @@ -67,6 +76,36 @@ func MyJSONTeam(t *Team, started bool) (interface{}, error) { exercice.Solved, exercice.SolvedTime, exercice.SolvedNumber = t.HasSolved(e) } + // Expose exercice files + + exercice.Files = []myTeamFile{} + + if files, err := e.GetFiles(); err != nil { + return nil, err + } else { + for _, f := range files { + exercice.Files = append(exercice.Files, myTeamFile{path.Join(FilesDir, f.Path), f.Name, hex.EncodeToString(f.Checksum), f.Size}) + } + } + + // Expose exercice hints + + exercice.Hints = []myTeamHint{} + + if hints, err := e.GetHints(); err != nil { + return nil, err + } else { + for _, h := range hints { + if t == nil || t.HasHint(h) { + exercice.Hints = append(exercice.Hints, myTeamHint{h.Id, h.Title, h.Content, h.Cost, true}) + } else { + exercice.Hints = append(exercice.Hints, myTeamHint{h.Id, h.Title, "", h.Cost, false}) + } + } + } + + // Expose exercice keys + exercice.Keys = []string{} if keys, err := e.GetKeys(); err != nil { @@ -81,16 +120,7 @@ func MyJSONTeam(t *Team, started bool) (interface{}, error) { } } - exercice.Files = []myTeamFile{} - - if files, err := e.GetFiles(); err != nil { - return nil, err - } else { - for _, f := range files { - exercice.Files = append(exercice.Files, myTeamFile{path.Join(FilesDir, f.Path), f.Name, hex.EncodeToString(f.Checksum), f.Size}) - } - } - + // Hash table ordered by exercice Id ret.Exercices[fmt.Sprintf("%d", e.Id)] = exercice } }