Write docs!
This commit is contained in:
parent
c460bb7bf5
commit
bcc598ebd5
@ -21,7 +21,9 @@ func init() {
|
||||
router.GET("/api/ca/", apiHandler(infoCA))
|
||||
router.GET("/api/ca.pem", apiHandler(getCAPEM))
|
||||
router.POST("/api/ca/new", apiHandler(
|
||||
func(_ httprouter.Params, _ []byte) (interface{}, error) { return true, pki.GenerateCA(time.Date(2018, 01, 21, 0, 0, 0, 0, time.UTC), time.Date(2018, 01, 24, 23, 59, 59, 0, time.UTC)) }))
|
||||
func(_ httprouter.Params, _ []byte) (interface{}, error) {
|
||||
return true, pki.GenerateCA(time.Date(2018, 01, 21, 0, 0, 0, 0, time.UTC), time.Date(2018, 01, 24, 23, 59, 59, 0, time.UTC))
|
||||
}))
|
||||
|
||||
router.GET("/api/teams/:tid/certificates", apiHandler(teamHandler(
|
||||
func(team fic.Team, _ []byte) (interface{}, error) { return fic.GetTeamCertificates(team) })))
|
||||
@ -142,7 +144,6 @@ func getCertificates(_ httprouter.Params, _ []byte) (interface{}, error) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
type CertUploaded struct {
|
||||
Team *int64 `json:"id_team"`
|
||||
}
|
||||
|
@ -119,7 +119,6 @@ func clearClaims(_ httprouter.Params, _ []byte) (interface{}, error) {
|
||||
return fic.ClearClaims()
|
||||
}
|
||||
|
||||
|
||||
func addClaimDescription(claim fic.Claim, body []byte) (interface{}, error) {
|
||||
var ud fic.ClaimDescription
|
||||
if err := json.Unmarshal(body, &ud); err != nil {
|
||||
@ -152,7 +151,6 @@ func deleteClaim(claim fic.Claim, _ []byte) (interface{}, error) {
|
||||
return claim.Delete()
|
||||
}
|
||||
|
||||
|
||||
func getAssignees(_ httprouter.Params, _ []byte) (interface{}, error) {
|
||||
return fic.GetAssignees()
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"srs.epita.fr/fic-server/libfic"
|
||||
"srs.epita.fr/fic-server/admin/sync"
|
||||
"srs.epita.fr/fic-server/libfic"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
@ -40,16 +40,23 @@ func init() {
|
||||
router.GET("/api/exercices/:eid/quiz/:qid", apiHandler(quizHandler(showExerciceQuiz)))
|
||||
router.DELETE("/api/exercices/:eid/quiz/:qid", apiHandler(quizHandler(deleteExerciceQuiz)))
|
||||
|
||||
|
||||
// Synchronize
|
||||
router.POST("/api/sync/exercices/:eid/files", apiHandler(exerciceHandler(
|
||||
func(exercice fic.Exercice, _ []byte) (interface{}, error) { return sync.SyncExerciceFiles(sync.GlobalImporter, exercice), nil })))
|
||||
func(exercice fic.Exercice, _ []byte) (interface{}, error) {
|
||||
return sync.SyncExerciceFiles(sync.GlobalImporter, exercice), nil
|
||||
})))
|
||||
router.POST("/api/sync/exercices/:eid/hints", apiHandler(exerciceHandler(
|
||||
func(exercice fic.Exercice, _ []byte) (interface{}, error) { return sync.SyncExerciceHints(sync.GlobalImporter, exercice), nil })))
|
||||
func(exercice fic.Exercice, _ []byte) (interface{}, error) {
|
||||
return sync.SyncExerciceHints(sync.GlobalImporter, exercice), nil
|
||||
})))
|
||||
router.POST("/api/sync/exercices/:eid/keys", apiHandler(exerciceHandler(
|
||||
func(exercice fic.Exercice, _ []byte) (interface{}, error) { return sync.SyncExerciceKeys(sync.GlobalImporter, exercice), nil })))
|
||||
func(exercice fic.Exercice, _ []byte) (interface{}, error) {
|
||||
return sync.SyncExerciceKeys(sync.GlobalImporter, exercice), nil
|
||||
})))
|
||||
router.POST("/api/sync/exercices/:eid/quiz", apiHandler(exerciceHandler(
|
||||
func(exercice fic.Exercice, _ []byte) (interface{}, error) { return sync.SyncExerciceMCQ(sync.GlobalImporter, exercice), nil })))
|
||||
func(exercice fic.Exercice, _ []byte) (interface{}, error) {
|
||||
return sync.SyncExerciceMCQ(sync.GlobalImporter, exercice), nil
|
||||
})))
|
||||
|
||||
router.POST("/api/sync/exercices/:eid/fixurlid", apiHandler(exerciceHandler(
|
||||
func(exercice fic.Exercice, _ []byte) (interface{}, error) {
|
||||
@ -131,7 +138,6 @@ func createExercice(theme fic.Theme, body []byte) (interface{}, error) {
|
||||
return theme.AddExercice(ue.Title, ue.URLId, ue.Path, ue.Statement, ue.Overview, depend, ue.Gain, ue.VideoURI)
|
||||
}
|
||||
|
||||
|
||||
type uploadedHint struct {
|
||||
Title string
|
||||
Path string
|
||||
@ -185,7 +191,6 @@ func deleteExerciceHint(hint fic.EHint, _ []byte) (interface{}, error) {
|
||||
return hint.Delete()
|
||||
}
|
||||
|
||||
|
||||
type uploadedKey struct {
|
||||
Label string
|
||||
Key string
|
||||
@ -234,7 +239,6 @@ func deleteExerciceKey(key fic.Key, _ fic.Exercice, _ []byte) (interface{}, erro
|
||||
return key.Delete()
|
||||
}
|
||||
|
||||
|
||||
func showExerciceQuiz(quiz fic.MCQ, _ fic.Exercice, body []byte) (interface{}, error) {
|
||||
return quiz, nil
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ import (
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
|
||||
type DispatchFunction func(httprouter.Params, []byte) (interface{}, error)
|
||||
|
||||
func apiHandler(f DispatchFunction) func(http.ResponseWriter, *http.Request, httprouter.Params) {
|
||||
@ -177,7 +176,7 @@ func keyHandler(f func(fic.Key,fic.Exercice,[]byte) (interface{}, error)) func (
|
||||
return nil, err
|
||||
} else {
|
||||
for _, key := range keys {
|
||||
if (key.Id == int64(kid)) {
|
||||
if key.Id == int64(kid) {
|
||||
return f(key, exercice, body)
|
||||
}
|
||||
}
|
||||
@ -200,7 +199,7 @@ func quizHandler(f func(fic.MCQ,fic.Exercice,[]byte) (interface{}, error)) func
|
||||
return nil, err
|
||||
} else {
|
||||
for _, mcq := range mcqs {
|
||||
if (mcq.Id == int64(qid)) {
|
||||
if mcq.Id == int64(qid) {
|
||||
return f(mcq, exercice, body)
|
||||
}
|
||||
}
|
||||
@ -223,7 +222,7 @@ func exerciceFileHandler(f func(fic.EFile,[]byte) (interface{}, error)) func (ht
|
||||
return nil, err
|
||||
} else {
|
||||
for _, file := range files {
|
||||
if (file.Id == int64(fid)) {
|
||||
if file.Id == int64(fid) {
|
||||
return f(file, body)
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package api
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -15,36 +14,45 @@ import (
|
||||
func init() {
|
||||
router.GET("/api/teams.json", apiHandler(
|
||||
func(httprouter.Params, []byte) (interface{}, error) {
|
||||
return fic.ExportTeams() }))
|
||||
return fic.ExportTeams()
|
||||
}))
|
||||
router.GET("/api/teams-binding", apiHandler(
|
||||
func(httprouter.Params, []byte) (interface{}, error) {
|
||||
return bindingTeams() }))
|
||||
return bindingTeams()
|
||||
}))
|
||||
router.GET("/api/teams-nginx-members", apiHandler(
|
||||
func(httprouter.Params, []byte) (interface{}, error) {
|
||||
return nginxGenMember() }))
|
||||
return nginxGenMember()
|
||||
}))
|
||||
router.GET("/api/teams-tries.json", apiHandler(
|
||||
func(httprouter.Params, []byte) (interface{}, error) {
|
||||
return fic.GetTries(nil, nil) }))
|
||||
return fic.GetTries(nil, nil)
|
||||
}))
|
||||
|
||||
router.GET("/api/teams/", apiHandler(
|
||||
func(httprouter.Params, []byte) (interface{}, error) {
|
||||
return fic.GetTeams() }))
|
||||
return fic.GetTeams()
|
||||
}))
|
||||
router.POST("/api/teams/", apiHandler(createTeam))
|
||||
|
||||
router.GET("/api/teams/:tid/", apiHandler(teamHandler(
|
||||
func(team fic.Team, _ []byte) (interface{}, error) {
|
||||
return team, nil })))
|
||||
return team, nil
|
||||
})))
|
||||
router.PUT("/api/teams/:tid/", apiHandler(teamHandler(updateTeam)))
|
||||
router.POST("/api/teams/:tid/", apiHandler(teamHandler(addTeamMember)))
|
||||
router.DELETE("/api/teams/:tid/", apiHandler(teamHandler(
|
||||
func(team fic.Team, _ []byte) (interface{}, error) {
|
||||
return team.Delete() })))
|
||||
return team.Delete()
|
||||
})))
|
||||
router.GET("/api/teams/:tid/my.json", apiHandler(teamPublicHandler(
|
||||
func(team *fic.Team, _ []byte) (interface{}, error) {
|
||||
return fic.MyJSONTeam(team, true) })))
|
||||
return fic.MyJSONTeam(team, true)
|
||||
})))
|
||||
router.GET("/api/teams/:tid/wait.json", apiHandler(teamPublicHandler(
|
||||
func(team *fic.Team, _ []byte) (interface{}, error) {
|
||||
return fic.MyJSONTeam(team, false) })))
|
||||
return fic.MyJSONTeam(team, false)
|
||||
})))
|
||||
router.GET("/api/teams/:tid/stats.json", apiHandler(teamPublicHandler(
|
||||
func(team *fic.Team, _ []byte) (interface{}, error) {
|
||||
if team != nil {
|
||||
@ -64,14 +72,14 @@ func init() {
|
||||
router.DELETE("/api/teams/:tid/history.json", apiHandler(teamPublicHandler(delHistory)))
|
||||
router.GET("/api/teams/:tid/tries", apiHandler(teamPublicHandler(
|
||||
func(team *fic.Team, _ []byte) (interface{}, error) {
|
||||
return fic.GetTries(team, nil) })))
|
||||
return fic.GetTries(team, nil)
|
||||
})))
|
||||
router.GET("/api/teams/:tid/members", apiHandler(teamHandler(
|
||||
func(team fic.Team, _ []byte) (interface{}, error) {
|
||||
return team.GetMembers() })))
|
||||
return team.GetMembers()
|
||||
})))
|
||||
router.POST("/api/teams/:tid/members", apiHandler(teamHandler(addTeamMember)))
|
||||
router.PUT("/api/teams/:tid/members", apiHandler(teamHandler(setTeamMember)))
|
||||
|
||||
router.GET("/api/members/:mid/team", apiHandler(dispMemberTeam))
|
||||
}
|
||||
|
||||
func nginxGenMember() (string, error) {
|
||||
@ -176,15 +184,6 @@ func setTeamMember(team fic.Team, body []byte) (interface{}, error) {
|
||||
return team.GetMembers()
|
||||
}
|
||||
|
||||
func dispMemberTeam(ps httprouter.Params, body []byte) (interface{}, error) {
|
||||
if mid, err := strconv.Atoi(string(ps.ByName("mid"))); err != nil {
|
||||
return fic.Team{}, err
|
||||
} else {
|
||||
return fic.GetMember(mid)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
type uploadedHistory struct {
|
||||
Kind string
|
||||
Time time.Time
|
||||
|
@ -29,7 +29,6 @@ func init() {
|
||||
router.PUT("/api/themes/:thid/exercices/:eid", apiHandler(exerciceHandler(updateExercice)))
|
||||
router.DELETE("/api/themes/:thid/exercices/:eid", apiHandler(exerciceHandler(deleteExercice)))
|
||||
|
||||
|
||||
router.GET("/api/themes/:thid/exercices/:eid/files", apiHandler(exerciceHandler(listExerciceFiles)))
|
||||
router.POST("/api/themes/:thid/exercices/:eid/files", apiHandler(exerciceHandler(createExerciceFile)))
|
||||
|
||||
@ -46,19 +45,33 @@ func init() {
|
||||
|
||||
// Synchronize
|
||||
router.POST("/api/sync/deep", apiHandler(
|
||||
func(_ httprouter.Params, _ []byte) (interface{}, error) { return sync.SyncDeep(sync.GlobalImporter), nil }))
|
||||
func(_ httprouter.Params, _ []byte) (interface{}, error) {
|
||||
return sync.SyncDeep(sync.GlobalImporter), nil
|
||||
}))
|
||||
router.POST("/api/sync/themes", apiHandler(
|
||||
func(_ httprouter.Params, _ []byte) (interface{}, error) { return sync.SyncThemes(sync.GlobalImporter), nil }))
|
||||
func(_ httprouter.Params, _ []byte) (interface{}, error) {
|
||||
return sync.SyncThemes(sync.GlobalImporter), nil
|
||||
}))
|
||||
router.POST("/api/sync/themes/:thid/exercices", apiHandler(themeHandler(
|
||||
func(theme fic.Theme, _ []byte) (interface{}, error) { return sync.SyncExercices(sync.GlobalImporter, theme), nil })))
|
||||
func(theme fic.Theme, _ []byte) (interface{}, error) {
|
||||
return sync.SyncExercices(sync.GlobalImporter, theme), nil
|
||||
})))
|
||||
router.POST("/api/sync/themes/:thid/exercices/:eid/files", apiHandler(exerciceHandler(
|
||||
func(exercice fic.Exercice, _ []byte) (interface{}, error) { return sync.SyncExerciceFiles(sync.GlobalImporter, exercice), nil })))
|
||||
func(exercice fic.Exercice, _ []byte) (interface{}, error) {
|
||||
return sync.SyncExerciceFiles(sync.GlobalImporter, exercice), nil
|
||||
})))
|
||||
router.POST("/api/sync/themes/:thid/exercices/:eid/hints", apiHandler(exerciceHandler(
|
||||
func(exercice fic.Exercice, _ []byte) (interface{}, error) { return sync.SyncExerciceHints(sync.GlobalImporter, exercice), nil })))
|
||||
func(exercice fic.Exercice, _ []byte) (interface{}, error) {
|
||||
return sync.SyncExerciceHints(sync.GlobalImporter, exercice), nil
|
||||
})))
|
||||
router.POST("/api/sync/themes/:thid/exercices/:eid/keys", apiHandler(exerciceHandler(
|
||||
func(exercice fic.Exercice, _ []byte) (interface{}, error) { return sync.SyncExerciceKeys(sync.GlobalImporter, exercice), nil })))
|
||||
func(exercice fic.Exercice, _ []byte) (interface{}, error) {
|
||||
return sync.SyncExerciceKeys(sync.GlobalImporter, exercice), nil
|
||||
})))
|
||||
router.POST("/api/sync/themes/:thid/exercices/:eid/quiz", apiHandler(exerciceHandler(
|
||||
func(exercice fic.Exercice, _ []byte) (interface{}, error) { return sync.SyncExerciceMCQ(sync.GlobalImporter, exercice), nil })))
|
||||
func(exercice fic.Exercice, _ []byte) (interface{}, error) {
|
||||
return sync.SyncExerciceMCQ(sync.GlobalImporter, exercice), nil
|
||||
})))
|
||||
|
||||
router.POST("/api/sync/themes/:thid/fixurlid", apiHandler(themeHandler(
|
||||
func(theme fic.Theme, _ []byte) (interface{}, error) {
|
||||
@ -137,7 +150,6 @@ func showThemedExercice(theme fic.Theme, exercice fic.Exercice, body []byte) (in
|
||||
return exercice, nil
|
||||
}
|
||||
|
||||
|
||||
type uploadedTheme struct {
|
||||
Name string
|
||||
URLId string
|
||||
|
2
admin/sync/doc.go
Normal file
2
admin/sync/doc.go
Normal file
@ -0,0 +1,2 @@
|
||||
// Package sync provides helpers to import the challenge from various locations.
|
||||
package sync
|
@ -6,17 +6,20 @@ import (
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
|
||||
// ExerciceHintParams holds EHint definition infomation.
|
||||
type ExerciceHintParams struct {
|
||||
Filename string
|
||||
Cost int64
|
||||
Title string
|
||||
}
|
||||
|
||||
// ExerciceParams contains values parsed from defines.txt.
|
||||
type ExerciceParams struct {
|
||||
Gain int64
|
||||
Hints []ExerciceHintParams `toml:"hint"`
|
||||
}
|
||||
|
||||
// parseExerciceParams reads challenge definitions from defines.txt and extract usefull data to set up the challenge.
|
||||
func parseExerciceParams(i Importer, exPath string) (p ExerciceParams, err error) {
|
||||
var defs string
|
||||
defs, err = getFileContent(i, path.Join(exPath, "defines.txt"))
|
||||
|
@ -10,6 +10,8 @@ import (
|
||||
"srs.epita.fr/fic-server/libfic"
|
||||
)
|
||||
|
||||
// SyncExerciceFiles reads the content of files/ directory and import it as EFile for the given challenge.
|
||||
// It takes care of DIGESTS.txt and ensure imported files match.
|
||||
func SyncExerciceFiles(i Importer, exercice fic.Exercice) (errs []string) {
|
||||
// If no files directory, don't display error
|
||||
if ! i.exists(path.Join(exercice.Path, "files")) {
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
_ "golang.org/x/crypto/blake2b"
|
||||
)
|
||||
|
||||
// SyncExerciceHints reads the content of hints/ directories and import it as EHint for the given challenge.
|
||||
func SyncExerciceHints(i Importer, exercice fic.Exercice) (errs []string) {
|
||||
params, err := parseExerciceParams(i, exercice.Path)
|
||||
if err != nil {
|
||||
|
@ -9,6 +9,8 @@ import (
|
||||
"srs.epita.fr/fic-server/libfic"
|
||||
)
|
||||
|
||||
// isFullGraphic detects if some rune are not graphic one.
|
||||
// This function is usefull to display warning when importing key ending with \r.
|
||||
func isFullGraphic(s string) bool {
|
||||
for _, c := range s {
|
||||
if !unicode.IsGraphic(c) {
|
||||
@ -18,6 +20,7 @@ func isFullGraphic(s string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// SyncExerciceKeys reads the content of flags.txt and import them as Key for the given challenge.
|
||||
func SyncExerciceKeys(i Importer, exercice fic.Exercice) []string {
|
||||
var errs []string
|
||||
|
||||
@ -60,6 +63,7 @@ func SyncExerciceKeys(i Importer, exercice fic.Exercice) []string {
|
||||
return errs
|
||||
}
|
||||
|
||||
// SyncExerciceMCQ reads the content of flags-ucq.txt and flags-mcq.txt, and import them as MCQs for the given challenge.
|
||||
func SyncExerciceMCQ(i Importer, exercice fic.Exercice) (errs []string) {
|
||||
if _, err := exercice.WipeMCQs(); err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
|
@ -16,10 +16,15 @@ import (
|
||||
"golang.org/x/crypto/blake2b"
|
||||
)
|
||||
|
||||
// Importer are abstract methods required to import challenges.
|
||||
type Importer interface {
|
||||
// Kind returns information about the Importer, for human interrest.
|
||||
Kind() string
|
||||
// exists checks if the given location exists from the Importer point of view.
|
||||
exists(filename string) bool
|
||||
// toURL gets the full path/URL to the given file, the Importer will look internaly (used for debuging purpose).
|
||||
toURL(filename string) string
|
||||
// importFile imports the file at the given URI
|
||||
importFile(URI string, next func(string, string) (interface{}, error)) (interface{}, error)
|
||||
getFile(filename string, writer *bufio.Writer) error
|
||||
listDir(filename string) ([]string, error)
|
||||
@ -62,6 +67,7 @@ func getFile(i Importer, URI string, writer *bufio.Writer) error {
|
||||
return errors.New(fmt.Sprintf("%q: no such file or directory", URI))
|
||||
}
|
||||
|
||||
// getFileContent
|
||||
func getFileContent(i Importer, URI string) (string, error) {
|
||||
cnt := bytes.Buffer{}
|
||||
|
||||
@ -72,11 +78,16 @@ func getFileContent(i Importer, URI string) (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// getDestinationFilePath generates the destination path, from the URI.
|
||||
// This function permits to obfusce to player the original URI.
|
||||
// Theoricaly, changing the import method doesn't change destination URI.
|
||||
func getDestinationFilePath(URI string) string {
|
||||
hash := blake2b.Sum512([]byte(URI))
|
||||
return path.Join(fic.FilesDir, strings.ToLower(base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(hash[:])), path.Base(URI))
|
||||
}
|
||||
|
||||
// ImportFile imports the file at the given URI, using helpers of the given Importer.
|
||||
// After import, next is called with relative path where the file has been saved and the original URI.
|
||||
func ImportFile(i Importer, URI string, next func(string, string) (interface{}, error)) (interface{}, error) {
|
||||
dest := getDestinationFilePath(URI)
|
||||
|
||||
|
@ -37,7 +37,7 @@ func treatSubmission(pathname string, team fic.Team, exercice_id string) {
|
||||
log.Println(id, "[ERR]", err)
|
||||
} else if theme, err := exercice.GetTheme(); err != nil {
|
||||
log.Println(id, "[ERR]", err)
|
||||
} else if s, tm, _ := team.HasSolved(exercice); s {
|
||||
} else if s, tm := team.HasSolved(exercice); s {
|
||||
log.Printf("%s [WRN] Team %d ALREADY solved exercice %d (%s : %s)\n", id, team.Id, exercice.Id, theme.Name, exercice.Title)
|
||||
} else {
|
||||
if solved, err := exercice.CheckResponse(responses.Keys, responses.MCQs, team); err != nil {
|
||||
|
@ -6,6 +6,16 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Certificate represents a client certificate, which can be associated to a team.
|
||||
//
|
||||
// This is one method usable to handle authentication.
|
||||
// To use it in nginx, you'll need to add following lines in your configuration:
|
||||
//
|
||||
// ssl_client_certificate PKI/shared/ca.pem;
|
||||
// ssl_trusted_certificate PKI/shared/ca.pem;
|
||||
// ssl_verify_client optional;
|
||||
//
|
||||
// Non-recognized clients will have access to a registration form.
|
||||
type Certificate struct {
|
||||
Id uint64 `json:"id,string"`
|
||||
Creation time.Time `json:"creation"`
|
||||
@ -14,6 +24,7 @@ type Certificate struct {
|
||||
Revoked *time.Time `json:"revoked"`
|
||||
}
|
||||
|
||||
// GetCertificates returns the list of all generated certificates.
|
||||
func GetCertificates() (certificates []Certificate, err error) {
|
||||
var rows *sql.Rows
|
||||
if rows, err = DBQuery("SELECT id_cert, creation, password, id_team, revoked FROM certificates ORDER BY creation"); err == nil {
|
||||
@ -32,6 +43,8 @@ func GetCertificates() (certificates []Certificate, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// GetTeamCertificates returns all certificates generated for a given Team.
|
||||
func GetTeamCertificates(team Team) (certificates []Certificate, err error) {
|
||||
var rows *sql.Rows
|
||||
if rows, err = DBQuery("SELECT id_cert, creation, password, id_team, revoked FROM certificates WHERE id_team = ? ORDER BY creation", team.Id); err == nil {
|
||||
@ -50,12 +63,14 @@ func GetTeamCertificates(team Team) (certificates []Certificate, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// GetCertificate retrieves a certificate from its serial number.
|
||||
func GetCertificate(serial uint64) (c Certificate, err error) {
|
||||
err = DBQueryRow("SELECT id_cert, creation, password, id_team, revoked FROM certificates WHERE id_cert = ?", serial).Scan(&c.Id, &c.Creation, &c.Password, &c.IdTeam, &c.Revoked)
|
||||
return
|
||||
}
|
||||
|
||||
func ExistingCertSerial(serial [8]byte) (bool) {
|
||||
// ExistingCertSerial tells you if the given bytes correspond to a know certificate.
|
||||
func ExistingCertSerial(serial [8]byte) bool {
|
||||
var m big.Int
|
||||
m.SetBytes(serial[:])
|
||||
|
||||
@ -63,6 +78,10 @@ func ExistingCertSerial(serial [8]byte) (bool) {
|
||||
return c.Id > 0
|
||||
}
|
||||
|
||||
// RegisterCertificate registers a certificate in the database.
|
||||
//
|
||||
// "serial" is the certificate serial number
|
||||
// "password" is the one used to crypt privatekey and .p12
|
||||
func RegisterCertificate(serial uint64, password string) (Certificate, error) {
|
||||
now := time.Now()
|
||||
if _, err := DBExec("INSERT INTO certificates (id_cert, creation, password) VALUES (?, ?, ?)", serial, now, password); err != nil {
|
||||
@ -72,6 +91,7 @@ func RegisterCertificate(serial uint64, password string) (Certificate, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Update applies modifications back to the database.
|
||||
func (c Certificate) Update() (int64, error) {
|
||||
if res, err := DBExec("UPDATE certificates SET creation = ?, password = ?, id_team = ?, revoked = ? WHERE id_cert = ?", c.Creation, c.Password, c.IdTeam, c.Revoked, c.Id); err != nil {
|
||||
return 0, err
|
||||
@ -82,12 +102,14 @@ func (c Certificate) Update() (int64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Revoke the certificate in database.
|
||||
func (c *Certificate) Revoke() (int64, error) {
|
||||
now := time.Now()
|
||||
c.Revoked = &now
|
||||
return c.Update()
|
||||
}
|
||||
|
||||
// Delete the certificate entry in the database.
|
||||
func (c Certificate) Delete() (int64, error) {
|
||||
if res, err := DBExec("DELETE FROM certificates WHERE id_cert = ?", c.Id); err != nil {
|
||||
return 0, err
|
||||
@ -98,6 +120,8 @@ func (c Certificate) Delete() (int64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ClearCertificates removes all certificates from database.
|
||||
func ClearCertificates() (int64, error) {
|
||||
if res, err := DBExec("DELETE FROM certificates"); err != nil {
|
||||
return 0, err
|
||||
|
@ -8,8 +8,10 @@ import (
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
// db stores the connection to the database
|
||||
var db *sql.DB
|
||||
|
||||
// DSNGenerator returns DSN filed with values from environment
|
||||
func DSNGenerator() string {
|
||||
db_user := "fic"
|
||||
db_password := "fic"
|
||||
@ -35,6 +37,7 @@ func DSNGenerator() string {
|
||||
return db_user + ":" + db_password + "@" + db_host + "/" + db_db
|
||||
}
|
||||
|
||||
// DBInit establishes the connection to the database
|
||||
func DBInit(dsn string) (err error) {
|
||||
if db, err = sql.Open("mysql", dsn + "?parseTime=true&foreign_key_checks=1"); err != nil {
|
||||
return
|
||||
@ -51,6 +54,7 @@ func DBInit(dsn string) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// DBCreate creates all necessary tables used by the package
|
||||
func DBCreate() error {
|
||||
if _, err := db.Exec(`
|
||||
CREATE TABLE IF NOT EXISTS events(
|
||||
@ -284,6 +288,7 @@ CREATE TABLE IF NOT EXISTS claim_descriptions(
|
||||
return nil
|
||||
}
|
||||
|
||||
// DBClose closes the connection to the database
|
||||
func DBClose() error {
|
||||
return db.Close()
|
||||
}
|
||||
|
3
libfic/doc.go
Normal file
3
libfic/doc.go
Normal file
@ -0,0 +1,3 @@
|
||||
// Package fic provides access to common structures shared between multiple
|
||||
// services required to run the fic-server project.
|
||||
package fic
|
@ -4,6 +4,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Event represents a challenge event.
|
||||
type Event struct {
|
||||
Id int64 `json:"id"`
|
||||
Kind string `json:"kind"`
|
||||
@ -11,6 +12,7 @@ type Event struct {
|
||||
Time time.Time `json:"time"`
|
||||
}
|
||||
|
||||
// GetLastEvents returns the list of the last 10 events, sorted by date, last first
|
||||
func GetLastEvents() ([]Event, error) {
|
||||
if rows, err := DBQuery("SELECT id_event, txt, kind, time FROM events ORDER BY time DESC LIMIT 10"); err != nil {
|
||||
return nil, err
|
||||
@ -33,6 +35,7 @@ func GetLastEvents() ([]Event, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetEvents returns the list of all events, sorted by date, last first
|
||||
func GetEvents() ([]Event, error) {
|
||||
if rows, err := DBQuery("SELECT id_event, txt, kind, time FROM events ORDER BY time DESC"); err != nil {
|
||||
return nil, err
|
||||
@ -55,6 +58,7 @@ func GetEvents() ([]Event, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetEvent retrieves the event with the given id
|
||||
func GetEvent(id int) (Event, error) {
|
||||
var e Event
|
||||
if err := DBQueryRow("SELECT id_event, txt, kind, time FROM events WHERE id_event=?", id).Scan(&e.Id, &e.Text, &e.Kind, &e.Time); err != nil {
|
||||
@ -64,6 +68,7 @@ func GetEvent(id int) (Event, error) {
|
||||
return e, nil
|
||||
}
|
||||
|
||||
// NewEvent creates a new event in the database and returns the corresponding structure
|
||||
func NewEvent(txt string, kind string) (Event, error) {
|
||||
if res, err := DBExec("INSERT INTO events (txt, kind, time) VALUES (?, ?, ?)", txt, kind, time.Now()); err != nil {
|
||||
return Event{}, err
|
||||
@ -74,6 +79,7 @@ func NewEvent(txt string, kind string) (Event, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Update applies modifications back to the database
|
||||
func (e Event) Update() (int64, error) {
|
||||
if res, err := DBExec("UPDATE events SET txt = ?, kind = ?, time = ? WHERE id_event = ?", e.Text, e.Kind, e.Time, e.Id); err != nil {
|
||||
return 0, err
|
||||
@ -84,6 +90,7 @@ func (e Event) Update() (int64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the event from the database
|
||||
func (e Event) Delete() (int64, error) {
|
||||
if res, err := DBExec("DELETE FROM events WHERE id_event = ?", e.Id); err != nil {
|
||||
return 0, err
|
||||
@ -94,6 +101,7 @@ func (e Event) Delete() (int64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// ClearEvents removes all events from database
|
||||
func ClearEvents() (int64, error) {
|
||||
if res, err := DBExec("DELETE FROM events"); err != nil {
|
||||
return 0, err
|
||||
|
@ -5,22 +5,40 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// PartialValidation allows a Team to validate an Exercice at a given point if
|
||||
// it has ever found all classical flags, even if the last validation is not
|
||||
// complete or contains mistakes on previously validated flags.
|
||||
var PartialValidation bool
|
||||
|
||||
// PartialMCQValidation allows a Team to validate each MCQ independently.
|
||||
// Otherwise, all MCQ has to be correct for being validated.
|
||||
var PartialMCQValidation bool
|
||||
|
||||
// Exercice represents a challenge inside a Theme.
|
||||
type Exercice struct {
|
||||
Id int64 `json:"id"`
|
||||
Title string `json:"title"`
|
||||
// URLid is used to reference the challenge from the URL path
|
||||
URLId string `json:"urlid"`
|
||||
// Path is the relative import location where find challenge data
|
||||
Path string `json:"path"`
|
||||
// Statement is the challenge description shown to players
|
||||
Statement string `json:"statement"`
|
||||
// Overview is the challenge description shown to public
|
||||
Overview string `json:"overview"`
|
||||
Depend *int64 `json:"depend"`
|
||||
// Gain is the basis amount of points player will earn if it solve the challenge
|
||||
// If this value fluctuate during the challenge, players' scores will follow changes.
|
||||
// Use Coefficient value to create temporary bonus/malus.
|
||||
Gain int64 `json:"gain"`
|
||||
// Coefficient is a temporary bonus/malus applied on Gain when the challenge is solved.
|
||||
// This value is saved along with solved informations: changing it doesn't affect Team that already solved the challenge.
|
||||
Coefficient float64 `json:"coefficient"`
|
||||
// VideoURI is the link to the resolution video
|
||||
VideoURI string `json:"videoURI"`
|
||||
}
|
||||
|
||||
// GetExercice retrieves the challenge with the given id.
|
||||
func GetExercice(id int64) (Exercice, error) {
|
||||
var e Exercice
|
||||
if err := DBQueryRow("SELECT id_exercice, title, url_id, path, statement, overview, depend, gain, coefficient_cur, video_uri FROM exercices WHERE id_exercice = ?", id).Scan(&e.Id, &e.Title, &e.URLId, &e.Path, &e.Statement, &e.Overview, &e.Depend, &e.Gain, &e.Coefficient, &e.VideoURI); err != nil {
|
||||
@ -30,6 +48,7 @@ func GetExercice(id int64) (Exercice, error) {
|
||||
return e, nil
|
||||
}
|
||||
|
||||
// GetExercice retrieves the challenge with the given id.
|
||||
func (t Theme) GetExercice(id int) (Exercice, error) {
|
||||
var e Exercice
|
||||
if err := DBQueryRow("SELECT id_exercice, title, url_id, path, statement, overview, depend, gain, coefficient_cur, video_uri FROM exercices WHERE id_theme = ? AND id_exercice = ?", t.Id, id).Scan(&e.Id, &e.Title, &e.URLId, &e.Path, &e.Statement, &e.Overview, &e.Depend, &e.Gain, &e.Coefficient, &e.VideoURI); err != nil {
|
||||
@ -39,6 +58,7 @@ func (t Theme) GetExercice(id int) (Exercice, error) {
|
||||
return e, nil
|
||||
}
|
||||
|
||||
// GetExerciceByTitle retrieves the challenge with the given title.
|
||||
func (t Theme) GetExerciceByTitle(title string) (Exercice, error) {
|
||||
var e Exercice
|
||||
if err := DBQueryRow("SELECT id_exercice, title, url_id, path, statement, overview, depend, gain, coefficient_cur, video_uri FROM exercices WHERE id_theme = ? AND title = ?", t.Id, title).Scan(&e.Id, &e.Title, &t.URLId, &e.Path, &e.Statement, &e.Overview, &e.Depend, &e.Gain, &e.Coefficient, &e.VideoURI); err != nil {
|
||||
@ -48,6 +68,7 @@ func (t Theme) GetExerciceByTitle(title string) (Exercice, error) {
|
||||
return e, nil
|
||||
}
|
||||
|
||||
// GetExercices returns the list of all challenges present in the database.
|
||||
func GetExercices() ([]Exercice, error) {
|
||||
if rows, err := DBQuery("SELECT id_exercice, title, url_id, path, statement, overview, depend, gain, coefficient_cur, video_uri FROM exercices"); err != nil {
|
||||
return nil, err
|
||||
@ -70,6 +91,7 @@ func GetExercices() ([]Exercice, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetExercices returns the list of all challenges in the Theme.
|
||||
func (t Theme) GetExercices() ([]Exercice, error) {
|
||||
if rows, err := DBQuery("SELECT id_exercice, title, url_id, path, statement, overview, depend, gain, coefficient_cur, video_uri FROM exercices WHERE id_theme = ?", t.Id); err != nil {
|
||||
return nil, err
|
||||
@ -92,6 +114,7 @@ func (t Theme) GetExercices() ([]Exercice, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// AddExercice creates and fills a new struct Exercice and registers it into the database.
|
||||
func (t Theme) AddExercice(title string, urlId string, path string, statement string, overview string, depend *Exercice, gain int64, videoURI string) (Exercice, error) {
|
||||
var dpd interface{}
|
||||
if depend == nil {
|
||||
@ -112,6 +135,7 @@ func (t Theme) AddExercice(title string, urlId string, path string, statement st
|
||||
}
|
||||
}
|
||||
|
||||
// Update applies modifications back to the database.
|
||||
func (e Exercice) Update() (int64, error) {
|
||||
if res, err := DBExec("UPDATE exercices SET title = ?, url_id = ?, path = ?, statement = ?, overview = ?, depend = ?, gain = ?, coefficient_cur = ?, video_uri = ? WHERE id_exercice = ?", e.Title, e.URLId, e.Path, e.Statement, e.Overview, e.Depend, e.Gain, e.Coefficient, e.VideoURI, e.Id); err != nil {
|
||||
return 0, err
|
||||
@ -122,6 +146,8 @@ func (e Exercice) Update() (int64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// FixURLId generates a valid URLid from the challenge Title.
|
||||
// It returns true if something has been generated.
|
||||
func (e *Exercice) FixURLId() bool {
|
||||
if e.URLId == "" {
|
||||
e.URLId = ToURLid(e.Title)
|
||||
@ -130,6 +156,7 @@ func (e *Exercice) FixURLId() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// GetThemeId returns the theme's id for the Exercice.
|
||||
func (e Exercice) GetThemeId() (int, error) {
|
||||
var tid int
|
||||
if err := DBQueryRow("SELECT id_theme FROM exercices WHERE id_exercice=?", e.Id).Scan(&tid); err != nil {
|
||||
@ -138,6 +165,7 @@ func (e Exercice) GetThemeId() (int, error) {
|
||||
return tid, nil
|
||||
}
|
||||
|
||||
// GetTheme returns the parent Theme where the Exercice lives.
|
||||
func (e Exercice) GetTheme() (Theme, error) {
|
||||
if tid, err := e.GetThemeId(); err != nil {
|
||||
return Theme{}, err
|
||||
@ -146,6 +174,7 @@ func (e Exercice) GetTheme() (Theme, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the challenge from the database.
|
||||
func (e Exercice) Delete() (int64, error) {
|
||||
if res, err := DBExec("DELETE FROM exercices WHERE id_exercice = ?", e.Id); err != nil {
|
||||
return 0, err
|
||||
@ -156,6 +185,7 @@ func (e Exercice) Delete() (int64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetLevel returns the number of dependancy challenges.
|
||||
func (e Exercice) GetLevel() (int, error) {
|
||||
dep := e.Depend
|
||||
nb := 1
|
||||
@ -170,6 +200,7 @@ func (e Exercice) GetLevel() (int, error) {
|
||||
return nb, nil
|
||||
}
|
||||
|
||||
// NewTry registers a solving attempt for the given Team.
|
||||
func (e Exercice) NewTry(t Team) error {
|
||||
if _, err := DBExec("INSERT INTO exercice_tries (id_exercice, id_team, time) VALUES (?, ?, ?)", e.Id, t.Id, time.Now()); err != nil {
|
||||
return err
|
||||
@ -178,6 +209,8 @@ func (e Exercice) NewTry(t Team) error {
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateTry applies modifications to the latest try registered for the given Team.
|
||||
// Updated values are time and the given nbdiff.
|
||||
func (e Exercice) UpdateTry(t Team, nbdiff int) error {
|
||||
if _, err := DBExec("UPDATE exercice_tries SET nbdiff = ?, time = ? WHERE id_exercice = ? AND id_team = ? ORDER BY time DESC LIMIT 1", nbdiff, time.Now(), e.Id, t.Id); err != nil {
|
||||
return err
|
||||
@ -186,6 +219,7 @@ func (e Exercice) UpdateTry(t Team, nbdiff int) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Solved registers that the given Team solves the challenge.
|
||||
func (e Exercice) Solved(t Team) error {
|
||||
if _, err := DBExec("INSERT INTO exercice_solved (id_exercice, id_team, time, coefficient) VALUES (?, ?, ?, ?)", e.Id, t.Id, time.Now(), e.Coefficient); err != nil {
|
||||
return err
|
||||
@ -194,6 +228,7 @@ func (e Exercice) Solved(t Team) error {
|
||||
}
|
||||
}
|
||||
|
||||
// SolvedCount returns the number of Team that already have solved the challenge.
|
||||
func (e Exercice) SolvedCount() int64 {
|
||||
var nb int64
|
||||
if err := DBQueryRow("SELECT COUNT(id_exercice) FROM exercice_solved WHERE id_exercice = ?", e.Id).Scan(&nb); err != nil {
|
||||
@ -203,6 +238,7 @@ func (e Exercice) SolvedCount() int64 {
|
||||
}
|
||||
}
|
||||
|
||||
// TriedTeamCount returns the number of Team that attempted to solve the exercice.
|
||||
func (e Exercice) TriedTeamCount() int64 {
|
||||
var nb int64
|
||||
if err := DBQueryRow("SELECT COUNT(DISTINCT id_team) FROM exercice_tries WHERE id_exercice = ?", e.Id).Scan(&nb); err != nil {
|
||||
@ -212,6 +248,7 @@ func (e Exercice) TriedTeamCount() int64 {
|
||||
}
|
||||
}
|
||||
|
||||
// TriedCount returns the number of cumulative attempts, all Team combined, for the exercice.
|
||||
func (e Exercice) TriedCount() int64 {
|
||||
var nb int64
|
||||
if err := DBQueryRow("SELECT COUNT(id_team) FROM exercice_tries WHERE id_exercice = ?", e.Id).Scan(&nb); err != nil {
|
||||
@ -221,6 +258,8 @@ func (e Exercice) TriedCount() int64 {
|
||||
}
|
||||
}
|
||||
|
||||
// CheckResponse, given both flags and MCQ responses, figures out if thoses are correct (or if they are previously solved).
|
||||
// In the meanwhile, CheckResponse registers good answers given (but it does not mark the challenge as solved at the end).
|
||||
func (e Exercice) CheckResponse(respkeys map[string]string, respmcq map[int64]bool, t Team) (bool, error) {
|
||||
if err := e.NewTry(t); err != nil {
|
||||
return false, err
|
||||
@ -273,3 +312,14 @@ func (e Exercice) CheckResponse(respkeys map[string]string, respmcq map[int64]bo
|
||||
return valid, nil
|
||||
}
|
||||
}
|
||||
|
||||
// IsSolved returns the number of time this challenge has been solved and the time of the first solve occurence.
|
||||
func (e Exercice) IsSolved() (int, time.Time) {
|
||||
var nb *int
|
||||
var tm *time.Time
|
||||
if DBQueryRow("SELECT COUNT(id_exercice), MIN(time) FROM exercice_solved WHERE id_exercice = ?", e.Id).Scan(&nb, &tm); nb == nil || tm == nil {
|
||||
return 0, time.Time{}
|
||||
} else {
|
||||
return *nb, *tm
|
||||
}
|
||||
}
|
||||
|
@ -14,20 +14,33 @@ import (
|
||||
_ "golang.org/x/crypto/blake2b"
|
||||
)
|
||||
|
||||
// FilesDir stores the location where files to be served are stored.
|
||||
var FilesDir string = "./FILES/"
|
||||
|
||||
// OptionalDigest permits to avoid importation failure if no digest are given.
|
||||
var OptionalDigest bool = false
|
||||
|
||||
// StrongDigest forces the use of BLAKE2b hash in place of SHA1 (or mixed SHA1/BLAKE2b).
|
||||
var StrongDigest bool = false
|
||||
|
||||
// EFile represents a challenge file.
|
||||
type EFile struct {
|
||||
Id int64 `json:"id"`
|
||||
// origin holds the import relative path of the file
|
||||
origin string
|
||||
// Path is the location where the file is stored, relatively to FilesDir
|
||||
Path string `json:"path"`
|
||||
// IdExercice is the identifier of the underlying challenge
|
||||
IdExercice int64 `json:"idExercice"`
|
||||
// Name is the title displayed to players
|
||||
Name string `json:"name"`
|
||||
// Checksum stores the cached hash of the file
|
||||
Checksum []byte `json:"checksum"`
|
||||
// Size contains the cached size of the file
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
|
||||
// GetFiles returns a list of all files living in the database.
|
||||
func GetFiles() ([]EFile, error) {
|
||||
if rows, err := DBQuery("SELECT id_file, id_exercice, origin, path, name, cksum, size FROM exercice_files"); err != nil {
|
||||
return nil, err
|
||||
@ -50,11 +63,13 @@ func GetFiles() ([]EFile, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetFile retrieves the file with the given id.
|
||||
func GetFile(id int) (f EFile, err error) {
|
||||
err = DBQueryRow("SELECT id_file, origin, path, name, cksum, size FROM exercice_files WHERE id_file = ?", id).Scan(&f.Id, &f.origin, &f.Path, &f.Name, &f.Checksum, &f.Size)
|
||||
return f, err
|
||||
}
|
||||
|
||||
// GetFileByPath retrieves the file that should be found at the given location.
|
||||
func GetFileByPath(path string) (EFile, error) {
|
||||
path = strings.TrimPrefix(path, FilesDir)
|
||||
|
||||
@ -66,6 +81,7 @@ func GetFileByPath(path string) (EFile, error) {
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// GetFiles returns a list of files coming with the challenge.
|
||||
func (e Exercice) GetFiles() ([]EFile, error) {
|
||||
if rows, err := DBQuery("SELECT id_file, origin, path, name, cksum, size FROM exercice_files WHERE id_exercice = ?", e.Id); err != nil {
|
||||
return nil, err
|
||||
@ -89,6 +105,7 @@ func (e Exercice) GetFiles() ([]EFile, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetFileByPath retrieves the file that should be found at the given location, limited to the challenge files.
|
||||
func (e Exercice) GetFileByPath(path string) (EFile, error) {
|
||||
path = strings.TrimPrefix(path, FilesDir)
|
||||
|
||||
@ -100,6 +117,8 @@ func (e Exercice) GetFileByPath(path string) (EFile, error) {
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// checkFileHash checks if the file at the given filePath has the given digest.
|
||||
// It also returns the file's size.
|
||||
func checkFileHash(filePath string, digest []byte) ([]byte, int64, error) {
|
||||
if digest == nil {
|
||||
return []byte{}, 0, errors.New("No digest given.")
|
||||
@ -145,6 +164,7 @@ func checkFileHash(filePath string, digest []byte) ([]byte, int64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// ImportFile registers (ou updates if it already exists in database) the file in database.
|
||||
func (e Exercice) ImportFile(filePath string, origin string, digest []byte) (interface{}, error) {
|
||||
if result512, size, err := checkFileHash(filePath, digest); !OptionalDigest && err != nil {
|
||||
return EFile{}, err
|
||||
@ -169,6 +189,7 @@ func (e Exercice) ImportFile(filePath string, origin string, digest []byte) (int
|
||||
}
|
||||
}
|
||||
|
||||
// AddFile creates and fills a new struct File and registers it into the database.
|
||||
func (e Exercice) AddFile(path string, origin string, name string, checksum []byte, size int64) (EFile, error) {
|
||||
if res, err := DBExec("INSERT INTO exercice_files (id_exercice, origin, path, name, cksum, size) VALUES (?, ?, ?, ?, ?, ?)", e.Id, origin, path, name, checksum, size); err != nil {
|
||||
return EFile{}, err
|
||||
@ -179,6 +200,7 @@ func (e Exercice) AddFile(path string, origin string, name string, checksum []by
|
||||
}
|
||||
}
|
||||
|
||||
// Update applies modifications back to the database.
|
||||
func (f EFile) Update() (int64, error) {
|
||||
if res, err := DBExec("UPDATE exercice_files SET id_exercice = ?, origin = ?, path = ?, name = ?, cksum = ?, size = ? WHERE id_file = ?", f.IdExercice, f.origin, f.Path, f.Name, f.Checksum, f.Size, f.Id); err != nil {
|
||||
return 0, err
|
||||
@ -189,6 +211,7 @@ func (f EFile) Update() (int64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the file from the database.
|
||||
func (f EFile) Delete() (int64, error) {
|
||||
if res, err := DBExec("DELETE FROM exercice_files WHERE id_file = ?", f.Id); err != nil {
|
||||
return 0, err
|
||||
@ -199,6 +222,7 @@ func (f EFile) Delete() (int64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// WipeFiles deletes (only in the database, not on disk) files coming with the challenge.
|
||||
func (e Exercice) WipeFiles() (int64, error) {
|
||||
if res, err := DBExec("DELETE FROM exercice_files WHERE id_exercice = ?", e.Id); err != nil {
|
||||
return 0, err
|
||||
@ -209,6 +233,7 @@ func (e Exercice) WipeFiles() (int64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// ClearFiles removes all certificates from database (but not files on disks).
|
||||
func ClearFiles() (int64, error) {
|
||||
if res, err := DBExec("DELETE FROM exercice_files"); err != nil {
|
||||
return 0, err
|
||||
@ -219,6 +244,7 @@ func ClearFiles() (int64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrigin access the private field origin of the file.
|
||||
func (f EFile) GetOrigin() string {
|
||||
return f.origin
|
||||
}
|
||||
|
@ -5,15 +5,23 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// EHint represents a challenge hint.
|
||||
type EHint struct {
|
||||
Id int64 `json:"id"`
|
||||
// IdExercice is the identifier of the underlying challenge
|
||||
IdExercice int64 `json:"idExercice"`
|
||||
// Title is the hint name displayed to players
|
||||
Title string `json:"title"`
|
||||
// Content is the actual content of small text hints (mutually exclusive with File field)
|
||||
// When File is filled, Content contains the hexadecimal file's hash.
|
||||
Content string `json:"content"`
|
||||
// File is path, relative to FilesDir where the file hint is stored (mutually exclusive with Content field)
|
||||
File string `json:"file"`
|
||||
// Cost is the amount of points the player will loose if it unlocks the hint
|
||||
Cost int64 `json:"cost"`
|
||||
}
|
||||
|
||||
// treatHintContent reads Content to detect if this is a hint file in order to convert to such hint.
|
||||
func treatHintContent(h *EHint) {
|
||||
if strings.HasPrefix(h.Content, "$FILES") {
|
||||
fpath := strings.TrimPrefix(h.Content, "$FILES")
|
||||
@ -23,6 +31,7 @@ func treatHintContent(h *EHint) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetHint retrieves the hint with the given id.
|
||||
func GetHint(id int64) (EHint, error) {
|
||||
var h EHint
|
||||
if err := DBQueryRow("SELECT id_hint, id_exercice, title, content, cost FROM exercice_hints WHERE id_hint = ?", id).Scan(&h.Id, &h.IdExercice, &h.Title, &h.Content, &h.Cost); err != nil {
|
||||
@ -33,6 +42,7 @@ func GetHint(id int64) (EHint, error) {
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// GetHints returns a list of hints comming with the challenge.
|
||||
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
|
||||
@ -57,6 +67,7 @@ func (e Exercice) GetHints() ([]EHint, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// AddHint creates and fills a new struct EHint and registers it into the database.
|
||||
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
|
||||
@ -67,6 +78,7 @@ func (e Exercice) AddHint(title string, content string, cost int64) (EHint, erro
|
||||
}
|
||||
}
|
||||
|
||||
// Update applies modifications back to the database.
|
||||
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
|
||||
@ -77,6 +89,7 @@ func (h EHint) Update() (int64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the hint from the database.
|
||||
func (h EHint) Delete() (int64, error) {
|
||||
if res, err := DBExec("DELETE FROM exercice_hints WHERE id_hint = ?", h.Id); err != nil {
|
||||
return 0, err
|
||||
@ -87,6 +100,7 @@ func (h EHint) Delete() (int64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// WipeHints deletes (only in the database, not on disk) hints coming with the challenge.
|
||||
func (e Exercice) WipeHints() (int64, error) {
|
||||
if res, err := DBExec("DELETE FROM exercice_hints WHERE id_exercice = ?", e.Id); err != nil {
|
||||
return 0, err
|
||||
@ -97,6 +111,7 @@ func (e Exercice) WipeHints() (int64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetExercice returns the parent Exercice where this hint can be found.
|
||||
func (h EHint) GetExercice() (Exercice, error) {
|
||||
var eid int64
|
||||
if err := DBQueryRow("SELECT id_exercice FROM exercice_hints WHERE id_hint = ?", h.Id).Scan(&eid); err != nil {
|
||||
|
@ -6,13 +6,19 @@ import (
|
||||
"golang.org/x/crypto/blake2b"
|
||||
)
|
||||
|
||||
|
||||
// Key represents a flag's challenge, stored as hash.
|
||||
type Key struct {
|
||||
Id int64 `json:"id"`
|
||||
// IdExercice is the identifier of the underlying challenge
|
||||
IdExercice int64 `json:"idExercice"`
|
||||
// Label is the title of the flag as displayed to players
|
||||
Label string `json:"label"`
|
||||
// Checksum is the expected hashed flag
|
||||
Checksum []byte `json:"value"`
|
||||
}
|
||||
|
||||
// GetKeys returns a list of flags comming with the challenge.
|
||||
func (e Exercice) GetKeys() ([]Key, error) {
|
||||
if rows, err := DBQuery("SELECT id_key, id_exercice, type, cksum FROM exercice_keys WHERE id_exercice = ?", e.Id); err != nil {
|
||||
return nil, err
|
||||
@ -37,16 +43,19 @@ func (e Exercice) GetKeys() ([]Key, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// getHashedKey calculates the expected checksum for the given raw_value.
|
||||
func getHashedKey(raw_value string) [blake2b.Size]byte {
|
||||
hash := blake2b.Sum512([]byte(raw_value))
|
||||
return hash
|
||||
}
|
||||
|
||||
// AddRawKey creates and fills a new struct Key, from a non-hashed flag, and registers it into the database.
|
||||
func (e Exercice) AddRawKey(name string, raw_value string) (Key, error) {
|
||||
hash := getHashedKey(raw_value)
|
||||
return e.AddKey(name, hash[:])
|
||||
}
|
||||
|
||||
// AddKey creates and fills a new struct Key, from a hashed flag, and registers it into the database.
|
||||
func (e Exercice) AddKey(name string, checksum []byte) (Key, error) {
|
||||
if res, err := DBExec("INSERT INTO exercice_keys (id_exercice, type, cksum) VALUES (?, ?, ?)", e.Id, name, checksum); err != nil {
|
||||
return Key{}, err
|
||||
@ -57,6 +66,7 @@ func (e Exercice) AddKey(name string, checksum []byte) (Key, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Update applies modifications back to the database.
|
||||
func (k Key) Update() (int64, error) {
|
||||
if res, err := DBExec("UPDATE exercice_keys SET id_exercice = ?, type = ?, cksum = ? WHERE id_key = ?", k.IdExercice, k.Label, k.Checksum, k.Id); err != nil {
|
||||
return 0, err
|
||||
@ -67,6 +77,7 @@ func (k Key) Update() (int64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the flag from the database.
|
||||
func (k Key) Delete() (int64, error) {
|
||||
if res, err := DBExec("DELETE FROM exercice_keys WHERE id_key = ?", k.Id); err != nil {
|
||||
return 0, err
|
||||
@ -77,6 +88,7 @@ func (k Key) Delete() (int64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// WipeKeys deletes keys coming with the challenge.
|
||||
func (e Exercice) WipeKeys() (int64, error) {
|
||||
if res, err := DBExec("DELETE FROM exercice_keys WHERE id_exercice = ?", e.Id); err != nil {
|
||||
return 0, err
|
||||
@ -87,6 +99,7 @@ func (e Exercice) WipeKeys() (int64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the given val is the expected one for this flag.
|
||||
func (k Key) Check(val string) bool {
|
||||
hash := getHashedKey(val)
|
||||
if len(k.Checksum) != len(hash) {
|
||||
@ -102,6 +115,7 @@ func (k Key) Check(val string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// FoundBy registers in the database that the given Team solved the flag.
|
||||
func (k Key) FoundBy(t Team) {
|
||||
DBExec("INSERT INTO key_found (id_key, id_team, time) VALUES (?, ?, ?)", k.Id, t.Id, time.Now())
|
||||
}
|
||||
|
@ -4,19 +4,27 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// MCQ represents a flag's challenge, in the form of checkbox.
|
||||
type MCQ struct {
|
||||
Id int64 `json:"id"`
|
||||
// IdExercice is the identifier of the underlying challenge
|
||||
IdExercice int64 `json:"idExercice"`
|
||||
// Title is the label of the question
|
||||
Title string `json:"title"`
|
||||
// Entries stores the set of proposed answers
|
||||
Entries []MCQ_entry `json:"entries"`
|
||||
}
|
||||
|
||||
// MCQ_entry represents a proposed response for a given MCQ.
|
||||
type MCQ_entry struct {
|
||||
Id int64 `json:"id"`
|
||||
// Label is the text displayed to players as proposed answer
|
||||
Label string `json:"label"`
|
||||
// Response stores if expected checked state.
|
||||
Response bool `json:"response"`
|
||||
}
|
||||
|
||||
// GetMCQ returns the MCQs coming with the challenge.
|
||||
func (e Exercice) GetMCQ() ([]MCQ, error) {
|
||||
if rows, err := DBQuery("SELECT id_mcq, id_exercice, title FROM exercice_mcq WHERE id_exercice = ?", e.Id); err != nil {
|
||||
return nil, err
|
||||
@ -58,6 +66,7 @@ func (e Exercice) GetMCQ() ([]MCQ, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// AddMCQ creates and fills a new struct MCQ and registers it into the database.
|
||||
func (e Exercice) AddMCQ(title string) (MCQ, error) {
|
||||
if res, err := DBExec("INSERT INTO exercice_mcq (id_exercice, title) VALUES (?, ?)", e.Id, title); err != nil {
|
||||
return MCQ{}, err
|
||||
@ -68,6 +77,7 @@ func (e Exercice) AddMCQ(title string) (MCQ, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Update applies modifications back to the database.
|
||||
func (m MCQ) Update() (int64, error) {
|
||||
if res, err := DBExec("UPDATE exercice_mcq SET id_exercice = ?, title = ? WHERE id_mcq = ?", m.IdExercice, m.Title, m.Id); err != nil {
|
||||
return 0, err
|
||||
@ -78,6 +88,7 @@ func (m MCQ) Update() (int64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the MCQ from the database.
|
||||
func (m MCQ) Delete() (int64, error) {
|
||||
if res, err := DBExec("DELETE FROM exercice_mcq WHERE id_mcq = ?", m.Id); err != nil {
|
||||
return 0, err
|
||||
@ -88,6 +99,7 @@ func (m MCQ) Delete() (int64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// AddEntry creates and fills a new struct MCQ_entry and registers it into the database.
|
||||
func (m MCQ) AddEntry(label string, response bool) (MCQ_entry, error) {
|
||||
if res, err := DBExec("INSERT INTO mcq_entries (id_mcq, label, response) VALUES (?, ?, ?)", m.Id, label, response); err != nil {
|
||||
return MCQ_entry{}, err
|
||||
@ -98,6 +110,7 @@ func (m MCQ) AddEntry(label string, response bool) (MCQ_entry, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Update applies modifications back to the database.
|
||||
func (n MCQ_entry) Update() (int64, error) {
|
||||
if res, err := DBExec("UPDATE mcq_entries SET label = ?, response = ? WHERE id_mcq = ?", n.Label, n.Response, n.Id); err != nil {
|
||||
return 0, err
|
||||
@ -108,6 +121,7 @@ func (n MCQ_entry) Update() (int64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the MCQ entry from the database.
|
||||
func (n MCQ_entry) Delete() (int64, error) {
|
||||
if res, err := DBExec("DELETE FROM mcq_entries WHERE id_mcq_entry = ?", n.Id); err != nil {
|
||||
return 0, err
|
||||
@ -118,6 +132,7 @@ func (n MCQ_entry) Delete() (int64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// WipeMCQs deletes MCQs coming with the challenge.
|
||||
func (e Exercice) WipeMCQs() (int64, error) {
|
||||
if res, err := DBExec("DELETE FROM exercice_mcq, mcq_found, mcq_entries USING exercice_mcq NATURAL JOIN mcq_entries NATURAL JOIN mcq_found WHERE exercice_mcq.id_exercice = ?", e.Id); err != nil {
|
||||
return 0, err
|
||||
@ -128,6 +143,7 @@ func (e Exercice) WipeMCQs() (int64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the given vals are the expected ones to validate this flag.
|
||||
func (m MCQ) Check(vals map[int64]bool) int {
|
||||
diff := 0
|
||||
|
||||
@ -141,6 +157,7 @@ func (m MCQ) Check(vals map[int64]bool) int {
|
||||
return diff
|
||||
}
|
||||
|
||||
// FoundBy registers in the database that the given Team solved the MCQ.
|
||||
func (m MCQ) FoundBy(t Team) {
|
||||
DBExec("INSERT INTO mcq_found (id_mcq, id_team, time) VALUES (?, ?, ?)", m.Id, t.Id, time.Now())
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package fic
|
||||
|
||||
import ()
|
||||
|
||||
// Member represents a team member
|
||||
type Member struct {
|
||||
Id int64 `json:"id"`
|
||||
Firstname string `json:"firstname"`
|
||||
@ -10,15 +11,7 @@ type Member struct {
|
||||
Company string `json:"company"`
|
||||
}
|
||||
|
||||
func GetMember(cnt int) (Team, error) {
|
||||
var t Team
|
||||
if err := DBQueryRow("SELECT T.id_team, T.name, T.color FROM team_members M RIGHT OUTER JOIN teams T ON T.id_team = M.id_team LIMIT ?, 1", cnt - 1).Scan(&t.Id, &t.Name, &t.Color); err != nil {
|
||||
return t, err
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// GetMembers retrieves the members of the Team
|
||||
func (t Team) GetMembers() ([]Member, error) {
|
||||
if rows, err := DBQuery("SELECT id_member, firstname, lastname, nickname, company FROM team_members WHERE id_team = ?", t.Id); err != nil {
|
||||
return nil, err
|
||||
|
@ -2,6 +2,7 @@ package fic
|
||||
|
||||
import ()
|
||||
|
||||
// truncateTable performs an insecure wipe on the given tables.
|
||||
func truncateTable(tables ...string) (error) {
|
||||
if _, err := DBExec("SET FOREIGN_KEY_CHECKS = 0;"); err != nil {
|
||||
return err
|
||||
@ -17,14 +18,17 @@ func truncateTable(tables ...string) (error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResetGame resets all tables containing team attempts and solves.
|
||||
func ResetGame() (error) {
|
||||
return truncateTable("team_hints", "key_found", "mcq_found", "exercice_solved", "exercice_tries")
|
||||
}
|
||||
|
||||
// ResetExercices wipes out all challenges (both attempts and statements).
|
||||
func ResetExercices() (error) {
|
||||
return truncateTable("team_hints", "exercice_files", "key_found", "exercice_keys", "exercice_solved", "exercice_tries", "exercice_hints", "mcq_found", "mcq_entries", "exercice_mcq", "exercices", "themes")
|
||||
}
|
||||
|
||||
// ResetTeams wipes out all teams, incluings members and attempts.
|
||||
func ResetTeams() (error) {
|
||||
return truncateTable("team_hints", "key_found", "mcq_found", "exercice_solved", "exercice_tries", "team_members", "teams")
|
||||
}
|
||||
|
@ -6,7 +6,10 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// FirstBlood is the coefficient added to the challenge coefficient when a Team is the first to solve a challenge.
|
||||
var FirstBlood = 0.12
|
||||
|
||||
// SubmissionCostBase is the basis amount of point lost per submission
|
||||
var SubmissionCostBase = 0.5
|
||||
|
||||
func exoptsQuery(whereExo string) string {
|
||||
@ -19,6 +22,7 @@ func rankQuery(whereTeam string) string {
|
||||
|
||||
// Points
|
||||
|
||||
// EstimateGain calculates the amount of point the Team has (or could have, if not already solved) won.
|
||||
func (e Exercice) EstimateGain(t Team, solved bool) (float64, error) {
|
||||
var pts float64
|
||||
err := DBQueryRow("SELECT SUM(A.points * A.coeff) AS score FROM (" + exoptsQuery("WHERE S.id_team = ? AND S.id_exercice = ?") + ") A GROUP BY id_team", t.Id, e.Id, t.Id, e.Id).Scan(&pts)
|
||||
@ -33,6 +37,7 @@ func (e Exercice) EstimateGain(t Team, solved bool) (float64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetPoints returns the score for the Team.
|
||||
func (t Team) GetPoints() (float64, error) {
|
||||
var tid *int64
|
||||
var nb *float64
|
||||
@ -45,6 +50,7 @@ func (t Team) GetPoints() (float64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetRank returns a map which associates team ID their rank.
|
||||
func GetRank() (map[int64]int, error) {
|
||||
if rows, err := DBQuery(rankQuery("")); err != nil {
|
||||
return nil, err
|
||||
@ -72,11 +78,11 @@ func GetRank() (map[int64]int, error) {
|
||||
}
|
||||
|
||||
|
||||
// Tries
|
||||
// Attempts
|
||||
|
||||
func GetTries(t *Team, e *Exercice) ([]time.Time, error) {
|
||||
// GetTries retrieves all attempts made by the matching Team or challenge (both can be nil to not filter).
|
||||
func GetTries(t *Team, e *Exercice) (times []time.Time, err error) {
|
||||
var rows *sql.Rows
|
||||
var err error
|
||||
|
||||
if t == nil {
|
||||
if e == nil {
|
||||
@ -93,26 +99,23 @@ func GetTries(t *Team, e *Exercice) ([]time.Time, error) {
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return
|
||||
} else {
|
||||
defer rows.Close()
|
||||
|
||||
times := make([]time.Time, 0)
|
||||
for rows.Next() {
|
||||
var tm time.Time
|
||||
if err := rows.Scan(&tm); err != nil {
|
||||
return nil, err
|
||||
if err = rows.Scan(&tm); err != nil {
|
||||
return
|
||||
}
|
||||
times = append(times, tm)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return times, nil
|
||||
err = rows.Err()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// GetTryRank generates a special rank based on number of attempts
|
||||
func GetTryRank() ([]int64, error) {
|
||||
if rows, err := DBQuery("SELECT id_team, COUNT(*) AS score FROM exercice_tries GROUP BY id_team HAVING score > 0 ORDER BY score DESC"); err != nil {
|
||||
return nil, err
|
||||
|
@ -4,16 +4,17 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// UnlockedChallenges disables dependancy requirement between challenges.
|
||||
var UnlockedChallenges bool
|
||||
|
||||
// Team represents a group of players, come to solve our challenges.
|
||||
type Team struct {
|
||||
Id int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Color uint32 `json:"color"`
|
||||
}
|
||||
|
||||
// Access functions
|
||||
|
||||
// GetTeams returns a list of registered Team from the database.
|
||||
func GetTeams() ([]Team, error) {
|
||||
if rows, err := DBQuery("SELECT id_team, name, color FROM teams"); err != nil {
|
||||
return nil, err
|
||||
@ -36,6 +37,7 @@ func GetTeams() ([]Team, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetTeam retrieves a Team from its identifier.
|
||||
func GetTeam(id int64) (Team, error) {
|
||||
var t Team
|
||||
if err := DBQueryRow("SELECT id_team, name, color FROM teams WHERE id_team = ?", id).Scan(&t.Id, &t.Name, &t.Color); err != nil {
|
||||
@ -45,6 +47,7 @@ func GetTeam(id int64) (Team, error) {
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// GetTeamBySerial retrieves a Team from one of its associated certificates.
|
||||
func GetTeamBySerial(serial int64) (Team, error) {
|
||||
var t Team
|
||||
if err := DBQueryRow("SELECT T.id_team, T.name, T.color FROM certificates C INNER JOIN teams T ON T.id_team = C.id_team WHERE id_cert = ?", serial).Scan(&t.Id, &t.Name, &t.Color); err != nil {
|
||||
@ -54,9 +57,7 @@ func GetTeamBySerial(serial int64) (Team, error) {
|
||||
return t, nil
|
||||
}
|
||||
|
||||
|
||||
// CRUD method
|
||||
|
||||
// CreateTeam creates and fills a new struct Team and registers it into the database.
|
||||
func CreateTeam(name string, color uint32) (Team, error) {
|
||||
if res, err := DBExec("INSERT INTO teams (name, color) VALUES (?, ?)", name, color); err != nil {
|
||||
return Team{}, err
|
||||
@ -67,6 +68,7 @@ func CreateTeam(name string, color uint32) (Team, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Update applies modifications back to the database.
|
||||
func (t Team) Update() (int64, error) {
|
||||
if res, err := DBExec("UPDATE teams SET name = ?, color = ? WHERE id_team = ?", t.Name, t.Color, t.Id); err != nil {
|
||||
return 0, err
|
||||
@ -77,6 +79,7 @@ func (t Team) Update() (int64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the challenge from the database
|
||||
func (t Team) Delete() (int64, error) {
|
||||
if _, err := DBExec("DELETE FROM team_members WHERE id_team = ?", t.Id); err != nil {
|
||||
return 0, err
|
||||
@ -89,20 +92,19 @@ func (t Team) Delete() (int64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Exercice related functions
|
||||
|
||||
// HasAccess checks if the Team has access to the given challenge.
|
||||
func (t Team) HasAccess(e Exercice) bool {
|
||||
if e.Depend == nil || UnlockedChallenges {
|
||||
return true
|
||||
} else {
|
||||
ed := Exercice{}
|
||||
ed.Id = *e.Depend
|
||||
s, _, _ := t.HasSolved(ed)
|
||||
s, _ := t.HasSolved(ed)
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
// NbTry retrieves the number of attempts made by the Team to the given challenge.
|
||||
func NbTry(t *Team, e Exercice) int {
|
||||
var cnt *int
|
||||
|
||||
@ -119,17 +121,20 @@ func NbTry(t *Team, e Exercice) int {
|
||||
}
|
||||
}
|
||||
|
||||
// HasHint checks if the Team has revealed the given Hint.
|
||||
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
|
||||
}
|
||||
|
||||
// OpenHint registers to the database that the Team has now revealed.
|
||||
func (t Team) OpenHint(h EHint) (error) {
|
||||
_, err := DBExec("INSERT INTO team_hints (id_team, id_hint, time) VALUES (?, ?, ?)", t.Id, h.Id, time.Now())
|
||||
return err
|
||||
}
|
||||
|
||||
// CountTries gets the amount of attempts made by the Team and retrieves the time of the latest attempt.
|
||||
func (t Team) CountTries(e Exercice) (int64, time.Time) {
|
||||
var nb *int64
|
||||
var tm *time.Time
|
||||
@ -142,6 +147,8 @@ func (t Team) CountTries(e Exercice) (int64, time.Time) {
|
||||
}
|
||||
}
|
||||
|
||||
// LastTryDist retrieves the distance to the correct answers, for the given challenge.
|
||||
// The distance is the number of bad responses given in differents MCQs.
|
||||
func (t Team) LastTryDist(e Exercice) int64 {
|
||||
var nb *int64
|
||||
if DBQueryRow("SELECT nbdiff FROM exercice_tries WHERE id_team = ? AND id_exercice = ? ORDER BY time DESC LIMIT 1", t.Id, e.Id).Scan(&nb); nb == nil {
|
||||
@ -151,36 +158,45 @@ func (t Team) LastTryDist(e Exercice) int64 {
|
||||
}
|
||||
}
|
||||
|
||||
func (t Team) HasSolved(e Exercice) (bool, time.Time, int64) {
|
||||
var nb *int64
|
||||
// HasSolved checks if the Team already has validated the given challenge.
|
||||
// Note that the function also returns the effective validation timestamp.
|
||||
func (t Team) HasSolved(e Exercice) (bool, time.Time) {
|
||||
var tm *time.Time
|
||||
if DBQueryRow("SELECT MIN(time) FROM exercice_solved WHERE id_team = ? AND id_exercice = ?", t.Id, e.Id).Scan(&tm); tm == nil {
|
||||
return false, time.Unix(0, 0), 0
|
||||
} else if DBQueryRow("SELECT COUNT(id_exercice) FROM exercice_solved WHERE id_exercice = ? AND time < ?", e.Id, tm).Scan(&nb); nb == nil {
|
||||
return true, *tm, 0
|
||||
return false, time.Unix(0, 0)
|
||||
} else {
|
||||
return true, *tm, *nb + 1
|
||||
return true, *tm
|
||||
}
|
||||
}
|
||||
|
||||
func IsSolved(e Exercice) (int, time.Time) {
|
||||
var nb *int
|
||||
var tm *time.Time
|
||||
if DBQueryRow("SELECT COUNT(id_exercice), MIN(time) FROM exercice_solved WHERE id_exercice = ?", e.Id).Scan(&nb, &tm); nb == nil || tm == nil {
|
||||
return 0, time.Time{}
|
||||
// GetSolvedRank returns the number of teams that solved the challenge before the Team.
|
||||
func (t Team) GetSolvedRank(e Exercice) (nb int64, err error) {
|
||||
if rows, errr := DBQuery("SELECT id_team FROM exercice_solved WHERE id_exercice = ? ORDER BY time ASC", e.Id); err != nil {
|
||||
return nb, errr
|
||||
} else {
|
||||
return *nb, *tm
|
||||
for rows.Next() {
|
||||
var tid int64
|
||||
if err = rows.Scan(&tid); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
nb += 1
|
||||
if t.Id == tid {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (t Team) HasPartiallySolved(k Key) (*time.Time) {
|
||||
var tm *time.Time
|
||||
// HasPartiallySolved checks if the Team already has unlocked the given key and returns the validation's timestamp.
|
||||
func (t Team) HasPartiallySolved(k Key) (tm *time.Time) {
|
||||
DBQueryRow("SELECT MIN(time) FROM key_found WHERE id_team = ? AND id_key = ?", t.Id, k.Id).Scan(&tm)
|
||||
return tm
|
||||
return
|
||||
}
|
||||
|
||||
func (t Team) HasPartiallyRespond(m MCQ) (*time.Time) {
|
||||
var tm *time.Time
|
||||
// HasPartiallyRespond checks if the Team already has unlocked the given MCQ and returns the validation's timestamp.
|
||||
func (t Team) HasPartiallyRespond(m MCQ) (tm *time.Time) {
|
||||
DBQueryRow("SELECT MIN(time) FROM mcq_found WHERE id_team = ? AND id_mcq = ?", t.Id, m.Id).Scan(&tm)
|
||||
return tm
|
||||
return
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// exportedTeam is a structure representing a Team, as exposed to players.
|
||||
type exportedTeam struct {
|
||||
Name string `json:"name"`
|
||||
Color string `json:"color"`
|
||||
@ -11,13 +12,17 @@ type exportedTeam struct {
|
||||
Points float64 `json:"score"`
|
||||
}
|
||||
|
||||
func ExportTeams() (interface{}, error) {
|
||||
if teams, err := GetTeams(); err != nil {
|
||||
return nil, err
|
||||
} else if rank, err := GetRank(); err != nil {
|
||||
// Exportedteam creates the structure to respond as teams.json.
|
||||
func ExportTeams() (ret map[string]exportedTeam, err error) {
|
||||
var teams []Team
|
||||
var rank map[int64]int
|
||||
|
||||
if teams, err = GetTeams(); err != nil {
|
||||
return
|
||||
} else if rank, err = GetRank(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
ret := map[string]exportedTeam{}
|
||||
ret = map[string]exportedTeam{}
|
||||
for _, team := range teams {
|
||||
points, _ := team.GetPoints()
|
||||
ret[fmt.Sprintf("%d", team.Id)] = exportedTeam{
|
||||
@ -28,6 +33,6 @@ func ExportTeams() (interface{}, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// GetHistory aggregates all sources of events or actions for a Team
|
||||
func (t Team) GetHistory() ([]map[string]interface{}, error) {
|
||||
hist := make([]map[string]interface{}, 0)
|
||||
|
||||
@ -45,6 +46,7 @@ func (t Team) GetHistory() ([]map[string]interface{}, error) {
|
||||
return hist, 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 {
|
||||
if res, err := DBExec("DELETE FROM exercice_tries WHERE id_team = ? AND time = ? AND id_exercice = ?", t.Id, h, *primary); err != nil {
|
||||
|
@ -88,7 +88,8 @@ func MyJSONTeam(t *Team, started bool) (interface{}, error) {
|
||||
exercice.Gain = int(float64(e.Gain) * e.Coefficient)
|
||||
} else {
|
||||
var solved bool
|
||||
solved, exercice.SolvedTime, exercice.SolvedRank = t.HasSolved(e)
|
||||
solved, exercice.SolvedTime = t.HasSolved(e)
|
||||
exercice.SolvedRank, _ = t.GetSolvedRank(e)
|
||||
|
||||
if solved {
|
||||
exercice.Tries, _ = t.CountTries(e)
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// statLine is a line of statistics for the file stats.json exposed to players.
|
||||
type statLine struct {
|
||||
Tip string `json:"tip"`
|
||||
Total int `json:"total"`
|
||||
@ -12,11 +13,13 @@ type statLine struct {
|
||||
Tries int `json:"tries"`
|
||||
}
|
||||
|
||||
// teamStats represents the structure of stats.json.
|
||||
type teamStats struct {
|
||||
Levels []statLine `json:"levels"`
|
||||
Themes map[int64]statLine `json:"themes"`
|
||||
}
|
||||
|
||||
// GetLevel
|
||||
func (s *teamStats) GetLevel(level int) *statLine {
|
||||
level -= 1
|
||||
|
||||
@ -33,10 +36,12 @@ func (s *teamStats) GetLevel(level int) *statLine {
|
||||
return &s.Levels[level]
|
||||
}
|
||||
|
||||
// GetStats generates statistics for the Team.
|
||||
func (t Team) GetStats() (interface{}, error) {
|
||||
return GetTeamsStats(&t)
|
||||
}
|
||||
|
||||
// GetTeamsStats returns statistics limited to the given Team.
|
||||
func GetTeamsStats(t *Team) (interface{}, error) {
|
||||
stat := teamStats{
|
||||
[]statLine{},
|
||||
@ -66,12 +71,12 @@ func GetTeamsStats(t *Team) (interface{}, error) {
|
||||
sLvl.Total += 1
|
||||
|
||||
if t != nil {
|
||||
if b, _, _ := t.HasSolved(exercice); b {
|
||||
if b, _ := t.HasSolved(exercice); b {
|
||||
solved += 1
|
||||
sLvl.Solved += 1
|
||||
}
|
||||
} else {
|
||||
if n, _ := IsSolved(exercice); n > 0 {
|
||||
if n, _ := exercice.IsSolved(); n > 0 {
|
||||
solved += 1
|
||||
sLvl.Solved += 1
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package fic
|
||||
|
||||
import ()
|
||||
|
||||
// Theme represents a group of challenges, to display to players
|
||||
type Theme struct {
|
||||
Id int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
@ -10,6 +11,7 @@ type Theme struct {
|
||||
Intro string `json:"intro,omitempty"`
|
||||
}
|
||||
|
||||
// GetThemes returns a list of registered Themes from the database.
|
||||
func GetThemes() ([]Theme, error) {
|
||||
if rows, err := DBQuery("SELECT id_theme, name, url_id, authors, intro FROM themes"); err != nil {
|
||||
return nil, err
|
||||
@ -32,6 +34,7 @@ func GetThemes() ([]Theme, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetTheme retrieves a Theme from its identifier.
|
||||
func GetTheme(id int) (Theme, error) {
|
||||
var t Theme
|
||||
if err := DBQueryRow("SELECT id_theme, name, url_id, authors, intro FROM themes WHERE id_theme=?", id).Scan(&t.Id, &t.Name, &t.URLId, &t.Authors, &t.Intro); err != nil {
|
||||
@ -41,6 +44,7 @@ func GetTheme(id int) (Theme, error) {
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// GetThemeByName retrieves a Theme from its title
|
||||
func GetThemeByName(name string) (Theme, error) {
|
||||
var t Theme
|
||||
if err := DBQueryRow("SELECT id_theme, name, url_id, authors, intro FROM themes WHERE name=?", name).Scan(&t.Id, &t.Name, &t.URLId, &t.Authors, &t.Intro); err != nil {
|
||||
@ -50,6 +54,7 @@ func GetThemeByName(name string) (Theme, error) {
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// CreateTheme creates and fills a new struct Theme and registers it into the database.
|
||||
func CreateTheme(name string, url_id string, authors string, intro string) (Theme, error) {
|
||||
if res, err := DBExec("INSERT INTO themes (name, url_id, authors, intro) VALUES (?, ?, ?, ?)", name, url_id, authors, intro); err != nil {
|
||||
return Theme{}, err
|
||||
@ -60,6 +65,8 @@ func CreateTheme(name string, url_id string, authors string, intro string) (Them
|
||||
}
|
||||
}
|
||||
|
||||
// FixURLId generates a valid URLid from the Theme Title.
|
||||
// It returns true if something has been generated.
|
||||
func (t *Theme) FixURLId() bool {
|
||||
if t.URLId == "" {
|
||||
t.URLId = ToURLid(t.Name)
|
||||
@ -68,6 +75,7 @@ func (t *Theme) FixURLId() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Update applies modifications back to the database.
|
||||
func (t Theme) Update() (int64, error) {
|
||||
if res, err := DBExec("UPDATE themes SET name = ?, url_id = ?, authors = ?, intro = ? WHERE id_theme = ?", t.Name, t.URLId, t.Authors, t.Intro, t.Id); err != nil {
|
||||
return 0, err
|
||||
@ -78,6 +86,7 @@ func (t Theme) Update() (int64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the theme from the database.
|
||||
func (t Theme) Delete() (int64, error) {
|
||||
if res, err := DBExec("DELETE FROM themes WHERE id_theme = ?", t.Id); err != nil {
|
||||
return 0, err
|
||||
|
@ -4,7 +4,8 @@ import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type ExportedExercice struct {
|
||||
// exportedExercice is a structure representing a challenge, as exposed to players.
|
||||
type exportedExercice struct {
|
||||
Title string `json:"title"`
|
||||
URLId string `json:"urlid"`
|
||||
Gain int64 `json:"gain"`
|
||||
@ -13,14 +14,16 @@ type ExportedExercice struct {
|
||||
Tried int64 `json:"tried"`
|
||||
}
|
||||
|
||||
// exportedTheme is a structure representing a Theme, as exposed to players.
|
||||
type exportedTheme struct {
|
||||
Name string `json:"name"`
|
||||
URLId string `json:"urlid"`
|
||||
Authors string `json:"authors"`
|
||||
Intro string `json:"intro"`
|
||||
Exercices map[string]ExportedExercice `json:"exercices"`
|
||||
Exercices map[string]exportedExercice `json:"exercices"`
|
||||
}
|
||||
|
||||
// Exportedthemes exports themes from the database, to be displayed to players.
|
||||
func ExportThemes() (interface{}, error) {
|
||||
if themes, err := GetThemes(); err != nil {
|
||||
return nil, err
|
||||
@ -30,9 +33,9 @@ func ExportThemes() (interface{}, error) {
|
||||
if exercices, err := theme.GetExercices(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
exos := map[string]ExportedExercice{}
|
||||
exos := map[string]exportedExercice{}
|
||||
for _, exercice := range exercices {
|
||||
exos[fmt.Sprintf("%d", exercice.Id)] = ExportedExercice{
|
||||
exos[fmt.Sprintf("%d", exercice.Id)] = exportedExercice{
|
||||
exercice.Title,
|
||||
exercice.URLId,
|
||||
exercice.Gain,
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Claim represents an issue, a bug or a ToDo item.
|
||||
type Claim struct {
|
||||
Id int64 `json:"id"`
|
||||
Subject string `json:"subject"`
|
||||
@ -15,11 +16,13 @@ type Claim struct {
|
||||
Priority string `json:"priority"`
|
||||
}
|
||||
|
||||
// GetClaim retrieves the claim with the given identifier.
|
||||
func GetClaim(id int) (c Claim, err error) {
|
||||
err = DBQueryRow("SELECT id_claim, subject, id_team, id_assignee, creation, state, priority FROM claims WHERE id_claim = ?", id).Scan(&c.Id, &c.Subject, &c.IdTeam, &c.IdAssignee, &c.Creation, &c.State, &c.Priority)
|
||||
return
|
||||
}
|
||||
|
||||
// GetClaims returns a list of all Claim registered in the database.
|
||||
func GetClaims() (res []Claim, err error) {
|
||||
var rows *sql.Rows
|
||||
if rows, err = DBQuery("SELECT id_claim, subject, id_team, id_assignee, creation, state, priority FROM claims"); err != nil {
|
||||
@ -39,6 +42,7 @@ func GetClaims() (res []Claim, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// GetClaims returns a list of all Claim registered for the Team.
|
||||
func (t Team) GetClaims() (res []Claim, err error) {
|
||||
var rows *sql.Rows
|
||||
if rows, err = DBQuery("SELECT id_claim, subject, IdTeam, id_assignee, creation, state, priority FROM claims WHERE IdTeam = ?", t.Id); err != nil {
|
||||
@ -58,6 +62,7 @@ func (t Team) GetClaims() (res []Claim, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// NewClaim creates and fills a new struct Claim and registers it into the database.
|
||||
func NewClaim(subject string, team *Team, assignee *ClaimAssignee, priority string) (Claim, error) {
|
||||
var tid *int64
|
||||
if team == nil {
|
||||
@ -82,6 +87,7 @@ func NewClaim(subject string, team *Team, assignee *ClaimAssignee, priority stri
|
||||
}
|
||||
}
|
||||
|
||||
// GetTeam returns the Team linked to the issue, if any.
|
||||
func (c Claim) GetTeam() (*Team, error) {
|
||||
if c.IdTeam == nil {
|
||||
return nil, nil
|
||||
@ -92,10 +98,12 @@ func (c Claim) GetTeam() (*Team, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// SetTeam defines the Team that is linked to this issue.
|
||||
func (c Claim) SetTeam(t Team) {
|
||||
c.IdTeam = &t.Id
|
||||
}
|
||||
|
||||
// Update applies modifications back to the database.
|
||||
func (c Claim) Update() (int64, error) {
|
||||
if res, err := DBExec("UPDATE claims SET subject = ?, id_team = ?, id_assignee = ?, creation = ?, state = ?, priority = ? WHERE id_claim = ?", c.Subject, c.IdTeam, c.IdAssignee, c.Creation, c.State, c.Priority, c.Id); err != nil {
|
||||
return 0, err
|
||||
@ -106,6 +114,7 @@ func (c Claim) Update() (int64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the issue from the database.
|
||||
func (c Claim) Delete() (int64, error) {
|
||||
if res, err := DBExec("DELETE FROM claims WHERE id_claim = ?", c.Id); err != nil {
|
||||
return 0, err
|
||||
@ -116,6 +125,7 @@ func (c Claim) Delete() (int64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// ClearClaims removes all issues from database.
|
||||
func ClearClaims() (int64, error) {
|
||||
if res, err := DBExec("DELETE FROM claims"); err != nil {
|
||||
return 0, err
|
||||
@ -126,14 +136,18 @@ func ClearClaims() (int64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ClaimDescription represents some text describing an issue.
|
||||
type ClaimDescription struct {
|
||||
Id int64 `json:"id"`
|
||||
// IdAssignee stores the user who handle the claim (or 0 if nobody handles it).
|
||||
IdAssignee int64 `json:"id_assignee"`
|
||||
// Content is the raw description.
|
||||
Content string `json:"content"`
|
||||
// Date is the timestamp when the description was written.
|
||||
Date time.Time `json:"date"`
|
||||
}
|
||||
|
||||
// GetDescriptions returns a list of all descriptions stored in the database for the Claim.
|
||||
func (c Claim) GetDescriptions() (res []ClaimDescription, err error) {
|
||||
var rows *sql.Rows
|
||||
if rows, err = DBQuery("SELECT id_description, id_assignee, content, date FROM claim_descriptions WHERE id_claim = ?", c.Id); err != nil {
|
||||
@ -153,6 +167,7 @@ func (c Claim) GetDescriptions() (res []ClaimDescription, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// AddDescription append in the database a new description; then returns the corresponding structure.
|
||||
func (c Claim) AddDescription(content string, assignee ClaimAssignee) (ClaimDescription, error) {
|
||||
if res, err := DBExec("INSERT INTO claim_descriptions (id_claim, id_assignee, content, date) VALUES (?, ?, ?, ?)", c.Id, assignee.Id, content, time.Now()); err != nil {
|
||||
return ClaimDescription{}, err
|
||||
@ -163,6 +178,7 @@ func (c Claim) AddDescription(content string, assignee ClaimAssignee) (ClaimDesc
|
||||
}
|
||||
}
|
||||
|
||||
// Update applies modifications back to the database
|
||||
func (d ClaimDescription) Update() (int64, error) {
|
||||
if res, err := DBExec("UPDATE claim_descriptions SET id_assignee = ?, content = ?, date = ? WHERE id_description = ?", d.IdAssignee, d.Content, d.Date, d.Id); err != nil {
|
||||
return 0, err
|
||||
@ -173,6 +189,7 @@ func (d ClaimDescription) Update() (int64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the description in the database.
|
||||
func (d ClaimDescription) Delete() (int64, error) {
|
||||
if res, err := DBExec("DELETE FROM claim_descriptions WHERE id_description = ?", d.Id); err != nil {
|
||||
return 0, err
|
||||
@ -183,17 +200,19 @@ func (d ClaimDescription) Delete() (int64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ClaimAssignee represents a user that can handle claims.
|
||||
type ClaimAssignee struct {
|
||||
Id int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// GetAssignee retrieves an assignee from its identifier.
|
||||
func GetAssignee(id int64) (a ClaimAssignee, err error) {
|
||||
err = DBQueryRow("SELECT id_assignee, name FROM claim_assignees WHERE id_assignee = ?", id).Scan(&a.Id, &a.Name)
|
||||
return
|
||||
}
|
||||
|
||||
// GetAssignees returns a list of all assignees found in the database.
|
||||
func GetAssignees() (res []ClaimAssignee, err error) {
|
||||
var rows *sql.Rows
|
||||
if rows, err = DBQuery("SELECT id_assignee, name FROM claim_assignees"); err != nil {
|
||||
@ -213,6 +232,7 @@ func GetAssignees() (res []ClaimAssignee, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// NewClaimAssignee creates and fills a new struct ClaimAssignee and registers it into the database.
|
||||
func NewClaimAssignee(name string) (ClaimAssignee, error) {
|
||||
if res, err := DBExec("INSERT INTO claim_assignees (name) VALUES (?)", name); err != nil {
|
||||
return ClaimAssignee{}, err
|
||||
@ -223,6 +243,7 @@ func NewClaimAssignee(name string) (ClaimAssignee, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Update applies modifications back to the database
|
||||
func (a ClaimAssignee) Update() (int64, error) {
|
||||
if res, err := DBExec("UPDATE claim_assignees SET name = ? WHERE id_assignee = ?", a.Name, a.Id); err != nil {
|
||||
return 0, err
|
||||
@ -233,6 +254,7 @@ func (a ClaimAssignee) Update() (int64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the assignee in the database.
|
||||
func (a ClaimAssignee) Delete() (int64, error) {
|
||||
if res, err := DBExec("DELETE FROM claim_assignees WHERE id_assignee = ?", a.Id); err != nil {
|
||||
return 0, err
|
||||
@ -243,6 +265,7 @@ func (a ClaimAssignee) Delete() (int64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// ClearAssignees removes all assignees from database.
|
||||
func ClearAssignees() (int64, error) {
|
||||
if res, err := DBExec("DELETE FROM claim_assignees"); err != nil {
|
||||
return 0, err
|
||||
@ -253,6 +276,7 @@ func ClearAssignees() (int64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetAssignee returns the assignee assigned to the claim.
|
||||
func (c Claim) GetAssignee() (*ClaimAssignee, error) {
|
||||
if c.IdAssignee == nil {
|
||||
return nil, nil
|
||||
@ -263,6 +287,7 @@ func (c Claim) GetAssignee() (*ClaimAssignee, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// SetAssignee defines the assignee that'll handle the claim.
|
||||
func (c Claim) SetAssignee(a ClaimAssignee) {
|
||||
c.IdAssignee = &a.Id
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ToURLid converts the given string to a valid URLid.
|
||||
func ToURLid(str string) string {
|
||||
re := regexp.MustCompile("[^a-zA-Z0-9]+")
|
||||
return strings.TrimSuffix(re.ReplaceAllLiteralString(str, "-"), "-")
|
||||
|
@ -1,3 +1,5 @@
|
||||
// Package settings is shared across multiple services for easy parsing and
|
||||
// retrieval of the challenge settings.
|
||||
package settings
|
||||
|
||||
import (
|
||||
@ -10,33 +12,50 @@ import (
|
||||
"gopkg.in/fsnotify.v1"
|
||||
)
|
||||
|
||||
// SettingsFile is the expected name of the file containing the settings.
|
||||
const SettingsFile = "settings.json"
|
||||
|
||||
// SettingsDir is the relative location where the SettingsFile lies.
|
||||
var SettingsDir string = "./SETTINGS"
|
||||
|
||||
// FICSettings represents the settings panel.
|
||||
type FICSettings struct {
|
||||
// Title is the displayed name of the challenge.
|
||||
Title string `json:"title"`
|
||||
// Authors is the group name of people making the challenge.
|
||||
Authors string `json:"authors"`
|
||||
|
||||
// Start is the departure time (expected or effective).
|
||||
Start time.Time `json:"start"`
|
||||
// End is the expected end time.
|
||||
End time.Time `json:"end"`
|
||||
// Generation is a value used to regenerate static files.
|
||||
Generation time.Time `json:"generation"`
|
||||
|
||||
// FirstBlood is the coefficient applied to each first team who solve a challenge.
|
||||
FirstBlood float64 `json:"firstBlood"`
|
||||
// SubmissionCostBase is a complex number representing the cost of each attempts.
|
||||
SubmissionCostBase float64 `json:"submissionCostBase"`
|
||||
|
||||
// AllowRegistration permits unregistered Team to register themselves.
|
||||
AllowRegistration bool `json:"allowRegistration"`
|
||||
// DenyNameChange disallow Team to change their name.
|
||||
DenyNameChange bool `json:"denyNameChange"`
|
||||
// EnableResolutionRoute activates the route displaying resolution movies.
|
||||
EnableResolutionRoute bool `json:"enableResolutionRoute"`
|
||||
// PartialValidation validates each correct given answers, don't expect Team to give all correct answer in a try.
|
||||
PartialValidation bool `json:"partialValidation"`
|
||||
// EnableExerciceDepend don't show (or permit to solve) to team challenges they are not unlocked through dependancies.
|
||||
EnableExerciceDepend bool `json:"enableExerciceDepend"`
|
||||
}
|
||||
|
||||
// ExistsSettings checks if the settings file can by found at the given path.
|
||||
func ExistsSettings(settingsPath string) bool {
|
||||
_, err := os.Stat(settingsPath)
|
||||
return !os.IsNotExist(err)
|
||||
}
|
||||
|
||||
// ReadSettings parses the file at the given location.
|
||||
func ReadSettings(path string) (FICSettings, error) {
|
||||
var s FICSettings
|
||||
if fd, err := os.Open(path); err != nil {
|
||||
@ -53,6 +72,7 @@ func ReadSettings(path string) (FICSettings, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// SaveSettings saves settings at the given location.
|
||||
func SaveSettings(path string, s FICSettings) error {
|
||||
if fd, err := os.Create(path); err != nil {
|
||||
return err
|
||||
@ -68,6 +88,7 @@ func SaveSettings(path string, s FICSettings) error {
|
||||
}
|
||||
}
|
||||
|
||||
// ForceRegeneration makes a small change to the settings structure in order to force the regeneration of all static files.
|
||||
func ForceRegeneration() error {
|
||||
location := path.Join(SettingsDir, SettingsFile)
|
||||
if settings, err := ReadSettings(location); err != nil {
|
||||
@ -78,6 +99,10 @@ func ForceRegeneration() error {
|
||||
}
|
||||
}
|
||||
|
||||
// LoadAndWatchSettings is the function you are looking for!
|
||||
// Giving the location and a callback, this function will first call your reload function
|
||||
// before returning (if the file can be parsed); then it starts watching modifications made to
|
||||
// this file. Your callback is then run each time the file is modified.
|
||||
func LoadAndWatchSettings(settingsPath string, reload func (FICSettings)) {
|
||||
// First load of configuration if it exists
|
||||
if _, err := os.Stat(settingsPath); !os.IsNotExist(err) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user