From 493502e4870452d5f46404ec7c9784cbafa96822 Mon Sep 17 00:00:00 2001 From: Alexander Gorbunov Date: Wed, 22 Nov 2023 22:21:52 +0300 Subject: [PATCH] feat: valeria valuer builder (using api) --- .gitignore | 1 + .golangci.yml | 3 + cmd/ejik/main.go | 11 +-- config/config.go | 9 ++- polygon/polygon.go | 167 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 180 insertions(+), 11 deletions(-) create mode 100644 polygon/polygon.go diff --git a/.gitignore b/.gitignore index e14acb4..09743ad 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ go.work ejik config/config.json +valeria diff --git a/.golangci.yml b/.golangci.yml index 86e14e4..1517a8c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -19,3 +19,6 @@ linters: - nlreturn - goerr113 - exhaustruct + - godox + - varnamelen + - cyclop diff --git a/cmd/ejik/main.go b/cmd/ejik/main.go index 615ad3e..12f84c7 100644 --- a/cmd/ejik/main.go +++ b/cmd/ejik/main.go @@ -1,7 +1,6 @@ package main import ( - "fmt" "os" "github.com/Gornak40/algolymp/config" @@ -11,7 +10,7 @@ import ( ) func main() { - parser := argparse.NewParser("algolymp", "Algolymp contest manager") + parser := argparse.NewParser("ejik", "Ejudge contest config reloader") cIDArg := parser.Int("i", "cid", &argparse.Options{ Required: true, Help: "Ejudge contest ID", @@ -20,17 +19,11 @@ func main() { Required: false, Help: "Show full output of check contest settings", }) - confDir, _ := os.UserHomeDir() - configArg := parser.String("c", "config", &argparse.Options{ - Required: false, - Help: "JSON config path", - Default: fmt.Sprintf("%s/.config/algolymp/config.json", confDir), - }) if err := parser.Parse(os.Args); err != nil { logrus.WithError(err).Fatal("bad arguments") } - cfg := config.NewConfig(*configArg) + cfg := config.NewConfig() ejClient := ejudge.NewEjudge(&cfg.Ejudge) sid, err := ejClient.Login() diff --git a/config/config.go b/config/config.go index 57bb1b1..710c32d 100644 --- a/config/config.go +++ b/config/config.go @@ -2,17 +2,22 @@ package config import ( "encoding/json" + "fmt" "os" "github.com/Gornak40/algolymp/ejudge" + "github.com/Gornak40/algolymp/polygon" "github.com/sirupsen/logrus" ) type Config struct { - Ejudge ejudge.Config `json:"ejudge"` + Ejudge ejudge.Config `json:"ejudge"` + Polygon polygon.Config `json:"polygon"` } -func NewConfig(path string) *Config { +func NewConfig() *Config { + confDir, _ := os.UserHomeDir() + path := fmt.Sprintf("%s/.config/algolymp/config.json", confDir) data, err := os.ReadFile(path) if err != nil { logrus.WithError(err).Fatal("failed to read config") diff --git a/polygon/polygon.go b/polygon/polygon.go new file mode 100644 index 0000000..b8ebc3d --- /dev/null +++ b/polygon/polygon.go @@ -0,0 +1,167 @@ +package polygon + +import ( + "crypto/sha512" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "time" + + "github.com/sirupsen/logrus" +) + +const ( + sixSecretSymbols = "gorill" +) + +type Config struct { + URL string `json:"url"` + APIKey string `json:"apiKey"` + APISecret string `json:"apiSecret"` +} + +type Polygon struct { + cfg *Config +} + +func NewPolygon(cfg *Config) *Polygon { + return &Polygon{ + cfg: cfg, + } +} + +func (p *Polygon) makeQuery(method string, params url.Values) ([]byte, error) { + url, _ := url.JoinPath(p.cfg.URL, "api", method) + logrus.WithFields(logrus.Fields{"params": params.Encode()}).Info(method) + + params["apiKey"] = []string{p.cfg.APIKey} + params["time"] = []string{fmt.Sprint(time.Now().Unix())} + sig := fmt.Sprintf("%s/%s?%s#%s", sixSecretSymbols, method, params.Encode(), p.cfg.APISecret) + + b := sha512.Sum512([]byte(sig)) + hsh := hex.EncodeToString(b[:]) + params["apiSig"] = []string{sixSecretSymbols + hsh} + + url = fmt.Sprintf("%s?%s", url, params.Encode()) + resp, err := http.Get(url) //nolint:gosec,noctx // it's just get query, relax + if err != nil { + return nil, err + } + defer resp.Body.Close() + return io.ReadAll(resp.Body) +} + +type TestAnswer struct { + Index int `json:"index"` + Group string `json:"group"` + Points float32 `json:"points"` +} + +type GroupAnswer struct { + Name string `json:"name"` + PointsPolicy string `json:"pointsPolicy"` + FeedbackPolicy string `json:"feedbackPolicy"` + Dependencies []string `json:"dependencies"` + Tests int `json:"tests"` +} + +type Answer struct { + Status string `json:"status"` + Comment string `json:"comment"` + Result json.RawMessage `json:"result"` +} + +func (p *Polygon) getGroups(pID int) ([]GroupAnswer, error) { + data, err := p.makeQuery("problem.viewTestGroup", url.Values{ + "problemId": []string{fmt.Sprint(pID)}, + "testset": []string{"tests"}, + }) + if err != nil { + return nil, err + } + var ansG Answer + if err := json.Unmarshal(data, &ansG); err != nil { + return nil, err + } + if ansG.Status != "OK" { + return nil, errors.New(ansG.Comment) + } + var groups []GroupAnswer + _ = json.Unmarshal(ansG.Result, &groups) + return groups, nil +} + +func (p *Polygon) getTests(pID int) ([]TestAnswer, error) { + data, err := p.makeQuery("problem.tests", url.Values{ + "problemId": []string{fmt.Sprint(pID)}, + "testset": []string{"tests"}, + }) + if err != nil { + return nil, err + } + var ansT Answer + if err := json.Unmarshal(data, &ansT); err != nil { + return nil, err + } + if ansT.Status != "OK" { + return nil, errors.New(ansT.Comment) + } + var tests []TestAnswer + _ = json.Unmarshal(ansT.Result, &tests) + return tests, nil +} + +func (p *Polygon) GetValuer(pID int) error { + groups, err := p.getGroups(pID) + if err != nil { + return err + } + + tests, err := p.getTests(pID) + if err != nil { + return err + } + + score := map[string]int{} + count := map[string]int{} + first := map[string]int{} + last := map[string]int{} + + for _, t := range tests { + score[t.Group] += int(t.Points) // TODO: ensure ejudge doesn't support float points + count[t.Group]++ + if val, ok := first[t.Group]; !ok || val > t.Index { + first[t.Group] = t.Index + } + if val, ok := last[t.Group]; !ok || val < t.Index { + last[t.Group] = t.Index + } + } + + res := []string{} + for _, g := range groups { + if g.PointsPolicy != "COMPLETE_GROUP" { + return errors.New("test_score not supported yet") + } + if last[g.Name]-first[g.Name]+1 != count[g.Name] { + return errors.New("bad tests order, fix in polygon required") + } + cur := fmt.Sprintf("group %s {\n\ttests %d-%d;\n\tscore %d;\n", + g.Name, first[g.Name], last[g.Name], score[g.Name]) + if len(g.Dependencies) != 0 { + cur += fmt.Sprintf("\trequires %s;\n", strings.Join(g.Dependencies, ",")) + } + cur += "}\n" + res = append(res, cur) + } + + valuer := strings.Join(res, "\n") + logrus.Info(valuer) + + return nil +}