Distribute and handle challenge.json
This commit is contained in:
parent
e8f6a03cd9
commit
dff4f4eb63
|
@ -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 && \
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -6,27 +6,6 @@
|
|||
|
||||
<input type="hidden" class="form-control form-control-sm" id="lastRegeneration" ng-model="config.generation">
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="challengeName" class="col-sm-3 col-form-label col-form-label-sm">Nom du challenge</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control form-control-sm" id="challengeName" ng-model="config.title">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="challengeAuthors" class="col-sm-3 col-form-label col-form-label-sm">Auteurs du challenge</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control form-control-sm" id="challengeAuthors" ng-model="config.authors">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="challengeVideo" class="col-sm-3 col-form-label col-form-label-sm">Lien vidéos de résolution</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control form-control-sm" id="challengeVideo" ng-model="config.videoslink">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="startTime" class="col-sm-3 col-form-label col-form-label-sm">Début du challenge</label>
|
||||
<div class="col-sm-9">
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
|
||||
<meta name="author" content="EPITA Laboratoire SRS">
|
||||
<meta name="robots" content="all">
|
||||
<base href="/">
|
||||
%svelte.head%
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
Icon,
|
||||
} from 'sveltestrap';
|
||||
|
||||
import { challengeInfo } from '../stores/challengeinfo.js';
|
||||
import { settings, time } from '../stores/settings.js';
|
||||
</script>
|
||||
|
||||
|
@ -53,9 +54,9 @@
|
|||
Classement
|
||||
</a>
|
||||
<a
|
||||
href="{$settings.videoslink}"
|
||||
href="{$challengeInfo.videoslink}"
|
||||
class="btn btn-light"
|
||||
class:disabled={$settings.videoslink === ''}
|
||||
class:disabled={$challengeInfo.videoslink === ''}
|
||||
>
|
||||
<Icon name="laptop-fill" />
|
||||
Vidéos
|
||||
|
|
|
@ -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 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{exercice?exercice.title+" - ":""}{$settings.title}</title>
|
||||
<title>{exercice?exercice.title+" - ":""}{$challengeInfo.title}</title>
|
||||
</svelte:head>
|
||||
|
||||
{#if exercice}
|
||||
|
|
|
@ -30,13 +30,13 @@
|
|||
Container,
|
||||
} from 'sveltestrap';
|
||||
|
||||
import { settings } from '../../stores/settings.js';
|
||||
import { challengeInfo } from '../../stores/challengeinfo.js';
|
||||
|
||||
export let theme;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{theme?theme.name:""} - {$settings.title}</title>
|
||||
<title>{theme?theme.name:""} - {$challengeInfo.title}</title>
|
||||
</svelte:head>
|
||||
|
||||
{#if theme}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<script context="module">
|
||||
import { challengeInfo } from '../stores/challengeinfo.js';
|
||||
import { issuesStore } from '../stores/issues.js';
|
||||
import { my } from '../stores/my.js';
|
||||
import { teamsStore } from '../stores/teams.js';
|
||||
|
@ -41,6 +42,10 @@
|
|||
settings.update(await fetch('settings.json', {headers: {'Accept': 'application/json'}}), cb);
|
||||
}
|
||||
|
||||
async function refresh_challengeInfo(cb=null) {
|
||||
challengeInfo.update(await fetch('challenge.json', {headers: {'Accept': 'application/json'}}), cb);
|
||||
}
|
||||
|
||||
let refresh_interval_teams = null;
|
||||
async function refresh_teams(cb=null, interval=null) {
|
||||
if (refresh_interval_teams)
|
||||
|
@ -102,6 +107,7 @@
|
|||
}
|
||||
|
||||
export async function load({ stuff }) {
|
||||
await refresh_challengeInfo();
|
||||
await refresh_settings();
|
||||
await refresh_themes();
|
||||
refresh_teams();
|
||||
|
@ -115,6 +121,7 @@
|
|||
return {
|
||||
stuff: {
|
||||
...stuff,
|
||||
refresh_challengeInfo,
|
||||
refresh_settings,
|
||||
refresh_teams,
|
||||
refresh_themes,
|
||||
|
@ -138,7 +145,8 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{$settings.title}</title>
|
||||
<title>{$challengeInfo.title}</title>
|
||||
<meta name="author" content="{$challengeInfo.authors}">
|
||||
</svelte:head>
|
||||
|
||||
<!--Styles /-->
|
||||
|
|
|
@ -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 @@
|
|||
|
||||
<Container fluid class="my-3">
|
||||
<h1 class="text-dark">
|
||||
{$settings.title}
|
||||
{$challengeInfo.title}
|
||||
<small class="text-muted">Classement</small>
|
||||
</h1>
|
||||
<div class="card niceborder text-light">
|
||||
|
|
|
@ -6,12 +6,13 @@
|
|||
Icon,
|
||||
} from 'sveltestrap';
|
||||
|
||||
import { challengeInfo } from '../stores/challengeinfo.js';
|
||||
import { settings } from '../stores/settings.js';
|
||||
</script>
|
||||
|
||||
<Container class="my-3">
|
||||
<h1 class="text-dark">
|
||||
{$settings.title}
|
||||
{$challengeInfo.title}
|
||||
<small class="text-muted">Règles générales</small>
|
||||
</h1>
|
||||
|
||||
|
|
|
@ -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();
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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"`
|
||||
|
||||
|
|
Loading…
Reference in New Issue