Rename frontend as receiver

This commit is contained in:
nemunaire 2023-07-09 20:40:53 +02:00
commit 1ca5452707
111 changed files with 79 additions and 81 deletions

1
frontend/.gitignore vendored
View file

@ -1 +0,0 @@
frontend

View file

@ -1,44 +0,0 @@
#!/bin/sh
# You can define the current base URL in your environment
[ -n "${CURRENT_BASE}" ] || CURRENT_BASE="/"
usage() {
echo "$0 NEWBASE STATICDIR"
echo
echo " This script can be used to change the base URL of the frontend."
echo " Gives as PATH, the path to the static directory."
echo " You should use this script only one time (ie. from a fresh git repository),"
echo " as it doesn't erase previously defined base: it assumes the current base is /."
echo
echo " For example:"
echo
echo " $0 /$(date -d 'next year' +%Y)/" static/
echo
}
run() {
local NEWBASE=$1
local FILE=$2
if [ -d "${FILE}" ]
then
for f in "${FILE}/"*.html "${FILE}/"*.js
do
run "${NEWBASE}" "${f}"
done
[ -d "${FILE}/js/" ] && run "${NEWBASE}" "${FILE}/js"
[ -d "${FILE}/views/" ] && run "${NEWBASE}" "${FILE}/views"
elif [ -f "${FILE}" ]
then
sed -ri "s@(href|src)=\"${CURRENT_BASE}@\1=\"${NEWBASE}@g;s@\\\$http.get\(\"${CURRENT_BASE}@\$http.get\(\"${NEWBASE}@g;s@\\\$http\((.*)\"${CURRENT_BASE}@\$http(\1\"${NEWBASE}@g" ${FILE}
fi
}
if [ $# -gt 1 ]
then
run $1 $2
else
usage
exit 1
fi

View file

@ -1,19 +0,0 @@
package main
import (
"log"
"net/http"
"path"
)
var denyNameChange bool = true
func ChNameHandler(w http.ResponseWriter, r *http.Request, team string, sURL []string) {
if denyNameChange {
log.Printf("UNHANDELED %s name change request from %s: %s [%s]\n", r.Method, r.RemoteAddr, r.URL.Path, r.UserAgent())
http.Error(w, "{\"errmsg\":\"Le changement de nom est prohibé.\"}", http.StatusForbidden)
} else if saveTeamFile(path.Join(team, "name"), w, r) {
// File enqueued for backend treatment
http.Error(w, "{\"errmsg\":\"Demande de changement de nom acceptée\"}", http.StatusAccepted)
}
}

View file

@ -1,19 +0,0 @@
package main
import (
"net/http"
"path"
"time"
)
func WantChoicesHandler(w http.ResponseWriter, r *http.Request, team string, sURL []string) {
if challengeEnd != nil && time.Now().After(*challengeEnd) {
http.Error(w, "{\"errmsg\":\"Le challenge est terminé, trop tard !\"}", http.StatusGone)
return
}
// Enqueue file for backend treatment
if saveTeamFile(path.Join(team, "choices"), w, r) {
http.Error(w, "{\"errmsg\":\"Demande de choix acceptée...\"}", http.StatusAccepted)
}
}

View file

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Before After
Before After

View file

@ -1,19 +0,0 @@
package main
import (
"net/http"
"path"
"time"
)
func HintHandler(w http.ResponseWriter, r *http.Request, team string, sURL []string) {
if challengeEnd != nil && time.Now().After(*challengeEnd) {
http.Error(w, "{\"errmsg\":\"Le challenge est terminé, trop tard pour un indice !\"}", http.StatusGone)
return
}
// Enqueue file for backend treatment
if saveTeamFile(path.Join(team, "hint"), w, r) {
http.Error(w, "{\"errmsg\":\"Demande d'astuce acceptée...\"}", http.StatusAccepted)
}
}

View file

@ -1,19 +0,0 @@
package main
import (
"log"
"net/http"
"path"
)
var acceptNewIssues bool = true
func IssueHandler(w http.ResponseWriter, r *http.Request, team string, sURL []string) {
if !acceptNewIssues {
log.Printf("UNHANDELED %s issue request from %s: %s [%s]\n", r.Method, r.RemoteAddr, r.URL.Path, r.UserAgent())
http.Error(w, "{\"errmsg\":\"Il n'est pas possible de rapporter d'anomalie.\"}", http.StatusForbidden)
} else if saveTeamFile(path.Join(team, "issue"), w, r) {
// File enqueued for backend treatment
http.Error(w, "{\"errmsg\":\"Anomalie signalée avec succès. Merci de votre patience...\"}", http.StatusAccepted)
}
}

View file

@ -1,118 +0,0 @@
package main
import (
"context"
"flag"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"path"
"strings"
"syscall"
"time"
"srs.epita.fr/fic-server/settings"
)
func main() {
var bind = flag.String("bind", "127.0.0.1:8080", "Bind port/socket")
var prefix = flag.String("prefix", "/", "Request path prefix to strip (from proxy)")
var teamsDir = flag.String("teams", "./TEAMS/", "Base directory where find existing teams")
flag.StringVar(&settings.SettingsDir, "settings", "./SETTINGSDIST", "Base directory where read settings")
flag.StringVar(&startedFile, "startedFile", startedFile, "Path to the file to create/remove whether or not the challenge is running")
flag.StringVar(&SubmissionDir, "submission", "./submissions/", "Base directory where save submissions")
var simulator = flag.String("simulator", "", "Team to simulate (for development only)")
flag.StringVar(&staticDir, "static", staticDir, "Set to serve pages as well (for development only, use with -simulator)")
flag.Parse()
log.SetPrefix("[frontend] ")
startedFile = path.Clean(startedFile)
SubmissionDir = path.Clean(SubmissionDir)
TmpSubmissionDir = path.Join(SubmissionDir, ".tmp")
log.Println("Creating submission directory...")
if _, err := os.Stat(TmpSubmissionDir); os.IsNotExist(err) {
if err = os.MkdirAll(TmpSubmissionDir, 0700); err != nil {
log.Fatal("Unable to create submission directory:", err)
}
}
*prefix = strings.TrimRight(*prefix, "/")
// Load configuration
settings.LoadAndWatchSettings(path.Join(settings.SettingsDir, settings.SettingsFile), reloadSettings)
// Register handlers
http.Handle(fmt.Sprintf("%s/chname", *prefix), http.StripPrefix(fmt.Sprintf("%s/chname", *prefix), submissionTeamChecker{"name change", ChNameHandler, *teamsDir, *simulator}))
http.Handle(fmt.Sprintf("%s/issue", *prefix), http.StripPrefix(fmt.Sprintf("%s/issue", *prefix), submissionTeamChecker{"issue", IssueHandler, *teamsDir, *simulator}))
http.Handle(fmt.Sprintf("%s/openhint/", *prefix), http.StripPrefix(fmt.Sprintf("%s/openhint/", *prefix), submissionTeamChecker{"opening hint", HintHandler, *teamsDir, *simulator}))
http.Handle(fmt.Sprintf("%s/wantchoices/", *prefix), http.StripPrefix(fmt.Sprintf("%s/wantchoices/", *prefix), submissionTeamChecker{"wantint choices", WantChoicesHandler, *teamsDir, *simulator}))
http.Handle(fmt.Sprintf("%s/registration", *prefix), http.StripPrefix(fmt.Sprintf("%s/registration", *prefix), submissionChecker{"registration", RegistrationHandler}))
http.Handle(fmt.Sprintf("%s/resolution/", *prefix), http.StripPrefix(fmt.Sprintf("%s/resolution/", *prefix), ResolutionHandler{}))
http.Handle(fmt.Sprintf("%s/submission/", *prefix), http.StripPrefix(fmt.Sprintf("%s/submission/", *prefix), submissionTeamChecker{"submission", SubmissionHandler, *teamsDir, *simulator}))
if *simulator != "" {
if _, err := os.Stat(path.Join(*teamsDir, *simulator)); os.IsNotExist(err) {
log.Printf("WARNING: Team '%s' doesn't exist yet in %s.", *simulator, *teamsDir)
}
// Serve team files
http.Handle(fmt.Sprintf("%s/wait.json", *prefix), http.StripPrefix(*prefix, http.FileServer(http.Dir(path.Join(*teamsDir, *simulator)))))
http.Handle(fmt.Sprintf("%s/my.json", *prefix), http.StripPrefix(*prefix, TeamMyServer{path.Join(*teamsDir, *simulator)}))
// Serve generated content
http.Handle(fmt.Sprintf("%s/teams.json", *prefix), http.StripPrefix(*prefix, http.FileServer(http.Dir(*teamsDir))))
http.Handle(fmt.Sprintf("%s/themes.json", *prefix), http.StripPrefix(*prefix, http.FileServer(http.Dir(*teamsDir))))
http.Handle(fmt.Sprintf("%s/stats.json", *prefix), http.StripPrefix(*prefix, http.FileServer(http.Dir(*teamsDir))))
http.Handle(fmt.Sprintf("%s/settings.json", *prefix), http.StripPrefix(*prefix, http.FileServer(http.Dir(settings.SettingsDir))))
// Serve static assets
http.Handle(fmt.Sprintf("%s/css/", *prefix), http.StripPrefix(*prefix, http.FileServer(http.Dir(staticDir))))
http.Handle(fmt.Sprintf("%s/js/", *prefix), http.StripPrefix(*prefix, http.FileServer(http.Dir(staticDir))))
http.Handle(fmt.Sprintf("%s/files/", *prefix), http.StripPrefix(*prefix, http.FileServer(http.Dir("FILES"))))
// Serve index
http.HandleFunc(fmt.Sprintf("%s/edit", *prefix), serveIndex)
http.HandleFunc(fmt.Sprintf("%s/rank", *prefix), serveIndex)
http.HandleFunc(fmt.Sprintf("%s/register", *prefix), serveIndex)
http.HandleFunc(fmt.Sprintf("%s/rules", *prefix), serveIndex)
http.HandleFunc(fmt.Sprintf("%s/videos", *prefix), serveIndex)
}
// Prepare graceful shutdown
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM)
srv := &http.Server{
Addr: *bind,
}
// Serve pages
go func() {
log.Fatal(srv.ListenAndServe())
}()
log.Println(fmt.Sprintf("Ready, listening on %s", *bind))
// Wait shutdown signal and touch timestamp
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
loop:
for {
select {
case <-interrupt:
break loop
case <-ticker.C:
now := time.Now()
os.Chtimes(SubmissionDir, now, now)
}
}
log.Print("The service is shutting down...")
srv.Shutdown(context.Background())
log.Println("done")
}

