Distribute and handle challenge.json

This commit is contained in:
nemunaire 2022-05-01 22:33:59 +02:00
parent e8f6a03cd9
commit dff4f4eb63
20 changed files with 167 additions and 48 deletions

View File

@ -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 && \

View File

@ -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,

View File

@ -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))

View File

@ -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">

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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 {

View File

@ -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()

View File

@ -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%

View File

@ -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

View File

@ -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}

View File

@ -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}

View File

@ -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 /-->

View File

@ -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">

View File

@ -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>

View File

@ -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();

View File

@ -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,

59
settings/challenge.go Normal file
View File

@ -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
}
}

View File

@ -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"`