diff --git a/Dockerfile-remote-scores-sync-zqds b/Dockerfile-remote-scores-sync-zqds index d1847391..d41fd37c 100644 --- a/Dockerfile-remote-scores-sync-zqds +++ b/Dockerfile-remote-scores-sync-zqds @@ -6,6 +6,7 @@ WORKDIR /go/src/srs.epita.fr/fic-server/ COPY go.mod go.sum ./ COPY libfic ./libfic/ +COPY settings ./settings/ COPY remote/scores-sync-zqds ./remote/scores-sync-zqds/ RUN go get -d -v ./remote/scores-sync-zqds && \ diff --git a/admin/api/settings.go b/admin/api/settings.go index 2f949449..9ac78cff 100644 --- a/admin/api/settings.go +++ b/admin/api/settings.go @@ -16,6 +16,9 @@ import ( var IsProductionEnv = false func init() { + router.GET("/api/challenge.json", apiHandler(getChallengeInfo)) + router.PUT("/api/challenge.json", apiHandler(saveChallengeInfo)) + router.GET("/api/settings-ro.json", apiHandler(getROSettings)) router.GET("/api/settings.json", apiHandler(getSettings)) router.PUT("/api/settings.json", apiHandler(saveSettings)) @@ -44,6 +47,23 @@ func getROSettings(_ httprouter.Params, body []byte) (interface{}, error) { }, nil } +func getChallengeInfo(_ httprouter.Params, body []byte) (interface{}, error) { + return settings.ReadChallengeInfo(path.Join(settings.SettingsDir, settings.ChallengeFile)) +} + +func saveChallengeInfo(_ httprouter.Params, body []byte) (interface{}, error) { + var info *settings.ChallengeInfo + if err := json.Unmarshal(body, &info); err != nil { + return nil, err + } + + if err := settings.SaveChallengeInfo(path.Join(settings.SettingsDir, settings.ChallengeFile), info); err != nil { + return nil, err + } else { + return info, err + } +} + func getSettings(_ httprouter.Params, body []byte) (interface{}, error) { if s, err := settings.ReadSettings(path.Join(settings.SettingsDir, settings.SettingsFile)); err != nil { return nil, err @@ -83,9 +103,7 @@ func ApplySettings(config *settings.Settings) { } func ResetSettings() error { - return settings.SaveSettings(path.Join(settings.SettingsDir, settings.SettingsFile), &settings.FICSettings{ - Title: "Challenge FIC", - Authors: "Laboratoire SRS, ÉPITA", + return settings.SaveSettings(path.Join(settings.SettingsDir, settings.SettingsFile), &settings.Settings{ WorkInProgress: IsProductionEnv, FirstBlood: fic.FirstBlood, SubmissionCostBase: fic.SubmissionCostBase, diff --git a/admin/api/theme.go b/admin/api/theme.go index e225a040..42af449b 100644 --- a/admin/api/theme.go +++ b/admin/api/theme.go @@ -22,8 +22,10 @@ func init() { router.GET("/api/session-forensic.yaml", apiHandler(func(_ httprouter.Params, _ []byte) (interface{}, error) { if s, err := settings.ReadSettings(path.Join(settings.SettingsDir, settings.SettingsFile)); err != nil { return nil, err + } else if c, err := settings.ReadChallengeInfo(path.Join(settings.SettingsDir, settings.ChallengeFile)); err != nil { + return nil, err } else { - return fic.GenZQDSSessionFile(s) + return fic.GenZQDSSessionFile(c, s) } })) router.GET("/api/files-bindings", apiHandler(bindingFiles)) diff --git a/admin/static/views/settings.html b/admin/static/views/settings.html index cbac41f8..89825f64 100644 --- a/admin/static/views/settings.html +++ b/admin/static/views/settings.html @@ -6,27 +6,6 @@ -
- -
- -
-
- -
- -
- -
-
- -
- -
- -
-
-
diff --git a/configs/nginx/base/demo.conf b/configs/nginx/base/demo.conf index 4b6c6864..d2234498 100644 --- a/configs/nginx/base/demo.conf +++ b/configs/nginx/base/demo.conf @@ -165,6 +165,12 @@ server { expires epoch; add_header Cache-Control no-cache; } + location = /challenge.json { + root /srv/SETTINGSDIST/; + expires epoch; + add_header X-FIC-time $msec; + add_header Cache-Control no-cache; + } location = /settings.json { root /srv/SETTINGSDIST/; expires epoch; diff --git a/configs/nginx/base/docker.conf b/configs/nginx/base/docker.conf index 105f820d..416147cb 100644 --- a/configs/nginx/base/docker.conf +++ b/configs/nginx/base/docker.conf @@ -146,6 +146,12 @@ server { expires epoch; add_header Cache-Control no-cache; } + location /challenge.json { + root ${PATH_SETTINGS}/; + expires epoch; + add_header X-FIC-time $msec; + add_header Cache-Control no-cache; + } location /settings.json { root ${PATH_SETTINGS}/; expires epoch; diff --git a/configs/nginx/base/prod.conf b/configs/nginx/base/prod.conf index e13add70..59bd79e6 100644 --- a/configs/nginx/base/prod.conf +++ b/configs/nginx/base/prod.conf @@ -155,6 +155,12 @@ server { expires epoch; add_header Cache-Control no-cache; } + location = /challenge.json { + root /srv/SETTINGSDIST/; + expires epoch; + add_header X-FIC-time $msec; + add_header Cache-Control no-cache; + } location = /settings.json { root /srv/SETTINGSDIST/; expires epoch; diff --git a/dashboard/static.go b/dashboard/static.go index bccfb3bf..ce1ac4e7 100644 --- a/dashboard/static.go +++ b/dashboard/static.go @@ -107,6 +107,14 @@ func init() { fwd_request(w, r, "http://127.0.0.1:8081/") } }) + api.Router().GET("/challenge.json", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + w.Header().Set("Cache-Control", "no-cache") + if forwarder != nil { + fwd_request(w, r, *forwarder) + } else { + http.ServeFile(w, r, path.Join(settings.SettingsDir, settings.ChallengeFile)) + } + }) api.Router().GET("/settings.json", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { w.Header().Set("Cache-Control", "no-cache") if forwarder != nil { diff --git a/frontend/settings.go b/frontend/settings.go index 3e60166a..3455c81f 100644 --- a/frontend/settings.go +++ b/frontend/settings.go @@ -37,6 +37,15 @@ func reloadSettings(config *settings.Settings) { log.Println("Unable to move new settings file:", err) } + // Copy the challenge info file for distribution + if data, err := ioutil.ReadFile(path.Join(settings.SettingsDir, settings.ChallengeFile)); err != nil { + log.Println("Unable to read challenge info file:", err) + } else if err = ioutil.WriteFile(path.Join(SettingsDistDir, settings.ChallengeFile+".tmp"), data, 0644); err != nil { + log.Println("Unable to write tmp challenge info file:", err) + } else if err := os.Rename(path.Join(SettingsDistDir, settings.ChallengeFile+".tmp"), path.Join(SettingsDistDir, settings.ChallengeFile)); err != nil { + log.Println("Unable to move new challenge info file:", err) + } + if challengeStart != config.Start || challengeEnd != config.End { if touchTimer != nil { touchTimer.Stop() diff --git a/frontend/ui/src/app.html b/frontend/ui/src/app.html index 132594a5..181c9bbb 100644 --- a/frontend/ui/src/app.html +++ b/frontend/ui/src/app.html @@ -5,7 +5,6 @@ - %svelte.head% diff --git a/frontend/ui/src/components/HeaderClock.svelte b/frontend/ui/src/components/HeaderClock.svelte index bd717178..9ba82da9 100644 --- a/frontend/ui/src/components/HeaderClock.svelte +++ b/frontend/ui/src/components/HeaderClock.svelte @@ -4,6 +4,7 @@ Icon, } from 'sveltestrap'; + import { challengeInfo } from '../stores/challengeinfo.js'; import { settings, time } from '../stores/settings.js'; @@ -53,9 +54,9 @@ Classement Vidéos diff --git a/frontend/ui/src/routes/[theme]/[exercice].svelte b/frontend/ui/src/routes/[theme]/[exercice].svelte index ed5b88df..0e5fd189 100644 --- a/frontend/ui/src/routes/[theme]/[exercice].svelte +++ b/frontend/ui/src/routes/[theme]/[exercice].svelte @@ -42,6 +42,7 @@ import ExerciceVideo from '../../components/ExerciceVideo.svelte'; import ThemeNav from '../../components/ThemeNav.svelte'; + import { challengeInfo } from '../../stores/challengeinfo.js'; import { my } from '../../stores/my.js'; import { settings } from '../../stores/settings.js'; @@ -54,7 +55,7 @@ - {exercice?exercice.title+" - ":""}{$settings.title} + {exercice?exercice.title+" - ":""}{$challengeInfo.title} {#if exercice} diff --git a/frontend/ui/src/routes/[theme]/__layout.svelte b/frontend/ui/src/routes/[theme]/__layout.svelte index dca44ac2..1cc12bb7 100644 --- a/frontend/ui/src/routes/[theme]/__layout.svelte +++ b/frontend/ui/src/routes/[theme]/__layout.svelte @@ -30,13 +30,13 @@ Container, } from 'sveltestrap'; - import { settings } from '../../stores/settings.js'; + import { challengeInfo } from '../../stores/challengeinfo.js'; export let theme; - {theme?theme.name:""} - {$settings.title} + {theme?theme.name:""} - {$challengeInfo.title} {#if theme} diff --git a/frontend/ui/src/routes/__layout.svelte b/frontend/ui/src/routes/__layout.svelte index 9fd2f9af..8d59cb6e 100644 --- a/frontend/ui/src/routes/__layout.svelte +++ b/frontend/ui/src/routes/__layout.svelte @@ -1,4 +1,5 @@ - {$settings.title} + {$challengeInfo.title} + diff --git a/frontend/ui/src/routes/rank.svelte b/frontend/ui/src/routes/rank.svelte index 568365a8..1a85a2ba 100644 --- a/frontend/ui/src/routes/rank.svelte +++ b/frontend/ui/src/routes/rank.svelte @@ -12,7 +12,7 @@ import { my } from '../stores/my.js'; import { rank } from '../stores/teams.js'; - import { settings } from '../stores/settings.js'; + import { challengeInfo } from '../stores/challengeinfo.js'; import CardTheme from '../components/CardTheme.svelte'; @@ -21,7 +21,7 @@

- {$settings.title} + {$challengeInfo.title} Classement

diff --git a/frontend/ui/src/routes/rules.svelte b/frontend/ui/src/routes/rules.svelte index 02d09e19..e32da6dd 100644 --- a/frontend/ui/src/routes/rules.svelte +++ b/frontend/ui/src/routes/rules.svelte @@ -6,12 +6,13 @@ Icon, } from 'sveltestrap'; + import { challengeInfo } from '../stores/challengeinfo.js'; import { settings } from '../stores/settings.js';

- {$settings.title} + {$challengeInfo.title} Règles générales

diff --git a/frontend/ui/src/stores/challengeinfo.js b/frontend/ui/src/stores/challengeinfo.js new file mode 100644 index 00000000..ad984b53 --- /dev/null +++ b/frontend/ui/src/stores/challengeinfo.js @@ -0,0 +1,22 @@ +import { readable, writable } from 'svelte/store'; + +function createChallengeStore() { + const { subscribe, set, update } = writable({}); + + return { + subscribe, + update: (res_challenge, cb) => { + if (res_challenge.status === 200) { + res_challenge.json().then((challenge) => { + update((s) => (Object.assign({}, challenge))); + + if (cb) { + cb(challenge); + } + }); + } + }, + } +} + +export const challengeInfo = createChallengeStore(); diff --git a/libfic/zqsd.go b/libfic/zqsd.go index 66118fa1..6bd0cd23 100644 --- a/libfic/zqsd.go +++ b/libfic/zqsd.go @@ -20,7 +20,7 @@ type ChallengeZQDS struct { type SessionZQDS struct { Name string `json:"name"` Description string `json:"description"` - Scenario string `json:"scenario"` + Scenario string `json:"scenario,omitempty"` YourMission string `json:"your_mission"` Rules string `json:"rules"` Start time.Time `json:"start"` @@ -28,7 +28,7 @@ type SessionZQDS struct { Challenges []*ChallengeZQDS `json:"challenges"` } -func GenZQDSSessionFile(s *settings.Settings) (*SessionZQDS, error) { +func GenZQDSSessionFile(c *settings.ChallengeInfo, s *settings.Settings) (*SessionZQDS, error) { themes, err := GetThemes() if err != nil { return nil, err @@ -58,11 +58,10 @@ func GenZQDSSessionFile(s *settings.Settings) (*SessionZQDS, error) { } return &SessionZQDS{ - Name: "Challenge Forensic", - Description: "", - Scenario: "", - YourMission: "", - Rules: "", + Name: c.Title, + Description: c.Description, + Rules: c.Rules, + YourMission: c.YourMission, Start: s.Start, End: s.End, Challenges: challenges, diff --git a/settings/challenge.go b/settings/challenge.go new file mode 100644 index 00000000..6c39b5ed --- /dev/null +++ b/settings/challenge.go @@ -0,0 +1,59 @@ +package settings + +import ( + "encoding/json" + "os" +) + +// ChallengeFile is the expected name of the file containing the challenge infos. +const ChallengeFile = "challenge.json" + +// ChallengeInfo stores common descriptions and informations about the challenge. +type ChallengeInfo 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"` + // VideoLink is the link to explaination videos when the challenge is over. + VideosLink string `json:"videoslink"` + + // Description gives an overview of the challenge. + Description string `json:"description"` + // Rules tell the player some help. + Rules string `json:"rules"` + // YourMission is a small introduction to understand the goals. + YourMission string `json:"your_mission"` +} + +// ReadChallenge parses the file at the given location. +func ReadChallengeInfo(path string) (*ChallengeInfo, error) { + var s ChallengeInfo + if fd, err := os.Open(path); err != nil { + return nil, err + } else { + defer fd.Close() + jdec := json.NewDecoder(fd) + + if err := jdec.Decode(&s); err != nil { + return &s, err + } + + return &s, nil + } +} + +// SaveChallenge saves challenge at the given location. +func SaveChallengeInfo(path string, s *ChallengeInfo) error { + if fd, err := os.Create(path); err != nil { + return err + } else { + defer fd.Close() + jenc := json.NewEncoder(fd) + + if err := jenc.Encode(s); err != nil { + return err + } + + return nil + } +} diff --git a/settings/settings.go b/settings/settings.go index b2d74d24..a16f5207 100644 --- a/settings/settings.go +++ b/settings/settings.go @@ -22,12 +22,6 @@ var SettingsDir string = "./SETTINGS" // Settings represents the settings panel. type Settings 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"` - // VideoLink is the link to explaination videos when the challenge is over. - VideosLink string `json:"videoslink"` // WorkInProgress indicates if the current challenge is under development or if it is in production. WorkInProgress bool `json:"wip,omitempty"`