View file

@ -1,42 +0,0 @@
package main
import (
"log"
"net/http"
"path"
)
var allowRegistration bool = false
func RegistrationHandler(w http.ResponseWriter, r *http.Request, sURL []string) {
if !allowRegistration {
log.Printf("UNHANDLED %s registration request from %s: %s [%s]\n", r.Method, r.RemoteAddr, r.URL.Path, r.UserAgent())
http.Error(w, "{\"errmsg\":\"L'enregistrement d'équipe n'est pas permis.\"}", http.StatusForbidden)
return
}
teamInitialName := "-"
if t := r.Header.Get("X-FIC-Team"); t != "" {
teamInitialName = t
} else {
http.Error(w, "{\"errmsg\":\"Votre jeton d'authentification semble invalide. Contactez l'équipe serveur.\"}", http.StatusInternalServerError)
return
}
// Check request type and size
if r.Method != "POST" {
http.Error(w, "{\"errmsg\":\"Requête invalide.\"}", http.StatusBadRequest)
return
} else if r.ContentLength < 0 || r.ContentLength > 4095 {
http.Error(w, "{\"errmsg\":\"Requête trop longue ou de taille inconnue\"}", http.StatusRequestEntityTooLarge)
return
}
if err := saveFile(path.Join(SubmissionDir, "_registration", teamInitialName), r); err != nil {
log.Println("Unable to open registration file:", err)
http.Error(w, "{\"errmsg\":\"Internal server error. Please retry in few seconds.\"}", http.StatusInternalServerError)
} else {
// File enqueued for backend treatment
http.Error(w, "{\"errmsg\":\"Demande d'enregistrement acceptée\"}", http.StatusAccepted)
}
}

