challenge-sync-airbus: Do job
This commit is contained in:
parent
18b8f0f722
commit
3344e05e0d
@ -6,6 +6,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
@ -32,6 +33,7 @@ func (a *AirbusAPI) request(method, endpoint string, data []byte, out interface{
|
||||
return fmt.Errorf("unable to prepare request to %q: %w", endpoint, err)
|
||||
}
|
||||
|
||||
req.Header.Add("Authorization", "Bearer "+a.Token)
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
@ -40,7 +42,7 @@ func (a *AirbusAPI) request(method, endpoint string, data []byte, out interface{
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
if out != nil {
|
||||
jdec := json.NewDecoder(resp.Body)
|
||||
|
||||
@ -149,30 +151,44 @@ func (a *AirbusAPI) GetChallengeFromName(name string) (*AirbusChallenge, error)
|
||||
return nil, fmt.Errorf("unable to find challenge %q", name)
|
||||
}
|
||||
|
||||
func (a *AirbusAPI) ValidateChallengeFromUser(userId AirbusUserId, challengeId AirbusChallengeId) (err error) {
|
||||
err = a.request("GET", fmt.Sprintf("/v1/sessions/%d/%s/%s/validate", a.SessionID, challengeId.String(), userId.String()), nil, nil)
|
||||
func (a *AirbusAPI) ValidateChallengeFromUser(team *AirbusTeam, challengeId AirbusChallengeId) (err error) {
|
||||
if dryRun {
|
||||
log.Printf("ValidateChallenge: %d, %s, %d", a.SessionID, challengeId.String(), team.Members[0].ID)
|
||||
return
|
||||
}
|
||||
|
||||
err = a.request("GET", fmt.Sprintf("/v1/sessions/%d/%s/%d/validate", a.SessionID, challengeId.String(), team.Members[0].ID), nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
type AirbusUserAwards struct {
|
||||
UserId AirbusUserId `json:"gaming_user_id"`
|
||||
Message string `json:"name"`
|
||||
Value int64 `json:"value"`
|
||||
UserId int64 `json:"gaming_user_id"`
|
||||
Message string `json:"name"`
|
||||
Value int64 `json:"value"`
|
||||
}
|
||||
|
||||
func (a *AirbusAPI) AwardUser(userId AirbusUserId, value int64, message string) (err error) {
|
||||
func (a *AirbusAPI) AwardUser(team *AirbusTeam, value int64, message string) (err error) {
|
||||
awards := AirbusUserAwards{
|
||||
UserId: userId,
|
||||
UserId: team.Members[0].ID,
|
||||
Message: message,
|
||||
Value: value,
|
||||
}
|
||||
|
||||
if dryRun {
|
||||
log.Printf("AwardUser: %v", awards)
|
||||
return
|
||||
}
|
||||
|
||||
j, err := json.Marshal(awards)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to marshall JSON from awards struct: %w", err)
|
||||
}
|
||||
|
||||
err = a.request("POST", fmt.Sprintf("/v1/sessions/%d/awards", a.SessionID), j, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -18,11 +18,13 @@ import (
|
||||
var (
|
||||
TeamsDir string
|
||||
skipInitialSync bool
|
||||
dryRun bool
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.StringVar(&TeamsDir, "teams", "./TEAMS", "Base directory where save teams JSON files")
|
||||
var debugINotify = flag.Bool("debuginotify", false, "Show skipped inotofy events")
|
||||
flag.BoolVar(&dryRun, "dry-run", dryRun, "Don't perform any write action, just display")
|
||||
flag.BoolVar(&skipInitialSync, "skipinitialsync", skipInitialSync, "Skip the initial synchronization")
|
||||
flag.BoolVar(&noValidateChallenge, "no-validate-challenge", noValidateChallenge, "Consider challenge validation as a standard award (if each exercice hasn't been imported on their side)")
|
||||
daemon := flag.Bool("watch", false, "Enable daemon mode by watching the directory")
|
||||
@ -41,13 +43,27 @@ func main() {
|
||||
if v, exists := os.LookupEnv("AIRBUS_TOKEN"); exists {
|
||||
api.Token = v
|
||||
}
|
||||
|
||||
if v, exists := os.LookupEnv("AIRBUS_SESSIONID"); exists {
|
||||
var err error
|
||||
api.SessionID, err = strconv.ParseInt(v, 10, 64)
|
||||
if err != nil {
|
||||
log.Fatal("AIRBUS_SESSIONID is invalid: ", err.Error())
|
||||
}
|
||||
} else if v, exists := os.LookupEnv("AIRBUS_SESSION_NAME"); exists {
|
||||
sessions, err := api.GetSessions()
|
||||
if err != nil {
|
||||
log.Fatal("Unable to retrieve session: ", err)
|
||||
}
|
||||
|
||||
for _, session := range sessions {
|
||||
if session.Name == v {
|
||||
api.SessionID = session.ID
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if v, exists := os.LookupEnv("AIRBUS_SESSIONUUID"); exists {
|
||||
api.SessionUUID = v
|
||||
}
|
||||
@ -62,12 +78,6 @@ func main() {
|
||||
log.Fatal("Unable to open timestamp file: ", err.Error())
|
||||
}
|
||||
|
||||
// Load exercices bindings
|
||||
exbindings, err := ReadExercicesBindings(*exercicespath)
|
||||
if err != nil {
|
||||
log.Fatal("Unable to open exercices bindings file: ", err.Error())
|
||||
}
|
||||
|
||||
// Load teams.json
|
||||
teamsbindings, err := getTeams(filepath.Join(TeamsDir, "teams.json"))
|
||||
if err != nil {
|
||||
@ -75,18 +85,28 @@ func main() {
|
||||
}
|
||||
|
||||
w := Walker{
|
||||
LastSync: ts,
|
||||
Exercices: exbindings,
|
||||
Teams: teamsbindings,
|
||||
API: api,
|
||||
Coeff: *coeff,
|
||||
LastSync: ts,
|
||||
Teams: teamsbindings,
|
||||
API: api,
|
||||
Coeff: *coeff,
|
||||
}
|
||||
if err = w.fetchTeams(); err != nil {
|
||||
log.Fatal("Unable to fetch Airbus teams: ", err.Error())
|
||||
}
|
||||
|
||||
if !noValidateChallenge {
|
||||
// Load exercices bindings
|
||||
w.Exercices, err = ReadExercicesBindings(*exercicespath)
|
||||
if err != nil {
|
||||
log.Fatal("Unable to open exercices bindings file: ", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if !skipInitialSync {
|
||||
// Iterate over teams scores
|
||||
err = filepath.WalkDir(TeamsDir, w.WalkScoreSync)
|
||||
if err != nil {
|
||||
log.Printf("Something goes wrong during walking")
|
||||
log.Println("Something goes wrong during walking: ", err.Error())
|
||||
}
|
||||
|
||||
// save current timestamp for teams
|
||||
@ -131,6 +151,9 @@ func main() {
|
||||
return
|
||||
}
|
||||
w.Teams = teamsbindings
|
||||
if err = w.fetchTeams(); err != nil {
|
||||
log.Fatal("Unable to fetch teams: ", err.Error())
|
||||
}
|
||||
|
||||
// save current timestamp for teams
|
||||
err = saveTS(*tspath, w.LastSync)
|
||||
@ -160,6 +183,9 @@ func main() {
|
||||
return
|
||||
}
|
||||
w.Teams = teamsbindings
|
||||
if err = w.fetchTeams(); err != nil {
|
||||
log.Fatal("Unable to fetch teams: ", err.Error())
|
||||
}
|
||||
|
||||
// FIXME
|
||||
|
||||
@ -183,6 +209,9 @@ func main() {
|
||||
return
|
||||
}
|
||||
w.Teams = teamsbindings
|
||||
if err = w.fetchTeams(); err != nil {
|
||||
log.Fatal("Unable to fetch teams: ", err.Error())
|
||||
}
|
||||
}
|
||||
} else if ev.Op&fsnotify.Write == fsnotify.Write {
|
||||
log.Println("FSNOTIFY WRITE SEEN. Prefer looking at them, as it appears files are not atomically moved.")
|
||||
|
15
remote/challenge-sync-airbus/session.go
Normal file
15
remote/challenge-sync-airbus/session.go
Normal file
@ -0,0 +1,15 @@
|
||||
package main
|
||||
|
||||
import ()
|
||||
|
||||
type Session struct {
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
ID int64 `json:"id"`
|
||||
Mode string `json:"mode"`
|
||||
}
|
||||
|
||||
func (a *AirbusAPI) GetSessions() (ret []Session, err error) {
|
||||
err = a.request("GET", "/api/v1/sessions", nil, &ret)
|
||||
return
|
||||
}
|
32
remote/challenge-sync-airbus/team.go
Normal file
32
remote/challenge-sync-airbus/team.go
Normal file
@ -0,0 +1,32 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type AirbusTeam struct {
|
||||
ID int64 `json:"id"`
|
||||
Members []TeamMember `json:"members"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type TeamMember struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Nickname string `json:"nickname"`
|
||||
EMail string `json:"email"`
|
||||
}
|
||||
|
||||
type airbusDataTeam struct {
|
||||
Data []AirbusTeam `json:"data"`
|
||||
}
|
||||
|
||||
func (a *AirbusAPI) GetTeams() ([]AirbusTeam, error) {
|
||||
var data airbusDataTeam
|
||||
err := a.request("GET", fmt.Sprintf("/api/v1/sessions/%d/teams", a.SessionID), nil, &data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return data.Data, nil
|
||||
}
|
||||
}
|
@ -6,10 +6,15 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func loadTS(tspath string) (timestamp map[AirbusUserId]time.Time, err error) {
|
||||
type TSValue struct {
|
||||
Time time.Time `json:"t"`
|
||||
Score int64 `json:"s"`
|
||||
}
|
||||
|
||||
func loadTS(tspath string) (timestamp map[string]*TSValue, err error) {
|
||||
var fd *os.File
|
||||
if _, err = os.Stat(tspath); os.IsNotExist(err) {
|
||||
timestamp = map[AirbusUserId]time.Time{}
|
||||
timestamp = map[string]*TSValue{}
|
||||
err = saveTS(tspath, timestamp)
|
||||
return
|
||||
} else if fd, err = os.Open(tspath); err != nil {
|
||||
@ -26,7 +31,7 @@ func loadTS(tspath string) (timestamp map[AirbusUserId]time.Time, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
func saveTS(tspath string, ts map[AirbusUserId]time.Time) error {
|
||||
func saveTS(tspath string, ts map[string]*TSValue) error {
|
||||
if fd, err := os.Create(tspath); err != nil {
|
||||
return err
|
||||
} else {
|
||||
|
@ -1,11 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"srs.epita.fr/fic-server/libfic"
|
||||
)
|
||||
@ -15,55 +15,144 @@ var (
|
||||
)
|
||||
|
||||
type Walker struct {
|
||||
LastSync map[AirbusUserId]time.Time
|
||||
Exercices AirbusExercicesBindings
|
||||
Teams map[string]fic.ExportedTeam
|
||||
API AirbusAPI
|
||||
Coeff float64
|
||||
LastSync map[string]*TSValue
|
||||
Exercices AirbusExercicesBindings
|
||||
Teams map[string]fic.ExportedTeam
|
||||
RevTeams map[string]string
|
||||
TeamBindings map[string]*AirbusTeam
|
||||
API AirbusAPI
|
||||
Coeff float64
|
||||
}
|
||||
|
||||
func (w *Walker) fetchTeams() error {
|
||||
teams, err := w.API.GetTeams()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.RevTeams = map[string]string{}
|
||||
w.TeamBindings = map[string]*AirbusTeam{}
|
||||
|
||||
for tid, team := range w.Teams {
|
||||
for _, t := range teams {
|
||||
if team.Name == t.Name || team.ExternalId == t.Name {
|
||||
w.TeamBindings[tid] = &t
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := w.TeamBindings[tid]; !ok {
|
||||
log.Printf("Team binding not found: %s - %s", tid, team.Name)
|
||||
}
|
||||
|
||||
w.RevTeams[team.Name] = tid
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Walker) treat(path string) {
|
||||
mypath := filepath.Join(filepath.Dir(path), "my.json")
|
||||
if _, err := os.Stat(mypath); !os.IsNotExist(err) {
|
||||
// Read team ID
|
||||
fdmy, err := os.Open(mypath)
|
||||
if err != nil {
|
||||
log.Println("Unable to open my.json:", err)
|
||||
return
|
||||
}
|
||||
defer fdmy.Close()
|
||||
teamid := filepath.Base(filepath.Dir(path))
|
||||
|
||||
teammy, err := fic.ReadMyJSON(fdmy)
|
||||
if err != nil {
|
||||
log.Println("Unable to parse my.json:", err)
|
||||
return
|
||||
}
|
||||
|
||||
airbusTeamId := NewAirbusUserId(w.Teams[fmt.Sprintf("%d", teammy.Id)].ExternalId)
|
||||
|
||||
// Treat score grid
|
||||
/*err = w.TreatScoreGrid(path, airbusTeamId)
|
||||
if err != nil {
|
||||
log.Println("Unable to treat score grid:", err)
|
||||
return
|
||||
}*/
|
||||
|
||||
// Balance scores
|
||||
err = w.BalanceScore(int64(float64(teammy.Points)*w.Coeff), airbusTeamId)
|
||||
if err != nil {
|
||||
log.Println("Unable to balance score:", err)
|
||||
return
|
||||
}
|
||||
if _, ok := w.TeamBindings[teamid]; ok {
|
||||
w.TreatScoreGrid(path, w.TeamBindings[teamid])
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Walker) LoadScoreState(path string) (int64, error) {
|
||||
mypath := filepath.Join(filepath.Dir(path), "airbus.json")
|
||||
if _, err := os.Stat(mypath); os.IsNotExist(err) {
|
||||
fd, err := os.Create(mypath)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
fd.Write([]byte("0"))
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
fd, err := os.Open(mypath)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
var ret int64
|
||||
|
||||
jdec := json.NewDecoder(fd)
|
||||
if err := jdec.Decode(&ret); err != nil {
|
||||
return 0, fmt.Errorf("an error occurs when trying to decode airbus.json: %w", err)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (w *Walker) LoadScoreGrid(path string) ([]fic.ScoreGridRow, error) {
|
||||
fd, err := os.Open(filepath.Join(filepath.Dir(path), "scores.json"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
var ret []fic.ScoreGridRow
|
||||
|
||||
jdec := json.NewDecoder(fd)
|
||||
if err := jdec.Decode(&ret); err != nil {
|
||||
return nil, fmt.Errorf("an error occurs when trying to decode airbus.json: %w", err)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (w *Walker) WalkScoreSync(path string, d os.DirEntry, err error) error {
|
||||
if filepath.Base(path) == "scores.json" {
|
||||
w.treat(path)
|
||||
}
|
||||
|
||||
for team, ts := range w.LastSync {
|
||||
team_id, ok := w.RevTeams[team]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
myteam, err := w.loadMyFile(filepath.Join(TeamsDir, team_id, "my.json"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to open %s/my.json: %w", team_id, err)
|
||||
}
|
||||
|
||||
airbusTeam := w.TeamBindings[fmt.Sprintf("%d", myteam.Id)]
|
||||
|
||||
if ts.Score != myteam.Points*int64(w.Coeff) {
|
||||
err := w.API.AwardUser(airbusTeam, myteam.Points-ts.Score, "Équilibrage")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to open %s/my.json: %w", team, err)
|
||||
}
|
||||
|
||||
w.LastSync[airbusTeam.Name].Score = myteam.Points * int64(w.Coeff)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Walker) loadMyFile(path string) (*fic.MyTeam, error) {
|
||||
fd, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
var ret fic.MyTeam
|
||||
|
||||
jdec := json.NewDecoder(fd)
|
||||
if err := jdec.Decode(&ret); err != nil {
|
||||
return nil, fmt.Errorf("an error occurs when trying to decode airbus.json: %w", err)
|
||||
}
|
||||
|
||||
return &ret, nil
|
||||
}
|
||||
|
||||
func (w *Walker) WalkScore(path string, d os.DirEntry, err error) error {
|
||||
if filepath.Base(path) == "scores.json" {
|
||||
go w.treat(path)
|
||||
@ -71,7 +160,7 @@ func (w *Walker) WalkScore(path string, d os.DirEntry, err error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Walker) TreatScoreGrid(path string, airbusTeamId AirbusUserId) error {
|
||||
func (w *Walker) TreatScoreGrid(path string, airbusTeam *AirbusTeam) error {
|
||||
// Read score grid
|
||||
fdscores, err := os.Open(path)
|
||||
if err != nil {
|
||||
@ -85,30 +174,35 @@ func (w *Walker) TreatScoreGrid(path string, airbusTeamId AirbusUserId) error {
|
||||
}
|
||||
|
||||
// Found all new entries
|
||||
maxts := w.LastSync[airbusTeamId]
|
||||
maxts := &TSValue{}
|
||||
if ts, ok := w.LastSync[airbusTeam.Name]; ok {
|
||||
maxts = ts
|
||||
}
|
||||
for _, row := range teamscores {
|
||||
if row.Time.After(maxts) {
|
||||
maxts = row.Time
|
||||
if row.Time.After(maxts.Time) {
|
||||
maxts.Time = row.Time
|
||||
}
|
||||
if row.Time.After(w.LastSync[airbusTeamId]) {
|
||||
if row.Time.After(w.LastSync[airbusTeam.Name].Time) {
|
||||
if !noValidateChallenge && row.Reason == "Validation" {
|
||||
err = w.API.ValidateChallengeFromUser(airbusTeamId, w.Exercices[row.IdExercice])
|
||||
err = w.API.ValidateChallengeFromUser(airbusTeam, w.Exercices[row.IdExercice])
|
||||
} else {
|
||||
err = w.API.AwardUser(airbusTeamId, int64(row.Points*row.Coeff*w.Coeff), row.Reason)
|
||||
err = w.API.AwardUser(airbusTeam, int64(row.Points*row.Coeff*w.Coeff), row.Reason)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
maxts.Score += int64(row.Points * row.Coeff * w.Coeff)
|
||||
}
|
||||
}
|
||||
|
||||
w.LastSync[airbusTeamId] = maxts
|
||||
w.LastSync[airbusTeam.Name] = maxts
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Walker) BalanceScore(score int64, airbusTeamId AirbusUserId) error {
|
||||
func (w *Walker) BalanceScore(score int64, airbusTeam *AirbusTeam) error {
|
||||
// Read current score on other platform
|
||||
stats, err := w.API.GetCurrentStats()
|
||||
if err != nil {
|
||||
@ -120,9 +214,9 @@ func (w *Walker) BalanceScore(score int64, airbusTeamId AirbusUserId) error {
|
||||
return fmt.Errorf("session not found")
|
||||
}
|
||||
|
||||
other_team := my_session.GetTeam(AirbusUUID(airbusTeamId))
|
||||
other_team := my_session.GetTeam(AirbusUUID(airbusTeam.Name))
|
||||
if other_team == nil {
|
||||
return fmt.Errorf("team %q not found", airbusTeamId)
|
||||
return fmt.Errorf("team %q not found", airbusTeam.Name)
|
||||
}
|
||||
|
||||
other_score := other_team.Score
|
||||
@ -130,7 +224,7 @@ func (w *Walker) BalanceScore(score int64, airbusTeamId AirbusUserId) error {
|
||||
// Send diff to the platform
|
||||
if other_score != score {
|
||||
diff := score - other_score
|
||||
return w.API.AwardUser(airbusTeamId, diff, "Équilibrage")
|
||||
return w.API.AwardUser(airbusTeam, diff, "Équilibrage")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
Loading…
x
Reference in New Issue
Block a user