feat: another big update

1. add consumer
2. add createVersion
3. add upload
4. fix runner
5. add rejudge

Co-authored-by: cxy004 <cxy004@qq.com>
Co-authored-by: wzt <w.zhongtao@qq.com>
This commit is contained in:
Paul Pan 2022-10-23 17:29:35 +08:00
parent d42ee0ce54
commit 26a81652b3
52 changed files with 729 additions and 184 deletions

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
### Project
/tmp
/server
/runner
my.secrets

View File

@ -21,6 +21,13 @@ Database:
MaxIdleConns: 60
ConnMaxLifetime: 60
Storage:
Endpoint: '127.0.0.1:9000'
UseSSL: false
AccessKey: 'EHd5Zj56QrTivhFI'
SecretKey: 'FUHy4RW1mn0Kbr5pibDZ6R2F9116FZKY'
Bucket: 'woj'
Metrics:
Namespace: 'OJ'
Subsystem: 'server'

13
go.mod
View File

@ -11,13 +11,14 @@ require (
github.com/golang-jwt/jwt/v4 v4.4.2
github.com/hibiken/asynq v0.23.0
github.com/jackc/pgtype v1.11.0
github.com/minio/minio-go/v7 v7.0.42
github.com/prometheus/client_golang v1.13.0
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a
github.com/swaggo/gin-swagger v1.5.3
github.com/swaggo/swag v1.8.5
github.com/urfave/cli/v2 v2.14.1
go.uber.org/zap v1.23.0
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
golang.org/x/text v0.3.7
gopkg.in/yaml.v3 v3.0.1
gorm.io/driver/postgres v1.3.9
@ -33,6 +34,7 @@ require (
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.6 // indirect
@ -55,10 +57,14 @@ require (
github.com/jinzhu/now v1.1.4 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.15.9 // indirect
github.com/klauspost/cpuid/v2 v2.1.0 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/minio/sha256-simd v1.0.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
@ -66,16 +72,19 @@ require (
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/rs/xid v1.4.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/ugorji/go/codec v1.2.7 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.7.0 // indirect
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
golang.org/x/tools v0.1.10 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/ini.v1 v1.66.6 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

28
go.sum
View File

@ -76,6 +76,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@ -271,6 +273,12 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY=
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.1.0 h1:eyi1Ad2aNJMW95zcSbmGg7Cg6cq3ADwLpMAP96d8rF0=
github.com/klauspost/cpuid/v2 v2.1.0/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@ -305,6 +313,12 @@ github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.42 h1:fP56plNR/Tkw/+Xczw9NL5TGxe5gJDvgd8LidNR3BEI=
github.com/minio/minio-go/v7 v7.0.42/go.mod h1:nCrRzjoSUQh8hgKKtu3Y708OLvRLtuASMg2/nvmbarw=
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -371,6 +385,8 @@ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@ -385,6 +401,8 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
@ -467,8 +485,9 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -541,8 +560,9 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -616,6 +636,8 @@ golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI=
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
@ -781,6 +803,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/ini.v1 v1.66.6 h1:LATuAqN/shcYAOkv3wl2L4rkaKqkcgTBQjOyYDvcPKI=
gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -0,0 +1,36 @@
package consumer
import (
"context"
"github.com/WHUPRJ/woj-server/internal/global"
"github.com/WHUPRJ/woj-server/internal/service/problem"
"github.com/WHUPRJ/woj-server/internal/service/status"
"github.com/WHUPRJ/woj-server/internal/service/task"
"github.com/hibiken/asynq"
"go.uber.org/zap"
)
var _ Handler = (*handler)(nil)
type Handler interface {
ProblemUpdate(_ context.Context, t *asynq.Task) error
SubmitUpdate(_ context.Context, t *asynq.Task) error
}
type handler struct {
log *zap.Logger
problemService problem.Service
statusService status.Service
taskService task.Service
}
func NewConsumer(g *global.Global) Handler {
hnd := &handler{
log: g.Log,
problemService: problem.NewService(g),
statusService: status.NewService(g),
taskService: task.NewService(g),
}
return hnd
}

View File

@ -0,0 +1,40 @@
package consumer
import (
"context"
"encoding/json"
"fmt"
"github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/model"
"github.com/hibiken/asynq"
"github.com/jackc/pgtype"
"go.uber.org/zap"
)
func (h *handler) ProblemUpdate(_ context.Context, t *asynq.Task) error {
p := new(model.ProblemUpdatePayload)
if err := json.Unmarshal(t.Payload(), &p); err != nil {
return fmt.Errorf("json.Unmarshal failed: %v: %w", err, asynq.SkipRetry)
}
if p.Status != e.Success {
h.log.Warn("RunnerError", zap.Any("payload", p))
return nil
}
status := h.problemService.UpdateVersion(
p.ProblemVersionID,
map[string]interface{}{
"Context": pgtype.JSON{
Bytes: []byte(p.Context),
Status: pgtype.Present,
},
"IsEnabled": true,
},
)
if status != e.Success {
return fmt.Errorf(status.String())
}
return nil
}

View File

@ -0,0 +1,37 @@
package consumer
import (
"context"
"encoding/json"
"fmt"
"github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/model"
"github.com/WHUPRJ/woj-server/internal/service/status"
"github.com/hibiken/asynq"
"go.uber.org/zap"
)
func (h *handler) SubmitUpdate(_ context.Context, t *asynq.Task) error {
p := new(model.SubmitUpdatePayload)
if err := json.Unmarshal(t.Payload(), &p); err != nil {
return fmt.Errorf("json.Unmarshal failed: %v: %w", err, asynq.SkipRetry)
}
if p.Status != e.Success {
h.log.Warn("RunnerError", zap.Any("payload", p))
return nil
}
createData := &status.CreateData{
SubmissionID: p.SubmissionID,
ProblemVersionID: p.ProblemVersionID,
Context: p.Context,
Point: p.Point,
}
_, eStatus := h.statusService.Create(createData)
if eStatus != e.Success {
return fmt.Errorf(eStatus.String())
}
return nil
}

View File

@ -0,0 +1,67 @@
package problem
import (
"github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/global"
"github.com/WHUPRJ/woj-server/internal/model"
"github.com/WHUPRJ/woj-server/internal/service/problem"
"github.com/gin-gonic/gin"
)
type createVersionRequest struct {
ProblemID uint `form:"pid" binding:"required"`
StorageKey string `form:"storage_key" binding:"required"`
}
// CreateVersion
// @Summary create a problem version
// @Description create a problem version
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param pid formData int true "problem id"
// @Param storage_key formData string true "storage key"
// @Response 200 {object} e.Response ""
// @Security Authentication
// @Router /v1/problem/create_version [post]
func (h *handler) CreateVersion(c *gin.Context) {
claim, exist := c.Get("claim")
if !exist {
e.Pong(c, e.UserUnauthenticated, nil)
return
}
// uid := claim.(*global.Claim).UID
role := claim.(*global.Claim).Role
req := new(createVersionRequest)
if err := c.ShouldBind(req); err != nil {
e.Pong(c, e.InvalidParameter, err.Error())
return
}
// guest can not submit
if role < model.RoleAdmin {
e.Pong(c, e.UserUnauthorized, nil)
return
}
// TODO: check pid exist
createVersionData := &problem.CreateVersionData{
ProblemID: req.ProblemID,
StorageKey: req.StorageKey,
}
pv, status := h.problemService.CreateVersion(createVersionData)
if status != e.Success {
e.Pong(c, status, nil)
return
}
payload := &model.ProblemBuildPayload{
ProblemVersionID: pv.ID,
StorageKey: pv.StorageKey,
}
_, status = h.taskService.ProblemBuild(payload)
e.Pong(c, status, nil)
}

View File

@ -37,9 +37,11 @@ func (h *handler) Details(c *gin.Context) {
}
pv, status := h.problemService.QueryLatestVersion(req.Pid)
e.Pong(c, status, gin.H{
"problem": p,
"context": pv.Context,
})
return
if status != e.Success {
e.Pong(c, status, nil)
}
e.Pong(c, e.Success, gin.H{
"problem": p,
"context": pv.Context.Get(),
})
}

View File

@ -3,6 +3,8 @@ package problem
import (
"github.com/WHUPRJ/woj-server/internal/global"
"github.com/WHUPRJ/woj-server/internal/service/problem"
"github.com/WHUPRJ/woj-server/internal/service/storage"
"github.com/WHUPRJ/woj-server/internal/service/task"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
@ -13,12 +15,15 @@ type Handler interface {
Details(c *gin.Context)
Search(c *gin.Context)
Update(c *gin.Context)
Upload(c *gin.Context)
}
type handler struct {
log *zap.Logger
jwtService global.JwtService
problemService problem.Service
taskService task.Service
storageService storage.Service
}
func RouteRegister(g *global.Global, group *gin.RouterGroup) {
@ -26,9 +31,13 @@ func RouteRegister(g *global.Global, group *gin.RouterGroup) {
log: g.Log,
jwtService: g.Jwt,
problemService: problem.NewService(g),
taskService: task.NewService(g),
storageService: storage.NewService(g),
}
group.POST("/details", app.jwtService.Handler(false), app.Details)
group.POST("/search", app.Search)
group.POST("/details", app.jwtService.Handler(false), app.Details)
group.POST("/update", app.jwtService.Handler(true), app.Update)
group.POST("/upload", app.jwtService.Handler(true), app.Upload)
group.POST("/create_version", app.jwtService.Handler(true), app.CreateVersion)
}

View File

@ -28,12 +28,12 @@ func (h *handler) Search(c *gin.Context) {
// TODO: pagination
if req.Search == "" {
// TODO: query without LIKE
problem, status := h.problemService.QueryFuzz(req.Search, true, true)
e.Pong(c, status, problem)
problems, status := h.problemService.QueryFuzz(req.Search, true, true)
e.Pong(c, status, problems)
return
} else {
problem, status := h.problemService.QueryFuzz(req.Search, true, true)
e.Pong(c, status, problem)
problems, status := h.problemService.QueryFuzz(req.Search, true, true)
e.Pong(c, status, problems)
return
}
}

View File

@ -0,0 +1,44 @@
package problem
import (
"github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/global"
"github.com/WHUPRJ/woj-server/internal/model"
"github.com/WHUPRJ/woj-server/pkg/utils"
"github.com/gin-gonic/gin"
"time"
)
// Upload
// @Summary get upload url
// @Description get upload url
// @Produce json
// @Response 200 {object} e.Response "upload url and key"
// @Security Authentication
// @Router /v1/problem/upload [post]
func (h *handler) Upload(c *gin.Context) {
claim, exist := c.Get("claim")
if !exist {
e.Pong(c, e.UserUnauthenticated, nil)
return
}
role := claim.(*global.Claim).Role
if role < model.RoleAdmin {
e.Pong(c, e.UserUnauthorized, nil)
return
}
key := utils.RandomString(16)
url, status := h.storageService.Upload(key, time.Second*60*60)
if status != e.Success {
e.Pong(c, status, nil)
return
}
e.Pong(c, e.Success, gin.H{
"key": key,
"url": url,
})
}

View File

@ -4,14 +4,14 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/model"
"github.com/hibiken/asynq"
"go.uber.org/zap"
"time"
)
func (h *handler) Build(_ context.Context, t *asynq.Task) error {
// TODO: configure timeout with context
var p model.ProblemBuildPayload
if err := json.Unmarshal(t.Payload(), &p); err != nil {
return fmt.Errorf("json.Unmarshal failed: %v: %w", err, asynq.SkipRetry)
@ -19,7 +19,16 @@ func (h *handler) Build(_ context.Context, t *asynq.Task) error {
h.log.Info("build", zap.Any("payload", p))
config, status := h.runnerService.NewProblem(p.ProblemVersionID, p.ProblemFile)
status, ctx := func() (e.Status, string) {
url, status := h.storageService.Get(p.StorageKey, time.Second*60*5)
if status != e.Success {
return e.InternalError, "{}"
}
config, status := h.runnerService.NewProblem(p.ProblemVersionID, url, true)
if status != e.Success {
return e.InternalError, "{}"
}
for i := range config.Languages {
config.Languages[i].Type = ""
@ -28,7 +37,14 @@ func (h *handler) Build(_ context.Context, t *asynq.Task) error {
}
b, _ := json.Marshal(config)
h.taskService.ProblemUpdate(status, p.ProblemVersionID, string(b))
return e.Success, string(b)
}()
h.taskService.ProblemUpdate(&model.ProblemUpdatePayload{
Status: status,
ProblemVersionID: p.ProblemVersionID,
Context: ctx,
})
return nil
}

View File

@ -6,6 +6,7 @@ import (
"github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/global"
"github.com/WHUPRJ/woj-server/internal/service/runner"
"github.com/WHUPRJ/woj-server/internal/service/storage"
"github.com/WHUPRJ/woj-server/internal/service/task"
"github.com/hibiken/asynq"
"go.uber.org/zap"
@ -22,6 +23,7 @@ type handler struct {
log *zap.Logger
runnerService runner.Service
taskService task.Service
storageService storage.Service
}
func NewRunner(g *global.Global) (Handler, error) {
@ -29,6 +31,7 @@ func NewRunner(g *global.Global) (Handler, error) {
log: g.Log,
runnerService: runner.NewService(g),
taskService: task.NewService(g),
storageService: storage.NewService(g),
}
status := hnd.runnerService.EnsureDeps(false)

View File

@ -11,6 +11,7 @@ import (
"github.com/hibiken/asynq"
"go.uber.org/zap"
"path/filepath"
"time"
)
func (h *handler) Judge(_ context.Context, t *asynq.Task) error {
@ -22,52 +23,55 @@ func (h *handler) Judge(_ context.Context, t *asynq.Task) error {
user := utils.RandomString(16)
h.log.Info("judge", zap.Any("payload", p), zap.String("user", user))
// common
status, point, ctx := func() (e.Status, int32, runner.JudgeStatus) {
systemError := runner.JudgeStatus{Message: "System Error"}
// write code
// 1. write user code
userCode := filepath.Join(runner.UserDir, user, fmt.Sprintf("%s.%s", user, p.Submission.Language))
if !utils.FileTouch(userCode) {
h.log.Info("Touch file failed", zap.String("userCode", userCode))
h.taskService.SubmitUpdate(e.InternalError, p.ProblemVersionId, 0, systemError)
return nil
return e.InternalError, 0, systemError
}
err := utils.FileWrite(userCode, []byte(p.Submission.Code))
if err != nil {
h.log.Info("Write file failed", zap.String("code", p.Submission.Code))
h.taskService.SubmitUpdate(e.InternalError, p.ProblemVersionId, 0, systemError)
return nil
return e.InternalError, 0, systemError
}
// compile
result, status := h.runnerService.Compile(p.ProblemVersionId, user, p.Submission.Language)
if status == e.RunnerProblemNotExist {
_, status := h.runnerService.NewProblem(p.ProblemVersionId, p.StorageKey)
// 2. check problem
if !h.runnerService.ProblemExists(p.ProblemVersionID) {
url, status := h.storageService.Get(p.StorageKey, time.Second*60*5)
if status != e.Success {
h.log.Warn("download problem failed",
zap.Any("status", status),
zap.Uint("pvid", p.ProblemVersionId),
zap.String("storageKey", p.StorageKey))
h.taskService.SubmitUpdate(status, p.ProblemVersionId, 0, systemError)
return nil
}
} else if status != e.Success {
h.taskService.SubmitUpdate(status, p.Submission.ID, 0, result)
return nil
return e.InternalError, 0, systemError
}
// config
config, err := h.runnerService.ParseConfig(p.ProblemVersionId, true)
_, status = h.runnerService.NewProblem(p.ProblemVersionID, url, false)
if status != e.Success {
return e.InternalError, 0, systemError
}
}
// 3. compile
compileResult, status := h.runnerService.Compile(p.ProblemVersionID, user, p.Submission.Language)
if status != e.Success {
return e.Success, 0, compileResult
}
// 4. config
config, err := h.runnerService.ParseConfig(p.ProblemVersionID, true)
if err != nil {
h.log.Info("parse config failed", zap.Error(err), zap.Uint("pvid", p.ProblemVersionId))
h.taskService.SubmitUpdate(e.InternalError, p.ProblemVersionId, 0, systemError)
return nil
return e.InternalError, 0, systemError
}
// run
var points int32
result, points, status = h.runnerService.RunAndJudge(p.ProblemVersionId, user, p.Submission.Language, &config)
h.taskService.SubmitUpdate(status, p.Submission.ID, points, result)
// 5. run and judge
result, point, status := h.runnerService.RunAndJudge(p.ProblemVersionID, user, p.Submission.Language, &config)
return utils.If(status != e.Success, e.InternalError, e.Success).(e.Status), point, result
}()
h.taskService.SubmitUpdate(&model.SubmitUpdatePayload{
Status: status,
SubmissionID: p.Submission.ID,
ProblemVersionID: p.ProblemVersionID,
Point: point,
}, ctx)
return nil
}

View File

@ -6,9 +6,17 @@ import (
)
type queryRequest struct {
SubmissionID uint `form:"sid"`
SubmissionID uint `form:"sid" binding:"required"`
}
// Query
// @Summary query submissions by via submission id
// @Description query submissions by via submission id
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param sid formData uint true "submission id"
// @Response 200 {object} e.Response "model.status"
// @Router /v1/status/query [post]
func (h *handler) Query(c *gin.Context) {
req := new(queryRequest)
@ -20,5 +28,4 @@ func (h *handler) Query(c *gin.Context) {
status, eStatus := h.statusService.Query(req.SubmissionID, true)
e.Pong(c, eStatus, status)
return
}

View File

@ -8,11 +8,21 @@ import (
)
type queryByVersionRequest struct {
ProblemVersionID uint `form:"pvid"`
ProblemVersionID uint `form:"pvid" binding:"required"`
Offset int `form:"offset"`
Limit int `form:"limit"`
Limit int `form:"limit" binding:"required"`
}
// QueryByProblemVersion
// @Summary query submissions by problem version (admin only)
// @Description query submissions by problem version (admin only)
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param pvid formData uint true "problem version id"
// @Param offset formData int true "start position"
// @Param limit formData int true "limit number of records"
// @Response 200 {object} e.Response "[]*model.status"
// @Router /v1/status/query/problem_version [post]
func (h *handler) QueryByProblemVersion(c *gin.Context) {
claim, exist := c.Get("claim")
@ -26,7 +36,7 @@ func (h *handler) QueryByProblemVersion(c *gin.Context) {
req := new(queryByVersionRequest)
if err := c.ShouldBind(req); err != nil {
e.Pong(c, e.InvalidParameter, nil)
e.Pong(c, e.InvalidParameter, err.Error())
return
}
@ -38,5 +48,4 @@ func (h *handler) QueryByProblemVersion(c *gin.Context) {
statuses, eStatus := h.statusService.QueryByVersion(req.ProblemVersionID, req.Offset, req.Limit)
e.Pong(c, eStatus, statuses)
return
}

View File

@ -3,6 +3,7 @@ package submission
import (
"github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/global"
"github.com/WHUPRJ/woj-server/internal/model"
"github.com/WHUPRJ/woj-server/internal/service/submission"
"github.com/gin-gonic/gin"
)
@ -10,7 +11,7 @@ import (
type createRequest struct {
Pid uint `form:"pid" binding:"required"`
Language string `form:"language" binding:"required"`
Code string `form:"statement" binding:"required"`
Code string `form:"code" binding:"required"`
}
// Create
@ -33,6 +34,7 @@ func (h *handler) Create(c *gin.Context) {
uid := claim.(*global.Claim).UID
role := claim.(*global.Claim).Role
req := new(createRequest)
if err := c.ShouldBind(req); err != nil {
@ -40,6 +42,12 @@ func (h *handler) Create(c *gin.Context) {
return
}
// guest can not submit
if role < model.RoleGeneral {
e.Pong(c, e.UserUnauthorized, nil)
return
}
createData := &submission.CreateData{
ProblemID: req.Pid,
UserID: uid,
@ -58,7 +66,12 @@ func (h *handler) Create(c *gin.Context) {
return
}
_, status = h.taskService.SubmitJudge(pv.ID, pv.StorageKey, *s)
e.Pong(c, status, nil)
return
payload := &model.SubmitJudgePayload{
ProblemVersionID: pv.ID,
StorageKey: pv.StorageKey,
Submission: *s,
}
_, status = h.taskService.SubmitJudge(payload)
e.Pong(c, status, nil)
}

View File

@ -15,6 +15,7 @@ var _ Handler = (*handler)(nil)
type Handler interface {
Create(c *gin.Context)
Query(c *gin.Context)
Rejudge(c *gin.Context)
}
type handler struct {
@ -38,4 +39,5 @@ func RouteRegister(g *global.Global, group *gin.RouterGroup) {
group.POST("/create", app.jwtService.Handler(true), app.Create)
group.POST("/query", app.Query)
group.POST("/rejudge", app.jwtService.Handler(true), app.Rejudge)
}

View File

@ -10,7 +10,7 @@ type queryRequest struct {
Pid uint `form:"pid"`
Uid uint `form:"uid"`
Offset int `form:"offset"`
Limit int `form:"limit"`
Limit int `form:"limit" binding:"required"`
}
type queryResponse struct {
@ -23,10 +23,10 @@ type queryResponse struct {
// @Description Query submissions
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param pid formData uint false "problem id"
// @Param uid formData uint false "user id"
// @Param offset formData int false "start position"
// @Param limit formData int false "limit number of records"
// @Param pid formData uint true "problem id"
// @Param uid formData uint true "user id"
// @Param offset formData int true "start position"
// @Param limit formData int true "limit number of records"
// @Response 200 {object} e.Response "queryResponse"
// @Router /v1/submission/query [post]
@ -67,7 +67,5 @@ func (h *handler) Query(c *gin.Context) {
response = append(response, newResponse)
}
e.Pong(c, status, submissions)
return
e.Pong(c, status, response)
}

View File

@ -0,0 +1,63 @@
package submission
import (
"github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/global"
"github.com/WHUPRJ/woj-server/internal/model"
"github.com/gin-gonic/gin"
)
type rejudgeRequest struct {
Sid uint `form:"sid" binding:"required"`
}
// Rejudge
// @Summary rejudge a submission
// @Description rejudge a submission
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param sid formData int true "submission id"
// @Response 200 {object} e.Response ""
// @Security Authentication
// @Router /v1/submission/rejudge [post]
func (h *handler) Rejudge(c *gin.Context) {
claim, exist := c.Get("claim")
if !exist {
e.Pong(c, e.UserUnauthenticated, nil)
return
}
role := claim.(*global.Claim).Role
req := new(rejudgeRequest)
if err := c.ShouldBind(req); err != nil {
e.Pong(c, e.InvalidParameter, err.Error())
return
}
// only admin can rejudge
if role < model.RoleAdmin {
e.Pong(c, e.UserUnauthorized, nil)
return
}
s, status := h.submissionService.QueryBySid(req.Sid, false)
if status != e.Success {
e.Pong(c, status, nil)
return
}
pv, status := h.problemService.QueryLatestVersion(s.ProblemID)
if status != e.Success {
e.Pong(c, status, nil)
return
}
_, status = h.taskService.SubmitJudge(&model.SubmitJudgePayload{
ProblemVersionID: pv.ID,
StorageKey: pv.StorageKey,
Submission: *s,
})
e.Pong(c, status, nil)
}

View File

@ -55,5 +55,4 @@ func (h *handler) Create(c *gin.Context) {
}
token, status := h.jwtService.SignClaim(claim)
e.Pong(c, status, token)
return
}

View File

@ -23,5 +23,4 @@ func (h *handler) Logout(c *gin.Context) {
_, status := h.userService.IncrVersion(claim.(*global.Claim).UID)
e.Pong(c, status, nil)
return
}

View File

@ -47,6 +47,8 @@ func (h *handler) Profile(c *gin.Context) {
}
user, status := h.userService.Profile(req.UID)
// TODO: >= admin can see is_enable
e.Pong(c, status, user)
return
}

View File

@ -3,6 +3,7 @@ package server
import (
"context"
"fmt"
"github.com/WHUPRJ/woj-server/internal/api/consumer"
"github.com/WHUPRJ/woj-server/internal/global"
"github.com/WHUPRJ/woj-server/internal/model"
"github.com/WHUPRJ/woj-server/internal/repo/postgresql"
@ -52,9 +53,11 @@ func RunServer(g *global.Global) error {
// Create Queue
queueMux := asynq.NewServeMux()
// TODO: fill
queueMux.HandleFunc(model.TypeProblemUpdate, func(ctx context.Context, t *asynq.Task) error { return nil })
queueMux.HandleFunc(model.TypeSubmitUpdate, func(ctx context.Context, t *asynq.Task) error { return nil })
{
handler := consumer.NewConsumer(g)
queueMux.HandleFunc(model.TypeProblemUpdate, handler.ProblemUpdate)
queueMux.HandleFunc(model.TypeSubmitUpdate, handler.SubmitUpdate)
}
queueSrv := asynq.NewServer(
asynq.RedisClientOpt{
Addr: g.Conf.Redis.Address,

View File

@ -37,6 +37,7 @@ const (
ProblemNotAvailable
ProblemVersionNotFound
ProblemVersionNotAvailable
SubmissionNotFound
StatusNotFound
)
@ -58,6 +59,11 @@ const (
RunnerJudgeFailed
)
const (
StorageUploadFailed Status = 800 + iota
StorageGetFailed
)
var msgText = map[Status]string{
Success: "Success",
Unknown: "Unknown error",
@ -88,6 +94,8 @@ var msgText = map[Status]string{
ProblemVersionNotFound: "Problem Version Not Found",
ProblemVersionNotAvailable: "Problem Version Not Available",
SubmissionNotFound: "Submission Not Found",
StatusNotFound: "Status Not Found",
TaskEnqueueFailed: "Task Enqueue Failed",
@ -103,4 +111,7 @@ var msgText = map[Status]string{
RunnerUserCompileFailed: "Runner User Compile Failed",
RunnerRunFailed: "Runner Run Failed",
RunnerJudgeFailed: "Runner Judge Failed",
StorageUploadFailed: "Storage Upload Failed",
StorageGetFailed: "Storage Get Failed",
}

View File

@ -24,3 +24,12 @@ func Pong(c *gin.Context, status Status, body interface{}) {
c.Set("err", status)
c.JSON(http.StatusOK, Wrap(status, body))
}
type Endpoint func(*gin.Context) (Status, interface{})
func PongWrapper(handler Endpoint) func(*gin.Context) {
return func(c *gin.Context) {
status, body := handler(c)
Pong(c, status, body)
}
}

View File

@ -26,6 +26,14 @@ type ConfigDatabase struct {
ConnMaxLifetime int `yaml:"ConnMaxLifetime"`
}
type ConfigStorage struct {
Endpoint string `yaml:"Endpoint"`
UseSSL bool `yaml:"UseSSL"`
AccessKey string `yaml:"AccessKey"`
SecretKey string `yaml:"SecretKey"`
Bucket string `yaml:"Bucket"`
}
type ConfigMetrics struct {
Namespace string `yaml:"Namespace"`
Subsystem string `yaml:"Subsystem"`
@ -35,6 +43,7 @@ type Config struct {
WebServer ConfigWebServer `yaml:"WebServer"`
Redis ConfigRedis `yaml:"Redis"`
Database ConfigDatabase `yaml:"Database"`
Storage ConfigStorage `yaml:"Storage"`
Metrics ConfigMetrics `yaml:"Metrics"`
Development bool `yaml:"Development"`
}

View File

@ -18,7 +18,7 @@ const (
type ProblemBuildPayload struct {
ProblemVersionID uint
ProblemFile string
StorageKey string
}
type ProblemUpdatePayload struct {
@ -28,14 +28,15 @@ type ProblemUpdatePayload struct {
}
type SubmitJudgePayload struct {
ProblemVersionId uint
ProblemVersionID uint
StorageKey string
Submission Submission
}
type SubmitUpdatePayload struct {
Status e.Status
Sid uint
SubmissionID uint
ProblemVersionID uint
Point int32
Context string
}

View File

@ -83,7 +83,9 @@ func (r *Repo) migrateDatabase() {
_ = r.db.AutoMigrate(&model.User{})
_ = r.db.AutoMigrate(&model.Problem{})
_ = r.db.AutoMigrate(&model.ProblemVersion{})
_ = r.db.AutoMigrate(&model.Submission{})
_ = r.db.AutoMigrate(&model.Status{})
}
// checkAlive deprecated

View File

@ -3,6 +3,7 @@ package problem
import (
"github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/model"
"github.com/jackc/pgtype"
"go.uber.org/zap"
)
@ -14,6 +15,7 @@ type CreateVersionData struct {
func (s *service) CreateVersion(data *CreateVersionData) (*model.ProblemVersion, e.Status) {
problemVersion := &model.ProblemVersion{
ProblemID: data.ProblemID,
Context: pgtype.JSON{Status: pgtype.Null},
StorageKey: data.StorageKey,
}

View File

@ -17,7 +17,7 @@ type Service interface {
QueryFuzz(search string, associations bool, shouldEnable bool) ([]*model.Problem, e.Status)
CreateVersion(data *CreateVersionData) (*model.ProblemVersion, e.Status)
UpdateVersion(problemVersion *model.ProblemVersion) (*model.ProblemVersion, e.Status)
UpdateVersion(pvid uint, values interface{}) e.Status
QueryVersion(pvid uint, shouldEnable bool) (*model.ProblemVersion, e.Status)
QueryLatestVersion(pid uint) (*model.ProblemVersion, e.Status)
}

View File

@ -6,12 +6,12 @@ import (
"go.uber.org/zap"
)
func (s *service) UpdateVersion(problemVersion *model.ProblemVersion) (*model.ProblemVersion, e.Status) {
err := s.db.Save(problemVersion).Error
func (s *service) UpdateVersion(pvid uint, values interface{}) e.Status {
err := s.db.Model(&model.ProblemVersion{}).Where("id = ?", pvid).Updates(values).Error
if err != nil {
s.log.Warn("DatabaseError", zap.Error(err), zap.Any("problemVersion", problemVersion))
return nil, e.DatabaseError
s.log.Warn("DatabaseError", zap.Error(err), zap.Any("pvid", pvid), zap.Any("values", values))
return e.DatabaseError
}
return problemVersion, e.Success
return e.Success
}

View File

@ -13,9 +13,9 @@ import (
var (
Prefix = "./resource/runner"
ProblemDir = "./problems/"
ProblemDir = "./problem/"
ScriptsDir = "./scripts/"
UserDir = "./users/"
UserDir = "./user/"
TmpDir = "./tmp/"
)
@ -40,7 +40,7 @@ func (s *service) execute(script string, args ...string) error {
}
func (s *service) checkAndExecute(version uint, user string, lang string, script string, fail e.Status) e.Status {
if !s.problemExists(version) {
if !s.ProblemExists(version) {
s.log.Info("problem not exists", zap.Uint("version", version))
return e.RunnerProblemNotExist
}
@ -50,7 +50,7 @@ func (s *service) checkAndExecute(version uint, user string, lang string, script
return e.RunnerUserNotExist
}
err := s.execute(script, fmt.Sprintf("%d", version), fmt.Sprintf("%s", user), lang)
err := s.execute(script, fmt.Sprintf("%d", version), user, lang)
if err != nil {
s.log.Info("execute failed",
@ -64,12 +64,12 @@ func (s *service) checkAndExecute(version uint, user string, lang string, script
return e.Success
}
func (s *service) problemExists(version uint) bool {
func (s *service) ProblemExists(version uint) bool {
problemPath := filepath.Join(ProblemDir, fmt.Sprintf("%d", version))
return utils.FileExist(problemPath)
}
func (s *service) userExists(user string, file string) bool {
userPath := filepath.Join(UserDir, fmt.Sprintf("%s", user), file)
userPath := filepath.Join(UserDir, user, file)
return utils.FileExist(userPath)
}

View File

@ -9,22 +9,22 @@ import (
)
func (s *service) Compile(version uint, user string, lang string) (JudgeStatus, e.Status) {
target := filepath.Join(UserDir, fmt.Sprintf("%s", user), fmt.Sprintf("%s.out", user))
target := filepath.Join(UserDir, user, fmt.Sprintf("%s.out", user))
_ = os.Remove(target)
status := s.checkAndExecute(version, user, lang, "problem_compile.sh", e.RunnerUserCompileFailed)
log := filepath.Join(UserDir, fmt.Sprintf("%s.compile.log", user))
log := filepath.Join(UserDir, user, fmt.Sprintf("%s.compile.log", user))
msg, err := utils.FileRead(log)
msg = utils.If(err == nil, msg, nil).([]byte)
msgText := string(msg)
if utils.FileExist(target) {
return JudgeStatus{}, e.Success
} else {
if !utils.FileExist(target) || utils.FileEmpty(target) {
return JudgeStatus{
Message: "compile failed",
Tasks: []TaskStatus{{Verdict: VerdictCompileError, Message: msgText}}},
utils.If(status == e.Success, e.RunnerUserCompileFailed, status).(e.Status)
}
return JudgeStatus{}, e.Success
}

View File

@ -31,7 +31,7 @@ func (s *service) download(version uint, url string) e.Status {
}
func (s *service) prebuild(version uint, force bool) e.Status {
if !s.problemExists(version) {
if !s.ProblemExists(version) {
return e.RunnerProblemNotExist
}
@ -52,18 +52,25 @@ func (s *service) prebuild(version uint, force bool) e.Status {
return e.Success
}
func (s *service) NewProblem(version uint, url string) (Config, e.Status) {
func (s *service) NewProblem(version uint, url string, force bool) (Config, e.Status) {
if force {
problemPath := filepath.Join(ProblemDir, fmt.Sprintf("%d", version))
_ = os.RemoveAll(problemPath)
}
if !s.ProblemExists(version) {
status := s.download(version, url)
if status != e.Success {
return Config{}, status
}
}
cfg, err := s.ParseConfig(version, false)
if err != nil {
return Config{}, e.RunnerProblemParseFailed
}
status = s.prebuild(version, true)
status := s.prebuild(version, true)
if status != e.Success {
return Config{}, status
}

View File

@ -12,7 +12,7 @@ type Service interface {
// EnsureDeps build docker images
EnsureDeps(force bool) e.Status
// NewProblem = Download + Parse + Prebuild
NewProblem(version uint, url string) (Config, e.Status)
NewProblem(version uint, url string, force bool) (Config, e.Status)
// Compile compile user submission
Compile(version uint, user string, lang string) (JudgeStatus, e.Status)
@ -21,6 +21,8 @@ type Service interface {
// ParseConfig parse config file
ParseConfig(version uint, skipCheck bool) (Config, error)
// ProblemExists check if problem exists
ProblemExists(version uint) bool
}
type service struct {

View File

@ -202,7 +202,7 @@ func (s *service) checkResults(user string, config *Config) (JudgeStatus, int32)
}
var results []TaskStatus
dir := filepath.Join(UserDir, fmt.Sprintf("%s", user))
dir := filepath.Join(UserDir, user)
var sum int32 = 0
for i := 1; i <= len(config.Tasks); i++ {
@ -213,14 +213,15 @@ func (s *service) checkResults(user string, config *Config) (JudgeStatus, int32)
result.getInfoText(info).
getInfo().
checkExit().
checkTime(config).
checkMemory(config).
checkExit().
getJudgeText(judge).
getJudge().
checkJudge(&pts)
sum += result.Points
results = append(results, result)
}
return JudgeStatus{Message: "", Tasks: results}, sum

View File

@ -10,15 +10,18 @@ import (
type CreateData struct {
SubmissionID uint
ProblemVersionID uint
Context pgtype.JSON
Context string
Point int32
}
func (s service) Create(data *model.Status) (*model.Status, e.Status) {
func (s service) Create(data *CreateData) (*model.Status, e.Status) {
status := &model.Status{
SubmissionID: data.SubmissionID,
ProblemVersionID: data.ProblemVersionID,
Context: data.Context,
Context: pgtype.JSON{
Bytes: []byte(data.Context),
Status: pgtype.Present,
},
Point: data.Point,
IsEnabled: true,
}

View File

@ -1,11 +0,0 @@
package status
import (
"github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/model"
)
func (s service) Rejudge(statusID uint) ([]*model.Status, e.Status) {
//TODO implement me
panic("implement me")
}

View File

@ -11,10 +11,9 @@ import (
var _ Service = (*service)(nil)
type Service interface {
Create(*model.Status) (*model.Status, e.Status)
Create(data *CreateData) (*model.Status, e.Status)
Query(sid uint, associations bool) (*model.Status, e.Status)
QueryByVersion(pvid uint, offset int, limit int) ([]*model.Status, e.Status)
Rejudge(statusID uint) ([]*model.Status, e.Status)
}
type service struct {

View File

@ -0,0 +1,30 @@
package storage
import (
"context"
"github.com/WHUPRJ/woj-server/internal/e"
"go.uber.org/zap"
"net/url"
"time"
)
func (s *service) Get(objectName string, expiry time.Duration) (string, e.Status) {
preSignedURL, err := s.client.PresignedGetObject(
context.Background(),
s.bucket,
objectName,
expiry,
url.Values{},
)
if err != nil {
s.log.Warn("failed to generate pre-signed get url",
zap.Error(err),
zap.String("objectName", objectName),
zap.Duration("expiry", expiry),
)
return "", e.StorageGetFailed
}
return preSignedURL.String(), e.Success
}

View File

@ -0,0 +1,41 @@
package storage
import (
"github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/global"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
"go.uber.org/zap"
"time"
)
var _ Service = (*service)(nil)
type Service interface {
Upload(objectName string, expiry time.Duration) (string, e.Status)
Get(objectName string, expiry time.Duration) (string, e.Status)
}
type service struct {
log *zap.Logger
client *minio.Client
bucket string
}
func NewService(g *global.Global) Service {
minioClient, err := minio.New(g.Conf.Storage.Endpoint, &minio.Options{
Creds: credentials.NewStaticV4(g.Conf.Storage.AccessKey, g.Conf.Storage.SecretKey, ""),
Secure: g.Conf.Storage.UseSSL,
})
if err != nil {
g.Log.Fatal("failed to create minio client", zap.Error(err))
return nil
}
return &service{
log: g.Log,
client: minioClient,
bucket: g.Conf.Storage.Bucket,
}
}

View File

@ -0,0 +1,28 @@
package storage
import (
"context"
"github.com/WHUPRJ/woj-server/internal/e"
"go.uber.org/zap"
"time"
)
func (s *service) Upload(objectName string, expiry time.Duration) (string, e.Status) {
preSignedURL, err := s.client.PresignedPutObject(
context.Background(),
s.bucket,
objectName,
expiry,
)
if err != nil {
s.log.Warn("failed to generate pre-signed upload url",
zap.Error(err),
zap.String("objectName", objectName),
zap.Duration("expiry", expiry),
)
return "", e.StorageUploadFailed
}
return preSignedURL.String(), e.Success
}

View File

@ -1,9 +1,11 @@
package submission
import (
"errors"
"github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/model"
"go.uber.org/zap"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
@ -31,3 +33,25 @@ func (s *service) Query(pid uint, uid uint, offset int, limit int) ([]*model.Sub
}
return submissions, e.Success
}
func (s *service) QueryBySid(sid uint, associations bool) (*model.Submission, e.Status) {
submission := new(model.Submission)
query := s.db
if associations {
query = query.Preload(clause.Associations)
}
err := query.First(&submission, sid).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, e.SubmissionNotFound
}
if err != nil {
s.log.Warn("DatabaseError", zap.Error(err), zap.Any("sid", sid))
return nil, e.DatabaseError
}
return submission, e.Success
}

View File

@ -13,6 +13,7 @@ var _ Service = (*service)(nil)
type Service interface {
Create(data *CreateData) (*model.Submission, e.Status)
Query(pid uint, uid uint, offset int, limit int) ([]*model.Submission, e.Status)
QueryBySid(sid uint, associations bool) (*model.Submission, e.Status)
}
type service struct {

View File

@ -15,8 +15,6 @@ func (s *service) submit(typename string, payload []byte, queue string) (*asynq.
return nil, e.TaskEnqueueFailed
}
s.log.Debug("Successfully enqueued task", zap.Any("info", info))
return info, e.Success
}

View File

@ -7,16 +7,13 @@ import (
"go.uber.org/zap"
)
func (s *service) ProblemBuild(pvId uint, file string) (string, e.Status) {
payload, err := json.Marshal(model.ProblemBuildPayload{
ProblemVersionID: pvId,
ProblemFile: file,
})
func (s *service) ProblemBuild(data *model.ProblemBuildPayload) (string, e.Status) {
payload, err := json.Marshal(data)
if err != nil {
s.log.Warn("json marshal error",
zap.Error(err),
zap.Any("ProblemVersionID", pvId),
zap.String("ProblemFile", file))
zap.Any("data", data),
)
return "", e.InternalError
}
@ -25,18 +22,13 @@ func (s *service) ProblemBuild(pvId uint, file string) (string, e.Status) {
return info.ID, status
}
func (s *service) ProblemUpdate(status e.Status, pvId uint, ctx string) (string, e.Status) {
payload, err := json.Marshal(model.ProblemUpdatePayload{
Status: status,
ProblemVersionID: pvId,
Context: ctx,
})
func (s *service) ProblemUpdate(data *model.ProblemUpdatePayload) (string, e.Status) {
payload, err := json.Marshal(data)
if err != nil {
s.log.Warn("json marshal error",
zap.Error(err),
zap.Any("Status", status),
zap.Any("ProblemVersionID", pvId),
zap.Any("Context", ctx))
zap.Any("data", data),
)
return "", e.InternalError
}

View File

@ -12,10 +12,10 @@ import (
var _ Service = (*service)(nil)
type Service interface {
ProblemBuild(pvId uint, file string) (string, e.Status)
ProblemUpdate(status e.Status, pvId uint, ctx string) (string, e.Status)
SubmitJudge(pvid uint, storageKey string, submission model.Submission) (string, e.Status)
SubmitUpdate(status e.Status, sid uint, point int32, ctx runner.JudgeStatus) (string, e.Status)
ProblemBuild(data *model.ProblemBuildPayload) (string, e.Status)
ProblemUpdate(data *model.ProblemUpdatePayload) (string, e.Status)
SubmitJudge(data *model.SubmitJudgePayload) (string, e.Status)
SubmitUpdate(data *model.SubmitUpdatePayload, ctx runner.JudgeStatus) (string, e.Status)
GetTaskInfo(string, string) (*asynq.TaskInfo, e.Status)
}

View File

@ -8,15 +8,13 @@ import (
"go.uber.org/zap"
)
func (s *service) SubmitJudge(pvid uint, storageKey string, submission model.Submission) (string, e.Status) {
payload, err := json.Marshal(
model.SubmitJudgePayload{
ProblemVersionId: pvid,
StorageKey: storageKey,
Submission: submission,
})
func (s *service) SubmitJudge(data *model.SubmitJudgePayload) (string, e.Status) {
payload, err := json.Marshal(data)
if err != nil {
s.log.Warn("json marshal error", zap.Error(err), zap.Any("Submission", submission))
s.log.Warn("json marshal error",
zap.Error(err),
zap.Any("data", data),
)
return "", e.InternalError
}
@ -25,7 +23,7 @@ func (s *service) SubmitJudge(pvid uint, storageKey string, submission model.Sub
return info.ID, status
}
func (s *service) SubmitUpdate(status e.Status, sid uint, point int32, ctx runner.JudgeStatus) (string, e.Status) {
func (s *service) SubmitUpdate(data *model.SubmitUpdatePayload, ctx runner.JudgeStatus) (string, e.Status) {
ctxText, err := json.Marshal(ctx)
if err != nil {
s.log.Warn("json marshal error",
@ -34,18 +32,14 @@ func (s *service) SubmitUpdate(status e.Status, sid uint, point int32, ctx runne
return "", e.InternalError
}
payload, err := json.Marshal(model.SubmitUpdatePayload{
Status: status,
Sid: sid,
Point: point,
Context: string(ctxText),
})
data.Context = string(ctxText)
payload, err := json.Marshal(data)
if err != nil {
s.log.Warn("json marshal error",
zap.Error(err),
zap.Any("Status", status),
zap.Int32("Point", point),
zap.Any("Context", ctx))
zap.Any("data", data),
zap.Any("Context", ctx),
)
return "", e.InternalError
}

View File

@ -23,6 +23,14 @@ func FileExist(filePath string) bool {
return If(err == nil || os.IsExist(err), true, false).(bool)
}
func FileEmpty(filePath string) bool {
stat, err := os.Stat(filePath)
if err != nil {
return true
}
return stat.Size() == 0
}
func FileTouch(filePath string) bool {
base := filepath.Dir(filePath)
_ = os.MkdirAll(base, 0755)

View File

@ -31,7 +31,7 @@ log_info "NProcLimit: $Info_Limit_NProc"
# launcher will add 2 more seconds
# here add 3 more seconds
TIMEOUT=$(((LIMIT_TIME + 1000) / 1000 + 3))
TIMEOUT=$(((LIMIT_TIME + 1000) / 1000 + 4))
log_info "Timeout: $TIMEOUT"
for test_num in $(seq "$Info_Num"); do