View file

@ -1,48 +0,0 @@
package main
import (
"log"
"net/http"
"net/url"
"path"
"strings"
"text/template"
)
var enableResolutionRoute bool = false
type ResolutionHandler struct{}
const resolutiontpl = `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Résolution</title>
</head>
<body style="margin: 0">
<video src="{{.}}" controls width="100%" height="100%"></video>
</body>
</html>
`
func (s ResolutionHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if addr := r.Header.Get("X-Forwarded-For"); addr != "" {
r.RemoteAddr = addr
}
if !enableResolutionRoute {
log.Printf("UNHANDELED %s request from %s: /resolution%s [%s]\n", r.Method, r.RemoteAddr, r.URL.Path, r.UserAgent())
http.NotFound(w, r)
return
}
log.Printf("%s \"%s /resolution%s\" [%s]\n", r.RemoteAddr, r.Method, r.URL.Path, r.UserAgent())
w.Header().Set("Content-Type", "text/html")
if resolutionTmpl, err := template.New("resolution").Parse(resolutiontpl); err != nil {
log.Println("Cannot create template: ", err)
} else if err = resolutionTmpl.Execute(w, path.Join("/vids/", strings.Replace(url.PathEscape(r.URL.Path), "%2F", "/", -1))); err != nil {
log.Println("An error occurs during template execution: ", err)
}
}

View file

@ -1,63 +0,0 @@
package main
import (
"bufio"
"io/ioutil"
"log"
"net/http"
"os"
"path"
)
var SubmissionDir string
var TmpSubmissionDir string
func saveTeamFile(p string, w http.ResponseWriter, r *http.Request) bool {
if len(SubmissionDir) < 1 || len(p) < 1 {
http.Error(w, "{\"errmsg\":\"Requête invalide.\"}", http.StatusBadRequest)
return false
} else if len(p) <= 0 {
log.Println("EMPTY $EXERCICE RECEIVED:", p)
http.Error(w, "{\"errmsg\":\"Internal server error. Please retry in few seconds.\"}", http.StatusInternalServerError)
return false
} else if _, err := os.Stat(path.Join(SubmissionDir, p)); !os.IsNotExist(err) {
// Previous submission not treated
http.Error(w, "{\"errmsg\":\"Du calme ! une requête est déjà en cours de traitement.\"}", http.StatusPaymentRequired)
return false
} else if err = saveFile(path.Join(SubmissionDir, p), r); err != nil {
log.Println("Unable to handle submission file:", err)
http.Error(w, "{\"errmsg\":\"Internal server error. Please retry in few seconds.\"}", http.StatusInternalServerError)
return false
}
return true
}
func saveFile(p string, r *http.Request) error {
dirname := path.Dir(p)
if _, err := os.Stat(dirname); os.IsNotExist(err) {
if err = os.MkdirAll(dirname, 0755); err != nil {
return err
}
}
// Write content to temp file
tmpfile, err := ioutil.TempFile(TmpSubmissionDir, "")
if err != nil {
return err
}
writer := bufio.NewWriter(tmpfile)
reader := bufio.NewReader(r.Body)
if _, err = reader.WriteTo(writer); err != nil {
return err
}
writer.Flush()
tmpfile.Close()
if err = os.Rename(tmpfile.Name(), p); err != nil {
log.Println("[ERROR] Unable to move file: ", err)
return err
}
return nil
}

View file

@ -1,67 +0,0 @@
package main
import (
"log"
"os"
"time"
"srs.epita.fr/fic-server/settings"
)
var startedFile = "started"
var touchTimer *time.Timer = nil
var challengeStart time.Time
var challengeEnd *time.Time
func touchStartedFile() {
if fd, err := os.Create(startedFile); err == nil {
log.Println("Started! Go, Go, Go!!")
fd.Close()
} else {
log.Fatal("Unable to start challenge:", err)
}
}
func reloadSettings(config *settings.Settings) {
if challengeStart != config.Start || challengeEnd != config.End {
if touchTimer != nil {
touchTimer.Stop()
}
if config.Start.Unix() == 0 {
log.Println("WARNING: No challenge start defined!")
if _, err := os.Stat(startedFile); !os.IsNotExist(err) {
os.Remove(startedFile)
}
return
}
startSub := time.Until(config.Start)
if startSub > 0 {
log.Println("Challenge will starts at", config.Start, "in", startSub)
if _, err := os.Stat(startedFile); !os.IsNotExist(err) {
os.Remove(startedFile)
}
touchTimer = time.AfterFunc(config.Start.Sub(time.Now().Add(time.Duration(1*time.Second))), touchStartedFile)
} else {
log.Println("Challenge started at", config.Start, "since", -startSub)
touchStartedFile()
}
log.Println("Challenge ends on", config.End)
challengeStart = config.Start
challengeEnd = config.End
} else {
log.Println("Configuration reloaded, but start/end times doesn't change.")
}
enableResolutionRoute = config.EnableResolutionRoute
denyNameChange = config.DenyNameChange
acceptNewIssues = config.AcceptNewIssue
allowRegistration = config.AllowRegistration
}

View file

@ -1,25 +0,0 @@
package main
import (
"net/http"
"os"
"path"
)
var staticDir = "static"
func serveIndex(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, path.Join(staticDir, "index.html"))
}
type TeamMyServer struct {
path2Dir string
}
func (s TeamMyServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if _, err := os.Stat(startedFile); os.IsNotExist(err) {
http.ServeFile(w, r, path.Join(s.path2Dir, "wait.json"))
} else {
http.ServeFile(w, r, path.Join(s.path2Dir, "my.json"))
}
}

View file

@ -1,74 +0,0 @@
package main
import (
"log"
"net/http"
"os"
"path"
"strings"
)
type submissionHandler func(w http.ResponseWriter, r *http.Request, sURL []string)
type submissionChecker struct {
kind string
next submissionHandler
}
type submissionTeamHandler func(w http.ResponseWriter, r *http.Request, team string, sURL []string)
type submissionTeamChecker struct {
kind string
next submissionTeamHandler
teamsDir string
simulator string
}
func (c submissionChecker) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if addr := r.Header.Get("X-Forwarded-For"); addr != "" {
r.RemoteAddr = addr
}
team := "-"
if t := r.Header.Get("X-FIC-Team"); t != "" {
team = t
}
log.Printf("%s %s \"%s %s\" => %s [%s]\n", r.RemoteAddr, team, r.Method, r.URL.Path, c.kind, r.UserAgent())
w.Header().Set("Content-Type", "application/json")
// Check request type and size
if r.Method != "POST" || r.ContentLength < 0 {
http.Error(w, "{\"errmsg\":\"Requête invalide.\"}", http.StatusBadRequest)
return
} else if r.ContentLength > 4097 {
http.Error(w, "{\"errmsg\":\"Requête trop longue\"}", http.StatusRequestEntityTooLarge)
return
}
// Extract URL arguments
var sURL = strings.Split(strings.TrimPrefix(r.URL.Path, "/"), "/")
c.next(w, r, sURL)
}
func (c submissionTeamChecker) ServeHTTP(w http.ResponseWriter, r *http.Request) {
submissionChecker{c.kind, func(w http.ResponseWriter, r *http.Request, sURL []string) {
team := c.simulator
if t := r.Header.Get("X-FIC-Team"); t != "" {
team = t
}
// Check team validity and existance
if len(team) < 1 || team == "-" || team == "public" {
log.Println("INVALID TEAM:", team)
http.Error(w, "{\"errmsg\":\"Équipe inexistante.\"}", http.StatusBadRequest)
return
} else if _, err := os.Stat(path.Join(c.teamsDir, team)); os.IsNotExist(err) {
log.Println("UNKNOWN TEAM:", team)
http.Error(w, "{\"errmsg\":\"Équipe inexistante.\"}", http.StatusBadRequest)
return
}
c.next(w, r, team, sURL)
}}.ServeHTTP(w, r)
}

View file

@ -1,32 +0,0 @@
package main
import (
"fmt"
"net/http"
"path"
"strconv"
"time"
)
func SubmissionHandler(w http.ResponseWriter, r *http.Request, team string, sURL []string) {
if challengeEnd != nil && time.Now().After(*challengeEnd) {
http.Error(w, "{\"errmsg\":\"Vous ne pouvez plus soumettre, le challenge est terminé.\"}", http.StatusGone)
return
}
if len(sURL) != 1 {
http.Error(w, "{\"errmsg\":\"Arguments manquants.\"}", http.StatusBadRequest)
return
}
// Check exercice validity then save the submission
if pex, err := strconv.ParseInt(sURL[0], 10, 64); err != nil {
http.Error(w, "{\"errmsg\":\"Requête invalide.\"}", http.StatusBadRequest)
return
} else if exercice := fmt.Sprintf("%d", pex); len(exercice) < 1 {
http.Error(w, "{\"errmsg\":\"Requête invalide.\"}", http.StatusBadRequest)
return
} else if saveTeamFile(path.Join(team, exercice), w, r) {
http.Error(w, "{\"errmsg\":\"Son traitement est en cours...\"}", http.StatusAccepted)
}
}