From d1367ca1aba021e9244880c7b8944af645f2ffd1 Mon Sep 17 00:00:00 2001 From: Paul Pan Date: Thu, 8 Sep 2022 22:00:25 +0800 Subject: [PATCH 01/19] feat: add postgresql support --- .github/workflows/docker-image.yml | 4 +- .github/workflows/golangci-lint.yml | 4 +- cmd/app/main.go | 12 +- cmd/app/run.go | 21 +++ config.yaml | 11 ++ go.mod | 18 ++- go.sum | 128 +++++++++++++++++- internal/{controller => api}/debug/handler.go | 0 internal/{controller => api}/debug/random.go | 0 internal/api/user/create.go | 54 ++++++++ internal/api/user/handler.go | 30 ++++ internal/app/app.go | 21 ++- internal/e/code.go | 2 + internal/global/config.go | 31 ++--- internal/global/{model.go => global.go} | 2 +- internal/global/log.go | 27 ---- internal/global/repo.go | 12 ++ internal/global/router.go | 11 ++ internal/global/setup.go | 46 +++++-- internal/model/router.go | 12 -- internal/repo/postgresql/User.go | 13 ++ internal/repo/postgresql/postgresql.go | 107 +++++++++++++++ internal/router/api.go | 7 +- internal/router/router.go | 4 + internal/service/user/create.go | 28 ++++ internal/service/user/service.go | 24 ++++ 26 files changed, 531 insertions(+), 98 deletions(-) create mode 100644 cmd/app/run.go rename internal/{controller => api}/debug/handler.go (100%) rename internal/{controller => api}/debug/random.go (100%) create mode 100644 internal/api/user/create.go create mode 100644 internal/api/user/handler.go rename internal/global/{model.go => global.go} (88%) delete mode 100644 internal/global/log.go create mode 100644 internal/global/repo.go create mode 100644 internal/global/router.go delete mode 100644 internal/model/router.go create mode 100644 internal/repo/postgresql/User.go create mode 100644 internal/repo/postgresql/postgresql.go create mode 100644 internal/service/user/create.go create mode 100644 internal/service/user/service.go diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 8b0cb23..2c97491 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -2,10 +2,10 @@ name: Docker Image on: push: - branches: [ "master" ] + branches: [ "master", "develop" ] tags: [ "v*" ] pull_request: - branches: [ "master" ] + branches: [ "master", "develop" ] jobs: diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 757de0e..fd08158 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -2,10 +2,10 @@ name: Lint on: push: - branches: [ "master" ] + branches: [ "master", "develop" ] tags: [ "v*" ] pull_request: - branches: [ "master" ] + branches: [ "master", "develop" ] permissions: contents: read diff --git a/cmd/app/main.go b/cmd/app/main.go index 7089bfa..4e1e2d4 100644 --- a/cmd/app/main.go +++ b/cmd/app/main.go @@ -1,8 +1,6 @@ package main import ( - "github.com/WHUPRJ/woj-server/internal/app" - "github.com/WHUPRJ/woj-server/internal/global" "github.com/urfave/cli/v2" "log" "os" @@ -35,15 +33,7 @@ func main() { Name: "run", Aliases: []string{"r"}, Usage: "start the server", - Action: func(c *cli.Context) error { - g := new(global.Global) - g.Setup(c.String("config")) - defer func() { _ = g.Log.Sync() }() - //g.SetupRedis() - - g.Log.Info("starting server...") - return app.Run(g) - }, + Action: run, }, }, } diff --git a/cmd/app/run.go b/cmd/app/run.go new file mode 100644 index 0000000..0da7fbc --- /dev/null +++ b/cmd/app/run.go @@ -0,0 +1,21 @@ +package main + +import ( + "github.com/WHUPRJ/woj-server/internal/app" + "github.com/WHUPRJ/woj-server/internal/global" + "github.com/urfave/cli/v2" + "math/rand" + "time" +) + +func run(c *cli.Context) error { + rand.Seed(time.Now().Unix()) + + g := new(global.Global) + g.SetupConfig(c.String("config")) + g.SetupZap() + defer func() { _ = g.Log.Sync() }() + + g.Log.Info("starting server...") + return app.Run(g) +} diff --git a/config.yaml b/config.yaml index 1e10658..866d600 100644 --- a/config.yaml +++ b/config.yaml @@ -7,6 +7,17 @@ Redis: Address: '127.0.0.1:6379' Password: '' +Database: + Host: '127.0.0.1' + Port: 5432 + User: 'dev' + Password: 'password' + Database: 'dev' + Prefix: 'oj_' + MaxOpenConns: 100 + MaxIdleConns: 60 + ConnMaxLifetime: 60 + Metrics: Namespace: 'OJ' Subsystem: 'server' diff --git a/go.mod b/go.mod index d50b2c5..97ec06c 100644 --- a/go.mod +++ b/go.mod @@ -7,13 +7,18 @@ require ( github.com/gin-contrib/pprof v1.4.0 github.com/gin-contrib/zap v0.0.2 github.com/gin-gonic/gin v1.8.1 + github.com/pkg/errors v0.9.1 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 gopkg.in/yaml.v3 v3.0.1 + gorm.io/driver/postgres v1.3.9 + gorm.io/gorm v1.23.8 + moul.io/zapgorm2 v1.1.3 ) require ( @@ -33,6 +38,16 @@ require ( github.com/go-playground/validator/v10 v10.10.0 // indirect github.com/goccy/go-json v0.9.7 // indirect github.com/golang/protobuf v1.5.2 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgconn v1.12.1 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.3.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect + github.com/jackc/pgtype v1.11.0 // indirect + github.com/jackc/pgx/v4 v4.16.1 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + 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/leodido/go-urn v1.2.1 // indirect @@ -49,8 +64,7 @@ require ( 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.6.0 // indirect - golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect + go.uber.org/multierr v1.7.0 // indirect golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect golang.org/x/text v0.3.7 // indirect diff --git a/go.sum b/go.sum index 26fc6b7..21e963e 100644 --- a/go.sum +++ b/go.sum @@ -35,6 +35,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= @@ -60,9 +62,14 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -120,6 +127,8 @@ github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXS github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM= github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -178,6 +187,57 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.12.1 h1:rsDFzIpRk7xT4B8FufgpCCeyjdNpKyghZeSefViE5W8= +github.com/jackc/pgconn v1.12.1/go.mod h1:ZkhRC59Llhrq3oSfrikvwQ5NaxYExr6twkdkMLaKono= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.0 h1:brH0pCGBDkBW07HWlN/oSBXrmo3WB0UvZd1pIuDcL8Y= +github.com/jackc/pgproto3/v2 v2.3.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.11.0 h1:u4uiGPz/1hryuXzyaBhSk6dnIyyG2683olG2OV+UUgs= +github.com/jackc/pgtype v1.11.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.16.1 h1:JzTglcal01DrghUqt+PmzWsZx/Yh7SC/CTQmSBMTd0Y= +github.com/jackc/pgx/v4 v4.16.1/go.mod h1:SIhx0D5hoADaiXZVyv+3gSm3LCIIINTVO0PficsvWGQ= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas= +github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= @@ -194,6 +254,7 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V 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/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= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -201,16 +262,26 @@ github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= @@ -269,21 +340,31 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 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/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= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +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/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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -311,28 +392,48 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= -go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec= +go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= +go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY= go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= +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/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= @@ -379,6 +480,7 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -423,7 +525,9 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -431,7 +535,9 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -466,6 +572,7 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -473,6 +580,7 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -487,14 +595,18 @@ golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -502,6 +614,7 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -524,6 +637,8 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -615,6 +730,7 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -629,6 +745,12 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/postgres v1.3.9 h1:lWGiVt5CijhQAg0PWB7Od1RNcBw/jS4d2cAScBcSDXg= +gorm.io/driver/postgres v1.3.9/go.mod h1:qw/FeqjxmYqW5dBcYNBsnhQULIApQdk7YuuDPktVi1U= +gorm.io/gorm v1.23.1/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= +gorm.io/gorm v1.23.7/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= +gorm.io/gorm v1.23.8 h1:h8sGJ+biDgBA1AD1Ha9gFCx7h8npU7AsLdlkX0n2TpE= +gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -636,6 +758,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +moul.io/zapgorm2 v1.1.3 h1:PP9224dk0l2f56KE1anr3vcS2HzKV9PusKUE6UT9ncI= +moul.io/zapgorm2 v1.1.3/go.mod h1:HTO6sXgHhQD0s2D9HA4xcnJ+qxFRFwsCUxIeFDnKtq0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/internal/controller/debug/handler.go b/internal/api/debug/handler.go similarity index 100% rename from internal/controller/debug/handler.go rename to internal/api/debug/handler.go diff --git a/internal/controller/debug/random.go b/internal/api/debug/random.go similarity index 100% rename from internal/controller/debug/random.go rename to internal/api/debug/random.go diff --git a/internal/api/user/create.go b/internal/api/user/create.go new file mode 100644 index 0000000..72406a8 --- /dev/null +++ b/internal/api/user/create.go @@ -0,0 +1,54 @@ +package user + +import ( + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/internal/service/user" + "github.com/gin-gonic/gin" + "go.uber.org/zap" + "golang.org/x/crypto/bcrypt" +) + +type createRequest struct { + Username string `form:"username" binding:"required"` + Nickname string `form:"nickname" binding:"required"` + Password string `form:"password" binding:"required"` +} + +// Create godoc +// @Summary create a new user +// @Description create a new user +// @Accept application/x-www-form-urlencoded +// @Produce json +// @Param username formData string true "username" +// @Param nickname formData string true "nickname" +// @Param password formData string true "password" +// @Response 200 {object} e.Response "random string" +// @Router /v1/user [post] +func (h *handler) Create(c *gin.Context) { + req := new(createRequest) + + if err := c.ShouldBind(req); err != nil { + e.Pong(c, e.InvalidParameter, err.Error()) + return + } + + hashed, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) + if err != nil { + h.log.Debug("bcrypt error", zap.Error(err), zap.String("password", req.Password)) + e.Pong(c, e.InternalError, err.Error()) + return + } + + createData := new(user.CreateData) + createData.Nickname = req.Nickname + createData.Username = req.Username + createData.Password = hashed + + id, err := h.service.Create(createData) + if err != nil { + e.Pong(c, e.DatabaseError, err.Error()) + return + } + + e.Pong(c, e.Success, id) +} diff --git a/internal/api/user/handler.go b/internal/api/user/handler.go new file mode 100644 index 0000000..10bde4c --- /dev/null +++ b/internal/api/user/handler.go @@ -0,0 +1,30 @@ +package user + +import ( + "github.com/WHUPRJ/woj-server/internal/global" + "github.com/WHUPRJ/woj-server/internal/service/user" + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +var _ Handler = (*handler)(nil) + +type Handler interface { + Create(c *gin.Context) + // List(c *gin.Context) +} + +type handler struct { + log *zap.Logger + service user.Service +} + +func RouteRegister(g *global.Global, group *gin.RouterGroup) { + app := &handler{ + log: g.Log, + service: user.NewUserService(g), + } + + group.POST("/", app.Create) + // group.GET("/", app.List) +} diff --git a/internal/app/app.go b/internal/app/app.go index 91c2a2f..eb19eba 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "github.com/WHUPRJ/woj-server/internal/global" + "github.com/WHUPRJ/woj-server/internal/repo/postgresql" "github.com/WHUPRJ/woj-server/internal/router" "go.uber.org/zap" "net/http" @@ -14,15 +15,21 @@ import ( ) func Run(g *global.Global) error { - routersInit := router.InitRouters(g) + // Setup Database + g.Db = new(postgresql.PgRepo) + g.Db.Setup(g) + // Prepare Router + handler := router.InitRouters(g) + + // Create Server addr := fmt.Sprintf("%s:%d", g.Conf.WebServer.Address, g.Conf.WebServer.Port) - server := &http.Server{ Addr: addr, - Handler: routersInit, + Handler: handler, } + // Run Server go func() { if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { g.Log.Fatal("ListenAndServe Failed", zap.Error(err)) @@ -35,12 +42,16 @@ func Run(g *global.Global) error { <-quit g.Log.Info("Shutting down server ...") + // Graceful Shutdown ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - err := server.Shutdown(ctx) if err != nil { - g.Log.Fatal("Server Shutdown Failed", zap.Error(err)) + g.Log.Warn("Server Shutdown Failed", zap.Error(err)) + } + err = g.Db.Close() + if err != nil { + g.Log.Warn("Database Close Failed", zap.Error(err)) } return err diff --git a/internal/e/code.go b/internal/e/code.go index cdbd8d6..66bd331 100644 --- a/internal/e/code.go +++ b/internal/e/code.go @@ -6,6 +6,7 @@ const ( InternalError Err = 100 InvalidParameter Err = 101 NotFound Err = 102 + DatabaseError Err = 103 ) var msgText = map[Err]string{ @@ -13,4 +14,5 @@ var msgText = map[Err]string{ InternalError: "Internal Error", InvalidParameter: "Invalid Parameter", NotFound: "Not Found", + DatabaseError: "Database Error", } diff --git a/internal/global/config.go b/internal/global/config.go index 1326491..309781e 100644 --- a/internal/global/config.go +++ b/internal/global/config.go @@ -1,11 +1,5 @@ package global -import ( - "github.com/WHUPRJ/woj-server/pkg/utils" - "gopkg.in/yaml.v3" - "log" -) - type ConfigWebServer struct { Address string `yaml:"Address"` Port int `yaml:"Port"` @@ -17,6 +11,18 @@ type ConfigRedis struct { Password string `yaml:"Password"` } +type ConfigDatabase struct { + Host string `yaml:"Host"` + Port int `yaml:"Port"` + User string `yaml:"User"` + Password string `yaml:"Password"` + Database string `yaml:"Database"` + Prefix string `yaml:"Prefix"` + MaxOpenConns int `yaml:"MaxOpenConns"` + MaxIdleConns int `yaml:"MaxIdleConns"` + ConnMaxLifetime int `yaml:"ConnMaxLifetime"` +} + type ConfigMetrics struct { Namespace string `yaml:"Namespace"` Subsystem string `yaml:"Subsystem"` @@ -25,18 +31,7 @@ type ConfigMetrics struct { type Config struct { WebServer ConfigWebServer `yaml:"WebServer"` Redis ConfigRedis `yaml:"Redis"` + Database ConfigDatabase `yaml:"Database"` Metrics ConfigMetrics `yaml:"Metrics"` Development bool `yaml:"Development"` } - -func (g *Global) setupConfig(configFile string) { - data, err := utils.FileRead(configFile) - if err != nil { - log.Fatalf("Failed to setup config: %s\n", err.Error()) - } - - err = yaml.Unmarshal(data, &g.Conf) - if err != nil { - log.Fatalf("Failed to setup config: %s\n", err.Error()) - } -} diff --git a/internal/global/model.go b/internal/global/global.go similarity index 88% rename from internal/global/model.go rename to internal/global/global.go index 2939d42..725bca4 100644 --- a/internal/global/model.go +++ b/internal/global/global.go @@ -9,5 +9,5 @@ type Global struct { Log *zap.Logger Conf *Config Stat *metrics.Metrics - // Redis *redis.Client + Db Repo } diff --git a/internal/global/log.go b/internal/global/log.go deleted file mode 100644 index 6284967..0000000 --- a/internal/global/log.go +++ /dev/null @@ -1,27 +0,0 @@ -package global - -import ( - "github.com/WHUPRJ/woj-server/pkg/utils" - "go.uber.org/zap" - "go.uber.org/zap/zapcore" - "log" -) - -func (g *Global) setupZap() { - cfg := zap.Config{ - Level: zap.NewAtomicLevelAt( - utils.If(g.Conf.Development, zapcore.DebugLevel, zapcore.InfoLevel).(zapcore.Level), - ), - Development: g.Conf.Development, - Encoding: "console", // or json - EncoderConfig: zap.NewDevelopmentEncoderConfig(), - OutputPaths: []string{"stdout"}, - ErrorOutputPaths: []string{"stderr"}, - } - - var err error - g.Log, err = cfg.Build() - if err != nil { - log.Fatalf("Failed to setup Zap: %s\n", err.Error()) - } -} diff --git a/internal/global/repo.go b/internal/global/repo.go new file mode 100644 index 0000000..4bd5218 --- /dev/null +++ b/internal/global/repo.go @@ -0,0 +1,12 @@ +package global + +import ( + "gorm.io/gorm" +) + +type Repo interface { + Setup(*Global) + + Get() *gorm.DB + Close() error +} diff --git a/internal/global/router.go b/internal/global/router.go new file mode 100644 index 0000000..8ee2f78 --- /dev/null +++ b/internal/global/router.go @@ -0,0 +1,11 @@ +package global + +import ( + "github.com/gin-gonic/gin" +) + +type EndpointInfo struct { + Version string + Path string + Register func(g *Global, group *gin.RouterGroup) +} diff --git a/internal/global/setup.go b/internal/global/setup.go index ca6c8ac..d04d91c 100644 --- a/internal/global/setup.go +++ b/internal/global/setup.go @@ -1,20 +1,40 @@ package global import ( - "github.com/WHUPRJ/woj-server/internal/pkg/metrics" - "math/rand" - "time" + "github.com/WHUPRJ/woj-server/pkg/utils" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "gopkg.in/yaml.v3" + "log" ) -func (g *Global) Setup(configFile string) { - rand.Seed(time.Now().Unix()) +func (g *Global) SetupZap() { + cfg := zap.Config{ + Level: zap.NewAtomicLevelAt( + utils.If(g.Conf.Development, zapcore.DebugLevel, zapcore.InfoLevel).(zapcore.Level), + ), + Development: g.Conf.Development, + Encoding: "console", // or json + EncoderConfig: zap.NewDevelopmentEncoderConfig(), + OutputPaths: []string{"stdout"}, + ErrorOutputPaths: []string{"stderr"}, + } - g.setupConfig(configFile) - g.setupZap() - - g.Stat = new(metrics.Metrics) - g.Stat.Setup(g.Conf.Metrics.Namespace, g.Conf.Metrics.Subsystem) - g.Stat.SetLogPaths([]string{ - "/api", - }) + var err error + g.Log, err = cfg.Build() + if err != nil { + log.Fatalf("Failed to setup Zap: %s\n", err.Error()) + } +} + +func (g *Global) SetupConfig(configFile string) { + data, err := utils.FileRead(configFile) + if err != nil { + log.Fatalf("Failed to setup config: %s\n", err.Error()) + } + + err = yaml.Unmarshal(data, &g.Conf) + if err != nil { + log.Fatalf("Failed to setup config: %s\n", err.Error()) + } } diff --git a/internal/model/router.go b/internal/model/router.go deleted file mode 100644 index 52e5666..0000000 --- a/internal/model/router.go +++ /dev/null @@ -1,12 +0,0 @@ -package model - -import ( - "github.com/WHUPRJ/woj-server/internal/global" - "github.com/gin-gonic/gin" -) - -type EndpointInfo struct { - Version string - Path string - Register func(g *global.Global, group *gin.RouterGroup) -} diff --git a/internal/repo/postgresql/User.go b/internal/repo/postgresql/User.go new file mode 100644 index 0000000..f919b90 --- /dev/null +++ b/internal/repo/postgresql/User.go @@ -0,0 +1,13 @@ +package postgresql + +import ( + "gorm.io/gorm" +) + +type User struct { + gorm.Model `json:"-"` + UserName string `json:"user_name" gorm:"not null;uniqueIndex"` + NickName string `json:"nick_name" gorm:"not null"` + Password []byte `json:"-" gorm:"not null"` + IsEnabled bool `json:"is_enabled" gorm:"not null;index"` +} diff --git a/internal/repo/postgresql/postgresql.go b/internal/repo/postgresql/postgresql.go new file mode 100644 index 0000000..c5f4066 --- /dev/null +++ b/internal/repo/postgresql/postgresql.go @@ -0,0 +1,107 @@ +package postgresql + +import ( + "database/sql" + "errors" + "fmt" + "github.com/WHUPRJ/woj-server/internal/global" + "go.uber.org/zap" + "gorm.io/driver/postgres" + "gorm.io/gorm" + "gorm.io/gorm/schema" + "moul.io/zapgorm2" + "time" +) + +var _ global.Repo = (*PgRepo)(nil) + +type PgRepo struct { + db *gorm.DB + log *zap.Logger +} + +func (r *PgRepo) Get() *gorm.DB { + return r.db +} + +func (r *PgRepo) Close() error { + db, err := r.db.DB() + if err != nil { + return err + } + return db.Close() +} + +func (r *PgRepo) Setup(g *global.Global) { + r.log = g.Log + + r.log.Info("Connecting to database...") + + logger := zapgorm2.New(r.log) + logger.IgnoreRecordNotFoundError = true + + dsn := fmt.Sprintf("user=%s password=%s dbname=%s host=%s port=%d sslmode=disable TimeZone=Asia/Shanghai", + g.Conf.Database.User, + g.Conf.Database.Password, + g.Conf.Database.Database, + g.Conf.Database.Host, + g.Conf.Database.Port) + + var err error + r.db, err = gorm.Open( + postgres.Open(dsn), + &gorm.Config{ + NamingStrategy: schema.NamingStrategy{ + SingularTable: true, + TablePrefix: g.Conf.Database.Prefix, + }, + PrepareStmt: true, + Logger: logger, + }) + + if err != nil { + r.log.Fatal("Failed to connect to database", zap.Error(err)) + return + } + + db, err := r.checkAlive(3) + if err != nil { + r.log.Fatal("Database is not alive", zap.Error(err)) + return + } + + db.SetMaxOpenConns(g.Conf.Database.MaxOpenConns) + db.SetMaxIdleConns(g.Conf.Database.MaxIdleConns) + db.SetConnMaxLifetime(time.Duration(g.Conf.Database.ConnMaxLifetime) * time.Minute) + + r.migrateDatabase() +} + +func (r *PgRepo) migrateDatabase() { + r.log.Info("Auto Migrating database...") + + _ = r.db.AutoMigrate(&User{}) +} + +func (r *PgRepo) checkAlive(retry int) (*sql.DB, error) { + if retry <= 0 { + return nil, errors.New("all retries are used up. failed to connect to database") + } + + db, err := r.db.DB() + if err != nil { + r.log.Warn("failed to get sql.DB instance", zap.Error(err)) + time.Sleep(5 * time.Second) + return r.checkAlive(retry - 1) + } + + err = db.Ping() + if err != nil { + r.log.Warn("failed to ping database", zap.Error(err)) + time.Sleep(5 * time.Second) + return r.checkAlive(retry - 1) + } + + r.log.Info("database connect established") + return db, nil +} diff --git a/internal/router/api.go b/internal/router/api.go index d36ded2..50e4a30 100644 --- a/internal/router/api.go +++ b/internal/router/api.go @@ -1,9 +1,9 @@ package router import ( - "github.com/WHUPRJ/woj-server/internal/controller/debug" + "github.com/WHUPRJ/woj-server/internal/api/debug" + "github.com/WHUPRJ/woj-server/internal/api/user" "github.com/WHUPRJ/woj-server/internal/global" - "github.com/WHUPRJ/woj-server/internal/model" "github.com/gin-gonic/gin" ) @@ -17,6 +17,7 @@ func setupApi(g *global.Global, root *gin.RouterGroup) { } } -var endpoints = []model.EndpointInfo{ +var endpoints = []global.EndpointInfo{ {Version: "", Path: "/debug", Register: debug.RouteRegister}, + {Version: "/v1", Path: "/user", Register: user.RouteRegister}, } diff --git a/internal/router/router.go b/internal/router/router.go index b688f53..a5669f2 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -2,6 +2,7 @@ package router import ( "github.com/WHUPRJ/woj-server/internal/global" + "github.com/WHUPRJ/woj-server/internal/pkg/metrics" _ "github.com/WHUPRJ/woj-server/internal/router/docs" "github.com/WHUPRJ/woj-server/pkg/utils" "github.com/gin-contrib/cors" @@ -42,6 +43,9 @@ func InitRouters(g *global.Global) *gin.Engine { })) // Prometheus middleware + g.Stat = new(metrics.Metrics) + g.Stat.Setup(g.Conf.Metrics.Namespace, g.Conf.Metrics.Subsystem) + g.Stat.SetLogPaths([]string{"/api"}) r.Use(g.Stat.Handler()) // metrics diff --git a/internal/service/user/create.go b/internal/service/user/create.go new file mode 100644 index 0000000..dd51e1e --- /dev/null +++ b/internal/service/user/create.go @@ -0,0 +1,28 @@ +package user + +import ( + "github.com/WHUPRJ/woj-server/internal/repo/postgresql" + "github.com/pkg/errors" + "go.uber.org/zap" +) + +type CreateData struct { + Username string + Nickname string + Password []byte +} + +func (s *service) Create(data *CreateData) (id uint, err error) { + model := &postgresql.User{ + UserName: data.Username, + Password: data.Password, + NickName: data.Nickname, + IsEnabled: true, + } + + if err = s.db.Get().Create(model).Error; err != nil { + s.log.Debug("create user error", zap.Error(err), zap.Any("data", data)) + return 0, errors.Wrap(err, "create error") + } + return model.ID, nil +} diff --git a/internal/service/user/service.go b/internal/service/user/service.go new file mode 100644 index 0000000..d43d399 --- /dev/null +++ b/internal/service/user/service.go @@ -0,0 +1,24 @@ +package user + +import ( + "github.com/WHUPRJ/woj-server/internal/global" + "go.uber.org/zap" +) + +var _ Service = (*service)(nil) + +type Service interface { + Create(data *CreateData) (id uint, err error) +} + +type service struct { + log *zap.Logger + db global.Repo +} + +func NewUserService(g *global.Global) Service { + return &service{ + log: g.Log, + db: g.Db, + } +} From c6436125efb52ae763c9a335970c7aaffdb073b7 Mon Sep 17 00:00:00 2001 From: Paul Pan Date: Sat, 17 Sep 2022 09:23:36 +0800 Subject: [PATCH 02/19] chore: adjust packages --- Makefile | 8 ++++++-- internal/api/debug/random.go | 2 +- internal/api/user/create.go | 18 +++++------------- internal/e/code.go | 5 +++-- internal/pkg/metrics/middleware.go | 2 +- internal/repo/{postgresql => model}/User.go | 2 +- internal/repo/postgresql/postgresql.go | 4 +++- internal/service/user/create.go | 19 +++++++++++++------ 8 files changed, 33 insertions(+), 27 deletions(-) rename internal/repo/{postgresql => model}/User.go (94%) diff --git a/Makefile b/Makefile index a139c2e..2e82bd5 100644 --- a/Makefile +++ b/Makefile @@ -7,8 +7,9 @@ LDFLAGS += -X main.Version=$(shell cat VERSION)+$(shell git rev-parse HEAD) LDFLAGS += -s -w GOBUILD := $(GO) build -o $(PROJECT) -ldflags '$(LDFLAGS)' ./cmd/app +GOBIN := $(shell go env GOPATH)/bin -.PHONY: all build clean run dep swagger +.PHONY: all build clean run dep swagger fmt default: all @@ -28,4 +29,7 @@ dep: swagger: go install github.com/swaggo/swag/cmd/swag@latest - swag init -g internal/router/api.go -o internal/router/docs + $(GOBIN)/swag init -g internal/router/api.go -o internal/router/docs + +fmt: + go fmt ./... diff --git a/internal/api/debug/random.go b/internal/api/debug/random.go index 96dd6a5..25b839f 100644 --- a/internal/api/debug/random.go +++ b/internal/api/debug/random.go @@ -7,7 +7,7 @@ import ( "go.uber.org/zap" ) -// randomString godoc +// randomString // @Summary random string // @Description generate random string with length = 32 // @Tags debug diff --git a/internal/api/user/create.go b/internal/api/user/create.go index 72406a8..7be87a3 100644 --- a/internal/api/user/create.go +++ b/internal/api/user/create.go @@ -4,8 +4,6 @@ import ( "github.com/WHUPRJ/woj-server/internal/e" "github.com/WHUPRJ/woj-server/internal/service/user" "github.com/gin-gonic/gin" - "go.uber.org/zap" - "golang.org/x/crypto/bcrypt" ) type createRequest struct { @@ -14,7 +12,7 @@ type createRequest struct { Password string `form:"password" binding:"required"` } -// Create godoc +// Create // @Summary create a new user // @Description create a new user // @Accept application/x-www-form-urlencoded @@ -32,18 +30,12 @@ func (h *handler) Create(c *gin.Context) { return } - hashed, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) - if err != nil { - h.log.Debug("bcrypt error", zap.Error(err), zap.String("password", req.Password)) - e.Pong(c, e.InternalError, err.Error()) - return + createData := &user.CreateData{ + Username: req.Username, + Nickname: req.Nickname, + Password: req.Password, } - createData := new(user.CreateData) - createData.Nickname = req.Nickname - createData.Username = req.Username - createData.Password = hashed - id, err := h.service.Create(createData) if err != nil { e.Pong(c, e.DatabaseError, err.Error()) diff --git a/internal/e/code.go b/internal/e/code.go index 66bd331..55d37ba 100644 --- a/internal/e/code.go +++ b/internal/e/code.go @@ -1,8 +1,8 @@ package e const ( - Fallback Err = 0 - Success Err = 1 + Success Err = 0 + Unknown Err = 1 InternalError Err = 100 InvalidParameter Err = 101 NotFound Err = 102 @@ -11,6 +11,7 @@ const ( var msgText = map[Err]string{ Success: "Success", + Unknown: "Unknown error", InternalError: "Internal Error", InvalidParameter: "Invalid Parameter", NotFound: "Not Found", diff --git a/internal/pkg/metrics/middleware.go b/internal/pkg/metrics/middleware.go index 32cc4d0..255f17b 100644 --- a/internal/pkg/metrics/middleware.go +++ b/internal/pkg/metrics/middleware.go @@ -33,7 +33,7 @@ func (m *Metrics) Handler() gin.HandlerFunc { err, ok := errCode.(e.Err) if !ok { success = false - err = e.Fallback + err = e.Unknown } m.Record(method, url, success, status, err, elapsed) diff --git a/internal/repo/postgresql/User.go b/internal/repo/model/User.go similarity index 94% rename from internal/repo/postgresql/User.go rename to internal/repo/model/User.go index f919b90..07cc4f0 100644 --- a/internal/repo/postgresql/User.go +++ b/internal/repo/model/User.go @@ -1,4 +1,4 @@ -package postgresql +package model import ( "gorm.io/gorm" diff --git a/internal/repo/postgresql/postgresql.go b/internal/repo/postgresql/postgresql.go index c5f4066..311e971 100644 --- a/internal/repo/postgresql/postgresql.go +++ b/internal/repo/postgresql/postgresql.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "github.com/WHUPRJ/woj-server/internal/global" + "github.com/WHUPRJ/woj-server/internal/repo/model" "go.uber.org/zap" "gorm.io/driver/postgres" "gorm.io/gorm" @@ -80,9 +81,10 @@ func (r *PgRepo) Setup(g *global.Global) { func (r *PgRepo) migrateDatabase() { r.log.Info("Auto Migrating database...") - _ = r.db.AutoMigrate(&User{}) + _ = r.db.AutoMigrate(&model.User{}) } +// checkAlive deprecated func (r *PgRepo) checkAlive(retry int) (*sql.DB, error) { if retry <= 0 { return nil, errors.New("all retries are used up. failed to connect to database") diff --git a/internal/service/user/create.go b/internal/service/user/create.go index dd51e1e..5fac43a 100644 --- a/internal/service/user/create.go +++ b/internal/service/user/create.go @@ -1,28 +1,35 @@ package user import ( - "github.com/WHUPRJ/woj-server/internal/repo/postgresql" + "github.com/WHUPRJ/woj-server/internal/repo/model" "github.com/pkg/errors" "go.uber.org/zap" + "golang.org/x/crypto/bcrypt" ) type CreateData struct { Username string Nickname string - Password []byte + Password string } func (s *service) Create(data *CreateData) (id uint, err error) { - model := &postgresql.User{ + hashed, err := bcrypt.GenerateFromPassword([]byte(data.Password), bcrypt.DefaultCost) + if err != nil { + s.log.Debug("bcrypt error", zap.Error(err), zap.String("password", data.Password)) + return 0, errors.Wrap(err, "bcrypt error") + } + + user := &model.User{ UserName: data.Username, - Password: data.Password, + Password: hashed, NickName: data.Nickname, IsEnabled: true, } - if err = s.db.Get().Create(model).Error; err != nil { + if err := s.db.Get().Create(user).Error; err != nil { s.log.Debug("create user error", zap.Error(err), zap.Any("data", data)) return 0, errors.Wrap(err, "create error") } - return model.ID, nil + return user.ID, nil } From 787fc39c29ad15338be3299826b6b34eb8d981ab Mon Sep 17 00:00:00 2001 From: Paul Pan Date: Sat, 17 Sep 2022 10:10:53 +0800 Subject: [PATCH 03/19] feat: add JWT service --- config.yaml | 2 + go.mod | 1 + go.sum | 2 + internal/e/code.go | 26 ++++++++++-- internal/global/config.go | 6 ++- internal/service/jwt/middleware.go | 35 ++++++++++++++++ internal/service/jwt/model.go | 11 +++++ internal/service/jwt/service.go | 32 +++++++++++++++ internal/service/jwt/token.go | 65 ++++++++++++++++++++++++++++++ 9 files changed, 174 insertions(+), 6 deletions(-) create mode 100644 internal/service/jwt/middleware.go create mode 100644 internal/service/jwt/model.go create mode 100644 internal/service/jwt/service.go create mode 100644 internal/service/jwt/token.go diff --git a/config.yaml b/config.yaml index 866d600..8663f6a 100644 --- a/config.yaml +++ b/config.yaml @@ -1,6 +1,8 @@ WebServer: Address: 0.0.0.0 Port: 8000 + JwtSigningKey: 'rq67SdQIRABhHq40' + JwtExpireHour: 12 Redis: Db: 0 diff --git a/go.mod b/go.mod index 97ec06c..645f379 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/gin-contrib/pprof v1.4.0 github.com/gin-contrib/zap v0.0.2 github.com/gin-gonic/gin v1.8.1 + github.com/golang-jwt/jwt/v4 v4.4.2 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.13.0 github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a diff --git a/go.sum b/go.sum index 21e963e..dc97834 100644 --- a/go.sum +++ b/go.sum @@ -130,6 +130,8 @@ github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGF github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs= +github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= diff --git a/internal/e/code.go b/internal/e/code.go index 55d37ba..e0858b4 100644 --- a/internal/e/code.go +++ b/internal/e/code.go @@ -1,19 +1,37 @@ package e const ( - Success Err = 0 - Unknown Err = 1 + Success Err = 0 + Unknown Err = 1 + InternalError Err = 100 InvalidParameter Err = 101 NotFound Err = 102 DatabaseError Err = 103 + + TokenUnknown Err = 200 + TokenEmpty Err = 201 + TokenMalformed Err = 202 + TokenTimeError Err = 203 + TokenInvalid Err = 204 + TokenSignError Err = 205 + TokenRevoked Err = 206 ) var msgText = map[Err]string{ - Success: "Success", - Unknown: "Unknown error", + Success: "Success", + Unknown: "Unknown error", + InternalError: "Internal Error", InvalidParameter: "Invalid Parameter", NotFound: "Not Found", DatabaseError: "Database Error", + + TokenUnknown: "Unknown Error (Token)", + TokenEmpty: "Token Empty", + TokenMalformed: "Token Malformed", + TokenTimeError: "Token Time Error", + TokenInvalid: "Token Invalid", + TokenSignError: "Token Sign Error", + TokenRevoked: "Token Revoked", } diff --git a/internal/global/config.go b/internal/global/config.go index 309781e..aa1083e 100644 --- a/internal/global/config.go +++ b/internal/global/config.go @@ -1,8 +1,10 @@ package global type ConfigWebServer struct { - Address string `yaml:"Address"` - Port int `yaml:"Port"` + Address string `yaml:"Address"` + Port int `yaml:"Port"` + JwtSigningKey string `yaml:"JwtSigningKey"` + JwtExpireHour int `yaml:"JwtExpireHour"` } type ConfigRedis struct { diff --git a/internal/service/jwt/middleware.go b/internal/service/jwt/middleware.go new file mode 100644 index 0000000..c708684 --- /dev/null +++ b/internal/service/jwt/middleware.go @@ -0,0 +1,35 @@ +package jwt + +import ( + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/gin-gonic/gin" + "strings" +) + +func (s *service) Handler() gin.HandlerFunc { + return func(c *gin.Context) { + tokenHeader := c.GetHeader("Authorization") + if tokenHeader == "" || !strings.HasPrefix(strings.ToLower(tokenHeader), "bearer ") { + e.Pong(c, e.TokenEmpty, nil) + c.Abort() + return + } + token := tokenHeader[7:] + + claim, err := s.ParseToken(token) + if err != e.Success { + e.Pong(c, err, nil) + c.Abort() + return + } + + // TODO: validate claim version + // if !s.Validate(claim) { + // e.Pong(c, e.TokenRevoked, nil) + // c.Abort() + // } + + c.Set("claim", claim) + c.Next() + } +} diff --git a/internal/service/jwt/model.go b/internal/service/jwt/model.go new file mode 100644 index 0000000..44d8573 --- /dev/null +++ b/internal/service/jwt/model.go @@ -0,0 +1,11 @@ +package jwt + +import "github.com/golang-jwt/jwt/v4" + +type Claim struct { + UID uint `json:"id"` + UserName string `json:"user_name"` + NickName string `json:"nick_name"` + Version int `json:"version"` + jwt.RegisteredClaims +} diff --git a/internal/service/jwt/service.go b/internal/service/jwt/service.go new file mode 100644 index 0000000..eb8f38a --- /dev/null +++ b/internal/service/jwt/service.go @@ -0,0 +1,32 @@ +package jwt + +import ( + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/internal/global" + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +var _ Service = (*service)(nil) + +type Service interface { + ParseToken(tokenText string) (*Claim, e.Err) + SignClaim(claim *Claim) (string, e.Err) + // TODO: Validate(claim *Claim) bool + + Handler() gin.HandlerFunc +} + +type service struct { + log *zap.Logger + SigningKey []byte + ExpireHour int +} + +func NewJwtService(g *global.Global) Service { + return &service{ + log: g.Log, + SigningKey: []byte(g.Conf.WebServer.JwtSigningKey), + ExpireHour: g.Conf.WebServer.JwtExpireHour, + } +} diff --git a/internal/service/jwt/token.go b/internal/service/jwt/token.go new file mode 100644 index 0000000..b309a15 --- /dev/null +++ b/internal/service/jwt/token.go @@ -0,0 +1,65 @@ +package jwt + +import ( + "fmt" + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/pkg/utils" + "github.com/golang-jwt/jwt/v4" + "go.uber.org/zap" + "time" +) + +func (s *service) ParseToken(tokenText string) (*Claim, e.Err) { + if tokenText == "" { + return nil, e.TokenEmpty + } + + token, err := jwt.ParseWithClaims( + tokenText, + &Claim{}, + func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) + } + return s.SigningKey, nil + }) + + if ve, ok := err.(*jwt.ValidationError); ok { + if ve.Errors&jwt.ValidationErrorMalformed != 0 { + return nil, e.TokenMalformed + } else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 { + // Token is either expired or not active yet + return nil, e.TokenTimeError + } else { + return nil, e.TokenInvalid + } + } else if err != nil { + s.log.Warn("JWT Token Parse Error", zap.Error(err)) + return nil, e.TokenUnknown + } + + if token.Valid { + c := token.Claims.(*Claim) + return c, e.Success + } + + return nil, e.TokenInvalid +} + +func (s *service) SignClaim(claim *Claim) (string, e.Err) { + now := time.Now() + + claim.IssuedAt = jwt.NewNumericDate(now) + claim.ExpiresAt = jwt.NewNumericDate(now.Add(time.Duration(s.ExpireHour) * time.Hour)) + claim.ID = utils.RandomString(16) + // TODO: use per-user claim.Version to tracker invalidation + claim.NotBefore = jwt.NewNumericDate(time.Now()) + + token := jwt.NewWithClaims(jwt.SigningMethodHS512, claim) + ss, err := token.SignedString(s.SigningKey) + if err != nil { + s.log.Warn("jwt.SignedString error", zap.Error(err)) + return "", e.TokenSignError + } + return ss, e.Success +} From f163768aae42ebc5c8439d98d390eafb792b262c Mon Sep 17 00:00:00 2001 From: Paul Pan Date: Sat, 17 Sep 2022 11:22:55 +0800 Subject: [PATCH 04/19] feat: add user login --- internal/api/user/create.go | 12 +++------ internal/api/user/handler.go | 17 ++++++++---- internal/api/user/login.go | 44 ++++++++++++++++++++++++++++++++ internal/api/user/token.go | 18 +++++++++++++ internal/app/app.go | 4 +++ internal/e/code.go | 8 ++++++ internal/global/global.go | 1 + internal/global/jwt.go | 23 +++++++++++++++++ internal/router/api.go | 7 +++-- internal/service/jwt/model.go | 11 -------- internal/service/jwt/service.go | 14 ++-------- internal/service/jwt/token.go | 9 ++++--- internal/service/user/create.go | 14 ++++++---- internal/service/user/login.go | 28 ++++++++++++++++++++ internal/service/user/service.go | 5 +++- 15 files changed, 167 insertions(+), 48 deletions(-) create mode 100644 internal/api/user/login.go create mode 100644 internal/api/user/token.go create mode 100644 internal/global/jwt.go delete mode 100644 internal/service/jwt/model.go create mode 100644 internal/service/user/login.go diff --git a/internal/api/user/create.go b/internal/api/user/create.go index 7be87a3..78c07a7 100644 --- a/internal/api/user/create.go +++ b/internal/api/user/create.go @@ -21,7 +21,8 @@ type createRequest struct { // @Param nickname formData string true "nickname" // @Param password formData string true "password" // @Response 200 {object} e.Response "random string" -// @Router /v1/user [post] +// @Security Authentication +// @Router /v1/user/create [post] func (h *handler) Create(c *gin.Context) { req := new(createRequest) @@ -36,11 +37,6 @@ func (h *handler) Create(c *gin.Context) { Password: req.Password, } - id, err := h.service.Create(createData) - if err != nil { - e.Pong(c, e.DatabaseError, err.Error()) - return - } - - e.Pong(c, e.Success, id) + id, err := h.userService.Create(createData) + e.Pong(c, err, id) } diff --git a/internal/api/user/handler.go b/internal/api/user/handler.go index 10bde4c..549c498 100644 --- a/internal/api/user/handler.go +++ b/internal/api/user/handler.go @@ -2,6 +2,7 @@ package user import ( "github.com/WHUPRJ/woj-server/internal/global" + "github.com/WHUPRJ/woj-server/internal/repo/model" "github.com/WHUPRJ/woj-server/internal/service/user" "github.com/gin-gonic/gin" "go.uber.org/zap" @@ -11,20 +12,26 @@ var _ Handler = (*handler)(nil) type Handler interface { Create(c *gin.Context) + Login(c *gin.Context) // List(c *gin.Context) + + tokenNext(c *gin.Context, user *model.User) } type handler struct { - log *zap.Logger - service user.Service + log *zap.Logger + userService user.Service + jwtService global.JwtService } func RouteRegister(g *global.Global, group *gin.RouterGroup) { app := &handler{ - log: g.Log, - service: user.NewUserService(g), + log: g.Log, + userService: user.NewUserService(g), + jwtService: g.Jwt, } - group.POST("/", app.Create) + group.POST("/login", app.Login) + group.POST("/create", app.jwtService.Handler(), app.Create) // group.GET("/", app.List) } diff --git a/internal/api/user/login.go b/internal/api/user/login.go new file mode 100644 index 0000000..7b7c0dc --- /dev/null +++ b/internal/api/user/login.go @@ -0,0 +1,44 @@ +package user + +import ( + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/internal/repo/model" + "github.com/gin-gonic/gin" +) + +type loginRequest struct { + Username string `form:"username" binding:"required"` + Password string `form:"password" binding:"required"` +} + +// Login +// @Summary login +// @Description login and return token +// @Accept application/x-www-form-urlencoded +// @Produce json +// @Param username formData string true "username" +// @Param password formData string true "password" +// @Response 200 {object} e.Response "random string" +// @Router /v1/user/login [post] +func (h *handler) Login(c *gin.Context) { + req := new(loginRequest) + + if err := c.ShouldBind(req); err != nil { + e.Pong(c, e.InvalidParameter, err.Error()) + return + } + + // check password + userData := &model.User{ + UserName: req.Username, + Password: []byte(req.Password), + } + user, err := h.userService.Login(userData) + if err != e.Success { + e.Pong(c, err, nil) + return + } + + // sign and return token + h.tokenNext(c, user) +} diff --git a/internal/api/user/token.go b/internal/api/user/token.go new file mode 100644 index 0000000..10a38fa --- /dev/null +++ b/internal/api/user/token.go @@ -0,0 +1,18 @@ +package user + +import ( + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/internal/global" + "github.com/WHUPRJ/woj-server/internal/repo/model" + "github.com/gin-gonic/gin" +) + +func (h *handler) tokenNext(c *gin.Context, user *model.User) { + claim := &global.Claim{ + UID: user.ID, + UserName: user.UserName, + NickName: user.NickName, + } + token, err := h.jwtService.SignClaim(claim) + e.Pong(c, err, token) +} diff --git a/internal/app/app.go b/internal/app/app.go index eb19eba..7a5c46c 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -6,6 +6,7 @@ import ( "github.com/WHUPRJ/woj-server/internal/global" "github.com/WHUPRJ/woj-server/internal/repo/postgresql" "github.com/WHUPRJ/woj-server/internal/router" + "github.com/WHUPRJ/woj-server/internal/service/jwt" "go.uber.org/zap" "net/http" "os" @@ -19,6 +20,9 @@ func Run(g *global.Global) error { g.Db = new(postgresql.PgRepo) g.Db.Setup(g) + // Setup JWT + g.Jwt = jwt.NewJwtService(g) + // Prepare Router handler := router.InitRouters(g) diff --git a/internal/e/code.go b/internal/e/code.go index e0858b4..4320e61 100644 --- a/internal/e/code.go +++ b/internal/e/code.go @@ -16,6 +16,10 @@ const ( TokenInvalid Err = 204 TokenSignError Err = 205 TokenRevoked Err = 206 + + UserNotFound Err = 300 + UserWrongPassword Err = 301 + UserDuplicated Err = 302 ) var msgText = map[Err]string{ @@ -34,4 +38,8 @@ var msgText = map[Err]string{ TokenInvalid: "Token Invalid", TokenSignError: "Token Sign Error", TokenRevoked: "Token Revoked", + + UserNotFound: "User Not Found", + UserWrongPassword: "User Wrong Password", + UserDuplicated: "User Duplicated", } diff --git a/internal/global/global.go b/internal/global/global.go index 725bca4..9d445ba 100644 --- a/internal/global/global.go +++ b/internal/global/global.go @@ -10,4 +10,5 @@ type Global struct { Conf *Config Stat *metrics.Metrics Db Repo + Jwt JwtService } diff --git a/internal/global/jwt.go b/internal/global/jwt.go new file mode 100644 index 0000000..6311ce5 --- /dev/null +++ b/internal/global/jwt.go @@ -0,0 +1,23 @@ +package global + +import ( + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt/v4" +) + +type Claim struct { + UID uint `json:"id"` + UserName string `json:"user_name"` + NickName string `json:"nick_name"` + Version int `json:"version"` + jwt.RegisteredClaims +} + +type JwtService interface { + ParseToken(tokenText string) (*Claim, e.Err) + SignClaim(claim *Claim) (string, e.Err) + // TODO: Validate(claim *Claim) bool + + Handler() gin.HandlerFunc +} diff --git a/internal/router/api.go b/internal/router/api.go index 50e4a30..0687aae 100644 --- a/internal/router/api.go +++ b/internal/router/api.go @@ -8,8 +8,11 @@ import ( ) // @title OJ Server API Documentation -// @version 1.0 -// @BasePath /api +// @version 1.0 +// @BasePath /api +// @securityDefinitions.apikey Authentication +// @in header +// @name Authorization func setupApi(g *global.Global, root *gin.RouterGroup) { for _, v := range endpoints { group := root.Group(v.Version).Group(v.Path) diff --git a/internal/service/jwt/model.go b/internal/service/jwt/model.go deleted file mode 100644 index 44d8573..0000000 --- a/internal/service/jwt/model.go +++ /dev/null @@ -1,11 +0,0 @@ -package jwt - -import "github.com/golang-jwt/jwt/v4" - -type Claim struct { - UID uint `json:"id"` - UserName string `json:"user_name"` - NickName string `json:"nick_name"` - Version int `json:"version"` - jwt.RegisteredClaims -} diff --git a/internal/service/jwt/service.go b/internal/service/jwt/service.go index eb8f38a..25f7fb0 100644 --- a/internal/service/jwt/service.go +++ b/internal/service/jwt/service.go @@ -1,21 +1,11 @@ package jwt import ( - "github.com/WHUPRJ/woj-server/internal/e" "github.com/WHUPRJ/woj-server/internal/global" - "github.com/gin-gonic/gin" "go.uber.org/zap" ) -var _ Service = (*service)(nil) - -type Service interface { - ParseToken(tokenText string) (*Claim, e.Err) - SignClaim(claim *Claim) (string, e.Err) - // TODO: Validate(claim *Claim) bool - - Handler() gin.HandlerFunc -} +var _ global.JwtService = (*service)(nil) type service struct { log *zap.Logger @@ -23,7 +13,7 @@ type service struct { ExpireHour int } -func NewJwtService(g *global.Global) Service { +func NewJwtService(g *global.Global) global.JwtService { return &service{ log: g.Log, SigningKey: []byte(g.Conf.WebServer.JwtSigningKey), diff --git a/internal/service/jwt/token.go b/internal/service/jwt/token.go index b309a15..ddc2bbf 100644 --- a/internal/service/jwt/token.go +++ b/internal/service/jwt/token.go @@ -3,20 +3,21 @@ package jwt import ( "fmt" "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/internal/global" "github.com/WHUPRJ/woj-server/pkg/utils" "github.com/golang-jwt/jwt/v4" "go.uber.org/zap" "time" ) -func (s *service) ParseToken(tokenText string) (*Claim, e.Err) { +func (s *service) ParseToken(tokenText string) (*global.Claim, e.Err) { if tokenText == "" { return nil, e.TokenEmpty } token, err := jwt.ParseWithClaims( tokenText, - &Claim{}, + &global.Claim{}, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) @@ -39,14 +40,14 @@ func (s *service) ParseToken(tokenText string) (*Claim, e.Err) { } if token.Valid { - c := token.Claims.(*Claim) + c := token.Claims.(*global.Claim) return c, e.Success } return nil, e.TokenInvalid } -func (s *service) SignClaim(claim *Claim) (string, e.Err) { +func (s *service) SignClaim(claim *global.Claim) (string, e.Err) { now := time.Now() claim.IssuedAt = jwt.NewNumericDate(now) diff --git a/internal/service/user/create.go b/internal/service/user/create.go index 5fac43a..3c85674 100644 --- a/internal/service/user/create.go +++ b/internal/service/user/create.go @@ -1,10 +1,11 @@ package user import ( + "github.com/WHUPRJ/woj-server/internal/e" "github.com/WHUPRJ/woj-server/internal/repo/model" - "github.com/pkg/errors" "go.uber.org/zap" "golang.org/x/crypto/bcrypt" + "strings" ) type CreateData struct { @@ -13,11 +14,11 @@ type CreateData struct { Password string } -func (s *service) Create(data *CreateData) (id uint, err error) { +func (s *service) Create(data *CreateData) (uint, e.Err) { hashed, err := bcrypt.GenerateFromPassword([]byte(data.Password), bcrypt.DefaultCost) if err != nil { s.log.Debug("bcrypt error", zap.Error(err), zap.String("password", data.Password)) - return 0, errors.Wrap(err, "bcrypt error") + return 0, e.InternalError } user := &model.User{ @@ -28,8 +29,11 @@ func (s *service) Create(data *CreateData) (id uint, err error) { } if err := s.db.Get().Create(user).Error; err != nil { + if strings.Contains(err.Error(), "duplicate key") { + return 0, e.UserDuplicated + } s.log.Debug("create user error", zap.Error(err), zap.Any("data", data)) - return 0, errors.Wrap(err, "create error") + return 0, e.DatabaseError } - return user.ID, nil + return user.ID, e.Success } diff --git a/internal/service/user/login.go b/internal/service/user/login.go new file mode 100644 index 0000000..bc604a4 --- /dev/null +++ b/internal/service/user/login.go @@ -0,0 +1,28 @@ +package user + +import ( + "errors" + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/internal/repo/model" + "golang.org/x/crypto/bcrypt" + "gorm.io/gorm" +) + +func (s *service) Login(data *model.User) (*model.User, e.Err) { + user := &model.User{UserName: data.UserName} + + err := s.db.Get().Where(user).First(&user).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + return user, e.UserNotFound + } + if err != nil { + return user, e.DatabaseError + } + + err = bcrypt.CompareHashAndPassword(user.Password, data.Password) + if err != nil { + return user, e.UserWrongPassword + } + + return user, e.Success +} diff --git a/internal/service/user/service.go b/internal/service/user/service.go index d43d399..5e2f1fa 100644 --- a/internal/service/user/service.go +++ b/internal/service/user/service.go @@ -1,14 +1,17 @@ package user import ( + "github.com/WHUPRJ/woj-server/internal/e" "github.com/WHUPRJ/woj-server/internal/global" + "github.com/WHUPRJ/woj-server/internal/repo/model" "go.uber.org/zap" ) var _ Service = (*service)(nil) type Service interface { - Create(data *CreateData) (id uint, err error) + Create(data *CreateData) (uint, e.Err) + Login(data *model.User) (*model.User, e.Err) } type service struct { From ec660e706ef4a75c8188404a2a39fc8adb9f67c4 Mon Sep 17 00:00:00 2001 From: Paul Pan Date: Sat, 17 Sep 2022 18:10:06 +0800 Subject: [PATCH 05/19] feat: add logout Co-authored-by: cxy004 Co-authored-by: wzt --- cmd/app/main.go | 8 ++++++ go.mod | 3 +- go.sum | 9 ++++++ internal/api/user/create.go | 23 ++++++++++++--- internal/api/user/handler.go | 6 ++-- internal/api/user/login.go | 15 ++++++++-- internal/api/user/logout.go | 26 +++++++++++++++++ internal/api/user/token.go | 18 ------------ internal/app/app.go | 7 ++++- internal/e/code.go | 18 ++++++++---- internal/global/global.go | 11 ++++---- internal/global/jwt.go | 8 ++---- internal/global/repo.go | 7 +---- internal/repo/postgresql/postgresql.go | 14 ++++----- internal/repo/redis/redis.go | 39 ++++++++++++++++++++++++++ internal/service/jwt/middleware.go | 15 +++++----- internal/service/jwt/service.go | 3 ++ internal/service/jwt/token.go | 12 +++++++- internal/service/user/create.go | 12 ++++---- internal/service/user/login.go | 2 +- internal/service/user/service.go | 15 ++++++---- internal/service/user/version.go | 17 +++++++++++ 22 files changed, 209 insertions(+), 79 deletions(-) create mode 100644 internal/api/user/logout.go delete mode 100644 internal/api/user/token.go create mode 100644 internal/repo/redis/redis.go create mode 100644 internal/service/user/version.go diff --git a/cmd/app/main.go b/cmd/app/main.go index 4e1e2d4..afae6fa 100644 --- a/cmd/app/main.go +++ b/cmd/app/main.go @@ -18,6 +18,14 @@ func main() { Name: "Paul", Email: "i@0x7f.app", }, + { + Name: "cxy004", + Email: "cxy004@qq.com", + }, + { + Name: "wzt", + Email: "w.zhongtao@qq.com", + }, }, Flags: []cli.Flag{ &cli.StringFlag{ diff --git a/go.mod b/go.mod index 645f379..8db94ce 100644 --- a/go.mod +++ b/go.mod @@ -7,8 +7,8 @@ require ( github.com/gin-contrib/pprof v1.4.0 github.com/gin-contrib/zap v0.0.2 github.com/gin-gonic/gin v1.8.1 + github.com/go-redis/redis/v8 v8.11.5 github.com/golang-jwt/jwt/v4 v4.4.2 - github.com/pkg/errors v0.9.1 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 @@ -29,6 +29,7 @@ require ( github.com/beorn7/perks v1.0.1 // indirect 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/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 diff --git a/go.sum b/go.sum index dc97834..d2106f7 100644 --- a/go.sum +++ b/go.sum @@ -74,10 +74,13 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/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= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g= github.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURUfmBoMlcs= @@ -124,6 +127,8 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= +github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM= github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= @@ -299,6 +304,9 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= @@ -733,6 +741,7 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/internal/api/user/create.go b/internal/api/user/create.go index 78c07a7..68425f0 100644 --- a/internal/api/user/create.go +++ b/internal/api/user/create.go @@ -2,6 +2,7 @@ package user import ( "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/internal/global" "github.com/WHUPRJ/woj-server/internal/service/user" "github.com/gin-gonic/gin" ) @@ -20,8 +21,7 @@ type createRequest struct { // @Param username formData string true "username" // @Param nickname formData string true "nickname" // @Param password formData string true "password" -// @Response 200 {object} e.Response "random string" -// @Security Authentication +// @Response 200 {object} e.Response "jwt token" // @Router /v1/user/create [post] func (h *handler) Create(c *gin.Context) { req := new(createRequest) @@ -37,6 +37,21 @@ func (h *handler) Create(c *gin.Context) { Password: req.Password, } - id, err := h.userService.Create(createData) - e.Pong(c, err, id) + u, err := h.userService.Create(createData) + if err != e.Success { + e.Pong(c, err, nil) + return + } + + version, err := h.userService.IncrVersion(u.ID) + if err != e.Success { + e.Pong(c, err, nil) + return + } + claim := &global.Claim{ + UID: u.ID, + Version: version, + } + token, err := h.jwtService.SignClaim(claim) + e.Pong(c, err, token) } diff --git a/internal/api/user/handler.go b/internal/api/user/handler.go index 549c498..6f0e94b 100644 --- a/internal/api/user/handler.go +++ b/internal/api/user/handler.go @@ -2,7 +2,6 @@ package user import ( "github.com/WHUPRJ/woj-server/internal/global" - "github.com/WHUPRJ/woj-server/internal/repo/model" "github.com/WHUPRJ/woj-server/internal/service/user" "github.com/gin-gonic/gin" "go.uber.org/zap" @@ -14,8 +13,6 @@ type Handler interface { Create(c *gin.Context) Login(c *gin.Context) // List(c *gin.Context) - - tokenNext(c *gin.Context, user *model.User) } type handler struct { @@ -32,6 +29,7 @@ func RouteRegister(g *global.Global, group *gin.RouterGroup) { } group.POST("/login", app.Login) - group.POST("/create", app.jwtService.Handler(), app.Create) + group.POST("/create", app.Create) + group.POST("/logout", app.jwtService.Handler(), app.Logout) // group.GET("/", app.List) } diff --git a/internal/api/user/login.go b/internal/api/user/login.go index 7b7c0dc..c199ec3 100644 --- a/internal/api/user/login.go +++ b/internal/api/user/login.go @@ -2,6 +2,7 @@ package user import ( "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/internal/global" "github.com/WHUPRJ/woj-server/internal/repo/model" "github.com/gin-gonic/gin" ) @@ -18,7 +19,7 @@ type loginRequest struct { // @Produce json // @Param username formData string true "username" // @Param password formData string true "password" -// @Response 200 {object} e.Response "random string" +// @Response 200 {object} e.Response "jwt token" // @Router /v1/user/login [post] func (h *handler) Login(c *gin.Context) { req := new(loginRequest) @@ -40,5 +41,15 @@ func (h *handler) Login(c *gin.Context) { } // sign and return token - h.tokenNext(c, user) + version, err := h.userService.IncrVersion(user.ID) + if err != e.Success { + e.Pong(c, err, nil) + return + } + claim := &global.Claim{ + UID: user.ID, + Version: version, + } + token, err := h.jwtService.SignClaim(claim) + e.Pong(c, err, token) } diff --git a/internal/api/user/logout.go b/internal/api/user/logout.go new file mode 100644 index 0000000..0a66bb7 --- /dev/null +++ b/internal/api/user/logout.go @@ -0,0 +1,26 @@ +package user + +import ( + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/internal/global" + "github.com/gin-gonic/gin" +) + +// Logout +// @Summary logout +// @Description logout +// @Accept application/x-www-form-urlencoded +// @Produce json +// @Response 200 {object} e.Response "nil" +// @Security Authentication +// @Router /v1/user/logout [post] +func (h *handler) Logout(c *gin.Context) { + claim, exist := c.Get("claim") + if !exist { + e.Pong(c, e.UserUnauthenticated, nil) + return + } + + _, err := h.userService.IncrVersion(claim.(*global.Claim).UID) + e.Pong(c, err, nil) +} diff --git a/internal/api/user/token.go b/internal/api/user/token.go deleted file mode 100644 index 10a38fa..0000000 --- a/internal/api/user/token.go +++ /dev/null @@ -1,18 +0,0 @@ -package user - -import ( - "github.com/WHUPRJ/woj-server/internal/e" - "github.com/WHUPRJ/woj-server/internal/global" - "github.com/WHUPRJ/woj-server/internal/repo/model" - "github.com/gin-gonic/gin" -) - -func (h *handler) tokenNext(c *gin.Context, user *model.User) { - claim := &global.Claim{ - UID: user.ID, - UserName: user.UserName, - NickName: user.NickName, - } - token, err := h.jwtService.SignClaim(claim) - e.Pong(c, err, token) -} diff --git a/internal/app/app.go b/internal/app/app.go index 7a5c46c..58139f5 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/WHUPRJ/woj-server/internal/global" "github.com/WHUPRJ/woj-server/internal/repo/postgresql" + "github.com/WHUPRJ/woj-server/internal/repo/redis" "github.com/WHUPRJ/woj-server/internal/router" "github.com/WHUPRJ/woj-server/internal/service/jwt" "go.uber.org/zap" @@ -17,9 +18,13 @@ import ( func Run(g *global.Global) error { // Setup Database - g.Db = new(postgresql.PgRepo) + g.Db = new(postgresql.Repo) g.Db.Setup(g) + // Setup Redis + g.Redis = new(redis.Repo) + g.Redis.Setup(g) + // Setup JWT g.Jwt = jwt.NewJwtService(g) diff --git a/internal/e/code.go b/internal/e/code.go index 4320e61..83abc4f 100644 --- a/internal/e/code.go +++ b/internal/e/code.go @@ -17,9 +17,12 @@ const ( TokenSignError Err = 205 TokenRevoked Err = 206 - UserNotFound Err = 300 - UserWrongPassword Err = 301 - UserDuplicated Err = 302 + UserNotFound Err = 300 + UserWrongPassword Err = 301 + UserDuplicated Err = 302 + UserUnauthenticated Err = 303 + + RedisError Err = 400 ) var msgText = map[Err]string{ @@ -39,7 +42,10 @@ var msgText = map[Err]string{ TokenSignError: "Token Sign Error", TokenRevoked: "Token Revoked", - UserNotFound: "User Not Found", - UserWrongPassword: "User Wrong Password", - UserDuplicated: "User Duplicated", + UserNotFound: "User Not Found", + UserWrongPassword: "User Wrong Password", + UserDuplicated: "User Duplicated", + UserUnauthenticated: "User Unauthenticated", + + RedisError: "Redis Error", } diff --git a/internal/global/global.go b/internal/global/global.go index 9d445ba..609895a 100644 --- a/internal/global/global.go +++ b/internal/global/global.go @@ -6,9 +6,10 @@ import ( ) type Global struct { - Log *zap.Logger - Conf *Config - Stat *metrics.Metrics - Db Repo - Jwt JwtService + Log *zap.Logger + Conf *Config + Stat *metrics.Metrics + Db Repo + Redis Repo + Jwt JwtService } diff --git a/internal/global/jwt.go b/internal/global/jwt.go index 6311ce5..dbd5e77 100644 --- a/internal/global/jwt.go +++ b/internal/global/jwt.go @@ -7,17 +7,15 @@ import ( ) type Claim struct { - UID uint `json:"id"` - UserName string `json:"user_name"` - NickName string `json:"nick_name"` - Version int `json:"version"` + UID uint `json:"id"` + Version int64 `json:"version"` jwt.RegisteredClaims } type JwtService interface { ParseToken(tokenText string) (*Claim, e.Err) SignClaim(claim *Claim) (string, e.Err) - // TODO: Validate(claim *Claim) bool + Validate(claim *Claim) bool Handler() gin.HandlerFunc } diff --git a/internal/global/repo.go b/internal/global/repo.go index 4bd5218..89b8b28 100644 --- a/internal/global/repo.go +++ b/internal/global/repo.go @@ -1,12 +1,7 @@ package global -import ( - "gorm.io/gorm" -) - type Repo interface { Setup(*Global) - - Get() *gorm.DB + Get() interface{} Close() error } diff --git a/internal/repo/postgresql/postgresql.go b/internal/repo/postgresql/postgresql.go index 311e971..1181a77 100644 --- a/internal/repo/postgresql/postgresql.go +++ b/internal/repo/postgresql/postgresql.go @@ -14,18 +14,18 @@ import ( "time" ) -var _ global.Repo = (*PgRepo)(nil) +var _ global.Repo = (*Repo)(nil) -type PgRepo struct { +type Repo struct { db *gorm.DB log *zap.Logger } -func (r *PgRepo) Get() *gorm.DB { +func (r *Repo) Get() interface{} { return r.db } -func (r *PgRepo) Close() error { +func (r *Repo) Close() error { db, err := r.db.DB() if err != nil { return err @@ -33,7 +33,7 @@ func (r *PgRepo) Close() error { return db.Close() } -func (r *PgRepo) Setup(g *global.Global) { +func (r *Repo) Setup(g *global.Global) { r.log = g.Log r.log.Info("Connecting to database...") @@ -78,14 +78,14 @@ func (r *PgRepo) Setup(g *global.Global) { r.migrateDatabase() } -func (r *PgRepo) migrateDatabase() { +func (r *Repo) migrateDatabase() { r.log.Info("Auto Migrating database...") _ = r.db.AutoMigrate(&model.User{}) } // checkAlive deprecated -func (r *PgRepo) checkAlive(retry int) (*sql.DB, error) { +func (r *Repo) checkAlive(retry int) (*sql.DB, error) { if retry <= 0 { return nil, errors.New("all retries are used up. failed to connect to database") } diff --git a/internal/repo/redis/redis.go b/internal/repo/redis/redis.go new file mode 100644 index 0000000..d23da71 --- /dev/null +++ b/internal/repo/redis/redis.go @@ -0,0 +1,39 @@ +package redis + +import ( + "context" + "github.com/WHUPRJ/woj-server/internal/global" + "github.com/go-redis/redis/v8" + "go.uber.org/zap" +) + +var _ global.Repo = (*Repo)(nil) + +type Repo struct { + client *redis.Client + log *zap.Logger +} + +func (r *Repo) Setup(g *global.Global) { + r.log = g.Log + + r.client = redis.NewClient(&redis.Options{ + Addr: g.Conf.Redis.Address, + Password: g.Conf.Redis.Password, + DB: g.Conf.Redis.Db, + }) + + _, err := r.client.Ping(context.Background()).Result() + if err != nil { + r.log.Fatal("Redis ping failed", zap.Error(err)) + return + } +} + +func (r *Repo) Get() interface{} { + return r.client +} + +func (r *Repo) Close() error { + return r.client.Close() +} diff --git a/internal/service/jwt/middleware.go b/internal/service/jwt/middleware.go index c708684..22145e9 100644 --- a/internal/service/jwt/middleware.go +++ b/internal/service/jwt/middleware.go @@ -8,13 +8,14 @@ import ( func (s *service) Handler() gin.HandlerFunc { return func(c *gin.Context) { + const tokenPrefix = "bearer " tokenHeader := c.GetHeader("Authorization") - if tokenHeader == "" || !strings.HasPrefix(strings.ToLower(tokenHeader), "bearer ") { + if tokenHeader == "" || !strings.HasPrefix(strings.ToLower(tokenHeader), tokenPrefix) { e.Pong(c, e.TokenEmpty, nil) c.Abort() return } - token := tokenHeader[7:] + token := tokenHeader[len(tokenPrefix):] claim, err := s.ParseToken(token) if err != e.Success { @@ -23,11 +24,11 @@ func (s *service) Handler() gin.HandlerFunc { return } - // TODO: validate claim version - // if !s.Validate(claim) { - // e.Pong(c, e.TokenRevoked, nil) - // c.Abort() - // } + if !s.Validate(claim) { + e.Pong(c, e.TokenRevoked, nil) + c.Abort() + return + } c.Set("claim", claim) c.Next() diff --git a/internal/service/jwt/service.go b/internal/service/jwt/service.go index 25f7fb0..3cb372d 100644 --- a/internal/service/jwt/service.go +++ b/internal/service/jwt/service.go @@ -2,6 +2,7 @@ package jwt import ( "github.com/WHUPRJ/woj-server/internal/global" + "github.com/go-redis/redis/v8" "go.uber.org/zap" ) @@ -9,6 +10,7 @@ var _ global.JwtService = (*service)(nil) type service struct { log *zap.Logger + redis *redis.Client SigningKey []byte ExpireHour int } @@ -16,6 +18,7 @@ type service struct { func NewJwtService(g *global.Global) global.JwtService { return &service{ log: g.Log, + redis: g.Redis.Get().(*redis.Client), SigningKey: []byte(g.Conf.WebServer.JwtSigningKey), ExpireHour: g.Conf.WebServer.JwtExpireHour, } diff --git a/internal/service/jwt/token.go b/internal/service/jwt/token.go index ddc2bbf..19a2412 100644 --- a/internal/service/jwt/token.go +++ b/internal/service/jwt/token.go @@ -1,6 +1,7 @@ package jwt import ( + "context" "fmt" "github.com/WHUPRJ/woj-server/internal/e" "github.com/WHUPRJ/woj-server/internal/global" @@ -17,7 +18,7 @@ func (s *service) ParseToken(tokenText string) (*global.Claim, e.Err) { token, err := jwt.ParseWithClaims( tokenText, - &global.Claim{}, + new(global.Claim), func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) @@ -64,3 +65,12 @@ func (s *service) SignClaim(claim *global.Claim) (string, e.Err) { } return ss, e.Success } + +func (s *service) Validate(claim *global.Claim) bool { + curVersion, err := s.redis.Get(context.Background(), fmt.Sprintf("Version:%d", claim.UID)).Int64() + if err != nil { + s.log.Debug("redis.Get error", zap.Error(err)) + return false + } + return curVersion == claim.Version +} diff --git a/internal/service/user/create.go b/internal/service/user/create.go index 3c85674..a0b2b8d 100644 --- a/internal/service/user/create.go +++ b/internal/service/user/create.go @@ -14,11 +14,11 @@ type CreateData struct { Password string } -func (s *service) Create(data *CreateData) (uint, e.Err) { +func (s *service) Create(data *CreateData) (*model.User, e.Err) { hashed, err := bcrypt.GenerateFromPassword([]byte(data.Password), bcrypt.DefaultCost) if err != nil { s.log.Debug("bcrypt error", zap.Error(err), zap.String("password", data.Password)) - return 0, e.InternalError + return nil, e.InternalError } user := &model.User{ @@ -28,12 +28,12 @@ func (s *service) Create(data *CreateData) (uint, e.Err) { IsEnabled: true, } - if err := s.db.Get().Create(user).Error; err != nil { + if err := s.db.Create(user).Error; err != nil { if strings.Contains(err.Error(), "duplicate key") { - return 0, e.UserDuplicated + return nil, e.UserDuplicated } s.log.Debug("create user error", zap.Error(err), zap.Any("data", data)) - return 0, e.DatabaseError + return nil, e.DatabaseError } - return user.ID, e.Success + return user, e.Success } diff --git a/internal/service/user/login.go b/internal/service/user/login.go index bc604a4..48e8ee4 100644 --- a/internal/service/user/login.go +++ b/internal/service/user/login.go @@ -11,7 +11,7 @@ import ( func (s *service) Login(data *model.User) (*model.User, e.Err) { user := &model.User{UserName: data.UserName} - err := s.db.Get().Where(user).First(&user).Error + err := s.db.Where(user).First(&user).Error if errors.Is(err, gorm.ErrRecordNotFound) { return user, e.UserNotFound } diff --git a/internal/service/user/service.go b/internal/service/user/service.go index 5e2f1fa..00ae8f1 100644 --- a/internal/service/user/service.go +++ b/internal/service/user/service.go @@ -4,24 +4,29 @@ import ( "github.com/WHUPRJ/woj-server/internal/e" "github.com/WHUPRJ/woj-server/internal/global" "github.com/WHUPRJ/woj-server/internal/repo/model" + "github.com/go-redis/redis/v8" "go.uber.org/zap" + "gorm.io/gorm" ) var _ Service = (*service)(nil) type Service interface { - Create(data *CreateData) (uint, e.Err) + Create(data *CreateData) (*model.User, e.Err) Login(data *model.User) (*model.User, e.Err) + IncrVersion(id uint) (int64, e.Err) } type service struct { - log *zap.Logger - db global.Repo + log *zap.Logger + db *gorm.DB + redis *redis.Client } func NewUserService(g *global.Global) Service { return &service{ - log: g.Log, - db: g.Db, + log: g.Log, + db: g.Db.Get().(*gorm.DB), + redis: g.Redis.Get().(*redis.Client), } } diff --git a/internal/service/user/version.go b/internal/service/user/version.go new file mode 100644 index 0000000..1485dad --- /dev/null +++ b/internal/service/user/version.go @@ -0,0 +1,17 @@ +package user + +import ( + "context" + "fmt" + "github.com/WHUPRJ/woj-server/internal/e" + "go.uber.org/zap" +) + +func (s *service) IncrVersion(id uint) (int64, e.Err) { + version, err := s.redis.Incr(context.Background(), fmt.Sprintf("Version:%d", id)).Result() + if err != nil { + s.log.Debug("redis.Incr error", zap.Error(err)) + return -1, e.RedisError + } + return version, e.Success +} From 8603315a5d99ca8cc25d93e9bb66905b01ad088d Mon Sep 17 00:00:00 2001 From: Paul Pan Date: Tue, 20 Sep 2022 14:15:21 +0800 Subject: [PATCH 06/19] chore: rename Err to Status --- cmd/app/main.go | 1 + internal/api/user/create.go | 16 ++++++------- internal/api/user/login.go | 16 ++++++------- internal/api/user/logout.go | 4 ++-- internal/api/user/profile.go | 1 + internal/e/code.go | 38 +++++++++++++++--------------- internal/e/err.go | 4 ++-- internal/e/resp.go | 12 +++++----- internal/global/jwt.go | 4 ++-- internal/pkg/cast/cast.go | 2 +- internal/pkg/metrics/middleware.go | 2 +- internal/pkg/metrics/prometheus.go | 2 +- internal/service/jwt/middleware.go | 6 ++--- internal/service/jwt/token.go | 4 ++-- internal/service/user/create.go | 2 +- internal/service/user/login.go | 2 +- internal/service/user/service.go | 6 ++--- internal/service/user/version.go | 2 +- 18 files changed, 63 insertions(+), 61 deletions(-) create mode 100644 internal/api/user/profile.go diff --git a/cmd/app/main.go b/cmd/app/main.go index afae6fa..0d1f51f 100644 --- a/cmd/app/main.go +++ b/cmd/app/main.go @@ -10,6 +10,7 @@ import ( func main() { a := &cli.App{ Name: "OJ", + Usage: "woj-server", Compiled: getBuildTime(), Version: Version, EnableBashCompletion: true, diff --git a/internal/api/user/create.go b/internal/api/user/create.go index 68425f0..f00b5f2 100644 --- a/internal/api/user/create.go +++ b/internal/api/user/create.go @@ -37,21 +37,21 @@ func (h *handler) Create(c *gin.Context) { Password: req.Password, } - u, err := h.userService.Create(createData) - if err != e.Success { - e.Pong(c, err, nil) + u, status := h.userService.Create(createData) + if status != e.Success { + e.Pong(c, status, nil) return } - version, err := h.userService.IncrVersion(u.ID) - if err != e.Success { - e.Pong(c, err, nil) + version, status := h.userService.IncrVersion(u.ID) + if status != e.Success { + e.Pong(c, status, nil) return } claim := &global.Claim{ UID: u.ID, Version: version, } - token, err := h.jwtService.SignClaim(claim) - e.Pong(c, err, token) + token, status := h.jwtService.SignClaim(claim) + e.Pong(c, status, token) } diff --git a/internal/api/user/login.go b/internal/api/user/login.go index c199ec3..79ae4ed 100644 --- a/internal/api/user/login.go +++ b/internal/api/user/login.go @@ -34,22 +34,22 @@ func (h *handler) Login(c *gin.Context) { UserName: req.Username, Password: []byte(req.Password), } - user, err := h.userService.Login(userData) - if err != e.Success { - e.Pong(c, err, nil) + user, status := h.userService.Login(userData) + if status != e.Success { + e.Pong(c, status, nil) return } // sign and return token - version, err := h.userService.IncrVersion(user.ID) - if err != e.Success { - e.Pong(c, err, nil) + version, status := h.userService.IncrVersion(user.ID) + if status != e.Success { + e.Pong(c, status, nil) return } claim := &global.Claim{ UID: user.ID, Version: version, } - token, err := h.jwtService.SignClaim(claim) - e.Pong(c, err, token) + token, status := h.jwtService.SignClaim(claim) + e.Pong(c, status, token) } diff --git a/internal/api/user/logout.go b/internal/api/user/logout.go index 0a66bb7..f9a288c 100644 --- a/internal/api/user/logout.go +++ b/internal/api/user/logout.go @@ -21,6 +21,6 @@ func (h *handler) Logout(c *gin.Context) { return } - _, err := h.userService.IncrVersion(claim.(*global.Claim).UID) - e.Pong(c, err, nil) + _, status := h.userService.IncrVersion(claim.(*global.Claim).UID) + e.Pong(c, status, nil) } diff --git a/internal/api/user/profile.go b/internal/api/user/profile.go new file mode 100644 index 0000000..a00006b --- /dev/null +++ b/internal/api/user/profile.go @@ -0,0 +1 @@ +package user diff --git a/internal/e/code.go b/internal/e/code.go index 83abc4f..5a6fd2b 100644 --- a/internal/e/code.go +++ b/internal/e/code.go @@ -1,31 +1,31 @@ package e const ( - Success Err = 0 - Unknown Err = 1 + Success Status = 0 + Unknown Status = 1 - InternalError Err = 100 - InvalidParameter Err = 101 - NotFound Err = 102 - DatabaseError Err = 103 + InternalError Status = 100 + InvalidParameter Status = 101 + NotFound Status = 102 + DatabaseError Status = 103 - TokenUnknown Err = 200 - TokenEmpty Err = 201 - TokenMalformed Err = 202 - TokenTimeError Err = 203 - TokenInvalid Err = 204 - TokenSignError Err = 205 - TokenRevoked Err = 206 + TokenUnknown Status = 200 + TokenEmpty Status = 201 + TokenMalformed Status = 202 + TokenTimeError Status = 203 + TokenInvalid Status = 204 + TokenSignError Status = 205 + TokenRevoked Status = 206 - UserNotFound Err = 300 - UserWrongPassword Err = 301 - UserDuplicated Err = 302 - UserUnauthenticated Err = 303 + UserNotFound Status = 300 + UserWrongPassword Status = 301 + UserDuplicated Status = 302 + UserUnauthenticated Status = 303 - RedisError Err = 400 + RedisError Status = 400 ) -var msgText = map[Err]string{ +var msgText = map[Status]string{ Success: "Success", Unknown: "Unknown error", diff --git a/internal/e/err.go b/internal/e/err.go index 240f897..44204e0 100644 --- a/internal/e/err.go +++ b/internal/e/err.go @@ -1,8 +1,8 @@ package e -type Err int +type Status int -func (code Err) String() string { +func (code Status) String() string { msg, ok := msgText[code] if ok { return msg diff --git a/internal/e/resp.go b/internal/e/resp.go index 44b6b66..25da040 100644 --- a/internal/e/resp.go +++ b/internal/e/resp.go @@ -12,14 +12,14 @@ type Response struct { Body interface{} `json:"body"` } -func Wrap(err Err, body interface{}) interface{} { +func Wrap(status Status, body interface{}) interface{} { return Response{ - Code: int(err), - Msg: err.String(), - Body: utils.If(err == Success, body, nil), + Code: int(status), + Msg: status.String(), + Body: utils.If(status == Success, body, nil), } } -func Pong(c *gin.Context, err Err, body interface{}) { - c.JSON(http.StatusOK, Wrap(err, body)) +func Pong(c *gin.Context, status Status, body interface{}) { + c.JSON(http.StatusOK, Wrap(status, body)) } diff --git a/internal/global/jwt.go b/internal/global/jwt.go index dbd5e77..24cf814 100644 --- a/internal/global/jwt.go +++ b/internal/global/jwt.go @@ -13,8 +13,8 @@ type Claim struct { } type JwtService interface { - ParseToken(tokenText string) (*Claim, e.Err) - SignClaim(claim *Claim) (string, e.Err) + ParseToken(tokenText string) (*Claim, e.Status) + SignClaim(claim *Claim) (string, e.Status) Validate(claim *Claim) bool Handler() gin.HandlerFunc diff --git a/internal/pkg/cast/cast.go b/internal/pkg/cast/cast.go index 16639fa..d73466d 100644 --- a/internal/pkg/cast/cast.go +++ b/internal/pkg/cast/cast.go @@ -13,7 +13,7 @@ func ToString(obj interface{}) string { return strconv.FormatBool(t) case []byte: return string(t) - case e.Err: + case e.Status: return t.String() case error: return t.Error() diff --git a/internal/pkg/metrics/middleware.go b/internal/pkg/metrics/middleware.go index 255f17b..679c322 100644 --- a/internal/pkg/metrics/middleware.go +++ b/internal/pkg/metrics/middleware.go @@ -30,7 +30,7 @@ func (m *Metrics) Handler() gin.HandlerFunc { status := c.Writer.Status() success := !c.IsAborted() && (status == http.StatusOK) errCode, _ := c.Get("err") - err, ok := errCode.(e.Err) + err, ok := errCode.(e.Status) if !ok { success = false err = e.Unknown diff --git a/internal/pkg/metrics/prometheus.go b/internal/pkg/metrics/prometheus.go index 4940b09..8426604 100644 --- a/internal/pkg/metrics/prometheus.go +++ b/internal/pkg/metrics/prometheus.go @@ -44,7 +44,7 @@ func (m *Metrics) Setup(namespace string, subsystem string) { prometheus.MustRegister(m.counter, m.hist) } -func (m *Metrics) Record(method, url string, success bool, httpCode int, errCode e.Err, elapsed float64) { +func (m *Metrics) Record(method, url string, success bool, httpCode int, errCode e.Status, elapsed float64) { m.counter.With(prometheus.Labels{ "method": method, "url": url, diff --git a/internal/service/jwt/middleware.go b/internal/service/jwt/middleware.go index 22145e9..c998f59 100644 --- a/internal/service/jwt/middleware.go +++ b/internal/service/jwt/middleware.go @@ -17,9 +17,9 @@ func (s *service) Handler() gin.HandlerFunc { } token := tokenHeader[len(tokenPrefix):] - claim, err := s.ParseToken(token) - if err != e.Success { - e.Pong(c, err, nil) + claim, status := s.ParseToken(token) + if status != e.Success { + e.Pong(c, status, nil) c.Abort() return } diff --git a/internal/service/jwt/token.go b/internal/service/jwt/token.go index 19a2412..876757e 100644 --- a/internal/service/jwt/token.go +++ b/internal/service/jwt/token.go @@ -11,7 +11,7 @@ import ( "time" ) -func (s *service) ParseToken(tokenText string) (*global.Claim, e.Err) { +func (s *service) ParseToken(tokenText string) (*global.Claim, e.Status) { if tokenText == "" { return nil, e.TokenEmpty } @@ -48,7 +48,7 @@ func (s *service) ParseToken(tokenText string) (*global.Claim, e.Err) { return nil, e.TokenInvalid } -func (s *service) SignClaim(claim *global.Claim) (string, e.Err) { +func (s *service) SignClaim(claim *global.Claim) (string, e.Status) { now := time.Now() claim.IssuedAt = jwt.NewNumericDate(now) diff --git a/internal/service/user/create.go b/internal/service/user/create.go index a0b2b8d..72d4854 100644 --- a/internal/service/user/create.go +++ b/internal/service/user/create.go @@ -14,7 +14,7 @@ type CreateData struct { Password string } -func (s *service) Create(data *CreateData) (*model.User, e.Err) { +func (s *service) Create(data *CreateData) (*model.User, e.Status) { hashed, err := bcrypt.GenerateFromPassword([]byte(data.Password), bcrypt.DefaultCost) if err != nil { s.log.Debug("bcrypt error", zap.Error(err), zap.String("password", data.Password)) diff --git a/internal/service/user/login.go b/internal/service/user/login.go index 48e8ee4..4a5aaa3 100644 --- a/internal/service/user/login.go +++ b/internal/service/user/login.go @@ -8,7 +8,7 @@ import ( "gorm.io/gorm" ) -func (s *service) Login(data *model.User) (*model.User, e.Err) { +func (s *service) Login(data *model.User) (*model.User, e.Status) { user := &model.User{UserName: data.UserName} err := s.db.Where(user).First(&user).Error diff --git a/internal/service/user/service.go b/internal/service/user/service.go index 00ae8f1..e684c72 100644 --- a/internal/service/user/service.go +++ b/internal/service/user/service.go @@ -12,9 +12,9 @@ import ( var _ Service = (*service)(nil) type Service interface { - Create(data *CreateData) (*model.User, e.Err) - Login(data *model.User) (*model.User, e.Err) - IncrVersion(id uint) (int64, e.Err) + Create(data *CreateData) (*model.User, e.Status) + Login(data *model.User) (*model.User, e.Status) + IncrVersion(id uint) (int64, e.Status) } type service struct { diff --git a/internal/service/user/version.go b/internal/service/user/version.go index 1485dad..a342668 100644 --- a/internal/service/user/version.go +++ b/internal/service/user/version.go @@ -7,7 +7,7 @@ import ( "go.uber.org/zap" ) -func (s *service) IncrVersion(id uint) (int64, e.Err) { +func (s *service) IncrVersion(id uint) (int64, e.Status) { version, err := s.redis.Incr(context.Background(), fmt.Sprintf("Version:%d", id)).Result() if err != nil { s.log.Debug("redis.Incr error", zap.Error(err)) From 544a9fd071a5ecbf691074c31a0b2b1f2bf0acd7 Mon Sep 17 00:00:00 2001 From: Paul Pan Date: Tue, 20 Sep 2022 14:34:30 +0800 Subject: [PATCH 07/19] feat: add simple role support --- internal/api/user/create.go | 1 + internal/api/user/login.go | 1 + internal/global/jwt.go | 6 ++++-- internal/repo/model/Role.go | 9 +++++++++ internal/repo/model/User.go | 1 + internal/service/user/create.go | 1 + 6 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 internal/repo/model/Role.go diff --git a/internal/api/user/create.go b/internal/api/user/create.go index f00b5f2..8e2dc25 100644 --- a/internal/api/user/create.go +++ b/internal/api/user/create.go @@ -50,6 +50,7 @@ func (h *handler) Create(c *gin.Context) { } claim := &global.Claim{ UID: u.ID, + Role: u.Role, Version: version, } token, status := h.jwtService.SignClaim(claim) diff --git a/internal/api/user/login.go b/internal/api/user/login.go index 79ae4ed..78c6767 100644 --- a/internal/api/user/login.go +++ b/internal/api/user/login.go @@ -48,6 +48,7 @@ func (h *handler) Login(c *gin.Context) { } claim := &global.Claim{ UID: user.ID, + Role: user.Role, Version: version, } token, status := h.jwtService.SignClaim(claim) diff --git a/internal/global/jwt.go b/internal/global/jwt.go index 24cf814..18d2f70 100644 --- a/internal/global/jwt.go +++ b/internal/global/jwt.go @@ -2,13 +2,15 @@ package global import ( "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/internal/repo/model" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v4" ) type Claim struct { - UID uint `json:"id"` - Version int64 `json:"version"` + UID uint `json:"id"` + Role model.Role `json:"role"` + Version int64 `json:"version"` jwt.RegisteredClaims } diff --git a/internal/repo/model/Role.go b/internal/repo/model/Role.go new file mode 100644 index 0000000..184c77f --- /dev/null +++ b/internal/repo/model/Role.go @@ -0,0 +1,9 @@ +package model + +type Role int + +const ( + RoleGuest Role = 10 + RoleGeneral Role = 20 + RoleAdmin Role = 30 +) diff --git a/internal/repo/model/User.go b/internal/repo/model/User.go index 07cc4f0..e2c7644 100644 --- a/internal/repo/model/User.go +++ b/internal/repo/model/User.go @@ -8,6 +8,7 @@ type User struct { gorm.Model `json:"-"` UserName string `json:"user_name" gorm:"not null;uniqueIndex"` NickName string `json:"nick_name" gorm:"not null"` + Role Role `json:"role" gorm:"not null"` Password []byte `json:"-" gorm:"not null"` IsEnabled bool `json:"is_enabled" gorm:"not null;index"` } diff --git a/internal/service/user/create.go b/internal/service/user/create.go index 72d4854..cf8a8be 100644 --- a/internal/service/user/create.go +++ b/internal/service/user/create.go @@ -25,6 +25,7 @@ func (s *service) Create(data *CreateData) (*model.User, e.Status) { UserName: data.Username, Password: hashed, NickName: data.Nickname, + Role: model.RoleGeneral, IsEnabled: true, } From 92dc04041e492a09cabdfca267f968860d7fd05c Mon Sep 17 00:00:00 2001 From: Paul Pan Date: Tue, 20 Sep 2022 15:11:37 +0800 Subject: [PATCH 08/19] feat: add profile api --- internal/api/user/handler.go | 5 ++-- internal/api/user/profile.go | 47 ++++++++++++++++++++++++++++++++ internal/e/code.go | 2 ++ internal/service/user/profile.go | 22 +++++++++++++++ internal/service/user/service.go | 1 + 5 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 internal/service/user/profile.go diff --git a/internal/api/user/handler.go b/internal/api/user/handler.go index 6f0e94b..c61f324 100644 --- a/internal/api/user/handler.go +++ b/internal/api/user/handler.go @@ -12,7 +12,8 @@ var _ Handler = (*handler)(nil) type Handler interface { Create(c *gin.Context) Login(c *gin.Context) - // List(c *gin.Context) + Logout(c *gin.Context) + Profile(c *gin.Context) } type handler struct { @@ -31,5 +32,5 @@ func RouteRegister(g *global.Global, group *gin.RouterGroup) { group.POST("/login", app.Login) group.POST("/create", app.Create) group.POST("/logout", app.jwtService.Handler(), app.Logout) - // group.GET("/", app.List) + group.POST("/profile", app.jwtService.Handler(), app.Profile) } diff --git a/internal/api/user/profile.go b/internal/api/user/profile.go index a00006b..45eef59 100644 --- a/internal/api/user/profile.go +++ b/internal/api/user/profile.go @@ -1 +1,48 @@ package user + +import ( + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/internal/global" + "github.com/WHUPRJ/woj-server/internal/repo/model" + "github.com/gin-gonic/gin" +) + +type profileRequest struct { + UID uint `form:"uid"` +} + +// Profile +// @Summary profile +// @Description fetch user profile +// @Accept application/x-www-form-urlencoded +// @Produce json +// @Param uid formData string false "user id" +// @Response 200 {object} e.Response "user info" +// @Security Authentication +// @Router /v1/user/profile [post] +func (h *handler) Profile(c *gin.Context) { + // TODO: create a new struct for profile (user info & solve info) + + 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(profileRequest) + if err := c.ShouldBind(req); err == nil { + if req.UID != 0 && req.UID != uid { + if role >= model.RoleAdmin { + uid = req.UID + } else { + e.Pong(c, e.UserUnauthorized, nil) + return + } + } + } + + user, status := h.userService.Profile(uid) + e.Pong(c, status, user) +} diff --git a/internal/e/code.go b/internal/e/code.go index 5a6fd2b..d77f552 100644 --- a/internal/e/code.go +++ b/internal/e/code.go @@ -21,6 +21,7 @@ const ( UserWrongPassword Status = 301 UserDuplicated Status = 302 UserUnauthenticated Status = 303 + UserUnauthorized Status = 304 RedisError Status = 400 ) @@ -46,6 +47,7 @@ var msgText = map[Status]string{ UserWrongPassword: "User Wrong Password", UserDuplicated: "User Duplicated", UserUnauthenticated: "User Unauthenticated", + UserUnauthorized: "User Unauthorized", RedisError: "Redis Error", } diff --git a/internal/service/user/profile.go b/internal/service/user/profile.go new file mode 100644 index 0000000..07709db --- /dev/null +++ b/internal/service/user/profile.go @@ -0,0 +1,22 @@ +package user + +import ( + "errors" + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/internal/repo/model" + "gorm.io/gorm" +) + +func (s *service) Profile(id uint) (*model.User, e.Status) { + user := new(model.User) + + err := s.db.First(&user, id).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + return user, e.UserNotFound + } + if err != nil { + return user, e.DatabaseError + } + + return user, e.Success +} diff --git a/internal/service/user/service.go b/internal/service/user/service.go index e684c72..b430350 100644 --- a/internal/service/user/service.go +++ b/internal/service/user/service.go @@ -15,6 +15,7 @@ type Service interface { Create(data *CreateData) (*model.User, e.Status) Login(data *model.User) (*model.User, e.Status) IncrVersion(id uint) (int64, e.Status) + Profile(id uint) (*model.User, e.Status) } type service struct { From 5b5bf0a070f2b9a4343bcbcc7a48a0e1ba28e82c Mon Sep 17 00:00:00 2001 From: Paul Pan Date: Tue, 20 Sep 2022 15:15:28 +0800 Subject: [PATCH 09/19] fix: login service should test IsEnabled --- internal/e/code.go | 2 ++ internal/service/user/login.go | 10 +++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/internal/e/code.go b/internal/e/code.go index d77f552..91bfe00 100644 --- a/internal/e/code.go +++ b/internal/e/code.go @@ -22,6 +22,7 @@ const ( UserDuplicated Status = 302 UserUnauthenticated Status = 303 UserUnauthorized Status = 304 + UserDisabled Status = 305 RedisError Status = 400 ) @@ -48,6 +49,7 @@ var msgText = map[Status]string{ UserDuplicated: "User Duplicated", UserUnauthenticated: "User Unauthenticated", UserUnauthorized: "User Unauthorized", + UserDisabled: "User Disabled", RedisError: "Redis Error", } diff --git a/internal/service/user/login.go b/internal/service/user/login.go index 4a5aaa3..965759a 100644 --- a/internal/service/user/login.go +++ b/internal/service/user/login.go @@ -13,15 +13,19 @@ func (s *service) Login(data *model.User) (*model.User, e.Status) { err := s.db.Where(user).First(&user).Error if errors.Is(err, gorm.ErrRecordNotFound) { - return user, e.UserNotFound + return nil, e.UserNotFound } if err != nil { - return user, e.DatabaseError + return nil, e.DatabaseError + } + + if !user.IsEnabled { + return nil, e.UserDisabled } err = bcrypt.CompareHashAndPassword(user.Password, data.Password) if err != nil { - return user, e.UserWrongPassword + return nil, e.UserWrongPassword } return user, e.Success From 8d01144d8b0fc3e090564d02669ebdcd60b1facd Mon Sep 17 00:00:00 2001 From: Paul Pan Date: Tue, 20 Sep 2022 16:42:57 +0800 Subject: [PATCH 10/19] feat: add problem service --- internal/e/code.go | 6 ++++-- internal/repo/model/Problem.go | 14 +++++++++++++ internal/repo/postgresql/postgresql.go | 1 + internal/service/problem/create.go | 19 +++++++++++++++++ internal/service/problem/query.go | 23 ++++++++++++++++++++ internal/service/problem/queryFuzz.go | 25 ++++++++++++++++++++++ internal/service/problem/service.go | 29 ++++++++++++++++++++++++++ internal/service/user/profile.go | 4 ++-- 8 files changed, 117 insertions(+), 4 deletions(-) create mode 100644 internal/repo/model/Problem.go create mode 100644 internal/service/problem/create.go create mode 100644 internal/service/problem/query.go create mode 100644 internal/service/problem/queryFuzz.go create mode 100644 internal/service/problem/service.go diff --git a/internal/e/code.go b/internal/e/code.go index 91bfe00..37e90ca 100644 --- a/internal/e/code.go +++ b/internal/e/code.go @@ -8,6 +8,7 @@ const ( InvalidParameter Status = 101 NotFound Status = 102 DatabaseError Status = 103 + RedisError Status = 104 TokenUnknown Status = 200 TokenEmpty Status = 201 @@ -24,7 +25,7 @@ const ( UserUnauthorized Status = 304 UserDisabled Status = 305 - RedisError Status = 400 + ProblemNotFound Status = 500 ) var msgText = map[Status]string{ @@ -35,6 +36,7 @@ var msgText = map[Status]string{ InvalidParameter: "Invalid Parameter", NotFound: "Not Found", DatabaseError: "Database Error", + RedisError: "Redis Error", TokenUnknown: "Unknown Error (Token)", TokenEmpty: "Token Empty", @@ -51,5 +53,5 @@ var msgText = map[Status]string{ UserUnauthorized: "User Unauthorized", UserDisabled: "User Disabled", - RedisError: "Redis Error", + ProblemNotFound: "Problem Not Found", } diff --git a/internal/repo/model/Problem.go b/internal/repo/model/Problem.go new file mode 100644 index 0000000..bc5adfe --- /dev/null +++ b/internal/repo/model/Problem.go @@ -0,0 +1,14 @@ +package model + +import "gorm.io/gorm" + +type Problem struct { + gorm.Model `json:"-"` + Title string `json:"title" gorm:"not null"` + Content string `json:"content" gorm:"not null"` + TimeLimit uint `json:"time_limit" gorm:"not null"` + MemoryLimit uint `json:"memory_limit" gorm:"not null"` + ProviderID uint `json:"provider_id" gorm:"not null;index"` + Provider User `json:"provider" gorm:"foreignKey:ProviderID"` + IsEnabled bool `json:"is_enabled" gorm:"not null;index"` +} diff --git a/internal/repo/postgresql/postgresql.go b/internal/repo/postgresql/postgresql.go index 1181a77..54470fb 100644 --- a/internal/repo/postgresql/postgresql.go +++ b/internal/repo/postgresql/postgresql.go @@ -82,6 +82,7 @@ func (r *Repo) migrateDatabase() { r.log.Info("Auto Migrating database...") _ = r.db.AutoMigrate(&model.User{}) + _ = r.db.AutoMigrate(&model.Problem{}) } // checkAlive deprecated diff --git a/internal/service/problem/create.go b/internal/service/problem/create.go new file mode 100644 index 0000000..972cc5c --- /dev/null +++ b/internal/service/problem/create.go @@ -0,0 +1,19 @@ +package problem + +import ( + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/internal/repo/model" + "go.uber.org/zap" +) + +func (s *service) Create(uid uint, problem *model.Problem) (*model.Problem, e.Status) { + problem.ProviderID = uid + problem.IsEnabled = true + + if err := s.db.Create(problem).Error; err != nil { + s.log.Debug("create problem error", zap.Error(err), zap.Any("problem", problem)) + return nil, e.DatabaseError + } + + return problem, e.Success +} diff --git a/internal/service/problem/query.go b/internal/service/problem/query.go new file mode 100644 index 0000000..264f87a --- /dev/null +++ b/internal/service/problem/query.go @@ -0,0 +1,23 @@ +package problem + +import ( + "errors" + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/internal/repo/model" + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +func (s *service) Query(problemId uint) (*model.Problem, e.Status) { + problem := new(model.Problem) + + err := s.db.Preload(clause.Associations).First(&problem, problemId).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, e.ProblemNotFound + } + if err != nil { + return nil, e.DatabaseError + } + + return problem, e.Success +} diff --git a/internal/service/problem/queryFuzz.go b/internal/service/problem/queryFuzz.go new file mode 100644 index 0000000..b09da9a --- /dev/null +++ b/internal/service/problem/queryFuzz.go @@ -0,0 +1,25 @@ +package problem + +import ( + "errors" + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/internal/repo/model" + "gorm.io/gorm" +) + +func (s *service) QueryFuzz(search string) ([]*model.Problem, e.Status) { + var problems []*model.Problem + + err := s.db. + Where("title LIKE ?", "%"+search+"%"). + Or("content LIKE ?", "%"+search+"%"). + Find(&problems).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, e.ProblemNotFound + } + if err != nil { + return nil, e.DatabaseError + } + + return problems, e.Success +} diff --git a/internal/service/problem/service.go b/internal/service/problem/service.go new file mode 100644 index 0000000..6911eca --- /dev/null +++ b/internal/service/problem/service.go @@ -0,0 +1,29 @@ +package problem + +import ( + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/internal/global" + "github.com/WHUPRJ/woj-server/internal/repo/model" + "go.uber.org/zap" + "gorm.io/gorm" +) + +var _ Service = (*service)(nil) + +type Service interface { + Create(uint, *model.Problem) (*model.Problem, e.Status) + Query(uint) (*model.Problem, e.Status) + QueryFuzz(string) ([]*model.Problem, e.Status) +} + +type service struct { + log *zap.Logger + db *gorm.DB +} + +func NewProblemService(g *global.Global) Service { + return &service{ + log: g.Log, + db: g.Db.Get().(*gorm.DB), + } +} diff --git a/internal/service/user/profile.go b/internal/service/user/profile.go index 07709db..6577242 100644 --- a/internal/service/user/profile.go +++ b/internal/service/user/profile.go @@ -12,10 +12,10 @@ func (s *service) Profile(id uint) (*model.User, e.Status) { err := s.db.First(&user, id).Error if errors.Is(err, gorm.ErrRecordNotFound) { - return user, e.UserNotFound + return nil, e.UserNotFound } if err != nil { - return user, e.DatabaseError + return nil, e.DatabaseError } return user, e.Success From 529b41332cdd20b188c51f5a15a3a9effce6d6f2 Mon Sep 17 00:00:00 2001 From: Paul Pan Date: Mon, 26 Sep 2022 16:13:31 +0800 Subject: [PATCH 11/19] feat: add problem support --- internal/api/problem/handler.go | 32 +++++++++++ internal/api/problem/search.go | 44 +++++++++++++++ internal/api/problem/update.go | 81 +++++++++++++++++++++++++++ internal/api/user/handler.go | 2 +- internal/api/user/profile.go | 2 +- internal/e/code.go | 6 +- internal/repo/model/Problem.go | 4 +- internal/router/api.go | 2 + internal/service/problem/query.go | 3 +- internal/service/problem/queryFuzz.go | 8 ++- internal/service/problem/service.go | 3 +- internal/service/problem/update.go | 20 +++++++ internal/service/user/service.go | 2 +- 13 files changed, 197 insertions(+), 12 deletions(-) create mode 100644 internal/api/problem/handler.go create mode 100644 internal/api/problem/search.go create mode 100644 internal/api/problem/update.go create mode 100644 internal/service/problem/update.go diff --git a/internal/api/problem/handler.go b/internal/api/problem/handler.go new file mode 100644 index 0000000..12378f8 --- /dev/null +++ b/internal/api/problem/handler.go @@ -0,0 +1,32 @@ +package problem + +import ( + "github.com/WHUPRJ/woj-server/internal/global" + "github.com/WHUPRJ/woj-server/internal/service/problem" + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +var _ Handler = (*handler)(nil) + +type Handler interface { + Update(c *gin.Context) + Search(c *gin.Context) +} + +type handler struct { + log *zap.Logger + problemService problem.Service + jwtService global.JwtService +} + +func RouteRegister(g *global.Global, group *gin.RouterGroup) { + app := &handler{ + log: g.Log, + problemService: problem.NewService(g), + jwtService: g.Jwt, + } + + group.POST("/search", app.Search) + group.POST("/update", app.jwtService.Handler(), app.Update) +} diff --git a/internal/api/problem/search.go b/internal/api/problem/search.go new file mode 100644 index 0000000..5e932f6 --- /dev/null +++ b/internal/api/problem/search.go @@ -0,0 +1,44 @@ +package problem + +import ( + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/gin-gonic/gin" +) + +type searchRequest struct { + Pid uint `form:"pid"` + Search string `form:"search"` +} + +// Search +// @Summary get detail of a problem +// @Description get detail of a problem +// @Accept application/x-www-form-urlencoded +// @Produce json +// @Param pid formData int false "problem id" +// @Param search formData string false "search problem" +// @Response 200 {object} e.Response "problem info" +// @Router /v1/problem/search [post] +func (h *handler) Search(c *gin.Context) { + req := new(searchRequest) + + if err := c.ShouldBind(req); err != nil { + e.Pong(c, e.InvalidParameter, err.Error()) + return + } + + if req.Pid == 0 && req.Search == "" { + e.Pong(c, e.InvalidParameter, nil) + return + } + + if req.Pid != 0 { + problem, status := h.problemService.Query(req.Pid) + e.Pong(c, status, problem) + return + } else { + problem, status := h.problemService.QueryFuzz(req.Search) + e.Pong(c, status, problem) + return + } +} diff --git a/internal/api/problem/update.go b/internal/api/problem/update.go new file mode 100644 index 0000000..83192d2 --- /dev/null +++ b/internal/api/problem/update.go @@ -0,0 +1,81 @@ +package problem + +import ( + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/internal/global" + "github.com/WHUPRJ/woj-server/internal/repo/model" + "github.com/gin-gonic/gin" +) + +type updateRequest struct { + Pid uint `form:"pid"` + Title string `form:"title" binding:"required"` + Content string `form:"content" binding:"required"` + TimeLimit uint `form:"time_limit" binding:"required"` + MemoryLimit uint `form:"memory_limit" binding:"required"` + IsEnabled bool `form:"is_enabled"` +} + +// Update +// @Summary create or update a problem +// @Description create or update a problem +// @Accept application/x-www-form-urlencoded +// @Produce json +// @Param pid formData int false "problem id, 0 for create" +// @Param title formData string true "title" +// @Param content formData string true "content" +// @Param time_limit formData int true "time limit in ms" +// @Param memory_limit formData int true "memory limit in kb" +// @Param is_enabled formData bool false "is enabled" +// @Response 200 {object} e.Response "problem info without provider information" +// @Security Authentication +// @Router /v1/problem/update [post] +func (h *handler) Update(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 + if role < model.RoleAdmin { + e.Pong(c, e.UserUnauthorized, nil) + return + } + + req := new(updateRequest) + if err := c.ShouldBind(req); err != nil { + e.Pong(c, e.InvalidParameter, err.Error()) + return + } + + problem := &model.Problem{ + Title: req.Title, + Content: req.Content, + TimeLimit: req.TimeLimit, + MemoryLimit: req.MemoryLimit, + IsEnabled: req.IsEnabled, + } + + if req.Pid == 0 { + problem, status := h.problemService.Create(uid, problem) + e.Pong(c, status, problem) + return + } else { + inDb, status := h.problemService.Query(req.Pid) + if status != e.Success && status != e.ProblemNotAvailable { + e.Pong(c, status, nil) + return + } + + if inDb.ProviderID != uid { + e.Pong(c, e.UserUnauthorized, nil) + return + } + + problem, status := h.problemService.Update(req.Pid, problem) + e.Pong(c, status, problem) + return + } +} diff --git a/internal/api/user/handler.go b/internal/api/user/handler.go index c61f324..1dcc8b5 100644 --- a/internal/api/user/handler.go +++ b/internal/api/user/handler.go @@ -25,7 +25,7 @@ type handler struct { func RouteRegister(g *global.Global, group *gin.RouterGroup) { app := &handler{ log: g.Log, - userService: user.NewUserService(g), + userService: user.NewService(g), jwtService: g.Jwt, } diff --git a/internal/api/user/profile.go b/internal/api/user/profile.go index 45eef59..b8b6117 100644 --- a/internal/api/user/profile.go +++ b/internal/api/user/profile.go @@ -16,7 +16,7 @@ type profileRequest struct { // @Description fetch user profile // @Accept application/x-www-form-urlencoded // @Produce json -// @Param uid formData string false "user id" +// @Param uid formData int false "user id" // @Response 200 {object} e.Response "user info" // @Security Authentication // @Router /v1/user/profile [post] diff --git a/internal/e/code.go b/internal/e/code.go index 37e90ca..5165c0c 100644 --- a/internal/e/code.go +++ b/internal/e/code.go @@ -25,7 +25,8 @@ const ( UserUnauthorized Status = 304 UserDisabled Status = 305 - ProblemNotFound Status = 500 + ProblemNotFound Status = 500 + ProblemNotAvailable Status = 501 ) var msgText = map[Status]string{ @@ -53,5 +54,6 @@ var msgText = map[Status]string{ UserUnauthorized: "User Unauthorized", UserDisabled: "User Disabled", - ProblemNotFound: "Problem Not Found", + ProblemNotFound: "Problem Not Found", + ProblemNotAvailable: "Problem Not Available", } diff --git a/internal/repo/model/Problem.go b/internal/repo/model/Problem.go index bc5adfe..075ce26 100644 --- a/internal/repo/model/Problem.go +++ b/internal/repo/model/Problem.go @@ -3,12 +3,12 @@ package model import "gorm.io/gorm" type Problem struct { - gorm.Model `json:"-"` + gorm.Model `json:"meta"` Title string `json:"title" gorm:"not null"` Content string `json:"content" gorm:"not null"` TimeLimit uint `json:"time_limit" gorm:"not null"` MemoryLimit uint `json:"memory_limit" gorm:"not null"` ProviderID uint `json:"provider_id" gorm:"not null;index"` - Provider User `json:"provider" gorm:"foreignKey:ProviderID"` + Provider User `json:"-" gorm:"foreignKey:ProviderID"` IsEnabled bool `json:"is_enabled" gorm:"not null;index"` } diff --git a/internal/router/api.go b/internal/router/api.go index 0687aae..80fab98 100644 --- a/internal/router/api.go +++ b/internal/router/api.go @@ -2,6 +2,7 @@ package router import ( "github.com/WHUPRJ/woj-server/internal/api/debug" + "github.com/WHUPRJ/woj-server/internal/api/problem" "github.com/WHUPRJ/woj-server/internal/api/user" "github.com/WHUPRJ/woj-server/internal/global" "github.com/gin-gonic/gin" @@ -23,4 +24,5 @@ func setupApi(g *global.Global, root *gin.RouterGroup) { var endpoints = []global.EndpointInfo{ {Version: "", Path: "/debug", Register: debug.RouteRegister}, {Version: "/v1", Path: "/user", Register: user.RouteRegister}, + {Version: "/v1", Path: "/problem", Register: problem.RouteRegister}, } diff --git a/internal/service/problem/query.go b/internal/service/problem/query.go index 264f87a..9930338 100644 --- a/internal/service/problem/query.go +++ b/internal/service/problem/query.go @@ -4,6 +4,7 @@ import ( "errors" "github.com/WHUPRJ/woj-server/internal/e" "github.com/WHUPRJ/woj-server/internal/repo/model" + "github.com/WHUPRJ/woj-server/pkg/utils" "gorm.io/gorm" "gorm.io/gorm/clause" ) @@ -19,5 +20,5 @@ func (s *service) Query(problemId uint) (*model.Problem, e.Status) { return nil, e.DatabaseError } - return problem, e.Success + return problem, utils.If(problem.IsEnabled, e.Success, e.ProblemNotAvailable).(e.Status) } diff --git a/internal/service/problem/queryFuzz.go b/internal/service/problem/queryFuzz.go index b09da9a..e7ddee6 100644 --- a/internal/service/problem/queryFuzz.go +++ b/internal/service/problem/queryFuzz.go @@ -5,14 +5,16 @@ import ( "github.com/WHUPRJ/woj-server/internal/e" "github.com/WHUPRJ/woj-server/internal/repo/model" "gorm.io/gorm" + "gorm.io/gorm/clause" ) func (s *service) QueryFuzz(search string) ([]*model.Problem, e.Status) { var problems []*model.Problem - err := s.db. - Where("title LIKE ?", "%"+search+"%"). - Or("content LIKE ?", "%"+search+"%"). + err := s.db.Preload(clause.Associations). + Where("is_enabled = true"). + Where(s.db.Where("title LIKE ?", "%"+search+"%"). + Or("content LIKE ?", "%"+search+"%")). Find(&problems).Error if errors.Is(err, gorm.ErrRecordNotFound) { return nil, e.ProblemNotFound diff --git a/internal/service/problem/service.go b/internal/service/problem/service.go index 6911eca..1abd869 100644 --- a/internal/service/problem/service.go +++ b/internal/service/problem/service.go @@ -12,6 +12,7 @@ var _ Service = (*service)(nil) type Service interface { Create(uint, *model.Problem) (*model.Problem, e.Status) + Update(uint, *model.Problem) (*model.Problem, e.Status) Query(uint) (*model.Problem, e.Status) QueryFuzz(string) ([]*model.Problem, e.Status) } @@ -21,7 +22,7 @@ type service struct { db *gorm.DB } -func NewProblemService(g *global.Global) Service { +func NewService(g *global.Global) Service { return &service{ log: g.Log, db: g.Db.Get().(*gorm.DB), diff --git a/internal/service/problem/update.go b/internal/service/problem/update.go new file mode 100644 index 0000000..7617f8d --- /dev/null +++ b/internal/service/problem/update.go @@ -0,0 +1,20 @@ +package problem + +import ( + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/internal/repo/model" + "go.uber.org/zap" + "gorm.io/gorm/clause" +) + +func (s *service) Update(pid uint, problem *model.Problem) (*model.Problem, e.Status) { + if err := s.db.Clauses(clause.Returning{}).Model(problem). + Where("ID = (?)", pid). + Select("Title", "Content", "TimeLimit", "MemoryLimit", "IsEnabled"). + Updates(problem).Error; err != nil { + s.log.Debug("update problem error", zap.Error(err), zap.Any("problem", problem)) + return nil, e.DatabaseError + } + + return problem, e.Success +} diff --git a/internal/service/user/service.go b/internal/service/user/service.go index b430350..9242231 100644 --- a/internal/service/user/service.go +++ b/internal/service/user/service.go @@ -24,7 +24,7 @@ type service struct { redis *redis.Client } -func NewUserService(g *global.Global) Service { +func NewService(g *global.Global) Service { return &service{ log: g.Log, db: g.Db.Get().(*gorm.DB), From 2ae99e2636896b17ce9bf7d4707f533dd24aa445 Mon Sep 17 00:00:00 2001 From: Paul Pan Date: Mon, 26 Sep 2022 17:15:31 +0800 Subject: [PATCH 12/19] fix: middleware now record real success info --- internal/e/resp.go | 1 + internal/pkg/metrics/middleware.go | 2 ++ 2 files changed, 3 insertions(+) diff --git a/internal/e/resp.go b/internal/e/resp.go index 25da040..b3a8e4c 100644 --- a/internal/e/resp.go +++ b/internal/e/resp.go @@ -21,5 +21,6 @@ func Wrap(status Status, body interface{}) interface{} { } func Pong(c *gin.Context, status Status, body interface{}) { + c.Set("err", status) c.JSON(http.StatusOK, Wrap(status, body)) } diff --git a/internal/pkg/metrics/middleware.go b/internal/pkg/metrics/middleware.go index 679c322..e1fd20e 100644 --- a/internal/pkg/metrics/middleware.go +++ b/internal/pkg/metrics/middleware.go @@ -34,6 +34,8 @@ func (m *Metrics) Handler() gin.HandlerFunc { if !ok { success = false err = e.Unknown + } else if err != e.Success { + success = false } m.Record(method, url, success, status, err, elapsed) From 2cec7d46bc62140fe8cdb73a82500b4ce638498a Mon Sep 17 00:00:00 2001 From: Paul Pan Date: Tue, 4 Oct 2022 15:31:07 +0800 Subject: [PATCH 13/19] feat: problem and submission framework Co-authored-by: cxy004 Co-authored-by: wzt --- go.mod | 1 + go.sum | 3 ++- internal/api/user/login.go | 9 ++++++--- internal/api/user/profile.go | 2 +- internal/repo/model/Problem.go | 21 +++++++++++++-------- internal/repo/model/Status.go | 11 +++++++++++ internal/repo/model/Submission.go | 13 +++++++++++++ internal/repo/model/Verdict.go | 17 +++++++++++++++++ internal/repo/postgresql/postgresql.go | 1 + internal/service/submission/service.go | 24 ++++++++++++++++++++++++ internal/service/submission/submit.go | 1 + 11 files changed, 90 insertions(+), 13 deletions(-) create mode 100644 internal/repo/model/Status.go create mode 100644 internal/repo/model/Submission.go create mode 100644 internal/repo/model/Verdict.go create mode 100644 internal/service/submission/service.go create mode 100644 internal/service/submission/submit.go diff --git a/go.mod b/go.mod index 8db94ce..ce789c6 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/gin-gonic/gin v1.8.1 github.com/go-redis/redis/v8 v8.11.5 github.com/golang-jwt/jwt/v4 v4.4.2 + github.com/lib/pq v1.10.2 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 diff --git a/go.sum b/go.sum index d2106f7..fd55f08 100644 --- a/go.sum +++ b/go.sum @@ -415,8 +415,9 @@ go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= +go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= diff --git a/internal/api/user/login.go b/internal/api/user/login.go index 78c6767..a5812a3 100644 --- a/internal/api/user/login.go +++ b/internal/api/user/login.go @@ -19,13 +19,13 @@ type loginRequest struct { // @Produce json // @Param username formData string true "username" // @Param password formData string true "password" -// @Response 200 {object} e.Response "jwt token" +// @Response 200 {object} e.Response "jwt token and user nickname" // @Router /v1/user/login [post] func (h *handler) Login(c *gin.Context) { req := new(loginRequest) if err := c.ShouldBind(req); err != nil { - e.Pong(c, e.InvalidParameter, err.Error()) + e.Pong(c, e.InvalidParameter, nil) return } @@ -52,5 +52,8 @@ func (h *handler) Login(c *gin.Context) { Version: version, } token, status := h.jwtService.SignClaim(claim) - e.Pong(c, status, token) + e.Pong(c, status, gin.H{ + "token": token, + "nickname": user.NickName, + }) } diff --git a/internal/api/user/profile.go b/internal/api/user/profile.go index b8b6117..a7bd388 100644 --- a/internal/api/user/profile.go +++ b/internal/api/user/profile.go @@ -34,7 +34,7 @@ func (h *handler) Profile(c *gin.Context) { req := new(profileRequest) if err := c.ShouldBind(req); err == nil { if req.UID != 0 && req.UID != uid { - if role >= model.RoleAdmin { + if role >= model.RoleGeneral { uid = req.UID } else { e.Pong(c, e.UserUnauthorized, nil) diff --git a/internal/repo/model/Problem.go b/internal/repo/model/Problem.go index 075ce26..2e2de22 100644 --- a/internal/repo/model/Problem.go +++ b/internal/repo/model/Problem.go @@ -1,14 +1,19 @@ package model -import "gorm.io/gorm" +import ( + "github.com/lib/pq" + "gorm.io/gorm" +) type Problem struct { gorm.Model `json:"meta"` - Title string `json:"title" gorm:"not null"` - Content string `json:"content" gorm:"not null"` - TimeLimit uint `json:"time_limit" gorm:"not null"` - MemoryLimit uint `json:"memory_limit" gorm:"not null"` - ProviderID uint `json:"provider_id" gorm:"not null;index"` - Provider User `json:"-" gorm:"foreignKey:ProviderID"` - IsEnabled bool `json:"is_enabled" gorm:"not null;index"` + Title string `json:"title" gorm:"not null"` + Content string `json:"content" gorm:"not null"` + TimeLimit uint `json:"time_limit" gorm:"not null"` + MemoryLimit uint `json:"memory_limit" gorm:"not null"` + ProviderID uint `json:"provider_id" gorm:"not null;index"` + Provider User `json:"-" gorm:"foreignKey:ProviderID"` + Languages pq.Int32Array `json:"languages" gorm:"type:int[]"` + Points pq.Int32Array `json:"points" gorm:"type:int[]"` + IsEnabled bool `json:"is_enabled" gorm:"not null;index"` } diff --git a/internal/repo/model/Status.go b/internal/repo/model/Status.go new file mode 100644 index 0000000..76fab37 --- /dev/null +++ b/internal/repo/model/Status.go @@ -0,0 +1,11 @@ +package model + +import "gorm.io/gorm" + +type Status struct { + gorm.Model `json:"-"` + SubmissionID uint `json:"submission_id" gorm:"not null;index"` + Submission Submission `json:"-" gorm:"foreignKey:SubmissionID"` + Verdict Verdict `json:"verdict" gorm:"not null"` + Point int32 `json:"point" gorm:"not null"` +} diff --git a/internal/repo/model/Submission.go b/internal/repo/model/Submission.go new file mode 100644 index 0000000..26407a1 --- /dev/null +++ b/internal/repo/model/Submission.go @@ -0,0 +1,13 @@ +package model + +import "gorm.io/gorm" + +type Submission struct { + gorm.Model `json:"-"` + ProblemID uint `json:"problem_id" gorm:"not null;index"` + Problem Problem `json:"-" gorm:"foreignKey:ProblemID"` + UserID uint `json:"user_id" gorm:"not null;index"` + User User `json:"-" gorm:"foreignKey:UserID"` + Language Lang `json:"language" gorm:"not null"` + Code string `json:"code" gorm:"not null"` +} diff --git a/internal/repo/model/Verdict.go b/internal/repo/model/Verdict.go new file mode 100644 index 0000000..6fafc69 --- /dev/null +++ b/internal/repo/model/Verdict.go @@ -0,0 +1,17 @@ +package model + +type Verdict int + +const ( + VerdictJudging Verdict = 1 + VerdictAccepted Verdict = 2 + VerdictWrongAnswer Verdict = 3 + VerdictTimeLimitExceeded Verdict = 4 + VerdictMemoryLimitExceeded Verdict = 5 + VerdictRuntimeError Verdict = 6 + VerdictCompileError Verdict = 7 + VerdictSystemError Verdict = 8 + VerdictJuryFailed Verdict = 9 + VerdictSkipped Verdict = 10 + VerdictPartiallyCorrect Verdict = 11 +) diff --git a/internal/repo/postgresql/postgresql.go b/internal/repo/postgresql/postgresql.go index 54470fb..40fe689 100644 --- a/internal/repo/postgresql/postgresql.go +++ b/internal/repo/postgresql/postgresql.go @@ -83,6 +83,7 @@ func (r *Repo) migrateDatabase() { _ = r.db.AutoMigrate(&model.User{}) _ = r.db.AutoMigrate(&model.Problem{}) + _ = r.db.AutoMigrate(&model.Submission{}) } // checkAlive deprecated diff --git a/internal/service/submission/service.go b/internal/service/submission/service.go new file mode 100644 index 0000000..96b2f2a --- /dev/null +++ b/internal/service/submission/service.go @@ -0,0 +1,24 @@ +package submission + +import ( + "github.com/WHUPRJ/woj-server/internal/global" + "github.com/WHUPRJ/woj-server/internal/repo/amqp" + "go.uber.org/zap" +) + +var _ Service = (*service)(nil) + +type Service interface { +} + +type service struct { + log *zap.Logger + mq *amqp.Repo +} + +func NewService(g *global.Global) Service { + return &service{ + log: g.Log, + mq: g.Mq.Get().(*amqp.Repo), + } +} diff --git a/internal/service/submission/submit.go b/internal/service/submission/submit.go new file mode 100644 index 0000000..cb33958 --- /dev/null +++ b/internal/service/submission/submit.go @@ -0,0 +1 @@ +package submission From cb10c3b28c25c1da162113517fe81733cafc3ece Mon Sep 17 00:00:00 2001 From: Paul Pan Date: Tue, 4 Oct 2022 18:47:50 +0800 Subject: [PATCH 14/19] chore: move model to root --- go.mod | 7 +++- go.sum | 42 ++++++++++++++++++++ internal/api/problem/update.go | 2 +- internal/api/user/login.go | 2 +- internal/api/user/profile.go | 2 +- internal/e/code.go | 6 +++ internal/global/jwt.go | 2 +- internal/repo/model/Verdict.go | 17 -------- internal/repo/postgresql/postgresql.go | 2 +- internal/service/problem/create.go | 2 +- internal/service/problem/query.go | 2 +- internal/service/problem/queryFuzz.go | 2 +- internal/service/problem/service.go | 2 +- internal/service/problem/update.go | 2 +- internal/service/submission/service.go | 24 ----------- internal/service/submission/submit.go | 1 - internal/service/task/common.go | 30 ++++++++++++++ internal/service/task/judge.go | 20 ++++++++++ internal/service/task/service.go | 35 ++++++++++++++++ internal/service/user/create.go | 2 +- internal/service/user/login.go | 2 +- internal/service/user/profile.go | 2 +- internal/service/user/service.go | 2 +- model/Language.go | 6 +++ {internal/repo/model => model}/Problem.go | 0 {internal/repo/model => model}/Role.go | 0 {internal/repo/model => model}/Status.go | 0 {internal/repo/model => model}/Submission.go | 2 +- model/Task.go | 20 ++++++++++ {internal/repo/model => model}/User.go | 0 model/Verdict.go | 17 ++++++++ 31 files changed, 197 insertions(+), 58 deletions(-) delete mode 100644 internal/repo/model/Verdict.go delete mode 100644 internal/service/submission/service.go delete mode 100644 internal/service/submission/submit.go create mode 100644 internal/service/task/common.go create mode 100644 internal/service/task/judge.go create mode 100644 internal/service/task/service.go create mode 100644 model/Language.go rename {internal/repo/model => model}/Problem.go (100%) rename {internal/repo/model => model}/Role.go (100%) rename {internal/repo/model => model}/Status.go (100%) rename {internal/repo/model => model}/Submission.go (87%) create mode 100644 model/Task.go rename {internal/repo/model => model}/User.go (100%) create mode 100644 model/Verdict.go diff --git a/go.mod b/go.mod index ce789c6..9b4c2d7 100644 --- a/go.mod +++ b/go.mod @@ -41,6 +41,8 @@ require ( github.com/go-playground/validator/v10 v10.10.0 // indirect github.com/goccy/go-json v0.9.7 // indirect github.com/golang/protobuf v1.5.2 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/hibiken/asynq v0.23.0 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect github.com/jackc/pgconn v1.12.1 // indirect github.com/jackc/pgio v1.0.0 // indirect @@ -63,14 +65,17 @@ require ( github.com/prometheus/client_model v0.2.0 // indirect 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/russross/blackfriday/v2 v2.1.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/sys v0.0.0-20220520151302-bc2c85ada10a // indirect + golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect golang.org/x/text v0.3.7 // 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/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index fd55f08..52a4312 100644 --- a/go.sum +++ b/go.sum @@ -80,7 +80,9 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF 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= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g= github.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURUfmBoMlcs= @@ -127,6 +129,7 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= +github.com/go-redis/redis/v8 v8.11.2/go.mod h1:DLomh7y2e3ggQXQLd1YgmvIfecPJoFl7WU5SOQ/r06M= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -176,6 +179,7 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -188,11 +192,17 @@ github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hibiken/asynq v0.23.0 h1:kmKkNFgqiXBatC8oz94Mer6uvKoGn4STlIVDV5wnKyE= +github.com/hibiken/asynq v0.23.0/go.mod h1:K70jPVx+CAmmQrXot7Dru0D52EO7ob4BIun3ri5z1Qw= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= @@ -304,8 +314,15 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48= github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= @@ -346,6 +363,8 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= @@ -367,6 +386,9 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= 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= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= @@ -400,6 +422,7 @@ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsr github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= @@ -414,6 +437,7 @@ go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v0.10.0/go.mod h1:VCZuO8V8mFPlL0F5J5GK1rtHV3DrFcQ1R8ryq7FK0aI= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= @@ -479,6 +503,7 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -502,10 +527,13 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= @@ -530,10 +558,12 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -547,8 +577,11 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -568,7 +601,9 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -583,6 +618,8 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/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= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -598,6 +635,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y= +golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -644,6 +683,7 @@ golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20= @@ -741,8 +781,10 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 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/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= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/internal/api/problem/update.go b/internal/api/problem/update.go index 83192d2..11550c1 100644 --- a/internal/api/problem/update.go +++ b/internal/api/problem/update.go @@ -3,7 +3,7 @@ package problem import ( "github.com/WHUPRJ/woj-server/internal/e" "github.com/WHUPRJ/woj-server/internal/global" - "github.com/WHUPRJ/woj-server/internal/repo/model" + "github.com/WHUPRJ/woj-server/model" "github.com/gin-gonic/gin" ) diff --git a/internal/api/user/login.go b/internal/api/user/login.go index a5812a3..b922962 100644 --- a/internal/api/user/login.go +++ b/internal/api/user/login.go @@ -3,7 +3,7 @@ package user import ( "github.com/WHUPRJ/woj-server/internal/e" "github.com/WHUPRJ/woj-server/internal/global" - "github.com/WHUPRJ/woj-server/internal/repo/model" + "github.com/WHUPRJ/woj-server/model" "github.com/gin-gonic/gin" ) diff --git a/internal/api/user/profile.go b/internal/api/user/profile.go index a7bd388..90e8904 100644 --- a/internal/api/user/profile.go +++ b/internal/api/user/profile.go @@ -3,7 +3,7 @@ package user import ( "github.com/WHUPRJ/woj-server/internal/e" "github.com/WHUPRJ/woj-server/internal/global" - "github.com/WHUPRJ/woj-server/internal/repo/model" + "github.com/WHUPRJ/woj-server/model" "github.com/gin-gonic/gin" ) diff --git a/internal/e/code.go b/internal/e/code.go index 5165c0c..d23f510 100644 --- a/internal/e/code.go +++ b/internal/e/code.go @@ -27,6 +27,9 @@ const ( ProblemNotFound Status = 500 ProblemNotAvailable Status = 501 + + TaskEnqueueFailed Status = 600 + TaskGetInfoFailed Status = 601 ) var msgText = map[Status]string{ @@ -56,4 +59,7 @@ var msgText = map[Status]string{ ProblemNotFound: "Problem Not Found", ProblemNotAvailable: "Problem Not Available", + + TaskEnqueueFailed: "Task Enqueue Failed", + TaskGetInfoFailed: "Task Get Info Failed", } diff --git a/internal/global/jwt.go b/internal/global/jwt.go index 18d2f70..2d3e5e3 100644 --- a/internal/global/jwt.go +++ b/internal/global/jwt.go @@ -2,7 +2,7 @@ package global import ( "github.com/WHUPRJ/woj-server/internal/e" - "github.com/WHUPRJ/woj-server/internal/repo/model" + "github.com/WHUPRJ/woj-server/model" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v4" ) diff --git a/internal/repo/model/Verdict.go b/internal/repo/model/Verdict.go deleted file mode 100644 index 6fafc69..0000000 --- a/internal/repo/model/Verdict.go +++ /dev/null @@ -1,17 +0,0 @@ -package model - -type Verdict int - -const ( - VerdictJudging Verdict = 1 - VerdictAccepted Verdict = 2 - VerdictWrongAnswer Verdict = 3 - VerdictTimeLimitExceeded Verdict = 4 - VerdictMemoryLimitExceeded Verdict = 5 - VerdictRuntimeError Verdict = 6 - VerdictCompileError Verdict = 7 - VerdictSystemError Verdict = 8 - VerdictJuryFailed Verdict = 9 - VerdictSkipped Verdict = 10 - VerdictPartiallyCorrect Verdict = 11 -) diff --git a/internal/repo/postgresql/postgresql.go b/internal/repo/postgresql/postgresql.go index 40fe689..a5f8bc4 100644 --- a/internal/repo/postgresql/postgresql.go +++ b/internal/repo/postgresql/postgresql.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" "github.com/WHUPRJ/woj-server/internal/global" - "github.com/WHUPRJ/woj-server/internal/repo/model" + "github.com/WHUPRJ/woj-server/model" "go.uber.org/zap" "gorm.io/driver/postgres" "gorm.io/gorm" diff --git a/internal/service/problem/create.go b/internal/service/problem/create.go index 972cc5c..a06c392 100644 --- a/internal/service/problem/create.go +++ b/internal/service/problem/create.go @@ -2,7 +2,7 @@ package problem import ( "github.com/WHUPRJ/woj-server/internal/e" - "github.com/WHUPRJ/woj-server/internal/repo/model" + "github.com/WHUPRJ/woj-server/model" "go.uber.org/zap" ) diff --git a/internal/service/problem/query.go b/internal/service/problem/query.go index 9930338..4155f28 100644 --- a/internal/service/problem/query.go +++ b/internal/service/problem/query.go @@ -3,7 +3,7 @@ package problem import ( "errors" "github.com/WHUPRJ/woj-server/internal/e" - "github.com/WHUPRJ/woj-server/internal/repo/model" + "github.com/WHUPRJ/woj-server/model" "github.com/WHUPRJ/woj-server/pkg/utils" "gorm.io/gorm" "gorm.io/gorm/clause" diff --git a/internal/service/problem/queryFuzz.go b/internal/service/problem/queryFuzz.go index e7ddee6..0d82896 100644 --- a/internal/service/problem/queryFuzz.go +++ b/internal/service/problem/queryFuzz.go @@ -3,7 +3,7 @@ package problem import ( "errors" "github.com/WHUPRJ/woj-server/internal/e" - "github.com/WHUPRJ/woj-server/internal/repo/model" + "github.com/WHUPRJ/woj-server/model" "gorm.io/gorm" "gorm.io/gorm/clause" ) diff --git a/internal/service/problem/service.go b/internal/service/problem/service.go index 1abd869..584117a 100644 --- a/internal/service/problem/service.go +++ b/internal/service/problem/service.go @@ -3,7 +3,7 @@ package problem import ( "github.com/WHUPRJ/woj-server/internal/e" "github.com/WHUPRJ/woj-server/internal/global" - "github.com/WHUPRJ/woj-server/internal/repo/model" + "github.com/WHUPRJ/woj-server/model" "go.uber.org/zap" "gorm.io/gorm" ) diff --git a/internal/service/problem/update.go b/internal/service/problem/update.go index 7617f8d..31ddad2 100644 --- a/internal/service/problem/update.go +++ b/internal/service/problem/update.go @@ -2,7 +2,7 @@ package problem import ( "github.com/WHUPRJ/woj-server/internal/e" - "github.com/WHUPRJ/woj-server/internal/repo/model" + "github.com/WHUPRJ/woj-server/model" "go.uber.org/zap" "gorm.io/gorm/clause" ) diff --git a/internal/service/submission/service.go b/internal/service/submission/service.go deleted file mode 100644 index 96b2f2a..0000000 --- a/internal/service/submission/service.go +++ /dev/null @@ -1,24 +0,0 @@ -package submission - -import ( - "github.com/WHUPRJ/woj-server/internal/global" - "github.com/WHUPRJ/woj-server/internal/repo/amqp" - "go.uber.org/zap" -) - -var _ Service = (*service)(nil) - -type Service interface { -} - -type service struct { - log *zap.Logger - mq *amqp.Repo -} - -func NewService(g *global.Global) Service { - return &service{ - log: g.Log, - mq: g.Mq.Get().(*amqp.Repo), - } -} diff --git a/internal/service/submission/submit.go b/internal/service/submission/submit.go deleted file mode 100644 index cb33958..0000000 --- a/internal/service/submission/submit.go +++ /dev/null @@ -1 +0,0 @@ -package submission diff --git a/internal/service/task/common.go b/internal/service/task/common.go new file mode 100644 index 0000000..3ceb80a --- /dev/null +++ b/internal/service/task/common.go @@ -0,0 +1,30 @@ +package task + +import ( + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/hibiken/asynq" + "go.uber.org/zap" +) + +func (s *service) submit(typename string, payload []byte) (*asynq.TaskInfo, e.Status) { + task := asynq.NewTask(typename, payload) + + info, err := s.queue.Enqueue(task) + if err != nil { + s.log.Warn("failed to enqueue task", zap.Error(err), zap.Any("task", task)) + return nil, e.TaskEnqueueFailed + } + + s.log.Debug("Successfully enqueued task", zap.Any("info", info)) + + return info, e.Success +} + +func (s *service) GetTaskInfo(id string) (*asynq.TaskInfo, e.Status) { + task, err := s.inspector.GetTaskInfo("", id) + if err != nil { + s.log.Debug("get task info failed", zap.Error(err), zap.String("id", id)) + return nil, e.TaskGetInfoFailed + } + return task, e.Success +} diff --git a/internal/service/task/judge.go b/internal/service/task/judge.go new file mode 100644 index 0000000..2623414 --- /dev/null +++ b/internal/service/task/judge.go @@ -0,0 +1,20 @@ +package task + +import ( + "encoding/json" + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/model" + "go.uber.org/zap" +) + +func (s *service) NewJudge(submission model.Submission) e.Status { + payload, err := json.Marshal(model.SubmitJudge{Submission: submission}) + if err != nil { + s.log.Warn("json marshal error", zap.Error(err), zap.Any("payload", submission)) + return e.InternalError + } + + _, status := s.submit(model.TypeSubmitJudge, payload) + + return status +} diff --git a/internal/service/task/service.go b/internal/service/task/service.go new file mode 100644 index 0000000..679bb3a --- /dev/null +++ b/internal/service/task/service.go @@ -0,0 +1,35 @@ +package task + +import ( + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/internal/global" + "github.com/WHUPRJ/woj-server/model" + "github.com/hibiken/asynq" + "go.uber.org/zap" +) + +var _ Service = (*service)(nil) + +type Service interface { + NewJudge(submission model.Submission) e.Status + submit(typename string, payload []byte) (*asynq.TaskInfo, e.Status) +} + +type service struct { + log *zap.Logger + queue *asynq.Client + inspector *asynq.Inspector +} + +func NewService(g *global.Global) Service { + redisOpt := asynq.RedisClientOpt{ + Addr: g.Conf.Redis.Address, + Password: g.Conf.Redis.Password, + DB: g.Conf.Redis.Db, + } + return &service{ + log: g.Log, + queue: asynq.NewClient(redisOpt), + inspector: asynq.NewInspector(redisOpt), + } +} diff --git a/internal/service/user/create.go b/internal/service/user/create.go index cf8a8be..32f8059 100644 --- a/internal/service/user/create.go +++ b/internal/service/user/create.go @@ -2,7 +2,7 @@ package user import ( "github.com/WHUPRJ/woj-server/internal/e" - "github.com/WHUPRJ/woj-server/internal/repo/model" + "github.com/WHUPRJ/woj-server/model" "go.uber.org/zap" "golang.org/x/crypto/bcrypt" "strings" diff --git a/internal/service/user/login.go b/internal/service/user/login.go index 965759a..64ccad0 100644 --- a/internal/service/user/login.go +++ b/internal/service/user/login.go @@ -3,7 +3,7 @@ package user import ( "errors" "github.com/WHUPRJ/woj-server/internal/e" - "github.com/WHUPRJ/woj-server/internal/repo/model" + "github.com/WHUPRJ/woj-server/model" "golang.org/x/crypto/bcrypt" "gorm.io/gorm" ) diff --git a/internal/service/user/profile.go b/internal/service/user/profile.go index 6577242..a94d169 100644 --- a/internal/service/user/profile.go +++ b/internal/service/user/profile.go @@ -3,7 +3,7 @@ package user import ( "errors" "github.com/WHUPRJ/woj-server/internal/e" - "github.com/WHUPRJ/woj-server/internal/repo/model" + "github.com/WHUPRJ/woj-server/model" "gorm.io/gorm" ) diff --git a/internal/service/user/service.go b/internal/service/user/service.go index 9242231..00b83ba 100644 --- a/internal/service/user/service.go +++ b/internal/service/user/service.go @@ -3,7 +3,7 @@ package user import ( "github.com/WHUPRJ/woj-server/internal/e" "github.com/WHUPRJ/woj-server/internal/global" - "github.com/WHUPRJ/woj-server/internal/repo/model" + "github.com/WHUPRJ/woj-server/model" "github.com/go-redis/redis/v8" "go.uber.org/zap" "gorm.io/gorm" diff --git a/model/Language.go b/model/Language.go new file mode 100644 index 0000000..9a30357 --- /dev/null +++ b/model/Language.go @@ -0,0 +1,6 @@ +package model + +const ( + LangC int32 = iota + LangCPP +) diff --git a/internal/repo/model/Problem.go b/model/Problem.go similarity index 100% rename from internal/repo/model/Problem.go rename to model/Problem.go diff --git a/internal/repo/model/Role.go b/model/Role.go similarity index 100% rename from internal/repo/model/Role.go rename to model/Role.go diff --git a/internal/repo/model/Status.go b/model/Status.go similarity index 100% rename from internal/repo/model/Status.go rename to model/Status.go diff --git a/internal/repo/model/Submission.go b/model/Submission.go similarity index 87% rename from internal/repo/model/Submission.go rename to model/Submission.go index 26407a1..66b61ad 100644 --- a/internal/repo/model/Submission.go +++ b/model/Submission.go @@ -8,6 +8,6 @@ type Submission struct { Problem Problem `json:"-" gorm:"foreignKey:ProblemID"` UserID uint `json:"user_id" gorm:"not null;index"` User User `json:"-" gorm:"foreignKey:UserID"` - Language Lang `json:"language" gorm:"not null"` + Language int32 `json:"language" gorm:"not null"` Code string `json:"code" gorm:"not null"` } diff --git a/model/Task.go b/model/Task.go new file mode 100644 index 0000000..35b3694 --- /dev/null +++ b/model/Task.go @@ -0,0 +1,20 @@ +package model + +const ( + TypeProblemResolve = "problem:resolve" + TypeProblemPush = "problem:push" + TypeSubmitJudge = "submit:judge" +) + +type ProblemResolvePayload struct { + ProblemID uint + ProblemFile string +} + +type ProblemPushPayload struct { + ProblemFile string +} + +type SubmitJudge struct { + Submission Submission +} diff --git a/internal/repo/model/User.go b/model/User.go similarity index 100% rename from internal/repo/model/User.go rename to model/User.go diff --git a/model/Verdict.go b/model/Verdict.go new file mode 100644 index 0000000..3551512 --- /dev/null +++ b/model/Verdict.go @@ -0,0 +1,17 @@ +package model + +type Verdict int + +const ( + VerdictJudging Verdict = iota + VerdictAccepted + VerdictWrongAnswer + VerdictTimeLimitExceeded + VerdictMemoryLimitExceeded + VerdictRuntimeError + VerdictCompileError + VerdictSystemError + VerdictJuryFailed + VerdictSkipped + VerdictPartiallyCorrect +) From cb79b2090af0ac3a211b4ce0a01d87f8a3df5a5e Mon Sep 17 00:00:00 2001 From: Paul Pan Date: Thu, 13 Oct 2022 16:32:44 +0800 Subject: [PATCH 15/19] chore: move global and repo to root --- cmd/app/run.go | 2 +- config.yaml | 1 + {internal/global => global}/config.go | 1 + {internal/global => global}/global.go | 0 {internal/global => global}/jwt.go | 0 {internal/global => global}/repo.go | 0 {internal/global => global}/router.go | 0 {internal/global => global}/setup.go | 0 go.mod | 2 +- go.sum | 6 ++--- internal/api/debug/handler.go | 2 +- internal/api/problem/handler.go | 2 +- internal/api/problem/update.go | 2 +- internal/api/user/create.go | 2 +- internal/api/user/handler.go | 2 +- internal/api/user/login.go | 2 +- internal/api/user/logout.go | 2 +- internal/api/user/profile.go | 2 +- internal/app/app.go | 6 ++--- internal/router/api.go | 2 +- internal/router/router.go | 2 +- internal/service/jwt/service.go | 2 +- internal/service/jwt/token.go | 2 +- internal/service/problem/service.go | 2 +- internal/service/task/common.go | 2 +- internal/service/task/judge.go | 8 +++---- internal/service/task/push.go | 23 +++++++++++++++++++ internal/service/task/service.go | 8 ++++--- internal/service/user/service.go | 2 +- model/Problem.go | 1 + model/Task.go | 11 +++------ .../repo => repo}/postgresql/postgresql.go | 2 +- {internal/repo => repo}/redis/redis.go | 2 +- 33 files changed, 62 insertions(+), 41 deletions(-) rename {internal/global => global}/config.go (97%) rename {internal/global => global}/global.go (100%) rename {internal/global => global}/jwt.go (100%) rename {internal/global => global}/repo.go (100%) rename {internal/global => global}/router.go (100%) rename {internal/global => global}/setup.go (100%) create mode 100644 internal/service/task/push.go rename {internal/repo => repo}/postgresql/postgresql.go (97%) rename {internal/repo => repo}/redis/redis.go (93%) diff --git a/cmd/app/run.go b/cmd/app/run.go index 0da7fbc..96b23bd 100644 --- a/cmd/app/run.go +++ b/cmd/app/run.go @@ -1,8 +1,8 @@ package main import ( + "github.com/WHUPRJ/woj-server/global" "github.com/WHUPRJ/woj-server/internal/app" - "github.com/WHUPRJ/woj-server/internal/global" "github.com/urfave/cli/v2" "math/rand" "time" diff --git a/config.yaml b/config.yaml index 8663f6a..de46fd7 100644 --- a/config.yaml +++ b/config.yaml @@ -6,6 +6,7 @@ WebServer: Redis: Db: 0 + QueueDb: 0 Address: '127.0.0.1:6379' Password: '' diff --git a/internal/global/config.go b/global/config.go similarity index 97% rename from internal/global/config.go rename to global/config.go index aa1083e..e478dee 100644 --- a/internal/global/config.go +++ b/global/config.go @@ -9,6 +9,7 @@ type ConfigWebServer struct { type ConfigRedis struct { Db int `yaml:"Db"` + QueueDb int `yaml:"QueueDb"` Address string `yaml:"Address"` Password string `yaml:"Password"` } diff --git a/internal/global/global.go b/global/global.go similarity index 100% rename from internal/global/global.go rename to global/global.go diff --git a/internal/global/jwt.go b/global/jwt.go similarity index 100% rename from internal/global/jwt.go rename to global/jwt.go diff --git a/internal/global/repo.go b/global/repo.go similarity index 100% rename from internal/global/repo.go rename to global/repo.go diff --git a/internal/global/router.go b/global/router.go similarity index 100% rename from internal/global/router.go rename to global/router.go diff --git a/internal/global/setup.go b/global/setup.go similarity index 100% rename from internal/global/setup.go rename to global/setup.go diff --git a/go.mod b/go.mod index 9b4c2d7..79d3d95 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/gin-gonic/gin v1.8.1 github.com/go-redis/redis/v8 v8.11.5 github.com/golang-jwt/jwt/v4 v4.4.2 + github.com/hibiken/asynq v0.23.0 github.com/lib/pq v1.10.2 github.com/prometheus/client_golang v1.13.0 github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a @@ -42,7 +43,6 @@ require ( github.com/goccy/go-json v0.9.7 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/uuid v1.3.0 // indirect - github.com/hibiken/asynq v0.23.0 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect github.com/jackc/pgconn v1.12.1 // indirect github.com/jackc/pgio v1.0.0 // indirect diff --git a/go.sum b/go.sum index 52a4312..d858d78 100644 --- a/go.sum +++ b/go.sum @@ -80,6 +80,7 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF 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= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -439,9 +440,8 @@ go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v0.10.0/go.mod h1:VCZuO8V8mFPlL0F5J5GK1rtHV3DrFcQ1R8ryq7FK0aI= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= -go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= @@ -616,8 +616,6 @@ 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-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/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= diff --git a/internal/api/debug/handler.go b/internal/api/debug/handler.go index 23ae47e..d5d9e85 100644 --- a/internal/api/debug/handler.go +++ b/internal/api/debug/handler.go @@ -1,7 +1,7 @@ package debug import ( - "github.com/WHUPRJ/woj-server/internal/global" + "github.com/WHUPRJ/woj-server/global" "github.com/gin-gonic/gin" "go.uber.org/zap" ) diff --git a/internal/api/problem/handler.go b/internal/api/problem/handler.go index 12378f8..08fb97f 100644 --- a/internal/api/problem/handler.go +++ b/internal/api/problem/handler.go @@ -1,7 +1,7 @@ package problem import ( - "github.com/WHUPRJ/woj-server/internal/global" + "github.com/WHUPRJ/woj-server/global" "github.com/WHUPRJ/woj-server/internal/service/problem" "github.com/gin-gonic/gin" "go.uber.org/zap" diff --git a/internal/api/problem/update.go b/internal/api/problem/update.go index 11550c1..68e2d85 100644 --- a/internal/api/problem/update.go +++ b/internal/api/problem/update.go @@ -1,8 +1,8 @@ package problem import ( + "github.com/WHUPRJ/woj-server/global" "github.com/WHUPRJ/woj-server/internal/e" - "github.com/WHUPRJ/woj-server/internal/global" "github.com/WHUPRJ/woj-server/model" "github.com/gin-gonic/gin" ) diff --git a/internal/api/user/create.go b/internal/api/user/create.go index 8e2dc25..5149ee0 100644 --- a/internal/api/user/create.go +++ b/internal/api/user/create.go @@ -1,8 +1,8 @@ package user import ( + "github.com/WHUPRJ/woj-server/global" "github.com/WHUPRJ/woj-server/internal/e" - "github.com/WHUPRJ/woj-server/internal/global" "github.com/WHUPRJ/woj-server/internal/service/user" "github.com/gin-gonic/gin" ) diff --git a/internal/api/user/handler.go b/internal/api/user/handler.go index 1dcc8b5..e05fba1 100644 --- a/internal/api/user/handler.go +++ b/internal/api/user/handler.go @@ -1,7 +1,7 @@ package user import ( - "github.com/WHUPRJ/woj-server/internal/global" + "github.com/WHUPRJ/woj-server/global" "github.com/WHUPRJ/woj-server/internal/service/user" "github.com/gin-gonic/gin" "go.uber.org/zap" diff --git a/internal/api/user/login.go b/internal/api/user/login.go index b922962..40336f6 100644 --- a/internal/api/user/login.go +++ b/internal/api/user/login.go @@ -1,8 +1,8 @@ package user import ( + "github.com/WHUPRJ/woj-server/global" "github.com/WHUPRJ/woj-server/internal/e" - "github.com/WHUPRJ/woj-server/internal/global" "github.com/WHUPRJ/woj-server/model" "github.com/gin-gonic/gin" ) diff --git a/internal/api/user/logout.go b/internal/api/user/logout.go index f9a288c..015759a 100644 --- a/internal/api/user/logout.go +++ b/internal/api/user/logout.go @@ -1,8 +1,8 @@ package user import ( + "github.com/WHUPRJ/woj-server/global" "github.com/WHUPRJ/woj-server/internal/e" - "github.com/WHUPRJ/woj-server/internal/global" "github.com/gin-gonic/gin" ) diff --git a/internal/api/user/profile.go b/internal/api/user/profile.go index 90e8904..cd14ec9 100644 --- a/internal/api/user/profile.go +++ b/internal/api/user/profile.go @@ -1,8 +1,8 @@ package user import ( + "github.com/WHUPRJ/woj-server/global" "github.com/WHUPRJ/woj-server/internal/e" - "github.com/WHUPRJ/woj-server/internal/global" "github.com/WHUPRJ/woj-server/model" "github.com/gin-gonic/gin" ) diff --git a/internal/app/app.go b/internal/app/app.go index 58139f5..9e77a21 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -3,11 +3,11 @@ package app import ( "context" "fmt" - "github.com/WHUPRJ/woj-server/internal/global" - "github.com/WHUPRJ/woj-server/internal/repo/postgresql" - "github.com/WHUPRJ/woj-server/internal/repo/redis" + "github.com/WHUPRJ/woj-server/global" "github.com/WHUPRJ/woj-server/internal/router" "github.com/WHUPRJ/woj-server/internal/service/jwt" + "github.com/WHUPRJ/woj-server/repo/postgresql" + "github.com/WHUPRJ/woj-server/repo/redis" "go.uber.org/zap" "net/http" "os" diff --git a/internal/router/api.go b/internal/router/api.go index 80fab98..1e88076 100644 --- a/internal/router/api.go +++ b/internal/router/api.go @@ -1,10 +1,10 @@ package router import ( + "github.com/WHUPRJ/woj-server/global" "github.com/WHUPRJ/woj-server/internal/api/debug" "github.com/WHUPRJ/woj-server/internal/api/problem" "github.com/WHUPRJ/woj-server/internal/api/user" - "github.com/WHUPRJ/woj-server/internal/global" "github.com/gin-gonic/gin" ) diff --git a/internal/router/router.go b/internal/router/router.go index a5669f2..f03bb88 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -1,7 +1,7 @@ package router import ( - "github.com/WHUPRJ/woj-server/internal/global" + "github.com/WHUPRJ/woj-server/global" "github.com/WHUPRJ/woj-server/internal/pkg/metrics" _ "github.com/WHUPRJ/woj-server/internal/router/docs" "github.com/WHUPRJ/woj-server/pkg/utils" diff --git a/internal/service/jwt/service.go b/internal/service/jwt/service.go index 3cb372d..36b1d99 100644 --- a/internal/service/jwt/service.go +++ b/internal/service/jwt/service.go @@ -1,7 +1,7 @@ package jwt import ( - "github.com/WHUPRJ/woj-server/internal/global" + "github.com/WHUPRJ/woj-server/global" "github.com/go-redis/redis/v8" "go.uber.org/zap" ) diff --git a/internal/service/jwt/token.go b/internal/service/jwt/token.go index 876757e..5deaa17 100644 --- a/internal/service/jwt/token.go +++ b/internal/service/jwt/token.go @@ -3,8 +3,8 @@ package jwt import ( "context" "fmt" + "github.com/WHUPRJ/woj-server/global" "github.com/WHUPRJ/woj-server/internal/e" - "github.com/WHUPRJ/woj-server/internal/global" "github.com/WHUPRJ/woj-server/pkg/utils" "github.com/golang-jwt/jwt/v4" "go.uber.org/zap" diff --git a/internal/service/problem/service.go b/internal/service/problem/service.go index 584117a..b9c3e4a 100644 --- a/internal/service/problem/service.go +++ b/internal/service/problem/service.go @@ -1,8 +1,8 @@ package problem import ( + "github.com/WHUPRJ/woj-server/global" "github.com/WHUPRJ/woj-server/internal/e" - "github.com/WHUPRJ/woj-server/internal/global" "github.com/WHUPRJ/woj-server/model" "go.uber.org/zap" "gorm.io/gorm" diff --git a/internal/service/task/common.go b/internal/service/task/common.go index 3ceb80a..3629330 100644 --- a/internal/service/task/common.go +++ b/internal/service/task/common.go @@ -21,7 +21,7 @@ func (s *service) submit(typename string, payload []byte) (*asynq.TaskInfo, e.St } func (s *service) GetTaskInfo(id string) (*asynq.TaskInfo, e.Status) { - task, err := s.inspector.GetTaskInfo("", id) + task, err := s.inspector.GetTaskInfo("default", id) if err != nil { s.log.Debug("get task info failed", zap.Error(err), zap.String("id", id)) return nil, e.TaskGetInfoFailed diff --git a/internal/service/task/judge.go b/internal/service/task/judge.go index 2623414..12df05f 100644 --- a/internal/service/task/judge.go +++ b/internal/service/task/judge.go @@ -7,14 +7,14 @@ import ( "go.uber.org/zap" ) -func (s *service) NewJudge(submission model.Submission) e.Status { +func (s *service) NewJudge(submission model.Submission) (string, e.Status) { payload, err := json.Marshal(model.SubmitJudge{Submission: submission}) if err != nil { s.log.Warn("json marshal error", zap.Error(err), zap.Any("payload", submission)) - return e.InternalError + return "", e.InternalError } - _, status := s.submit(model.TypeSubmitJudge, payload) + info, status := s.submit(model.TypeSubmitJudge, payload) - return status + return info.ID, status } diff --git a/internal/service/task/push.go b/internal/service/task/push.go new file mode 100644 index 0000000..b6026af --- /dev/null +++ b/internal/service/task/push.go @@ -0,0 +1,23 @@ +package task + +import ( + "encoding/json" + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/model" + "go.uber.org/zap" +) + +func (s *service) PushProblem(id uint, file string) (string, e.Status) { + payload, err := json.Marshal(model.ProblemPushPayload{ + ProblemID: id, + ProblemFile: file, + }) + if err != nil { + s.log.Warn("json marshal error", zap.Error(err), zap.Any("id", id), zap.String("file", file)) + return "", e.InternalError + } + + info, status := s.submit(model.TypeSubmitJudge, payload) + + return info.ID, status +} diff --git a/internal/service/task/service.go b/internal/service/task/service.go index 679bb3a..8b104b5 100644 --- a/internal/service/task/service.go +++ b/internal/service/task/service.go @@ -1,8 +1,8 @@ package task import ( + "github.com/WHUPRJ/woj-server/global" "github.com/WHUPRJ/woj-server/internal/e" - "github.com/WHUPRJ/woj-server/internal/global" "github.com/WHUPRJ/woj-server/model" "github.com/hibiken/asynq" "go.uber.org/zap" @@ -11,7 +11,9 @@ import ( var _ Service = (*service)(nil) type Service interface { - NewJudge(submission model.Submission) e.Status + NewJudge(submission model.Submission) (string, e.Status) + PushProblem(id uint, file string) (string, e.Status) + GetTaskInfo(id string) (*asynq.TaskInfo, e.Status) submit(typename string, payload []byte) (*asynq.TaskInfo, e.Status) } @@ -25,7 +27,7 @@ func NewService(g *global.Global) Service { redisOpt := asynq.RedisClientOpt{ Addr: g.Conf.Redis.Address, Password: g.Conf.Redis.Password, - DB: g.Conf.Redis.Db, + DB: g.Conf.Redis.QueueDb, } return &service{ log: g.Log, diff --git a/internal/service/user/service.go b/internal/service/user/service.go index 00b83ba..4f38ef0 100644 --- a/internal/service/user/service.go +++ b/internal/service/user/service.go @@ -1,8 +1,8 @@ package user import ( + "github.com/WHUPRJ/woj-server/global" "github.com/WHUPRJ/woj-server/internal/e" - "github.com/WHUPRJ/woj-server/internal/global" "github.com/WHUPRJ/woj-server/model" "github.com/go-redis/redis/v8" "go.uber.org/zap" diff --git a/model/Problem.go b/model/Problem.go index 2e2de22..458daf2 100644 --- a/model/Problem.go +++ b/model/Problem.go @@ -15,5 +15,6 @@ type Problem struct { Provider User `json:"-" gorm:"foreignKey:ProviderID"` Languages pq.Int32Array `json:"languages" gorm:"type:int[]"` Points pq.Int32Array `json:"points" gorm:"type:int[]"` + StorageKey string `json:"storage_key" gorm:"not null"` IsEnabled bool `json:"is_enabled" gorm:"not null;index"` } diff --git a/model/Task.go b/model/Task.go index 35b3694..453ea8a 100644 --- a/model/Task.go +++ b/model/Task.go @@ -1,17 +1,12 @@ package model const ( - TypeProblemResolve = "problem:resolve" - TypeProblemPush = "problem:push" - TypeSubmitJudge = "submit:judge" + TypeProblemPush = "problem:push" + TypeSubmitJudge = "submit:judge" ) -type ProblemResolvePayload struct { - ProblemID uint - ProblemFile string -} - type ProblemPushPayload struct { + ProblemID uint ProblemFile string } diff --git a/internal/repo/postgresql/postgresql.go b/repo/postgresql/postgresql.go similarity index 97% rename from internal/repo/postgresql/postgresql.go rename to repo/postgresql/postgresql.go index a5f8bc4..a99d57d 100644 --- a/internal/repo/postgresql/postgresql.go +++ b/repo/postgresql/postgresql.go @@ -4,7 +4,7 @@ import ( "database/sql" "errors" "fmt" - "github.com/WHUPRJ/woj-server/internal/global" + "github.com/WHUPRJ/woj-server/global" "github.com/WHUPRJ/woj-server/model" "go.uber.org/zap" "gorm.io/driver/postgres" diff --git a/internal/repo/redis/redis.go b/repo/redis/redis.go similarity index 93% rename from internal/repo/redis/redis.go rename to repo/redis/redis.go index d23da71..1058f4d 100644 --- a/internal/repo/redis/redis.go +++ b/repo/redis/redis.go @@ -2,7 +2,7 @@ package redis import ( "context" - "github.com/WHUPRJ/woj-server/internal/global" + "github.com/WHUPRJ/woj-server/global" "github.com/go-redis/redis/v8" "go.uber.org/zap" ) From b77c9684093284227e3cdcb152e93c4c6aa7e4c4 Mon Sep 17 00:00:00 2001 From: Paul Pan Date: Thu, 20 Oct 2022 16:50:19 +0800 Subject: [PATCH 16/19] feat: manually revert back --- cmd/app/run.go | 2 +- internal/api/debug/handler.go | 2 +- internal/api/problem/handler.go | 2 +- internal/api/problem/update.go | 4 ++-- internal/api/user/create.go | 2 +- internal/api/user/handler.go | 2 +- internal/api/user/login.go | 4 ++-- internal/api/user/logout.go | 2 +- internal/api/user/profile.go | 4 ++-- internal/app/app.go | 6 +++--- {global => internal/global}/config.go | 0 {global => internal/global}/global.go | 0 {global => internal/global}/jwt.go | 2 +- {global => internal/global}/repo.go | 0 {global => internal/global}/router.go | 0 {global => internal/global}/setup.go | 0 {model => internal/model}/Language.go | 0 {model => internal/model}/Problem.go | 0 {model => internal/model}/Role.go | 0 {model => internal/model}/Status.go | 0 {model => internal/model}/Submission.go | 0 {model => internal/model}/Task.go | 0 {model => internal/model}/User.go | 0 {model => internal/model}/Verdict.go | 0 {repo => internal/repo}/postgresql/postgresql.go | 4 ++-- {repo => internal/repo}/redis/redis.go | 2 +- internal/router/api.go | 2 +- internal/router/router.go | 2 +- internal/service/jwt/service.go | 2 +- internal/service/jwt/token.go | 2 +- internal/service/problem/create.go | 2 +- internal/service/problem/query.go | 2 +- internal/service/problem/queryFuzz.go | 2 +- internal/service/problem/service.go | 4 ++-- internal/service/problem/update.go | 2 +- internal/service/task/judge.go | 2 +- internal/service/task/push.go | 2 +- internal/service/task/service.go | 4 ++-- internal/service/user/create.go | 2 +- internal/service/user/login.go | 2 +- internal/service/user/profile.go | 2 +- internal/service/user/service.go | 4 ++-- 42 files changed, 38 insertions(+), 38 deletions(-) rename {global => internal/global}/config.go (100%) rename {global => internal/global}/global.go (100%) rename {global => internal/global}/jwt.go (90%) rename {global => internal/global}/repo.go (100%) rename {global => internal/global}/router.go (100%) rename {global => internal/global}/setup.go (100%) rename {model => internal/model}/Language.go (100%) rename {model => internal/model}/Problem.go (100%) rename {model => internal/model}/Role.go (100%) rename {model => internal/model}/Status.go (100%) rename {model => internal/model}/Submission.go (100%) rename {model => internal/model}/Task.go (100%) rename {model => internal/model}/User.go (100%) rename {model => internal/model}/Verdict.go (100%) rename {repo => internal/repo}/postgresql/postgresql.go (95%) rename {repo => internal/repo}/redis/redis.go (93%) diff --git a/cmd/app/run.go b/cmd/app/run.go index 96b23bd..0da7fbc 100644 --- a/cmd/app/run.go +++ b/cmd/app/run.go @@ -1,8 +1,8 @@ package main import ( - "github.com/WHUPRJ/woj-server/global" "github.com/WHUPRJ/woj-server/internal/app" + "github.com/WHUPRJ/woj-server/internal/global" "github.com/urfave/cli/v2" "math/rand" "time" diff --git a/internal/api/debug/handler.go b/internal/api/debug/handler.go index d5d9e85..23ae47e 100644 --- a/internal/api/debug/handler.go +++ b/internal/api/debug/handler.go @@ -1,7 +1,7 @@ package debug import ( - "github.com/WHUPRJ/woj-server/global" + "github.com/WHUPRJ/woj-server/internal/global" "github.com/gin-gonic/gin" "go.uber.org/zap" ) diff --git a/internal/api/problem/handler.go b/internal/api/problem/handler.go index 08fb97f..12378f8 100644 --- a/internal/api/problem/handler.go +++ b/internal/api/problem/handler.go @@ -1,7 +1,7 @@ package problem import ( - "github.com/WHUPRJ/woj-server/global" + "github.com/WHUPRJ/woj-server/internal/global" "github.com/WHUPRJ/woj-server/internal/service/problem" "github.com/gin-gonic/gin" "go.uber.org/zap" diff --git a/internal/api/problem/update.go b/internal/api/problem/update.go index 68e2d85..03d5bd3 100644 --- a/internal/api/problem/update.go +++ b/internal/api/problem/update.go @@ -1,9 +1,9 @@ package problem import ( - "github.com/WHUPRJ/woj-server/global" "github.com/WHUPRJ/woj-server/internal/e" - "github.com/WHUPRJ/woj-server/model" + "github.com/WHUPRJ/woj-server/internal/global" + "github.com/WHUPRJ/woj-server/internal/model" "github.com/gin-gonic/gin" ) diff --git a/internal/api/user/create.go b/internal/api/user/create.go index 5149ee0..8e2dc25 100644 --- a/internal/api/user/create.go +++ b/internal/api/user/create.go @@ -1,8 +1,8 @@ package user import ( - "github.com/WHUPRJ/woj-server/global" "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/internal/global" "github.com/WHUPRJ/woj-server/internal/service/user" "github.com/gin-gonic/gin" ) diff --git a/internal/api/user/handler.go b/internal/api/user/handler.go index e05fba1..1dcc8b5 100644 --- a/internal/api/user/handler.go +++ b/internal/api/user/handler.go @@ -1,7 +1,7 @@ package user import ( - "github.com/WHUPRJ/woj-server/global" + "github.com/WHUPRJ/woj-server/internal/global" "github.com/WHUPRJ/woj-server/internal/service/user" "github.com/gin-gonic/gin" "go.uber.org/zap" diff --git a/internal/api/user/login.go b/internal/api/user/login.go index 40336f6..1252c33 100644 --- a/internal/api/user/login.go +++ b/internal/api/user/login.go @@ -1,9 +1,9 @@ package user import ( - "github.com/WHUPRJ/woj-server/global" "github.com/WHUPRJ/woj-server/internal/e" - "github.com/WHUPRJ/woj-server/model" + "github.com/WHUPRJ/woj-server/internal/global" + "github.com/WHUPRJ/woj-server/internal/model" "github.com/gin-gonic/gin" ) diff --git a/internal/api/user/logout.go b/internal/api/user/logout.go index 015759a..f9a288c 100644 --- a/internal/api/user/logout.go +++ b/internal/api/user/logout.go @@ -1,8 +1,8 @@ package user import ( - "github.com/WHUPRJ/woj-server/global" "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/internal/global" "github.com/gin-gonic/gin" ) diff --git a/internal/api/user/profile.go b/internal/api/user/profile.go index cd14ec9..07a2f45 100644 --- a/internal/api/user/profile.go +++ b/internal/api/user/profile.go @@ -1,9 +1,9 @@ package user import ( - "github.com/WHUPRJ/woj-server/global" "github.com/WHUPRJ/woj-server/internal/e" - "github.com/WHUPRJ/woj-server/model" + "github.com/WHUPRJ/woj-server/internal/global" + "github.com/WHUPRJ/woj-server/internal/model" "github.com/gin-gonic/gin" ) diff --git a/internal/app/app.go b/internal/app/app.go index 9e77a21..58139f5 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -3,11 +3,11 @@ package app import ( "context" "fmt" - "github.com/WHUPRJ/woj-server/global" + "github.com/WHUPRJ/woj-server/internal/global" + "github.com/WHUPRJ/woj-server/internal/repo/postgresql" + "github.com/WHUPRJ/woj-server/internal/repo/redis" "github.com/WHUPRJ/woj-server/internal/router" "github.com/WHUPRJ/woj-server/internal/service/jwt" - "github.com/WHUPRJ/woj-server/repo/postgresql" - "github.com/WHUPRJ/woj-server/repo/redis" "go.uber.org/zap" "net/http" "os" diff --git a/global/config.go b/internal/global/config.go similarity index 100% rename from global/config.go rename to internal/global/config.go diff --git a/global/global.go b/internal/global/global.go similarity index 100% rename from global/global.go rename to internal/global/global.go diff --git a/global/jwt.go b/internal/global/jwt.go similarity index 90% rename from global/jwt.go rename to internal/global/jwt.go index 2d3e5e3..cac10dd 100644 --- a/global/jwt.go +++ b/internal/global/jwt.go @@ -2,7 +2,7 @@ package global import ( "github.com/WHUPRJ/woj-server/internal/e" - "github.com/WHUPRJ/woj-server/model" + "github.com/WHUPRJ/woj-server/internal/model" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v4" ) diff --git a/global/repo.go b/internal/global/repo.go similarity index 100% rename from global/repo.go rename to internal/global/repo.go diff --git a/global/router.go b/internal/global/router.go similarity index 100% rename from global/router.go rename to internal/global/router.go diff --git a/global/setup.go b/internal/global/setup.go similarity index 100% rename from global/setup.go rename to internal/global/setup.go diff --git a/model/Language.go b/internal/model/Language.go similarity index 100% rename from model/Language.go rename to internal/model/Language.go diff --git a/model/Problem.go b/internal/model/Problem.go similarity index 100% rename from model/Problem.go rename to internal/model/Problem.go diff --git a/model/Role.go b/internal/model/Role.go similarity index 100% rename from model/Role.go rename to internal/model/Role.go diff --git a/model/Status.go b/internal/model/Status.go similarity index 100% rename from model/Status.go rename to internal/model/Status.go diff --git a/model/Submission.go b/internal/model/Submission.go similarity index 100% rename from model/Submission.go rename to internal/model/Submission.go diff --git a/model/Task.go b/internal/model/Task.go similarity index 100% rename from model/Task.go rename to internal/model/Task.go diff --git a/model/User.go b/internal/model/User.go similarity index 100% rename from model/User.go rename to internal/model/User.go diff --git a/model/Verdict.go b/internal/model/Verdict.go similarity index 100% rename from model/Verdict.go rename to internal/model/Verdict.go diff --git a/repo/postgresql/postgresql.go b/internal/repo/postgresql/postgresql.go similarity index 95% rename from repo/postgresql/postgresql.go rename to internal/repo/postgresql/postgresql.go index a99d57d..15b0226 100644 --- a/repo/postgresql/postgresql.go +++ b/internal/repo/postgresql/postgresql.go @@ -4,8 +4,8 @@ import ( "database/sql" "errors" "fmt" - "github.com/WHUPRJ/woj-server/global" - "github.com/WHUPRJ/woj-server/model" + "github.com/WHUPRJ/woj-server/internal/global" + "github.com/WHUPRJ/woj-server/internal/model" "go.uber.org/zap" "gorm.io/driver/postgres" "gorm.io/gorm" diff --git a/repo/redis/redis.go b/internal/repo/redis/redis.go similarity index 93% rename from repo/redis/redis.go rename to internal/repo/redis/redis.go index 1058f4d..d23da71 100644 --- a/repo/redis/redis.go +++ b/internal/repo/redis/redis.go @@ -2,7 +2,7 @@ package redis import ( "context" - "github.com/WHUPRJ/woj-server/global" + "github.com/WHUPRJ/woj-server/internal/global" "github.com/go-redis/redis/v8" "go.uber.org/zap" ) diff --git a/internal/router/api.go b/internal/router/api.go index 1e88076..80fab98 100644 --- a/internal/router/api.go +++ b/internal/router/api.go @@ -1,10 +1,10 @@ package router import ( - "github.com/WHUPRJ/woj-server/global" "github.com/WHUPRJ/woj-server/internal/api/debug" "github.com/WHUPRJ/woj-server/internal/api/problem" "github.com/WHUPRJ/woj-server/internal/api/user" + "github.com/WHUPRJ/woj-server/internal/global" "github.com/gin-gonic/gin" ) diff --git a/internal/router/router.go b/internal/router/router.go index f03bb88..a5669f2 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -1,7 +1,7 @@ package router import ( - "github.com/WHUPRJ/woj-server/global" + "github.com/WHUPRJ/woj-server/internal/global" "github.com/WHUPRJ/woj-server/internal/pkg/metrics" _ "github.com/WHUPRJ/woj-server/internal/router/docs" "github.com/WHUPRJ/woj-server/pkg/utils" diff --git a/internal/service/jwt/service.go b/internal/service/jwt/service.go index 36b1d99..3cb372d 100644 --- a/internal/service/jwt/service.go +++ b/internal/service/jwt/service.go @@ -1,7 +1,7 @@ package jwt import ( - "github.com/WHUPRJ/woj-server/global" + "github.com/WHUPRJ/woj-server/internal/global" "github.com/go-redis/redis/v8" "go.uber.org/zap" ) diff --git a/internal/service/jwt/token.go b/internal/service/jwt/token.go index 5deaa17..876757e 100644 --- a/internal/service/jwt/token.go +++ b/internal/service/jwt/token.go @@ -3,8 +3,8 @@ package jwt import ( "context" "fmt" - "github.com/WHUPRJ/woj-server/global" "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/internal/global" "github.com/WHUPRJ/woj-server/pkg/utils" "github.com/golang-jwt/jwt/v4" "go.uber.org/zap" diff --git a/internal/service/problem/create.go b/internal/service/problem/create.go index a06c392..ad48e3a 100644 --- a/internal/service/problem/create.go +++ b/internal/service/problem/create.go @@ -2,7 +2,7 @@ package problem import ( "github.com/WHUPRJ/woj-server/internal/e" - "github.com/WHUPRJ/woj-server/model" + "github.com/WHUPRJ/woj-server/internal/model" "go.uber.org/zap" ) diff --git a/internal/service/problem/query.go b/internal/service/problem/query.go index 4155f28..f6ef0df 100644 --- a/internal/service/problem/query.go +++ b/internal/service/problem/query.go @@ -3,7 +3,7 @@ package problem import ( "errors" "github.com/WHUPRJ/woj-server/internal/e" - "github.com/WHUPRJ/woj-server/model" + "github.com/WHUPRJ/woj-server/internal/model" "github.com/WHUPRJ/woj-server/pkg/utils" "gorm.io/gorm" "gorm.io/gorm/clause" diff --git a/internal/service/problem/queryFuzz.go b/internal/service/problem/queryFuzz.go index 0d82896..0fad2fc 100644 --- a/internal/service/problem/queryFuzz.go +++ b/internal/service/problem/queryFuzz.go @@ -3,7 +3,7 @@ package problem import ( "errors" "github.com/WHUPRJ/woj-server/internal/e" - "github.com/WHUPRJ/woj-server/model" + "github.com/WHUPRJ/woj-server/internal/model" "gorm.io/gorm" "gorm.io/gorm/clause" ) diff --git a/internal/service/problem/service.go b/internal/service/problem/service.go index b9c3e4a..693951d 100644 --- a/internal/service/problem/service.go +++ b/internal/service/problem/service.go @@ -1,9 +1,9 @@ package problem import ( - "github.com/WHUPRJ/woj-server/global" "github.com/WHUPRJ/woj-server/internal/e" - "github.com/WHUPRJ/woj-server/model" + "github.com/WHUPRJ/woj-server/internal/global" + "github.com/WHUPRJ/woj-server/internal/model" "go.uber.org/zap" "gorm.io/gorm" ) diff --git a/internal/service/problem/update.go b/internal/service/problem/update.go index 31ddad2..079bb13 100644 --- a/internal/service/problem/update.go +++ b/internal/service/problem/update.go @@ -2,7 +2,7 @@ package problem import ( "github.com/WHUPRJ/woj-server/internal/e" - "github.com/WHUPRJ/woj-server/model" + "github.com/WHUPRJ/woj-server/internal/model" "go.uber.org/zap" "gorm.io/gorm/clause" ) diff --git a/internal/service/task/judge.go b/internal/service/task/judge.go index 12df05f..057c150 100644 --- a/internal/service/task/judge.go +++ b/internal/service/task/judge.go @@ -3,7 +3,7 @@ package task import ( "encoding/json" "github.com/WHUPRJ/woj-server/internal/e" - "github.com/WHUPRJ/woj-server/model" + "github.com/WHUPRJ/woj-server/internal/model" "go.uber.org/zap" ) diff --git a/internal/service/task/push.go b/internal/service/task/push.go index b6026af..e252789 100644 --- a/internal/service/task/push.go +++ b/internal/service/task/push.go @@ -3,7 +3,7 @@ package task import ( "encoding/json" "github.com/WHUPRJ/woj-server/internal/e" - "github.com/WHUPRJ/woj-server/model" + "github.com/WHUPRJ/woj-server/internal/model" "go.uber.org/zap" ) diff --git a/internal/service/task/service.go b/internal/service/task/service.go index 8b104b5..66d48ff 100644 --- a/internal/service/task/service.go +++ b/internal/service/task/service.go @@ -1,9 +1,9 @@ package task import ( - "github.com/WHUPRJ/woj-server/global" "github.com/WHUPRJ/woj-server/internal/e" - "github.com/WHUPRJ/woj-server/model" + "github.com/WHUPRJ/woj-server/internal/global" + "github.com/WHUPRJ/woj-server/internal/model" "github.com/hibiken/asynq" "go.uber.org/zap" ) diff --git a/internal/service/user/create.go b/internal/service/user/create.go index 32f8059..faf5e16 100644 --- a/internal/service/user/create.go +++ b/internal/service/user/create.go @@ -2,7 +2,7 @@ package user import ( "github.com/WHUPRJ/woj-server/internal/e" - "github.com/WHUPRJ/woj-server/model" + "github.com/WHUPRJ/woj-server/internal/model" "go.uber.org/zap" "golang.org/x/crypto/bcrypt" "strings" diff --git a/internal/service/user/login.go b/internal/service/user/login.go index 64ccad0..77824e5 100644 --- a/internal/service/user/login.go +++ b/internal/service/user/login.go @@ -3,7 +3,7 @@ package user import ( "errors" "github.com/WHUPRJ/woj-server/internal/e" - "github.com/WHUPRJ/woj-server/model" + "github.com/WHUPRJ/woj-server/internal/model" "golang.org/x/crypto/bcrypt" "gorm.io/gorm" ) diff --git a/internal/service/user/profile.go b/internal/service/user/profile.go index a94d169..4705f31 100644 --- a/internal/service/user/profile.go +++ b/internal/service/user/profile.go @@ -3,7 +3,7 @@ package user import ( "errors" "github.com/WHUPRJ/woj-server/internal/e" - "github.com/WHUPRJ/woj-server/model" + "github.com/WHUPRJ/woj-server/internal/model" "gorm.io/gorm" ) diff --git a/internal/service/user/service.go b/internal/service/user/service.go index 4f38ef0..e5e398a 100644 --- a/internal/service/user/service.go +++ b/internal/service/user/service.go @@ -1,9 +1,9 @@ package user import ( - "github.com/WHUPRJ/woj-server/global" "github.com/WHUPRJ/woj-server/internal/e" - "github.com/WHUPRJ/woj-server/model" + "github.com/WHUPRJ/woj-server/internal/global" + "github.com/WHUPRJ/woj-server/internal/model" "github.com/go-redis/redis/v8" "go.uber.org/zap" "gorm.io/gorm" From 062c5ef96436d477db8bc5adbf2f70764e247aff Mon Sep 17 00:00:00 2001 From: Paul Pan Date: Thu, 20 Oct 2022 18:21:11 +0800 Subject: [PATCH 17/19] feat: merge woj-runner resources --- resource/runner/.gitignore | 2 + resource/runner/framework/scripts/.gitignore | 2 + resource/runner/framework/scripts/setup.sh | 15 ++++ resource/runner/framework/template/.gitignore | 1 + resource/runner/framework/template/Judger.mk | 10 +++ resource/runner/framework/template/c.mk | 2 + resource/runner/framework/template/cpp.mk | 2 + .../framework/template/default/c.Makefile | 7 ++ .../framework/template/default/cpp.Makefile | 7 ++ resource/runner/framework/template/setup.sh | 8 ++ resource/runner/problem/example/.gitignore | 5 ++ resource/runner/problem/example/README.md | 64 ++++++++++++++++ resource/runner/problem/example/config.json | 17 +++++ .../runner/problem/example/data/input/1.input | 1 + .../runner/problem/example/data/input/3.input | 1 + .../problem/example/data/output/1.output | 1 + .../problem/example/data/output/3.output | 1 + .../runner/problem/example/judge/XYZ.Makefile | 22 ++++++ .../runner/problem/example/judge/gadget.c | 3 + resource/runner/problem/example/judge/gen.cpp | 9 +++ .../problem/example/judge/prebuild.Makefile | 12 +++ resource/runner/scripts/common.sh | 10 +++ resource/runner/scripts/prepare_container.sh | 51 +++++++++++++ resource/runner/scripts/problem.sh | 75 +++++++++++++++++++ resource/runner/scripts/problem_compile.sh | 30 ++++++++ resource/runner/scripts/problem_judge.sh | 40 ++++++++++ resource/runner/scripts/problem_prebuild.sh | 32 ++++++++ resource/runner/scripts/problem_run.sh | 71 ++++++++++++++++++ resource/runner/scripts/run_timeout.sh | 16 ++++ resource/runner/tmp/.gitkeep | 0 resource/runner/user/.gitignore | 1 + 31 files changed, 518 insertions(+) create mode 100644 resource/runner/.gitignore create mode 100644 resource/runner/framework/scripts/.gitignore create mode 100755 resource/runner/framework/scripts/setup.sh create mode 100644 resource/runner/framework/template/.gitignore create mode 100644 resource/runner/framework/template/Judger.mk create mode 100644 resource/runner/framework/template/c.mk create mode 100644 resource/runner/framework/template/cpp.mk create mode 100644 resource/runner/framework/template/default/c.Makefile create mode 100644 resource/runner/framework/template/default/cpp.Makefile create mode 100755 resource/runner/framework/template/setup.sh create mode 100644 resource/runner/problem/example/.gitignore create mode 100644 resource/runner/problem/example/README.md create mode 100644 resource/runner/problem/example/config.json create mode 100644 resource/runner/problem/example/data/input/1.input create mode 100644 resource/runner/problem/example/data/input/3.input create mode 100644 resource/runner/problem/example/data/output/1.output create mode 100644 resource/runner/problem/example/data/output/3.output create mode 100644 resource/runner/problem/example/judge/XYZ.Makefile create mode 100644 resource/runner/problem/example/judge/gadget.c create mode 100644 resource/runner/problem/example/judge/gen.cpp create mode 100644 resource/runner/problem/example/judge/prebuild.Makefile create mode 100755 resource/runner/scripts/common.sh create mode 100755 resource/runner/scripts/prepare_container.sh create mode 100755 resource/runner/scripts/problem.sh create mode 100755 resource/runner/scripts/problem_compile.sh create mode 100755 resource/runner/scripts/problem_judge.sh create mode 100755 resource/runner/scripts/problem_prebuild.sh create mode 100755 resource/runner/scripts/problem_run.sh create mode 100755 resource/runner/scripts/run_timeout.sh create mode 100644 resource/runner/tmp/.gitkeep create mode 100644 resource/runner/user/.gitignore diff --git a/resource/runner/.gitignore b/resource/runner/.gitignore new file mode 100644 index 0000000..0cf8d7b --- /dev/null +++ b/resource/runner/.gitignore @@ -0,0 +1,2 @@ +.mark.* +*.zip diff --git a/resource/runner/framework/scripts/.gitignore b/resource/runner/framework/scripts/.gitignore new file mode 100644 index 0000000..bf3d440 --- /dev/null +++ b/resource/runner/framework/scripts/.gitignore @@ -0,0 +1,2 @@ +/libwoj_sandbox.so +/woj_launcher diff --git a/resource/runner/framework/scripts/setup.sh b/resource/runner/framework/scripts/setup.sh new file mode 100755 index 0000000..5b60d8b --- /dev/null +++ b/resource/runner/framework/scripts/setup.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -x + +rm -rf woj-sandbox +git clone https://github.com/WHUPRJ/woj-sandbox.git >/dev/null 2>&1 || exit 1 +cd woj-sandbox && ./build_libseccomp.sh || exit 1 + +mkdir -p build && cd build +cmake .. -DCMAKE_BUILD_TYPE=Release || exit 1 +make -j || exit 1 + +cd ../.. +cp woj-sandbox/build/libwoj_sandbox.so . || exit 1 +cp woj-sandbox/build/woj_launcher . || exit 1 +rm -rf woj-sandbox diff --git a/resource/runner/framework/template/.gitignore b/resource/runner/framework/template/.gitignore new file mode 100644 index 0000000..39fbd8f --- /dev/null +++ b/resource/runner/framework/template/.gitignore @@ -0,0 +1 @@ +/testlib \ No newline at end of file diff --git a/resource/runner/framework/template/Judger.mk b/resource/runner/framework/template/Judger.mk new file mode 100644 index 0000000..724c865 --- /dev/null +++ b/resource/runner/framework/template/Judger.mk @@ -0,0 +1,10 @@ +FCMP = $(TESTLIB)/checkers/fcmp +HCMP = $(TESTLIB)/checkers/hcmp +LCMP = $(TESTLIB)/checkers/lcmp +NCMP = $(TESTLIB)/checkers/ncmp +NYESNO = $(TESTLIB)/checkers/nyesno +RCMP4 = $(TESTLIB)/checkers/rcmp4 +RCMP6 = $(TESTLIB)/checkers/rcmp6 +RCMP9 = $(TESTLIB)/checkers/rcmp9 +WCMP = $(TESTLIB)/checkers/wcmp +YESNO = $(TESTLIB)/checkers/yesno diff --git a/resource/runner/framework/template/c.mk b/resource/runner/framework/template/c.mk new file mode 100644 index 0000000..756b5be --- /dev/null +++ b/resource/runner/framework/template/c.mk @@ -0,0 +1,2 @@ +CC = gcc +CFLAGS = -O2 -pipe -Wall -lm -std=c99 -DONLINE_JUDGE \ No newline at end of file diff --git a/resource/runner/framework/template/cpp.mk b/resource/runner/framework/template/cpp.mk new file mode 100644 index 0000000..6febe43 --- /dev/null +++ b/resource/runner/framework/template/cpp.mk @@ -0,0 +1,2 @@ +CXX = g++ +CFLAGS = -O2 -pipe -Wall -lm -std=c++20 -DONLINE_JUDGE \ No newline at end of file diff --git a/resource/runner/framework/template/default/c.Makefile b/resource/runner/framework/template/default/c.Makefile new file mode 100644 index 0000000..4f22696 --- /dev/null +++ b/resource/runner/framework/template/default/c.Makefile @@ -0,0 +1,7 @@ +include ${TEMPLATE}/c.mk ${TEMPLATE}/Judger.mk + +compile: + $(CC) $(CFLAGS) -o $(PREFIX)/user/$(USER_PROG).out $(PREFIX)/user/$(USER_PROG).$(LANG) + +judge: + $($(CMP)) $(PREFIX)/data/input/$(TEST_NUM).input $(PREFIX)/user/$(TEST_NUM).out.usr $(PREFIX)/data/output/$(TEST_NUM).output > $(PREFIX)/user/$(TEST_NUM).judge 2>&1 diff --git a/resource/runner/framework/template/default/cpp.Makefile b/resource/runner/framework/template/default/cpp.Makefile new file mode 100644 index 0000000..bcb89be --- /dev/null +++ b/resource/runner/framework/template/default/cpp.Makefile @@ -0,0 +1,7 @@ +include ${TEMPLATE}/cpp.mk ${TEMPLATE}/Judger.mk + +compile: + $(CXX) $(CFLAGS) -o $(PREFIX)/user/$(USER_PROG).out $(PREFIX)/user/$(USER_PROG).$(LANG) + +judge: + $($(CMP)) $(PREFIX)/data/input/$(TEST_NUM).input $(PREFIX)/user/$(TEST_NUM).out.usr $(PREFIX)/data/output/$(TEST_NUM).output > $(PREFIX)/user/$(TEST_NUM).judge 2>&1 diff --git a/resource/runner/framework/template/setup.sh b/resource/runner/framework/template/setup.sh new file mode 100755 index 0000000..5576343 --- /dev/null +++ b/resource/runner/framework/template/setup.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -x +rm -rf testlib +git clone https://github.com/MikeMirzayanov/testlib.git >/dev/null 2>&1 || exit 1 +rm -rf testlib/.git +rm -rf testlib/tests +cd testlib/checkers || exit 1 +parallel clang++ -Ofast -march=native -Wall -pipe -I.. {}.cpp -o {} ::: fcmp hcmp lcmp ncmp nyesno rcmp4 rcmp6 rcmp9 wcmp yesno diff --git a/resource/runner/problem/example/.gitignore b/resource/runner/problem/example/.gitignore new file mode 100644 index 0000000..cb3d7fa --- /dev/null +++ b/resource/runner/problem/example/.gitignore @@ -0,0 +1,5 @@ +/judge/*.out +/data/input/2.* +/data/input/4.* +/data/output/2.* +/data/output/4.* diff --git a/resource/runner/problem/example/README.md b/resource/runner/problem/example/README.md new file mode 100644 index 0000000..ba9485d --- /dev/null +++ b/resource/runner/problem/example/README.md @@ -0,0 +1,64 @@ +# 示例题目 + +## 文件构成 + +``` +. +├── config.json # 题目配置信息 +├── data # 数据目录 +│ ├── input # 输入数据 +│ │ ├── (x).input # 第 x 组输入数据 +│ │ └── ... +│ └── output # 输出数据 +│ ├── (x).output # 第 x 组输出数据 +│ └── ... +└── judge # 评测脚本目录 + ├── c.Makefile # (optional) 自定义评测脚本 + ├── prebuild.Makefile # (optional) 题目初始化脚本 + └── ... +``` + +## 详细说明 + +### 题目配置信息 + +```json5 +{ + "Runtime": { + // 运行时配置 + "TimeLimit": 1000, // 时间限制 (ms) + "MemoryLimit": 16, // 内存限制 (MB) + "NProcLimit": 1 // 进(线)程 限制 + }, + "Languages": [ + {"Lang": "c", "Type": "custom", "Script": "XYZ.Makefile", "Cmp": ""}, + {"Lang": "cpp", "Type": "default", "Script": "", "Cmp": "NCMP"} + ], // 支持的语言 + "Tasks": [ + // 评测点信息 + {"Id": 1, "Points": 25}, // 第一个评测点,分值 25 分,使用 ./data/1.? 为测试数据 + {"Id": 2, "Points": 25}, + {"Id": 3, "Points": 25}, + {"Id": 4, "Points": 25} + ] +} +``` + +### 评测脚本 + +见注释 + + + + + + + + + + + + + + + diff --git a/resource/runner/problem/example/config.json b/resource/runner/problem/example/config.json new file mode 100644 index 0000000..54dbaff --- /dev/null +++ b/resource/runner/problem/example/config.json @@ -0,0 +1,17 @@ +{ + "Runtime": { + "TimeLimit": 1000, + "MemoryLimit": 16, + "NProcLimit": 1 + }, + "Languages": [ + {"Lang": "c", "Type": "custom", "Script": "XYZ.Makefile", "Cmp": ""}, + {"Lang": "cpp", "Type": "default", "Script": "", "Cmp": "NCMP"} + ], + "Tasks": [ + {"Id": 1, "Points": 25}, + {"Id": 2, "Points": 25}, + {"Id": 3, "Points": 25}, + {"Id": 4, "Points": 25} + ] +} \ No newline at end of file diff --git a/resource/runner/problem/example/data/input/1.input b/resource/runner/problem/example/data/input/1.input new file mode 100644 index 0000000..8d04f96 --- /dev/null +++ b/resource/runner/problem/example/data/input/1.input @@ -0,0 +1 @@ +1 2 diff --git a/resource/runner/problem/example/data/input/3.input b/resource/runner/problem/example/data/input/3.input new file mode 100644 index 0000000..6fa0970 --- /dev/null +++ b/resource/runner/problem/example/data/input/3.input @@ -0,0 +1 @@ +-1 -2 \ No newline at end of file diff --git a/resource/runner/problem/example/data/output/1.output b/resource/runner/problem/example/data/output/1.output new file mode 100644 index 0000000..00750ed --- /dev/null +++ b/resource/runner/problem/example/data/output/1.output @@ -0,0 +1 @@ +3 diff --git a/resource/runner/problem/example/data/output/3.output b/resource/runner/problem/example/data/output/3.output new file mode 100644 index 0000000..7d105a7 --- /dev/null +++ b/resource/runner/problem/example/data/output/3.output @@ -0,0 +1 @@ +-3 \ No newline at end of file diff --git a/resource/runner/problem/example/judge/XYZ.Makefile b/resource/runner/problem/example/judge/XYZ.Makefile new file mode 100644 index 0000000..32b8f1c --- /dev/null +++ b/resource/runner/problem/example/judge/XYZ.Makefile @@ -0,0 +1,22 @@ +# 默认环境变量有 PREFIX, TEMPLATE, TESTLIB +include ${TEMPLATE}/c.mk ${TEMPLATE}/Judger.mk + +# 评测分四个阶段 +# 1. prebuild: 用于提前生成测试数据、评测器、spj等工具,runner 只执行一次 +# 只有 ./data, ./judge 目录可见 +# 2. compile: 用于编译用户提交的程序 +# 只有 ./user/$(USER_PROG).$(LANG) 和 ./judge 目录可见 +# 3. run: 运行用户程序 +# 只有 ./data/input/*.input 和 ./user/$(USER_PROG).out 可见 +# 用户输出存放于 ./user/?.out.usr +# 使用 woj-sandbox 运行,等效于 $(PREFIX)/user/$(USER_PROG).out < $(PREFIX)/data/input/$(TEST_NUM).input > $(PREFIX)/user/$(TEST_NUM).out.usr +# 4. judge: 用于判定输出结果 环境变量 TEST_NUM 表示当前测试点编号 +# 所有目录 ./data ./judge ./user 可见 + +compile: + $(CC) $(CFLAGS) -o $(PREFIX)/user/$(USER_PROG).out $(PREFIX)/user/$(USER_PROG).$(LANG) $(PREFIX)/judge/gadget.c + +judge: + # Rename on *.out.usr or *.judge is not allowed + sed '/gadgets/d' $(PREFIX)/user/$(TEST_NUM).out.usr > $(PREFIX)/user/$(TEST_NUM).out.usr1 + $(NCMP) $(PREFIX)/data/input/$(TEST_NUM).input $(PREFIX)/user/$(TEST_NUM).out.usr1 $(PREFIX)/data/output/$(TEST_NUM).output > $(PREFIX)/user/$(TEST_NUM).judge 2>&1 diff --git a/resource/runner/problem/example/judge/gadget.c b/resource/runner/problem/example/judge/gadget.c new file mode 100644 index 0000000..bc08434 --- /dev/null +++ b/resource/runner/problem/example/judge/gadget.c @@ -0,0 +1,3 @@ +#include +void construct() __attribute__((constructor(101))); +void construct() { puts("Greetings from gadgets..."); } diff --git a/resource/runner/problem/example/judge/gen.cpp b/resource/runner/problem/example/judge/gen.cpp new file mode 100644 index 0000000..55b3e38 --- /dev/null +++ b/resource/runner/problem/example/judge/gen.cpp @@ -0,0 +1,9 @@ +#include "testlib.h" + +using namespace std; + +int main(int argc, char *argv[]) { + registerGen(argc, argv, 1); + + println(rnd.next(-1000000000, 1000000000), rnd.next(-1000000000, 1000000000)); +} diff --git a/resource/runner/problem/example/judge/prebuild.Makefile b/resource/runner/problem/example/judge/prebuild.Makefile new file mode 100644 index 0000000..39daf6f --- /dev/null +++ b/resource/runner/problem/example/judge/prebuild.Makefile @@ -0,0 +1,12 @@ +include ${TEMPLATE}/c.mk ${TEMPLATE}/Judger.mk + +prebuild: + clang++ -I$(TESTLIB) -Ofast -o $(PREFIX)/judge/gen.out $(PREFIX)/judge/gen.cpp + @if [ ! -f $(PREFIX)/data/input/2.input ]; then \ + $(PREFIX)/judge/gen.out > $(PREFIX)/data/input/2.input; \ + python3 -c "print(sum(map(int, input().split())))" < $(PREFIX)/data/input/2.input > $(PREFIX)/data/output/2.output; \ + fi + @if [ ! -f $(PREFIX)/data/input/4.input ]; then \ + $(PREFIX)/judge/gen.out > $(PREFIX)/data/input/4.input; \ + python3 -c "print(sum(map(int, input().split())))" < $(PREFIX)/data/input/4.input > $(PREFIX)/data/output/4.output; \ + fi diff --git a/resource/runner/scripts/common.sh b/resource/runner/scripts/common.sh new file mode 100755 index 0000000..cd875a6 --- /dev/null +++ b/resource/runner/scripts/common.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +COLOR_RED="\e[0;31m" +COLOR_GREEN="\e[0;32m" +COLOR_YELLOW="\e[0;33m" +COLOR_NONE="\e[0m" + +function log_info() { echo -e "${COLOR_GREEN}$*${COLOR_NONE}" 1>&2; } +function log_warn() { echo -e "${COLOR_YELLOW}$*${COLOR_NONE}" 1>&2; } +function log_error() { echo -e "${COLOR_RED}$*${COLOR_NONE}" 1>&2; } diff --git a/resource/runner/scripts/prepare_container.sh b/resource/runner/scripts/prepare_container.sh new file mode 100755 index 0000000..af10035 --- /dev/null +++ b/resource/runner/scripts/prepare_container.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash + +. common.sh + +cd "$(dirname "$0")"/../ || exit 1 + +if [ -f ./.mark.docker ]; then + log_warn "Docker containers already prepared" + log_warn "If you want to re-prepare the containers, please remove the file `pwd`/.mark.docker" + exit 1 +fi + +# Full +cat <ubuntu-full.Dockerfile +FROM ubuntu:22.04 +WORKDIR /woj/ + +# Install dependencies +RUN apt-get update && apt-get install -y gcc g++ clang make cmake autoconf m4 libtool gperf git parallel python3 && apt-get clean && rm -rf /var/lib/apt/lists + +# Copy source code +RUN mkdir -p /woj/framework && mkdir -p /woj/problem +COPY framework /woj/framework + +# Build +RUN cd /woj/framework/template && ./setup.sh +RUN cd /woj/framework/scripts && ./setup.sh + +# Environment +ENV WOJ_LAUNCHER=/woj/framework/scripts/woj_launcher +ENV WOJ_SANDBOX=/woj/framework/scripts/libwoj_sandbox.so +ENV TEMPLATE=/woj/framework/template +ENV TESTLIB=/woj/framework/template/testlib +ENV PREFIX=/woj/problem +EOF +docker build -t woj/ubuntu-full -f ubuntu-full.Dockerfile . || exit 1 +rm ubuntu-full.Dockerfile + +# Tiny +cat <ubuntu-run.Dockerfile +FROM woj/ubuntu-full:latest AS builder +FROM ubuntu:22.04 +WORKDIR /woj/problem +RUN mkdir -p /woj/framework/scripts +COPY --from=builder /woj/framework/scripts/libwoj_sandbox.so /woj/framework/scripts/ +COPY --from=builder /woj/framework/scripts/woj_launcher /woj/framework/scripts/ +EOF +docker build -t woj/ubuntu-run -f ubuntu-run.Dockerfile . || exit 1 +rm ubuntu-run.Dockerfile + +touch ./.mark.docker diff --git a/resource/runner/scripts/problem.sh b/resource/runner/scripts/problem.sh new file mode 100755 index 0000000..ef407a2 --- /dev/null +++ b/resource/runner/scripts/problem.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash + +. common.sh + +# get_problem_info +# extract language info and limits +# $1: workspace +# $2: problem name +# $3: language +# exports: Info_Script, Info_Cmp, Info_Num, Info_Limit_Time, Info_Limit_Memory, Info_Limit_NProc +function get_problem_info() { + local err + + if [ ! -f "$1/problem/$2/config.json" ]; then + log_error "problem $2 not found" + return 1 + fi + + parse_language_info "$1" "$2" "$3" + err=$? + if [ "$err" -ne 0 ]; then + return "$err" + fi + + parse_limits "$1" "$2" + err=$? + if [ "$err" -ne 0 ]; then + return "$err" + fi +} + +function parse_language_info() { + export Info_Script + export Info_Cmp + + local lang_config + local lang_type + local lang_script + + lang_config=$(jq ".Languages[] | select(.Lang == \"$3\")" "$1/problem/$2/config.json") + if [ -z "$lang_config" ]; then + log_error "language $3 is not supported" + return 1 + fi + + Info_Cmp=$(echo "$lang_config" | jq -r ".Cmp") + + lang_type=$(echo "$lang_config" | jq -r ".Type") + lang_script=$(echo "$lang_config" | jq -r ".Script") + + if [ "$lang_type" == "custom" ]; then + Info_Script="/woj/problem/judge/$lang_script" + elif [ "$lang_type" == "default" ]; then + Info_Script="/woj/framework/template/default/$3.Makefile" + else + log_warn "Config file might be corrupted!" + log_error "Unknown language type: $lang_type" + return 1 + fi +} + +function parse_limits() { + export Info_Limit_Time + export Info_Limit_Memory + export Info_Limit_NProc + export Info_Num + + local cfg + cfg="$1/problem/$2/config.json" + + Info_Limit_Time=$(jq ".Runtime.TimeLimit" "$cfg") + Info_Limit_Memory=$(jq ".Runtime.MemoryLimit" "$cfg") + Info_Limit_NProc=$(jq ".Runtime.NProcLimit" "$cfg") + Info_Num=$(jq ".Tasks | length" "$1/problem/$2/config.json") +} diff --git a/resource/runner/scripts/problem_compile.sh b/resource/runner/scripts/problem_compile.sh new file mode 100755 index 0000000..874dbb8 --- /dev/null +++ b/resource/runner/scripts/problem_compile.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +WORKSPACE=$(cd "$(dirname "$0")"/.. && pwd) +. "$WORKSPACE"/scripts/run_timeout.sh +. "$WORKSPACE"/scripts/common.sh +. "$WORKSPACE"/scripts/problem.sh + +if [ "$1" == "" ] || [ ! -d "$WORKSPACE/problem/$1" ] || [ "$2" == "" ] || [ ! -d "$WORKSPACE/user/$2" ] || [ -z "$3" ]; then + log_warn "Usage: $0 " + exit 1 +fi + +get_problem_info "$WORKSPACE" "$1" "$3" + +SRC_FILE="$WORKSPACE"/user/"$2"/"$2"."$3" +EXE_FILE="$WORKSPACE"/user/"$2"/"$2".out +LOG_FILE="$WORKSPACE"/user/"$2"/"$2".compile.log + +rm -f "$EXE_FILE" && touch "$EXE_FILE" + +export TIMEOUT=${4:-60} +docker_run \ + -v "$WORKSPACE"/problem/"$1"/judge:/woj/problem/judge:ro \ + -v "$SRC_FILE":/woj/problem/user/"$2"."$3":ro \ + -v "$EXE_FILE":/woj/problem/user/"$2".out \ + -e USER_PROG="$2" \ + -e LANG="$3" \ + woj/ubuntu-full \ + sh -c \ + "cd /woj/problem/user && make -f $Info_Script compile" diff --git a/resource/runner/scripts/problem_judge.sh b/resource/runner/scripts/problem_judge.sh new file mode 100755 index 0000000..9a743aa --- /dev/null +++ b/resource/runner/scripts/problem_judge.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +WORKSPACE=$(cd "$(dirname "$0")"/.. && pwd) +. "$WORKSPACE"/scripts/run_timeout.sh +. "$WORKSPACE"/scripts/common.sh +. "$WORKSPACE"/scripts/problem.sh + +if [ "$1" == "" ] || [ ! -d "$WORKSPACE/problem/$1" ] || [ "$2" == "" ] || [ ! -d "$WORKSPACE/user/$2" ] || [ -z "$3" ]; then + log_warn "Usage: $0 " + exit 1 +fi + +get_problem_info "$WORKSPACE" "$1" "$3" + +TIMEOUT=${4:-60} +for test_num in $(seq "$Info_Num"); do + std_file="$WORKSPACE/problem/$1/data/output/$test_num.output" + ans_file="$WORKSPACE/user/$2/$test_num.out.usr" + jdg_file="$WORKSPACE/user/$2/$test_num.judge" + + if [ ! -f "$std_file" ] || [ ! -f "$ans_file" ]; then + log_error "Missing test case $test_num" + exit 1 + fi + + log_info "Judging test case $test_num" + + touch "$jdg_file" + + docker_run \ + -v "$WORKSPACE"/problem/"$1"/judge:/woj/problem/judge:ro \ + -v "$WORKSPACE"/problem/"$1"/data:/woj/problem/data:ro \ + -v "$ans_file":/woj/problem/user/"$test_num".out.usr \ + -v "$jdg_file":/woj/problem/user/"$test_num".judge \ + -e TEST_NUM="$test_num" \ + -e CMP="$Info_Cmp" \ + woj/ubuntu-full \ + sh -c \ + "cd /woj/problem/user && make -f $Info_Script judge" +done diff --git a/resource/runner/scripts/problem_prebuild.sh b/resource/runner/scripts/problem_prebuild.sh new file mode 100755 index 0000000..de3b45d --- /dev/null +++ b/resource/runner/scripts/problem_prebuild.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +WORKSPACE=$(cd "$(dirname "$0")"/.. && pwd) +. "$WORKSPACE"/scripts/run_timeout.sh +. "$WORKSPACE"/scripts/common.sh + +if [ "$1" == "" ] || [ ! -d "$WORKSPACE/problem/$1" ]; then + log_warn "Usage: $0 " + exit 1 +fi + +if [ -f "$WORKSPACE/problem/$1/.mark.prebuild" ]; then + log_warn "Problem $1 already prebuilt" + log_warn "If you want to re-prebuild the problem, please remove the file $WORKSPACE/problem/$1/.mark.prebuild" + exit 0 +fi + +if [ ! -f "$WORKSPACE/problem/$1/judge/prebuild.Makefile" ]; then + log_warn "Problem $1 does not have prebuild scripts" + log_warn "$WORKSPACE/problem/$1/.mark.prebuild" + exit 0 +fi + +TIMEOUT=${2:-300} +docker_run \ + -v "$WORKSPACE/problem/$1/data":/woj/problem/data \ + -v "$WORKSPACE/problem/$1/judge":/woj/problem/judge \ + -e PREFIX=/woj/problem \ + woj/ubuntu-full \ + sh -c "cd /woj/problem/judge && make -f prebuild.Makefile prebuild && touch .mark.prebuild" + +mv "$WORKSPACE/problem/$1/judge/.mark.prebuild" "$WORKSPACE/problem/$1/.mark.prebuild" || exit 1 diff --git a/resource/runner/scripts/problem_run.sh b/resource/runner/scripts/problem_run.sh new file mode 100755 index 0000000..8168fd4 --- /dev/null +++ b/resource/runner/scripts/problem_run.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash + +WORKSPACE=$(cd "$(dirname "$0")"/.. && pwd) +. "$WORKSPACE"/scripts/run_timeout.sh +. "$WORKSPACE"/scripts/common.sh +. "$WORKSPACE"/scripts/problem.sh + +if [ "$1" == "" ] || [ ! -d "$WORKSPACE/problem/$1" ] || [ "$2" == "" ] || [ ! -d "$WORKSPACE/user/$2" ] || [ -z "$3" ]; then + log_warn "Usage: $0 " + exit 1 +fi + +if [ ! -f "$WORKSPACE/problem/$1/.mark.prebuild" ]; then + log_warn "Problem $1 has not been prebuilt" + log_warn "Please run 'problem_prebuild.sh $1' first" + exit 1 +fi + +if [ ! -f "$WORKSPACE/user/$2/$2.out" ]; then + log_warn "User $2 has not been compiled" + log_warn "Please run 'problem_compile.sh ...' first" + exit 1 +fi + +parse_limits "$WORKSPACE" "$1" + +log_info "Running problem $1 for user $2" +log_info "TimeLimit: $Info_Limit_Time" +log_info "MemoryLimit: $Info_Limit_Memory" +log_info "NProcLimit: $Info_Limit_NProc" + +# launcher will add 2 more seconds +# here add 3 more seconds +TIMEOUT=$(((LIMIT_TIME + 1000) / 1000 + 3)) +log_info "Timeout: $TIMEOUT" + +for test_num in $(seq "$Info_Num"); do + test_case="$WORKSPACE/problem/$1/data/input/$test_num.input" + exe_file="$WORKSPACE/user/$2/$2.out" + ans_file="$WORKSPACE/user/$2/$test_num.out.usr" + ifo_file="$WORKSPACE/user/$2/$test_num.info" + + if [ ! -f "$test_case" ]; then + log_error "Test case $test_num does not exist" + exit 1 + fi + + log_info "Running test case $test_num" + rm -f "$ans_file" && touch "$ans_file" + rm -f "$ifo_file" && touch "$ifo_file" + docker_run \ + --cpus 1 \ + --network none \ + -v "$test_case":/woj/problem/data/input/"$test_num".input:ro \ + -v "$exe_file":/woj/user/"$2".out:ro \ + -v "$ans_file":/woj/user/"$test_num".out.usr \ + -v "$ifo_file":/woj/user/"$test_num".info \ + woj/ubuntu-run \ + sh -c \ + "cd /woj/user && /woj/framework/scripts/woj_launcher \ + --memory_limit=$Info_Limit_Memory \ + --nproc_limit=$Info_Limit_NProc \ + --time_limit=$Info_Limit_Time \ + --sandbox_path=/woj/framework/scripts/libwoj_sandbox.so \ + --sandbox_template=$3 \ + --sandbox_action=nothing \ + --file_input=/woj/problem/data/input/$test_num.input \ + --file_output=/woj/user/$test_num.out.usr \ + --file_info=/woj/user/$test_num.info \ + --program=/woj/user/$2.out" +done diff --git a/resource/runner/scripts/run_timeout.sh b/resource/runner/scripts/run_timeout.sh new file mode 100755 index 0000000..0cafef9 --- /dev/null +++ b/resource/runner/scripts/run_timeout.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +. common.sh + +function docker_run() { + local timeout=${TIMEOUT:-10} + local log_file=${LOG_FILE:-/dev/stderr} + log_info "Docker run with timeout $timeout" + CONTAINER_NAME=$(uuidgen) + ( + sleep "$timeout" + docker kill "$CONTAINER_NAME" + ) & + docker run --rm --name "$CONTAINER_NAME" "$@" > "$log_file" 2>&1 + pkill -P $$ +} diff --git a/resource/runner/tmp/.gitkeep b/resource/runner/tmp/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/resource/runner/user/.gitignore b/resource/runner/user/.gitignore new file mode 100644 index 0000000..2699330 --- /dev/null +++ b/resource/runner/user/.gitignore @@ -0,0 +1 @@ +/test_user \ No newline at end of file From d42ee0ce54420c037d5fae1c9743c5d21d52e50f Mon Sep 17 00:00:00 2001 From: Paul Pan Date: Sat, 22 Oct 2022 17:38:39 +0800 Subject: [PATCH 18/19] feat: a big update 1. merge woj-runner scripts into woj-server 2. add woj-runner app 3. refactor submission status problem ... 4. jwt middleware update Co-authored-by: cxy004 Co-authored-by: wzt --- .github/workflows/docker-image.yml | 11 +- .gitignore | 1 + Dockerfile.runner | 11 + Dockerfile => Dockerfile.server | 4 +- Makefile | 25 +- cmd/app/main.go | 68 ------ cmd/app/run.go | 21 -- cmd/common.go | 75 ++++++ cmd/runner/main.go | 34 +++ cmd/server/main.go | 34 +++ config.yaml | 2 +- go.mod | 5 +- internal/api/problem/details.go | 45 ++++ internal/api/problem/handler.go | 10 +- internal/api/problem/search.go | 19 +- internal/api/problem/update.go | 48 ++-- internal/api/runner/build.go | 34 +++ internal/api/runner/handler.go | 41 ++++ internal/api/runner/judge.go | 73 ++++++ internal/api/status/handler.go | 32 +++ internal/api/status/query.go | 24 ++ internal/api/status/queryByVersion.go | 42 ++++ internal/api/submission/create.go | 64 +++++ internal/api/submission/handler.go | 41 ++++ internal/api/submission/query.go | 73 ++++++ internal/api/user/create.go | 11 +- internal/api/user/handler.go | 10 +- internal/api/user/login.go | 20 +- internal/api/user/logout.go | 1 + internal/api/user/profile.go | 24 +- internal/app/runner/runner.go | 43 ++++ internal/app/{app.go => server/server.go} | 43 +++- internal/e/code.go | 93 +++++-- internal/global/jwt.go | 2 +- internal/model/Language.go | 6 - internal/model/Problem.go | 27 ++- internal/model/Status.go | 17 +- internal/model/Submission.go | 13 +- internal/model/Task.go | 42 +++- internal/model/User.go | 2 +- internal/model/Verdict.go | 17 -- internal/router/api.go | 4 + internal/service/jwt/middleware.go | 47 ++-- internal/service/problem/create.go | 22 +- internal/service/problem/createVersion.go | 27 +++ internal/service/problem/query.go | 16 +- internal/service/problem/queryFuzz.go | 25 +- .../service/problem/queryLatestVersion.go | 29 +++ internal/service/problem/queryVersion.go | 27 +++ internal/service/problem/service.go | 13 +- internal/service/problem/update.go | 11 +- internal/service/problem/updateVersion.go | 17 ++ internal/service/runner/common.go | 75 ++++++ internal/service/runner/compile.go | 30 +++ internal/service/runner/config.go | 125 ++++++++++ internal/service/runner/deps.go | 31 +++ internal/service/runner/newProblem.go | 72 ++++++ internal/service/runner/runAndJudge.go | 22 ++ internal/service/runner/service.go | 34 +++ internal/service/runner/status.go | 227 ++++++++++++++++++ internal/service/status/create.go | 33 +++ internal/service/status/query.go | 56 +++++ internal/service/status/rejudge.go | 11 + internal/service/status/service.go | 30 +++ internal/service/submission/create.go | 31 +++ internal/service/submission/query.go | 33 +++ internal/service/submission/service.go | 28 +++ internal/service/task/common.go | 8 +- internal/service/task/judge.go | 20 -- internal/service/task/problem.go | 46 ++++ internal/service/task/push.go | 23 -- internal/service/task/service.go | 11 +- internal/service/task/submit.go | 55 +++++ internal/service/user/create.go | 21 +- internal/service/user/login.go | 11 +- internal/service/user/profile.go | 6 +- internal/service/user/service.go | 6 +- internal/service/user/version.go | 7 +- pkg/down/down.go | 55 +++++ pkg/unzip/unzip.go | 60 +++++ pkg/utils/file.go | 3 + pkg/zapasynq/logger.go | 25 ++ resource/runner/framework/scripts/setup.sh | 2 +- .../framework/template/default/c.Makefile | 4 +- .../framework/template/default/cpp.Makefile | 4 +- resource/runner/framework/template/setup.sh | 2 +- .../runner/problem/example/judge/XYZ.Makefile | 2 +- resource/runner/scripts/prepare_container.sh | 2 +- resource/runner/scripts/problem_compile.sh | 2 +- resource/runner/scripts/problem_judge.sh | 2 +- resource/runner/scripts/problem_prebuild.sh | 2 +- 91 files changed, 2257 insertions(+), 406 deletions(-) create mode 100644 Dockerfile.runner rename Dockerfile => Dockerfile.server (73%) delete mode 100644 cmd/app/main.go delete mode 100644 cmd/app/run.go create mode 100644 cmd/common.go create mode 100644 cmd/runner/main.go create mode 100644 cmd/server/main.go create mode 100644 internal/api/problem/details.go create mode 100644 internal/api/runner/build.go create mode 100644 internal/api/runner/handler.go create mode 100644 internal/api/runner/judge.go create mode 100644 internal/api/status/handler.go create mode 100644 internal/api/status/query.go create mode 100644 internal/api/status/queryByVersion.go create mode 100644 internal/api/submission/create.go create mode 100644 internal/api/submission/handler.go create mode 100644 internal/api/submission/query.go create mode 100644 internal/app/runner/runner.go rename internal/app/{app.go => server/server.go} (53%) delete mode 100644 internal/model/Language.go delete mode 100644 internal/model/Verdict.go create mode 100644 internal/service/problem/createVersion.go create mode 100644 internal/service/problem/queryLatestVersion.go create mode 100644 internal/service/problem/queryVersion.go create mode 100644 internal/service/problem/updateVersion.go create mode 100644 internal/service/runner/common.go create mode 100644 internal/service/runner/compile.go create mode 100644 internal/service/runner/config.go create mode 100644 internal/service/runner/deps.go create mode 100644 internal/service/runner/newProblem.go create mode 100644 internal/service/runner/runAndJudge.go create mode 100644 internal/service/runner/service.go create mode 100644 internal/service/runner/status.go create mode 100644 internal/service/status/create.go create mode 100644 internal/service/status/query.go create mode 100644 internal/service/status/rejudge.go create mode 100644 internal/service/status/service.go create mode 100644 internal/service/submission/create.go create mode 100644 internal/service/submission/query.go create mode 100644 internal/service/submission/service.go delete mode 100644 internal/service/task/judge.go create mode 100644 internal/service/task/problem.go delete mode 100644 internal/service/task/push.go create mode 100644 internal/service/task/submit.go create mode 100644 pkg/down/down.go create mode 100644 pkg/unzip/unzip.go create mode 100644 pkg/zapasynq/logger.go diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 2c97491..e8a654c 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -18,14 +18,15 @@ jobs: with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Docker meta - id: meta + - name: Server Meta + id: server_meta uses: docker/metadata-action@v4 with: images: panpaul/woj-server - - name: Build and Push the Docker Image + - name: Build and Push the Server Image uses: docker/build-push-action@v3 with: push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} + tags: ${{ steps.server_meta.outputs.tags }} + file: ./Dockerfile.server + labels: ${{ steps.server_meta.outputs.labels }} diff --git a/.gitignore b/.gitignore index 2d4c8dc..75afa1a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ ### Project /server +/runner my.secrets ### JetBrains template diff --git a/Dockerfile.runner b/Dockerfile.runner new file mode 100644 index 0000000..af56a80 --- /dev/null +++ b/Dockerfile.runner @@ -0,0 +1,11 @@ +FROM golang:latest AS builder +WORKDIR /builder +COPY . /builder +RUN make runner + +FROM alpine:latest +WORKDIR /app +RUN apk --no-cache add tzdata ca-certificates libc6-compat +COPY --from=builder /builder/server /app +COPY --from=builder /builder/resource/runner /app/resource/runner +ENTRYPOINT ["/app/runner"] diff --git a/Dockerfile b/Dockerfile.server similarity index 73% rename from Dockerfile rename to Dockerfile.server index 4ddf587..a66ed21 100644 --- a/Dockerfile +++ b/Dockerfile.server @@ -1,12 +1,12 @@ FROM golang:latest AS builder WORKDIR /builder COPY . /builder -RUN make build +RUN make server FROM alpine:latest WORKDIR /app RUN apk --no-cache add tzdata ca-certificates libc6-compat COPY --from=builder /builder/server /app -COPY --from=builder /builder/resource /app/resource +COPY --from=builder /builder/resource/frontend /app/resource/frontend EXPOSE 8000 ENTRYPOINT ["/app/server"] diff --git a/Makefile b/Makefile index 2e82bd5..af08537 100644 --- a/Makefile +++ b/Makefile @@ -1,28 +1,29 @@ -PROJECT=server - GO := go -LDFLAGS += -X main.BuildTime=$(shell date -u '+%Y-%m-%d-%I-%M-%S') -LDFLAGS += -X main.Version=$(shell cat VERSION)+$(shell git rev-parse HEAD) +LDFLAGS += -X cmd.BuildTime=$(shell date -u '+%Y-%m-%d-%I-%M-%S') +LDFLAGS += -X cmd.Version=$(shell cat VERSION)+$(shell git rev-parse HEAD) LDFLAGS += -s -w -GOBUILD := $(GO) build -o $(PROJECT) -ldflags '$(LDFLAGS)' ./cmd/app +GOBUILD := $(GO) build -ldflags '$(LDFLAGS)' GOBIN := $(shell go env GOPATH)/bin -.PHONY: all build clean run dep swagger fmt +.PHONY: all server runner build clean dep swagger fmt default: all all: clean build -build: swagger dep - $(GOBUILD) +server: swagger dep + $(GOBUILD) -o server ./cmd/server + +runner: dep + $(GOBUILD) -o runner ./cmd/runner + +build: runner server clean: - rm -f $(PROJECT) - -run: clean swagger dep build - ./$(PROJECT) run + rm -f runner + rm -f server dep: go mod tidy && go mod download diff --git a/cmd/app/main.go b/cmd/app/main.go deleted file mode 100644 index 0d1f51f..0000000 --- a/cmd/app/main.go +++ /dev/null @@ -1,68 +0,0 @@ -package main - -import ( - "github.com/urfave/cli/v2" - "log" - "os" - "time" -) - -func main() { - a := &cli.App{ - Name: "OJ", - Usage: "woj-server", - Compiled: getBuildTime(), - Version: Version, - EnableBashCompletion: true, - Authors: []*cli.Author{ - { - Name: "Paul", - Email: "i@0x7f.app", - }, - { - Name: "cxy004", - Email: "cxy004@qq.com", - }, - { - Name: "wzt", - Email: "w.zhongtao@qq.com", - }, - }, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "config", - Aliases: []string{"c"}, - Usage: "path to the config file", - Value: "config.yaml", - EnvVars: []string{"APP_CONFIG"}, - }, - }, - Commands: []*cli.Command{ - { - Name: "run", - Aliases: []string{"r"}, - Usage: "start the server", - Action: run, - }, - }, - } - - err := a.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} - -var ( - BuildTime = "2022-09-06-01-00-00" - Version = "1.0.0+None" -) - -func getBuildTime() time.Time { - build, err := time.Parse("2006-01-02-15-04-05", BuildTime) - if err != nil { - log.Printf("failed to parse build time: %v", err) - build = time.Now() - } - return build -} diff --git a/cmd/app/run.go b/cmd/app/run.go deleted file mode 100644 index 0da7fbc..0000000 --- a/cmd/app/run.go +++ /dev/null @@ -1,21 +0,0 @@ -package main - -import ( - "github.com/WHUPRJ/woj-server/internal/app" - "github.com/WHUPRJ/woj-server/internal/global" - "github.com/urfave/cli/v2" - "math/rand" - "time" -) - -func run(c *cli.Context) error { - rand.Seed(time.Now().Unix()) - - g := new(global.Global) - g.SetupConfig(c.String("config")) - g.SetupZap() - defer func() { _ = g.Log.Sync() }() - - g.Log.Info("starting server...") - return app.Run(g) -} diff --git a/cmd/common.go b/cmd/common.go new file mode 100644 index 0000000..377871f --- /dev/null +++ b/cmd/common.go @@ -0,0 +1,75 @@ +package cmd + +import ( + "github.com/WHUPRJ/woj-server/internal/global" + "github.com/urfave/cli/v2" + "log" + "math/rand" + "time" +) + +var App = &cli.App{ + Name: "WOJ", + EnableBashCompletion: true, + Authors: []*cli.Author{ + { + Name: "Paul", + Email: "i@0x7f.app", + }, + { + Name: "cxy004", + Email: "cxy004@qq.com", + }, + { + Name: "wzt", + Email: "w.zhongtao@qq.com", + }, + }, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "config", + Aliases: []string{"c"}, + Usage: "path to the config file", + Value: "config.yaml", + EnvVars: []string{"APP_CONFIG"}, + }, + }, +} + +var ( + BuildTime string + Version string +) + +func init() { + if BuildTime == "" { + BuildTime = "2022-09-06-01-00-00" + } + App.Compiled = getBuildTime() + + if Version == "" { + Version = "0.0.0+None" + } + App.Version = Version +} + +func getBuildTime() time.Time { + build, err := time.Parse("2006-01-02-15-04-05", BuildTime) + if err != nil { + log.Printf("failed to parse build time: %v", err) + build = time.Now() + } + return build +} + +func CommonSetup(c *cli.Context) *global.Global { + rand.Seed(time.Now().Unix()) + + g := new(global.Global) + g.SetupConfig(c.String("config")) + g.SetupZap() + + g.Log.Info("starting...") + + return g +} diff --git a/cmd/runner/main.go b/cmd/runner/main.go new file mode 100644 index 0000000..aa7f406 --- /dev/null +++ b/cmd/runner/main.go @@ -0,0 +1,34 @@ +package main + +import ( + "github.com/WHUPRJ/woj-server/cmd" + "github.com/WHUPRJ/woj-server/internal/app/runner" + "github.com/urfave/cli/v2" + "log" + "os" +) + +func main() { + a := cmd.App + a.Usage = "woj-runner" + a.Commands = []*cli.Command{ + { + Name: "run", + Aliases: []string{"r"}, + Usage: "start the runner", + Action: run, + }, + } + + err := a.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} + +func run(c *cli.Context) error { + g := cmd.CommonSetup(c) + defer func() { _ = g.Log.Sync() }() + + return runner.RunRunner(g) +} diff --git a/cmd/server/main.go b/cmd/server/main.go new file mode 100644 index 0000000..4b23945 --- /dev/null +++ b/cmd/server/main.go @@ -0,0 +1,34 @@ +package main + +import ( + "github.com/WHUPRJ/woj-server/cmd" + "github.com/WHUPRJ/woj-server/internal/app/server" + "github.com/urfave/cli/v2" + "log" + "os" +) + +func main() { + a := cmd.App + a.Usage = "woj-server" + a.Commands = []*cli.Command{ + { + Name: "run", + Aliases: []string{"r"}, + Usage: "start the server", + Action: run, + }, + } + + err := a.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} + +func run(c *cli.Context) error { + g := cmd.CommonSetup(c) + defer func() { _ = g.Log.Sync() }() + + return server.RunServer(g) +} diff --git a/config.yaml b/config.yaml index de46fd7..7ff81ec 100644 --- a/config.yaml +++ b/config.yaml @@ -6,7 +6,7 @@ WebServer: Redis: Db: 0 - QueueDb: 0 + QueueDb: 1 Address: '127.0.0.1:6379' Password: '' diff --git a/go.mod b/go.mod index 79d3d95..f3d872a 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/go-redis/redis/v8 v8.11.5 github.com/golang-jwt/jwt/v4 v4.4.2 github.com/hibiken/asynq v0.23.0 - github.com/lib/pq v1.10.2 + github.com/jackc/pgtype v1.11.0 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 @@ -18,6 +18,7 @@ require ( 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/text v0.3.7 gopkg.in/yaml.v3 v3.0.1 gorm.io/driver/postgres v1.3.9 gorm.io/gorm v1.23.8 @@ -49,7 +50,6 @@ require ( github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgproto3/v2 v2.3.0 // indirect github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect - github.com/jackc/pgtype v1.11.0 // indirect github.com/jackc/pgx/v4 v4.16.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.4 // indirect @@ -74,7 +74,6 @@ require ( go.uber.org/multierr v1.7.0 // indirect golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect - golang.org/x/text v0.3.7 // 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 diff --git a/internal/api/problem/details.go b/internal/api/problem/details.go new file mode 100644 index 0000000..f9c42b3 --- /dev/null +++ b/internal/api/problem/details.go @@ -0,0 +1,45 @@ +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/gin-gonic/gin" +) + +type detailsRequest struct { + Pid uint `form:"pid"` +} + +// Details +// @Summary get details of a problem +// @Description get details of a problem +// @Accept application/x-www-form-urlencoded +// @Produce json +// @Param pid formData int true "problem id" +// @Response 200 {object} e.Response "problem details" +// @Router /v1/problem/details [post] +func (h *handler) Details(c *gin.Context) { + req := new(detailsRequest) + + if err := c.ShouldBind(req); err != nil { + e.Pong(c, e.InvalidParameter, err.Error()) + return + } + + claim, exist := c.Get("claim") + shouldEnable := !exist || claim.(*global.Claim).Role < model.RoleAdmin + + p, status := h.problemService.Query(req.Pid, true, shouldEnable) + if status != e.Success { + e.Pong(c, status, nil) + return + } + + pv, status := h.problemService.QueryLatestVersion(req.Pid) + e.Pong(c, status, gin.H{ + "problem": p, + "context": pv.Context, + }) + return +} diff --git a/internal/api/problem/handler.go b/internal/api/problem/handler.go index 12378f8..4e3a9a2 100644 --- a/internal/api/problem/handler.go +++ b/internal/api/problem/handler.go @@ -10,23 +10,25 @@ import ( var _ Handler = (*handler)(nil) type Handler interface { - Update(c *gin.Context) + Details(c *gin.Context) Search(c *gin.Context) + Update(c *gin.Context) } type handler struct { log *zap.Logger - problemService problem.Service jwtService global.JwtService + problemService problem.Service } func RouteRegister(g *global.Global, group *gin.RouterGroup) { app := &handler{ log: g.Log, - problemService: problem.NewService(g), jwtService: g.Jwt, + problemService: problem.NewService(g), } + group.POST("/details", app.jwtService.Handler(false), app.Details) group.POST("/search", app.Search) - group.POST("/update", app.jwtService.Handler(), app.Update) + group.POST("/update", app.jwtService.Handler(true), app.Update) } diff --git a/internal/api/problem/search.go b/internal/api/problem/search.go index 5e932f6..4384647 100644 --- a/internal/api/problem/search.go +++ b/internal/api/problem/search.go @@ -6,7 +6,6 @@ import ( ) type searchRequest struct { - Pid uint `form:"pid"` Search string `form:"search"` } @@ -15,9 +14,8 @@ type searchRequest struct { // @Description get detail of a problem // @Accept application/x-www-form-urlencoded // @Produce json -// @Param pid formData int false "problem id" -// @Param search formData string false "search problem" -// @Response 200 {object} e.Response "problem info" +// @Param search formData string false "word search" +// @Response 200 {object} e.Response "problemset" // @Router /v1/problem/search [post] func (h *handler) Search(c *gin.Context) { req := new(searchRequest) @@ -27,17 +25,14 @@ func (h *handler) Search(c *gin.Context) { return } - if req.Pid == 0 && req.Search == "" { - e.Pong(c, e.InvalidParameter, nil) - return - } - - if req.Pid != 0 { - problem, status := h.problemService.Query(req.Pid) + // TODO: pagination + if req.Search == "" { + // TODO: query without LIKE + problem, status := h.problemService.QueryFuzz(req.Search, true, true) e.Pong(c, status, problem) return } else { - problem, status := h.problemService.QueryFuzz(req.Search) + problem, status := h.problemService.QueryFuzz(req.Search, true, true) e.Pong(c, status, problem) return } diff --git a/internal/api/problem/update.go b/internal/api/problem/update.go index 03d5bd3..6905b16 100644 --- a/internal/api/problem/update.go +++ b/internal/api/problem/update.go @@ -4,16 +4,15 @@ 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 updateRequest struct { - Pid uint `form:"pid"` - Title string `form:"title" binding:"required"` - Content string `form:"content" binding:"required"` - TimeLimit uint `form:"time_limit" binding:"required"` - MemoryLimit uint `form:"memory_limit" binding:"required"` - IsEnabled bool `form:"is_enabled"` + Pid uint `form:"pid"` + Title string `form:"title" binding:"required"` + Statement string `form:"statement" binding:"required"` + IsEnabled bool `form:"is_enabled"` } // Update @@ -23,9 +22,7 @@ type updateRequest struct { // @Produce json // @Param pid formData int false "problem id, 0 for create" // @Param title formData string true "title" -// @Param content formData string true "content" -// @Param time_limit formData int true "time limit in ms" -// @Param memory_limit formData int true "memory limit in kb" +// @Param statement formData string true "statement" // @Param is_enabled formData bool false "is enabled" // @Response 200 {object} e.Response "problem info without provider information" // @Security Authentication @@ -50,32 +47,33 @@ func (h *handler) Update(c *gin.Context) { return } - problem := &model.Problem{ - Title: req.Title, - Content: req.Content, - TimeLimit: req.TimeLimit, - MemoryLimit: req.MemoryLimit, - IsEnabled: req.IsEnabled, - } - if req.Pid == 0 { - problem, status := h.problemService.Create(uid, problem) - e.Pong(c, status, problem) + createData := &problem.CreateData{ + Title: req.Title, + Statement: req.Statement, + ProviderID: uid, + IsEnabled: false, + } + p, status := h.problemService.Create(createData) + e.Pong(c, status, p) return } else { - inDb, status := h.problemService.Query(req.Pid) - if status != e.Success && status != e.ProblemNotAvailable { + p, status := h.problemService.Query(req.Pid, true, false) + if status != e.Success { e.Pong(c, status, nil) return } - - if inDb.ProviderID != uid { + if p.ProviderID != uid { e.Pong(c, e.UserUnauthorized, nil) return } - problem, status := h.problemService.Update(req.Pid, problem) - e.Pong(c, status, problem) + p.Title = req.Title + p.Statement = req.Statement + p.IsEnabled = req.IsEnabled + + p, status = h.problemService.Update(p) + e.Pong(c, status, p) return } } diff --git a/internal/api/runner/build.go b/internal/api/runner/build.go new file mode 100644 index 0000000..1f7f74a --- /dev/null +++ b/internal/api/runner/build.go @@ -0,0 +1,34 @@ +package runner + +import ( + "context" + "encoding/json" + "fmt" + "github.com/WHUPRJ/woj-server/internal/model" + "github.com/hibiken/asynq" + "go.uber.org/zap" +) + +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) + } + + h.log.Info("build", zap.Any("payload", p)) + + config, status := h.runnerService.NewProblem(p.ProblemVersionID, p.ProblemFile) + + for i := range config.Languages { + config.Languages[i].Type = "" + config.Languages[i].Script = "" + config.Languages[i].Cmp = "" + } + + b, _ := json.Marshal(config) + h.taskService.ProblemUpdate(status, p.ProblemVersionID, string(b)) + + return nil +} diff --git a/internal/api/runner/handler.go b/internal/api/runner/handler.go new file mode 100644 index 0000000..d8ddbff --- /dev/null +++ b/internal/api/runner/handler.go @@ -0,0 +1,41 @@ +package runner + +import ( + "context" + "errors" + "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/task" + "github.com/hibiken/asynq" + "go.uber.org/zap" +) + +var _ Handler = (*handler)(nil) + +type Handler interface { + Build(_ context.Context, t *asynq.Task) error + Judge(_ context.Context, t *asynq.Task) error +} + +type handler struct { + log *zap.Logger + runnerService runner.Service + taskService task.Service +} + +func NewRunner(g *global.Global) (Handler, error) { + hnd := &handler{ + log: g.Log, + runnerService: runner.NewService(g), + taskService: task.NewService(g), + } + + status := hnd.runnerService.EnsureDeps(false) + if status != e.Success { + g.Log.Error("failed to ensure runner dependencies", zap.String("status", status.String())) + return nil, errors.New("failed to ensure dependencies") + } + + return hnd, nil +} diff --git a/internal/api/runner/judge.go b/internal/api/runner/judge.go new file mode 100644 index 0000000..594af4d --- /dev/null +++ b/internal/api/runner/judge.go @@ -0,0 +1,73 @@ +package runner + +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/runner" + "github.com/WHUPRJ/woj-server/pkg/utils" + "github.com/hibiken/asynq" + "go.uber.org/zap" + "path/filepath" +) + +func (h *handler) Judge(_ context.Context, t *asynq.Task) error { + var p model.SubmitJudgePayload + if err := json.Unmarshal(t.Payload(), &p); err != nil { + return fmt.Errorf("json.Unmarshal failed: %v: %w", err, asynq.SkipRetry) + } + + user := utils.RandomString(16) + h.log.Info("judge", zap.Any("payload", p), zap.String("user", user)) + + // common + systemError := runner.JudgeStatus{Message: "System Error"} + + // write 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 + } + 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 + } + + // compile + result, status := h.runnerService.Compile(p.ProblemVersionId, user, p.Submission.Language) + if status == e.RunnerProblemNotExist { + _, status := h.runnerService.NewProblem(p.ProblemVersionId, p.StorageKey) + 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 + } + + // 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 + } + + // 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) + + return nil +} diff --git a/internal/api/status/handler.go b/internal/api/status/handler.go new file mode 100644 index 0000000..1b85a77 --- /dev/null +++ b/internal/api/status/handler.go @@ -0,0 +1,32 @@ +package status + +import ( + "github.com/WHUPRJ/woj-server/internal/global" + "github.com/WHUPRJ/woj-server/internal/service/status" + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +var _ Handler = (*handler)(nil) + +type Handler interface { + Query(c *gin.Context) + QueryByProblemVersion(c *gin.Context) +} + +type handler struct { + log *zap.Logger + statusService status.Service + jwtService global.JwtService +} + +func RouteRegister(g *global.Global, group *gin.RouterGroup) { + app := &handler{ + log: g.Log, + statusService: status.NewService(g), + jwtService: g.Jwt, + } + + group.POST("/query", app.Query) + group.POST("/query/problem_version", app.jwtService.Handler(true), app.QueryByProblemVersion) +} diff --git a/internal/api/status/query.go b/internal/api/status/query.go new file mode 100644 index 0000000..54b353d --- /dev/null +++ b/internal/api/status/query.go @@ -0,0 +1,24 @@ +package status + +import ( + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/gin-gonic/gin" +) + +type queryRequest struct { + SubmissionID uint `form:"sid"` +} + +func (h *handler) Query(c *gin.Context) { + req := new(queryRequest) + + if err := c.ShouldBind(req); err != nil { + e.Pong(c, e.InvalidParameter, err.Error()) + return + } + + status, eStatus := h.statusService.Query(req.SubmissionID, true) + + e.Pong(c, eStatus, status) + return +} diff --git a/internal/api/status/queryByVersion.go b/internal/api/status/queryByVersion.go new file mode 100644 index 0000000..d7e1728 --- /dev/null +++ b/internal/api/status/queryByVersion.go @@ -0,0 +1,42 @@ +package status + +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 queryByVersionRequest struct { + ProblemVersionID uint `form:"pvid"` + Offset int `form:"offset"` + Limit int `form:"limit"` +} + +func (h *handler) QueryByProblemVersion(c *gin.Context) { + + claim, exist := c.Get("claim") + if !exist { + e.Pong(c, e.UserUnauthenticated, nil) + return + } + + role := claim.(*global.Claim).Role + + req := new(queryByVersionRequest) + + if err := c.ShouldBind(req); err != nil { + e.Pong(c, e.InvalidParameter, nil) + return + } + + if role < model.RoleAdmin { + e.Pong(c, e.UserUnauthorized, nil) + return + } + + statuses, eStatus := h.statusService.QueryByVersion(req.ProblemVersionID, req.Offset, req.Limit) + + e.Pong(c, eStatus, statuses) + return +} diff --git a/internal/api/submission/create.go b/internal/api/submission/create.go new file mode 100644 index 0000000..a49fea4 --- /dev/null +++ b/internal/api/submission/create.go @@ -0,0 +1,64 @@ +package submission + +import ( + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/internal/global" + "github.com/WHUPRJ/woj-server/internal/service/submission" + "github.com/gin-gonic/gin" +) + +type createRequest struct { + Pid uint `form:"pid" binding:"required"` + Language string `form:"language" binding:"required"` + Code string `form:"statement" binding:"required"` +} + +// Create +// @Summary create a submission +// @Description create a submission +// @Accept application/x-www-form-urlencoded +// @Produce json +// @Param pid formData int true "problem id" +// @Param language formData string true "language" +// @Param code formData string true "code" +// @Response 200 {object} e.Response "" +// @Security Authentication +// @Router /v1/submission/create [post] +func (h *handler) Create(c *gin.Context) { + claim, exist := c.Get("claim") + if !exist { + e.Pong(c, e.UserUnauthenticated, nil) + return + } + + uid := claim.(*global.Claim).UID + + req := new(createRequest) + + if err := c.ShouldBind(req); err != nil { + e.Pong(c, e.InvalidParameter, err.Error()) + return + } + + createData := &submission.CreateData{ + ProblemID: req.Pid, + UserID: uid, + Language: req.Language, + Code: req.Code, + } + s, status := h.submissionService.Create(createData) + if status != e.Success { + e.Pong(c, status, nil) + return + } + + pv, status := h.problemService.QueryLatestVersion(req.Pid) + if status != e.Success { + e.Pong(c, status, nil) + return + } + + _, status = h.taskService.SubmitJudge(pv.ID, pv.StorageKey, *s) + e.Pong(c, status, nil) + return +} diff --git a/internal/api/submission/handler.go b/internal/api/submission/handler.go new file mode 100644 index 0000000..9a0c9c3 --- /dev/null +++ b/internal/api/submission/handler.go @@ -0,0 +1,41 @@ +package submission + +import ( + "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/submission" + "github.com/WHUPRJ/woj-server/internal/service/task" + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +var _ Handler = (*handler)(nil) + +type Handler interface { + Create(c *gin.Context) + Query(c *gin.Context) +} + +type handler struct { + log *zap.Logger + jwtService global.JwtService + problemService problem.Service + statusService status.Service + submissionService submission.Service + taskService task.Service +} + +func RouteRegister(g *global.Global, group *gin.RouterGroup) { + app := &handler{ + log: g.Log, + jwtService: g.Jwt, + problemService: problem.NewService(g), + statusService: status.NewService(g), + submissionService: submission.NewService(g), + taskService: task.NewService(g), + } + + group.POST("/create", app.jwtService.Handler(true), app.Create) + group.POST("/query", app.Query) +} diff --git a/internal/api/submission/query.go b/internal/api/submission/query.go new file mode 100644 index 0000000..7259fdf --- /dev/null +++ b/internal/api/submission/query.go @@ -0,0 +1,73 @@ +package submission + +import ( + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/internal/model" + "github.com/gin-gonic/gin" +) + +type queryRequest struct { + Pid uint `form:"pid"` + Uid uint `form:"uid"` + Offset int `form:"offset"` + Limit int `form:"limit"` +} + +type queryResponse struct { + Submission model.Submission `json:"submission"` + Point int32 `json:"point"` +} + +// Query +// @Summary Query submissions +// @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" +// @Response 200 {object} e.Response "queryResponse" +// @Router /v1/submission/query [post] + +func (h *handler) Query(c *gin.Context) { + req := new(queryRequest) + + if err := c.ShouldBind(req); err != nil { + e.Pong(c, e.InvalidParameter, err.Error()) + return + } + + if req.Pid == 0 && req.Uid == 0 { + e.Pong(c, e.InvalidParameter, nil) + return + } + + submissions, status := h.submissionService.Query(req.Pid, req.Uid, req.Offset, req.Limit) + + var response []*queryResponse + + for _, submission := range submissions { + currentStatus, _ := h.statusService.Query(submission.ID, false) + var currentPoint int32 + + if currentStatus == nil { + currentPoint = -1 + } else { + currentPoint = currentStatus.Point + } + + newResponse := &queryResponse{ + Submission: *submission, + Point: currentPoint, + } + + newResponse.Submission.Code = "" + + response = append(response, newResponse) + } + + e.Pong(c, status, submissions) + return + +} diff --git a/internal/api/user/create.go b/internal/api/user/create.go index 8e2dc25..f8c8fb0 100644 --- a/internal/api/user/create.go +++ b/internal/api/user/create.go @@ -8,9 +8,9 @@ import ( ) type createRequest struct { - Username string `form:"username" binding:"required"` - Nickname string `form:"nickname" binding:"required"` + UserName string `form:"username" binding:"required"` Password string `form:"password" binding:"required"` + NickName string `form:"nickname" binding:"required"` } // Create @@ -32,11 +32,10 @@ func (h *handler) Create(c *gin.Context) { } createData := &user.CreateData{ - Username: req.Username, - Nickname: req.Nickname, + UserName: req.UserName, Password: req.Password, + NickName: req.NickName, } - u, status := h.userService.Create(createData) if status != e.Success { e.Pong(c, status, nil) @@ -48,6 +47,7 @@ func (h *handler) Create(c *gin.Context) { e.Pong(c, status, nil) return } + claim := &global.Claim{ UID: u.ID, Role: u.Role, @@ -55,4 +55,5 @@ func (h *handler) Create(c *gin.Context) { } token, status := h.jwtService.SignClaim(claim) e.Pong(c, status, token) + return } diff --git a/internal/api/user/handler.go b/internal/api/user/handler.go index 1dcc8b5..70785b1 100644 --- a/internal/api/user/handler.go +++ b/internal/api/user/handler.go @@ -18,19 +18,19 @@ type Handler interface { type handler struct { log *zap.Logger - userService user.Service jwtService global.JwtService + userService user.Service } func RouteRegister(g *global.Global, group *gin.RouterGroup) { app := &handler{ log: g.Log, - userService: user.NewService(g), jwtService: g.Jwt, + userService: user.NewService(g), } - group.POST("/login", app.Login) group.POST("/create", app.Create) - group.POST("/logout", app.jwtService.Handler(), app.Logout) - group.POST("/profile", app.jwtService.Handler(), app.Profile) + group.POST("/login", app.Login) + group.POST("/logout", app.jwtService.Handler(true), app.Logout) + group.POST("/profile", app.jwtService.Handler(true), app.Profile) } diff --git a/internal/api/user/login.go b/internal/api/user/login.go index 1252c33..fdd3243 100644 --- a/internal/api/user/login.go +++ b/internal/api/user/login.go @@ -3,12 +3,12 @@ package user 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/user" "github.com/gin-gonic/gin" ) type loginRequest struct { - Username string `form:"username" binding:"required"` + UserName string `form:"username" binding:"required"` Password string `form:"password" binding:"required"` } @@ -30,30 +30,30 @@ func (h *handler) Login(c *gin.Context) { } // check password - userData := &model.User{ - UserName: req.Username, - Password: []byte(req.Password), + loginData := &user.LoginData{ + UserName: req.UserName, + Password: req.Password, } - user, status := h.userService.Login(userData) + u, status := h.userService.Login(loginData) if status != e.Success { e.Pong(c, status, nil) return } // sign and return token - version, status := h.userService.IncrVersion(user.ID) + version, status := h.userService.IncrVersion(u.ID) if status != e.Success { e.Pong(c, status, nil) return } claim := &global.Claim{ - UID: user.ID, - Role: user.Role, + UID: u.ID, + Role: u.Role, Version: version, } token, status := h.jwtService.SignClaim(claim) e.Pong(c, status, gin.H{ "token": token, - "nickname": user.NickName, + "nickname": u.NickName, }) } diff --git a/internal/api/user/logout.go b/internal/api/user/logout.go index f9a288c..c2554ec 100644 --- a/internal/api/user/logout.go +++ b/internal/api/user/logout.go @@ -23,4 +23,5 @@ func (h *handler) Logout(c *gin.Context) { _, status := h.userService.IncrVersion(claim.(*global.Claim).UID) e.Pong(c, status, nil) + return } diff --git a/internal/api/user/profile.go b/internal/api/user/profile.go index 07a2f45..072ed3a 100644 --- a/internal/api/user/profile.go +++ b/internal/api/user/profile.go @@ -31,18 +31,22 @@ func (h *handler) Profile(c *gin.Context) { uid := claim.(*global.Claim).UID role := claim.(*global.Claim).Role + req := new(profileRequest) - if err := c.ShouldBind(req); err == nil { - if req.UID != 0 && req.UID != uid { - if role >= model.RoleGeneral { - uid = req.UID - } else { - e.Pong(c, e.UserUnauthorized, nil) - return - } - } + + if err := c.ShouldBind(req); err != nil { + e.Pong(c, e.InvalidParameter, nil) + return } - user, status := h.userService.Profile(uid) + if req.UID == 0 { + req.UID = uid + } else if req.UID != uid && role < model.RoleGeneral { + e.Pong(c, e.UserUnauthorized, nil) + return + } + + user, status := h.userService.Profile(req.UID) e.Pong(c, status, user) + return } diff --git a/internal/app/runner/runner.go b/internal/app/runner/runner.go new file mode 100644 index 0000000..a57aa36 --- /dev/null +++ b/internal/app/runner/runner.go @@ -0,0 +1,43 @@ +package runner + +import ( + "github.com/WHUPRJ/woj-server/internal/api/runner" + "github.com/WHUPRJ/woj-server/internal/global" + "github.com/WHUPRJ/woj-server/internal/model" + "github.com/WHUPRJ/woj-server/pkg/utils" + "github.com/WHUPRJ/woj-server/pkg/zapasynq" + "github.com/hibiken/asynq" + "go.uber.org/zap" + "runtime" +) + +func RunRunner(g *global.Global) error { + hnd, err := runner.NewRunner(g) + if err != nil { + return err + } + + mux := asynq.NewServeMux() + mux.HandleFunc(model.TypeProblemBuild, hnd.Build) + mux.HandleFunc(model.TypeSubmitJudge, hnd.Judge) + + srv := asynq.NewServer( + asynq.RedisClientOpt{ + Addr: g.Conf.Redis.Address, + Password: g.Conf.Redis.Password, + DB: g.Conf.Redis.QueueDb, + }, + asynq.Config{ + Concurrency: utils.If(runtime.NumCPU() > 1, runtime.NumCPU()-1, 1).(int), + Logger: zapasynq.New(g.Log), + Queues: map[string]int{model.QueueRunner: 1}, + }, + ) + + if err := srv.Run(mux); err != nil { + g.Log.Warn("could not run server", zap.Error(err)) + return err + } + + return nil +} diff --git a/internal/app/app.go b/internal/app/server/server.go similarity index 53% rename from internal/app/app.go rename to internal/app/server/server.go index 58139f5..ed7d4a9 100644 --- a/internal/app/app.go +++ b/internal/app/server/server.go @@ -1,22 +1,27 @@ -package app +package server import ( "context" "fmt" "github.com/WHUPRJ/woj-server/internal/global" + "github.com/WHUPRJ/woj-server/internal/model" "github.com/WHUPRJ/woj-server/internal/repo/postgresql" "github.com/WHUPRJ/woj-server/internal/repo/redis" "github.com/WHUPRJ/woj-server/internal/router" "github.com/WHUPRJ/woj-server/internal/service/jwt" + "github.com/WHUPRJ/woj-server/pkg/utils" + "github.com/WHUPRJ/woj-server/pkg/zapasynq" + "github.com/hibiken/asynq" "go.uber.org/zap" "net/http" "os" "os/signal" + "runtime" "syscall" "time" ) -func Run(g *global.Global) error { +func RunServer(g *global.Global) error { // Setup Database g.Db = new(postgresql.Repo) g.Db.Setup(g) @@ -29,13 +34,13 @@ func Run(g *global.Global) error { g.Jwt = jwt.NewJwtService(g) // Prepare Router - handler := router.InitRouters(g) + routers := router.InitRouters(g) // Create Server addr := fmt.Sprintf("%s:%d", g.Conf.WebServer.Address, g.Conf.WebServer.Port) server := &http.Server{ Addr: addr, - Handler: handler, + Handler: routers, } // Run Server @@ -45,19 +50,47 @@ func Run(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 }) + queueSrv := asynq.NewServer( + asynq.RedisClientOpt{ + Addr: g.Conf.Redis.Address, + Password: g.Conf.Redis.Password, + DB: g.Conf.Redis.QueueDb, + }, + asynq.Config{ + Concurrency: utils.If(runtime.NumCPU() > 1, runtime.NumCPU()-1, 1).(int), + Logger: zapasynq.New(g.Log), + Queues: map[string]int{model.QueueServer: 1}, + }, + ) + + // Run Queue + if err := queueSrv.Start(queueMux); err != nil { + g.Log.Fatal("queueSrv.Start Failed", zap.Error(err)) + } + // Handle SIGINT and SIGTERM. quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit g.Log.Info("Shutting down server ...") - // Graceful Shutdown + // Graceful Shutdown Server ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() err := server.Shutdown(ctx) if err != nil { g.Log.Warn("Server Shutdown Failed", zap.Error(err)) } + + // Graceful Shutdown Queue + queueSrv.Shutdown() + + // Graceful Shutdown Database err = g.Db.Close() if err != nil { g.Log.Warn("Database Close Failed", zap.Error(err)) diff --git a/internal/e/code.go b/internal/e/code.go index d23f510..9161c73 100644 --- a/internal/e/code.go +++ b/internal/e/code.go @@ -1,35 +1,61 @@ package e const ( - Success Status = 0 - Unknown Status = 1 + Success Status = iota + Unknown +) - InternalError Status = 100 - InvalidParameter Status = 101 - NotFound Status = 102 - DatabaseError Status = 103 - RedisError Status = 104 +const ( + InternalError Status = 100 + iota + InvalidParameter + NotFound + DatabaseError + RedisError +) - TokenUnknown Status = 200 - TokenEmpty Status = 201 - TokenMalformed Status = 202 - TokenTimeError Status = 203 - TokenInvalid Status = 204 - TokenSignError Status = 205 - TokenRevoked Status = 206 +const ( + TokenUnknown Status = 200 + iota + TokenEmpty + TokenMalformed + TokenTimeError + TokenInvalid + TokenSignError + TokenRevoked +) - UserNotFound Status = 300 - UserWrongPassword Status = 301 - UserDuplicated Status = 302 - UserUnauthenticated Status = 303 - UserUnauthorized Status = 304 - UserDisabled Status = 305 +const ( + UserNotFound Status = 300 + iota + UserWrongPassword + UserDuplicated + UserUnauthenticated + UserUnauthorized + UserDisabled +) - ProblemNotFound Status = 500 - ProblemNotAvailable Status = 501 +const ( + ProblemNotFound Status = 500 + iota + ProblemNotAvailable + ProblemVersionNotFound + ProblemVersionNotAvailable + StatusNotFound +) - TaskEnqueueFailed Status = 600 - TaskGetInfoFailed Status = 601 +const ( + TaskEnqueueFailed Status = 600 + iota + TaskGetInfoFailed +) + +const ( + RunnerDepsBuildFailed Status = 700 + iota + RunnerDownloadFailed + RunnerUnzipFailed + RunnerProblemNotExist + RunnerProblemPrebuildFailed + RunnerProblemParseFailed + RunnerUserNotExist + RunnerUserCompileFailed + RunnerRunFailed + RunnerJudgeFailed ) var msgText = map[Status]string{ @@ -57,9 +83,24 @@ var msgText = map[Status]string{ UserUnauthorized: "User Unauthorized", UserDisabled: "User Disabled", - ProblemNotFound: "Problem Not Found", - ProblemNotAvailable: "Problem Not Available", + ProblemNotFound: "Problem Not Found", + ProblemNotAvailable: "Problem Not Available", + ProblemVersionNotFound: "Problem Version Not Found", + ProblemVersionNotAvailable: "Problem Version Not Available", + + StatusNotFound: "Status Not Found", TaskEnqueueFailed: "Task Enqueue Failed", TaskGetInfoFailed: "Task Get Info Failed", + + RunnerDepsBuildFailed: "Runner Deps Build Failed", + RunnerDownloadFailed: "Runner Download Failed", + RunnerUnzipFailed: "Runner Unzip Failed", + RunnerProblemNotExist: "Runner Problem Not Exist", + RunnerProblemPrebuildFailed: "Runner Problem Prebuild Failed", + RunnerProblemParseFailed: "Runner Problem Parse Failed", + RunnerUserNotExist: "Runner User Not Exist", + RunnerUserCompileFailed: "Runner User Compile Failed", + RunnerRunFailed: "Runner Run Failed", + RunnerJudgeFailed: "Runner Judge Failed", } diff --git a/internal/global/jwt.go b/internal/global/jwt.go index cac10dd..b39faf2 100644 --- a/internal/global/jwt.go +++ b/internal/global/jwt.go @@ -19,5 +19,5 @@ type JwtService interface { SignClaim(claim *Claim) (string, e.Status) Validate(claim *Claim) bool - Handler() gin.HandlerFunc + Handler(forced bool) gin.HandlerFunc } diff --git a/internal/model/Language.go b/internal/model/Language.go deleted file mode 100644 index 9a30357..0000000 --- a/internal/model/Language.go +++ /dev/null @@ -1,6 +0,0 @@ -package model - -const ( - LangC int32 = iota - LangCPP -) diff --git a/internal/model/Problem.go b/internal/model/Problem.go index 458daf2..c72b0b9 100644 --- a/internal/model/Problem.go +++ b/internal/model/Problem.go @@ -1,20 +1,23 @@ package model import ( - "github.com/lib/pq" + "github.com/jackc/pgtype" "gorm.io/gorm" ) type Problem struct { - gorm.Model `json:"meta"` - Title string `json:"title" gorm:"not null"` - Content string `json:"content" gorm:"not null"` - TimeLimit uint `json:"time_limit" gorm:"not null"` - MemoryLimit uint `json:"memory_limit" gorm:"not null"` - ProviderID uint `json:"provider_id" gorm:"not null;index"` - Provider User `json:"-" gorm:"foreignKey:ProviderID"` - Languages pq.Int32Array `json:"languages" gorm:"type:int[]"` - Points pq.Int32Array `json:"points" gorm:"type:int[]"` - StorageKey string `json:"storage_key" gorm:"not null"` - IsEnabled bool `json:"is_enabled" gorm:"not null;index"` + gorm.Model `json:"meta"` + Title string `json:"title" gorm:"not null"` + Statement string `json:"statement" gorm:"not null"` + ProviderID uint `json:"-" gorm:"not null;index"` + Provider User `json:"provider" gorm:"foreignKey:ProviderID"` + IsEnabled bool `json:"is_enabled" gorm:"not null;index"` +} + +type ProblemVersion struct { + gorm.Model `json:"meta"` + ProblemID uint `json:"-" gorm:"not null;index"` + Context pgtype.JSON `json:"context" gorm:"type:json"` + StorageKey string `json:"-" gorm:"not null"` + IsEnabled bool `json:"is_enabled" gorm:"not null;index"` } diff --git a/internal/model/Status.go b/internal/model/Status.go index 76fab37..2e14cf1 100644 --- a/internal/model/Status.go +++ b/internal/model/Status.go @@ -1,11 +1,16 @@ package model -import "gorm.io/gorm" +import ( + "github.com/jackc/pgtype" + "gorm.io/gorm" +) type Status struct { - gorm.Model `json:"-"` - SubmissionID uint `json:"submission_id" gorm:"not null;index"` - Submission Submission `json:"-" gorm:"foreignKey:SubmissionID"` - Verdict Verdict `json:"verdict" gorm:"not null"` - Point int32 `json:"point" gorm:"not null"` + gorm.Model `json:"meta"` + SubmissionID uint `json:"-" gorm:"not null;index"` + Submission Submission `json:"submission" gorm:"foreignKey:SubmissionID"` + ProblemVersionID uint `json:"problem_version_id" gorm:"not null;index"` + Context pgtype.JSON `json:"context" gorm:"type:json;not null"` + Point int32 `json:"point" gorm:"not null"` + IsEnabled bool `json:"is_enabled" gorm:"not null;index"` } diff --git a/internal/model/Submission.go b/internal/model/Submission.go index 66b61ad..37bb14d 100644 --- a/internal/model/Submission.go +++ b/internal/model/Submission.go @@ -3,11 +3,10 @@ package model import "gorm.io/gorm" type Submission struct { - gorm.Model `json:"-"` - ProblemID uint `json:"problem_id" gorm:"not null;index"` - Problem Problem `json:"-" gorm:"foreignKey:ProblemID"` - UserID uint `json:"user_id" gorm:"not null;index"` - User User `json:"-" gorm:"foreignKey:UserID"` - Language int32 `json:"language" gorm:"not null"` - Code string `json:"code" gorm:"not null"` + gorm.Model `json:"meta"` + ProblemID uint `json:"problem_id" gorm:"not null;index"` + UserID uint `json:"-" gorm:"not null;index"` + User User `json:"user" gorm:"foreignKey:UserID"` + Language string `json:"language" gorm:"not null"` + Code string `json:"code" gorm:"not null"` } diff --git a/internal/model/Task.go b/internal/model/Task.go index 453ea8a..ad2096a 100644 --- a/internal/model/Task.go +++ b/internal/model/Task.go @@ -1,15 +1,41 @@ package model -const ( - TypeProblemPush = "problem:push" - TypeSubmitJudge = "submit:judge" +import ( + "github.com/WHUPRJ/woj-server/internal/e" ) -type ProblemPushPayload struct { - ProblemID uint - ProblemFile string +const ( + TypeProblemBuild = "problem:build" + TypeProblemUpdate = "problem:update" + TypeSubmitJudge = "submit:judge" + TypeSubmitUpdate = "submit:update" +) + +const ( + QueueServer = "server" + QueueRunner = "runner" +) + +type ProblemBuildPayload struct { + ProblemVersionID uint + ProblemFile string } -type SubmitJudge struct { - Submission Submission +type ProblemUpdatePayload struct { + Status e.Status + ProblemVersionID uint + Context string +} + +type SubmitJudgePayload struct { + ProblemVersionId uint + StorageKey string + Submission Submission +} + +type SubmitUpdatePayload struct { + Status e.Status + Sid uint + Point int32 + Context string } diff --git a/internal/model/User.go b/internal/model/User.go index e2c7644..cc6507a 100644 --- a/internal/model/User.go +++ b/internal/model/User.go @@ -5,7 +5,7 @@ import ( ) type User struct { - gorm.Model `json:"-"` + gorm.Model `json:"meta"` UserName string `json:"user_name" gorm:"not null;uniqueIndex"` NickName string `json:"nick_name" gorm:"not null"` Role Role `json:"role" gorm:"not null"` diff --git a/internal/model/Verdict.go b/internal/model/Verdict.go deleted file mode 100644 index 3551512..0000000 --- a/internal/model/Verdict.go +++ /dev/null @@ -1,17 +0,0 @@ -package model - -type Verdict int - -const ( - VerdictJudging Verdict = iota - VerdictAccepted - VerdictWrongAnswer - VerdictTimeLimitExceeded - VerdictMemoryLimitExceeded - VerdictRuntimeError - VerdictCompileError - VerdictSystemError - VerdictJuryFailed - VerdictSkipped - VerdictPartiallyCorrect -) diff --git a/internal/router/api.go b/internal/router/api.go index 80fab98..ce7fa19 100644 --- a/internal/router/api.go +++ b/internal/router/api.go @@ -3,6 +3,8 @@ package router import ( "github.com/WHUPRJ/woj-server/internal/api/debug" "github.com/WHUPRJ/woj-server/internal/api/problem" + "github.com/WHUPRJ/woj-server/internal/api/status" + "github.com/WHUPRJ/woj-server/internal/api/submission" "github.com/WHUPRJ/woj-server/internal/api/user" "github.com/WHUPRJ/woj-server/internal/global" "github.com/gin-gonic/gin" @@ -25,4 +27,6 @@ var endpoints = []global.EndpointInfo{ {Version: "", Path: "/debug", Register: debug.RouteRegister}, {Version: "/v1", Path: "/user", Register: user.RouteRegister}, {Version: "/v1", Path: "/problem", Register: problem.RouteRegister}, + {Version: "/v1", Path: "/submission", Register: submission.RouteRegister}, + {Version: "/v1", Path: "/status", Register: status.RouteRegister}, } diff --git a/internal/service/jwt/middleware.go b/internal/service/jwt/middleware.go index c998f59..2d2eddc 100644 --- a/internal/service/jwt/middleware.go +++ b/internal/service/jwt/middleware.go @@ -2,35 +2,40 @@ package jwt import ( "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/internal/global" "github.com/gin-gonic/gin" "strings" ) -func (s *service) Handler() gin.HandlerFunc { +func (s *service) Handler(forced bool) gin.HandlerFunc { return func(c *gin.Context) { - const tokenPrefix = "bearer " - tokenHeader := c.GetHeader("Authorization") - if tokenHeader == "" || !strings.HasPrefix(strings.ToLower(tokenHeader), tokenPrefix) { - e.Pong(c, e.TokenEmpty, nil) - c.Abort() - return - } - token := tokenHeader[len(tokenPrefix):] + claim, status := func() (*global.Claim, e.Status) { + const tokenPrefix = "bearer " + tokenHeader := c.GetHeader("Authorization") + if tokenHeader == "" || !strings.HasPrefix(strings.ToLower(tokenHeader), tokenPrefix) { + return nil, e.TokenEmpty + } - claim, status := s.ParseToken(token) - if status != e.Success { + token := tokenHeader[len(tokenPrefix):] + claim, status := s.ParseToken(token) + if status != e.Success { + return nil, status + } + + if !s.Validate(claim) { + return nil, e.TokenRevoked + } + return claim, e.Success + }() + + if status == e.Success { + c.Set("claim", claim) + } + if forced && status != e.Success { e.Pong(c, status, nil) c.Abort() - return + } else { + c.Next() } - - if !s.Validate(claim) { - e.Pong(c, e.TokenRevoked, nil) - c.Abort() - return - } - - c.Set("claim", claim) - c.Next() } } diff --git a/internal/service/problem/create.go b/internal/service/problem/create.go index ad48e3a..d79c4dd 100644 --- a/internal/service/problem/create.go +++ b/internal/service/problem/create.go @@ -6,12 +6,24 @@ import ( "go.uber.org/zap" ) -func (s *service) Create(uid uint, problem *model.Problem) (*model.Problem, e.Status) { - problem.ProviderID = uid - problem.IsEnabled = true +type CreateData struct { + Title string + Statement string + ProviderID uint + IsEnabled bool +} - if err := s.db.Create(problem).Error; err != nil { - s.log.Debug("create problem error", zap.Error(err), zap.Any("problem", problem)) +func (s *service) Create(data *CreateData) (*model.Problem, e.Status) { + problem := &model.Problem{ + Title: data.Title, + Statement: data.Statement, + ProviderID: data.ProviderID, + IsEnabled: data.IsEnabled, + } + + err := s.db.Create(problem).Error + if err != nil { + s.log.Warn("DatabaseError", zap.Error(err), zap.Any("problem", problem)) return nil, e.DatabaseError } diff --git a/internal/service/problem/createVersion.go b/internal/service/problem/createVersion.go new file mode 100644 index 0000000..f355c6a --- /dev/null +++ b/internal/service/problem/createVersion.go @@ -0,0 +1,27 @@ +package problem + +import ( + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/internal/model" + "go.uber.org/zap" +) + +type CreateVersionData struct { + ProblemID uint + StorageKey string +} + +func (s *service) CreateVersion(data *CreateVersionData) (*model.ProblemVersion, e.Status) { + problemVersion := &model.ProblemVersion{ + ProblemID: data.ProblemID, + StorageKey: data.StorageKey, + } + + err := s.db.Create(problemVersion).Error + if err != nil { + s.log.Warn("DatabaseError", zap.Error(err), zap.Any("problemVersion", problemVersion)) + return nil, e.DatabaseError + } + + return problemVersion, e.Success +} diff --git a/internal/service/problem/query.go b/internal/service/problem/query.go index f6ef0df..aa10be5 100644 --- a/internal/service/problem/query.go +++ b/internal/service/problem/query.go @@ -4,21 +4,29 @@ import ( "errors" "github.com/WHUPRJ/woj-server/internal/e" "github.com/WHUPRJ/woj-server/internal/model" - "github.com/WHUPRJ/woj-server/pkg/utils" + "go.uber.org/zap" "gorm.io/gorm" "gorm.io/gorm/clause" ) -func (s *service) Query(problemId uint) (*model.Problem, e.Status) { +func (s *service) Query(pid uint, associations bool, shouldEnable bool) (*model.Problem, e.Status) { problem := new(model.Problem) - err := s.db.Preload(clause.Associations).First(&problem, problemId).Error + query := s.db + if associations { + query = query.Preload(clause.Associations) + } + err := query.First(&problem, pid).Error if errors.Is(err, gorm.ErrRecordNotFound) { return nil, e.ProblemNotFound } if err != nil { + s.log.Warn("DatabaseError", zap.Error(err), zap.Any("pid", pid)) return nil, e.DatabaseError } - return problem, utils.If(problem.IsEnabled, e.Success, e.ProblemNotAvailable).(e.Status) + if shouldEnable && !problem.IsEnabled { + return nil, e.ProblemNotAvailable + } + return problem, e.Success } diff --git a/internal/service/problem/queryFuzz.go b/internal/service/problem/queryFuzz.go index 0fad2fc..a461e37 100644 --- a/internal/service/problem/queryFuzz.go +++ b/internal/service/problem/queryFuzz.go @@ -1,25 +1,28 @@ package problem import ( - "errors" "github.com/WHUPRJ/woj-server/internal/e" "github.com/WHUPRJ/woj-server/internal/model" - "gorm.io/gorm" + "go.uber.org/zap" "gorm.io/gorm/clause" ) -func (s *service) QueryFuzz(search string) ([]*model.Problem, e.Status) { - var problems []*model.Problem +func (s *service) QueryFuzz(search string, associations bool, shouldEnable bool) ([]*model.Problem, e.Status) { + problems := make([]*model.Problem, 0) - err := s.db.Preload(clause.Associations). - Where("is_enabled = true"). - Where(s.db.Where("title LIKE ?", "%"+search+"%"). - Or("content LIKE ?", "%"+search+"%")). - Find(&problems).Error - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, e.ProblemNotFound + query := s.db + if associations { + query = query.Preload(clause.Associations) } + if shouldEnable { + query = query.Where("is_enabled = true") + } + query = query. + Where(s.db.Where("title LIKE ?", "%"+search+"%"). + Or("statement LIKE ?", "%"+search+"%")) + err := query.Find(&problems).Error if err != nil { + s.log.Warn("DatabaseError", zap.Error(err), zap.Any("search", search)) return nil, e.DatabaseError } diff --git a/internal/service/problem/queryLatestVersion.go b/internal/service/problem/queryLatestVersion.go new file mode 100644 index 0000000..55f0fc8 --- /dev/null +++ b/internal/service/problem/queryLatestVersion.go @@ -0,0 +1,29 @@ +package problem + +import ( + "errors" + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/internal/model" + "go.uber.org/zap" + "gorm.io/gorm" +) + +func (s *service) QueryLatestVersion(pid uint) (*model.ProblemVersion, e.Status) { + problemVersion := &model.ProblemVersion{ + ProblemID: pid, + IsEnabled: true, + } + + err := s.db. + Where(problemVersion). + Last(&problemVersion).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, e.ProblemVersionNotFound + } + if err != nil { + s.log.Warn("DatabaseError", zap.Error(err), zap.Any("problemVersion", problemVersion)) + return nil, e.DatabaseError + } + + return problemVersion, e.Success +} diff --git a/internal/service/problem/queryVersion.go b/internal/service/problem/queryVersion.go new file mode 100644 index 0000000..9ca08ae --- /dev/null +++ b/internal/service/problem/queryVersion.go @@ -0,0 +1,27 @@ +package problem + +import ( + "errors" + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/internal/model" + "go.uber.org/zap" + "gorm.io/gorm" +) + +func (s *service) QueryVersion(pvid uint, shouldEnable bool) (*model.ProblemVersion, e.Status) { + problemVersion := new(model.ProblemVersion) + + err := s.db.First(&problemVersion, pvid).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, e.ProblemVersionNotFound + } + if err != nil { + s.log.Warn("DatabaseError", zap.Error(err), zap.Any("pvid", pvid)) + return nil, e.DatabaseError + } + + if shouldEnable && !problemVersion.IsEnabled { + return nil, e.ProblemVersionNotAvailable + } + return problemVersion, e.Success +} diff --git a/internal/service/problem/service.go b/internal/service/problem/service.go index 693951d..8e6df02 100644 --- a/internal/service/problem/service.go +++ b/internal/service/problem/service.go @@ -11,10 +11,15 @@ import ( var _ Service = (*service)(nil) type Service interface { - Create(uint, *model.Problem) (*model.Problem, e.Status) - Update(uint, *model.Problem) (*model.Problem, e.Status) - Query(uint) (*model.Problem, e.Status) - QueryFuzz(string) ([]*model.Problem, e.Status) + Create(data *CreateData) (*model.Problem, e.Status) + Update(problem *model.Problem) (*model.Problem, e.Status) + Query(pid uint, associations bool, shouldEnable bool) (*model.Problem, e.Status) + 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) + QueryVersion(pvid uint, shouldEnable bool) (*model.ProblemVersion, e.Status) + QueryLatestVersion(pid uint) (*model.ProblemVersion, e.Status) } type service struct { diff --git a/internal/service/problem/update.go b/internal/service/problem/update.go index 079bb13..026b934 100644 --- a/internal/service/problem/update.go +++ b/internal/service/problem/update.go @@ -4,15 +4,12 @@ import ( "github.com/WHUPRJ/woj-server/internal/e" "github.com/WHUPRJ/woj-server/internal/model" "go.uber.org/zap" - "gorm.io/gorm/clause" ) -func (s *service) Update(pid uint, problem *model.Problem) (*model.Problem, e.Status) { - if err := s.db.Clauses(clause.Returning{}).Model(problem). - Where("ID = (?)", pid). - Select("Title", "Content", "TimeLimit", "MemoryLimit", "IsEnabled"). - Updates(problem).Error; err != nil { - s.log.Debug("update problem error", zap.Error(err), zap.Any("problem", problem)) +func (s *service) Update(problem *model.Problem) (*model.Problem, e.Status) { + err := s.db.Save(problem).Error + if err != nil { + s.log.Warn("DatabaseError", zap.Error(err), zap.Any("problem", problem)) return nil, e.DatabaseError } diff --git a/internal/service/problem/updateVersion.go b/internal/service/problem/updateVersion.go new file mode 100644 index 0000000..c5e360c --- /dev/null +++ b/internal/service/problem/updateVersion.go @@ -0,0 +1,17 @@ +package problem + +import ( + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/internal/model" + "go.uber.org/zap" +) + +func (s *service) UpdateVersion(problemVersion *model.ProblemVersion) (*model.ProblemVersion, e.Status) { + err := s.db.Save(problemVersion).Error + if err != nil { + s.log.Warn("DatabaseError", zap.Error(err), zap.Any("problemVersion", problemVersion)) + return nil, e.DatabaseError + } + + return problemVersion, e.Success +} diff --git a/internal/service/runner/common.go b/internal/service/runner/common.go new file mode 100644 index 0000000..01260c7 --- /dev/null +++ b/internal/service/runner/common.go @@ -0,0 +1,75 @@ +package runner + +import ( + "fmt" + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/pkg/utils" + "go.uber.org/zap" + "os" + "os/exec" + "path" + "path/filepath" +) + +var ( + Prefix = "./resource/runner" + ProblemDir = "./problems/" + ScriptsDir = "./scripts/" + UserDir = "./users/" + TmpDir = "./tmp/" +) + +func init() { + wd, err := os.Getwd() + if err != nil { + panic(err) + } + + Prefix = path.Join(wd, Prefix) + ProblemDir = path.Join(Prefix, ProblemDir) + ScriptsDir = path.Join(Prefix, ScriptsDir) + UserDir = path.Join(Prefix, UserDir) + TmpDir = path.Join(Prefix, TmpDir) +} + +func (s *service) execute(script string, args ...string) error { + p := filepath.Join(ScriptsDir, script) + cmd := exec.Command(p, args...) + cmd.Dir = ScriptsDir + return cmd.Run() +} + +func (s *service) checkAndExecute(version uint, user string, lang string, script string, fail e.Status) e.Status { + if !s.problemExists(version) { + s.log.Info("problem not exists", zap.Uint("version", version)) + return e.RunnerProblemNotExist + } + + if !s.userExists(user, fmt.Sprintf("%s.%s", user, lang)) { + s.log.Info("user program not exists", zap.String("user", user), zap.String("lang", lang)) + return e.RunnerUserNotExist + } + + err := s.execute(script, fmt.Sprintf("%d", version), fmt.Sprintf("%s", user), lang) + + if err != nil { + s.log.Info("execute failed", + zap.Error(err), + zap.Uint("version", version), + zap.String("user", user), + zap.String("lang", lang)) + return fail + } + + return e.Success +} + +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) + return utils.FileExist(userPath) +} diff --git a/internal/service/runner/compile.go b/internal/service/runner/compile.go new file mode 100644 index 0000000..2b0b270 --- /dev/null +++ b/internal/service/runner/compile.go @@ -0,0 +1,30 @@ +package runner + +import ( + "fmt" + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/pkg/utils" + "os" + "path/filepath" +) + +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)) + + _ = os.Remove(target) + status := s.checkAndExecute(version, user, lang, "problem_compile.sh", e.RunnerUserCompileFailed) + + log := filepath.Join(UserDir, 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 { + return JudgeStatus{ + Message: "compile failed", + Tasks: []TaskStatus{{Verdict: VerdictCompileError, Message: msgText}}}, + utils.If(status == e.Success, e.RunnerUserCompileFailed, status).(e.Status) + } +} diff --git a/internal/service/runner/config.go b/internal/service/runner/config.go new file mode 100644 index 0000000..94131c0 --- /dev/null +++ b/internal/service/runner/config.go @@ -0,0 +1,125 @@ +package runner + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" +) + +type Config struct { + Runtime struct { + TimeLimit int `json:"TimeLimit"` + MemoryLimit int `json:"MemoryLimit"` + NProcLimit int `json:"NProcLimit"` + } `json:"Runtime"` + Languages []struct { + Lang string `json:"Lang"` + Type string `json:"Type"` + Script string `json:"Script"` + Cmp string `json:"Cmp"` + } `json:"Languages"` + Tasks []struct { + Id int `json:"Id"` + Points int32 `json:"Points"` + } `json:"Tasks"` +} + +func (s *service) ParseConfig(version uint, skipCheck bool) (Config, error) { + base := filepath.Join(ProblemDir, fmt.Sprintf("%d", version)) + file := filepath.Join(base, "config.json") + + data, err := os.ReadFile(file) + if err != nil { + return Config{}, err + } + + config := Config{} + err = json.Unmarshal(data, &config) + if err != nil { + return Config{}, err + } + + if skipCheck { + return config, nil + } + + err = s.checkConfig(&config, base) + if err != nil { + return Config{}, err + } + + return config, nil +} + +func (s *service) checkConfig(config *Config, base string) error { + if config.Runtime.TimeLimit < 0 { + return errors.New("time limit is negative") + } + if config.Runtime.MemoryLimit < 0 { + return errors.New("memory limit is negative") + } + if config.Runtime.NProcLimit < 0 { + return errors.New("nproc limit is negative") + } + + allowedLang := map[string]struct{}{ + "c": {}, + "cpp": {}, + } + for _, lang := range config.Languages { + if _, ok := allowedLang[lang.Lang]; !ok { + return fmt.Errorf("language %s is not allowed", lang.Lang) + } + + if lang.Type != "custom" && lang.Type != "default" { + return fmt.Errorf("language %s has invalid type %s", lang.Lang, lang.Type) + } + + if lang.Type == "custom" { + if lang.Script == "" { + return fmt.Errorf("language %s has empty script", lang.Lang) + } + + file := filepath.Join(base, "judge", lang.Script) + _, err := os.Stat(file) + if err != nil { + return fmt.Errorf("language %s has invalid script %s", lang.Lang, lang.Script) + } + } + + if lang.Type == "default" { + if lang.Cmp == "" { + return fmt.Errorf("language %s has empty cmp", lang.Lang) + } + } + } + + if len(config.Tasks) == 0 { + return errors.New("no tasks") + } + ids := map[int]struct{}{} + total := (1 + len(config.Tasks)) * len(config.Tasks) / 2 + for _, task := range config.Tasks { + if task.Id <= 0 { + return fmt.Errorf("task %d has non-positive id", task.Id) + } + + if task.Points < 0 { + return fmt.Errorf("task %d has negative points", task.Id) + } + + if _, ok := ids[task.Id]; ok { + return fmt.Errorf("task %d has duplicate id", task.Id) + } + + total -= task.Id + ids[task.Id] = struct{}{} + } + if total != 0 { + return errors.New("task ids are not continuous") + } + + return nil +} diff --git a/internal/service/runner/deps.go b/internal/service/runner/deps.go new file mode 100644 index 0000000..96940f5 --- /dev/null +++ b/internal/service/runner/deps.go @@ -0,0 +1,31 @@ +package runner + +import ( + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/pkg/utils" + "go.uber.org/zap" + "os" + "os/exec" + "path/filepath" +) + +func (s *service) EnsureDeps(force bool) e.Status { + mark := filepath.Join(Prefix, ".mark.docker") + + if force { + _ = os.Remove(mark) + } else if utils.FileExist(mark) { + return e.Success + } + + script := filepath.Join(ScriptsDir, "prepare_container.sh") + cmd := exec.Command(script) + cmd.Dir = ScriptsDir + err := cmd.Run() + if err != nil { + s.log.Warn("prebuild docker images failed", zap.Error(err)) + return e.RunnerDepsBuildFailed + } + + return e.Success +} diff --git a/internal/service/runner/newProblem.go b/internal/service/runner/newProblem.go new file mode 100644 index 0000000..6c9c91a --- /dev/null +++ b/internal/service/runner/newProblem.go @@ -0,0 +1,72 @@ +package runner + +import ( + "fmt" + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/pkg/down" + "github.com/WHUPRJ/woj-server/pkg/unzip" + "github.com/WHUPRJ/woj-server/pkg/utils" + "go.uber.org/zap" + "os" + "path/filepath" +) + +func (s *service) download(version uint, url string) e.Status { + zipPath := filepath.Join(TmpDir, fmt.Sprintf("%d.zip", version)) + problemPath := filepath.Join(ProblemDir, fmt.Sprintf("%d", version)) + + err := down.Down(zipPath, url) + if err != nil { + s.log.Error("download problem failed", zap.Error(err)) + return e.RunnerDownloadFailed + } + + err = unzip.Unzip(zipPath, problemPath) + if err != nil { + s.log.Warn("unzip problem failed", zap.Error(err)) + return e.RunnerUnzipFailed + } + + return e.Success +} + +func (s *service) prebuild(version uint, force bool) e.Status { + if !s.problemExists(version) { + return e.RunnerProblemNotExist + } + + mark := filepath.Join(ProblemDir, fmt.Sprintf("%d", version), ".mark.prebuild") + if force { + _ = os.Remove(mark) + } else if utils.FileExist(mark) { + return e.Success + } + + err := s.execute("problem_prebuild.sh", fmt.Sprintf("%d", version)) + + if err != nil { + s.log.Warn("prebuild problem failed", zap.Error(err), zap.Uint("version", version)) + return e.RunnerProblemPrebuildFailed + } + + return e.Success +} + +func (s *service) NewProblem(version uint, url string) (Config, e.Status) { + 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) + if status != e.Success { + return Config{}, status + } + + return cfg, e.Success +} diff --git a/internal/service/runner/runAndJudge.go b/internal/service/runner/runAndJudge.go new file mode 100644 index 0000000..c093c22 --- /dev/null +++ b/internal/service/runner/runAndJudge.go @@ -0,0 +1,22 @@ +package runner + +import "github.com/WHUPRJ/woj-server/internal/e" + +func (s *service) RunAndJudge(version uint, user string, lang string, config *Config) (JudgeStatus, int32, e.Status) { + // run user program + status := s.checkAndExecute(version, user, lang, "problem_run.sh", e.RunnerRunFailed) + if status != e.Success { + return JudgeStatus{Message: "run failed"}, 0, status + } + + // run judger + status = s.checkAndExecute(version, user, lang, "problem_judge.sh", e.RunnerJudgeFailed) + if status != e.Success { + return JudgeStatus{Message: "judge failed"}, 0, status + } + + // check result + result, pts := s.checkResults(user, config) + + return result, pts, e.Success +} diff --git a/internal/service/runner/service.go b/internal/service/runner/service.go new file mode 100644 index 0000000..b4fd57b --- /dev/null +++ b/internal/service/runner/service.go @@ -0,0 +1,34 @@ +package runner + +import ( + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/internal/global" + "go.uber.org/zap" +) + +var _ Service = (*service)(nil) + +type Service interface { + // EnsureDeps build docker images + EnsureDeps(force bool) e.Status + // NewProblem = Download + Parse + Prebuild + NewProblem(version uint, url string) (Config, e.Status) + + // Compile compile user submission + Compile(version uint, user string, lang string) (JudgeStatus, e.Status) + // RunAndJudge execute user program + RunAndJudge(version uint, user string, lang string, config *Config) (JudgeStatus, int32, e.Status) + + // ParseConfig parse config file + ParseConfig(version uint, skipCheck bool) (Config, error) +} + +type service struct { + log *zap.Logger +} + +func NewService(g *global.Global) Service { + return &service{ + log: g.Log, + } +} diff --git a/internal/service/runner/status.go b/internal/service/runner/status.go new file mode 100644 index 0000000..8c9ddbe --- /dev/null +++ b/internal/service/runner/status.go @@ -0,0 +1,227 @@ +package runner + +import ( + "bytes" + "encoding/json" + "encoding/xml" + "fmt" + "github.com/WHUPRJ/woj-server/pkg/utils" + "golang.org/x/text/encoding/charmap" + "io" + "path/filepath" +) + +const ( + VerdictAccepted = iota + VerdictWrongAnswer + VerdictJuryFailed + VerdictPartialCorrect + VerdictTimeLimitExceeded + VerdictMemoryLimitExceeded + VerdictRuntimeError + VerdictCompileError + VerdictSystemError +) + +type TestLibReport struct { + XMLName xml.Name `xml:"result"` + Outcome string `xml:"outcome,attr"` + PCType int `xml:"pctype,attr"` + Points float64 `xml:"points,attr"` + Result string `xml:",chardata"` +} + +type TaskStatus struct { + Id int `json:"id"` + Points int32 `json:"points"` + RealTime int `json:"real_time"` + CpuTime int `json:"cpu_time"` + Memory int `json:"memory"` + Verdict int `json:"verdict"` + Message string `json:"message"` + + infoText []byte + info map[string]interface{} + judgeText string + judge TestLibReport +} + +type JudgeStatus struct { + Message string `json:"message"` + Tasks []TaskStatus `json:"tasks"` +} + +func (t *TaskStatus) getInfoText(infoFile string) *TaskStatus { + if t.Verdict != VerdictAccepted { + return t + } + + var err error + t.infoText, err = utils.FileRead(infoFile) + if err != nil { + t.Verdict = VerdictSystemError + t.Message = "cannot read info file" + } + + return t +} + +func (t *TaskStatus) getInfo() *TaskStatus { + if t.Verdict != VerdictAccepted { + return t + } + + err := json.Unmarshal(t.infoText, &t.info) + if err != nil { + t.Verdict = VerdictSystemError + t.Message = "cannot parse info file" + } else { + t.RealTime = int(t.info["real_time"].(float64)) + t.CpuTime = int(t.info["cpu_time"].(float64)) + t.Memory = int(t.info["memory"].(float64)) + } + + return t +} + +func (t *TaskStatus) checkExit() *TaskStatus { + if t.Verdict != VerdictAccepted { + return t + } + + if t.info["status"] != "exited" || t.info["code"] != 0.0 { + t.Verdict = VerdictRuntimeError + t.Message = fmt.Sprintf("status: %v, code: %v", t.info["status"], t.info["code"]) + } + + return t +} + +func (t *TaskStatus) checkTime(config *Config) *TaskStatus { + if t.Verdict != VerdictAccepted { + return t + } + + if t.info["real_time"].(float64) > float64(config.Runtime.TimeLimit)+5 { + t.Verdict = VerdictTimeLimitExceeded + t.Message = fmt.Sprintf("real_time: %v cpu_time: %v", t.info["real_time"], t.info["cpu_time"]) + } + + return t +} + +func (t *TaskStatus) checkMemory(config *Config) *TaskStatus { + if t.Verdict != VerdictAccepted { + return t + } + + if t.info["memory"].(float64) > float64((config.Runtime.MemoryLimit+1)*1024) { + t.Verdict = VerdictMemoryLimitExceeded + t.Message = fmt.Sprintf("memory: %v", t.info["memory"]) + } + + return t +} + +func (t *TaskStatus) getJudgeText(judgeFile string) *TaskStatus { + if t.Verdict != VerdictAccepted { + return t + } + + j, err := utils.FileRead(judgeFile) + if err != nil { + t.Verdict = VerdictSystemError + t.Message = "cannot read judge file" + } else { + t.judgeText = string(j) + } + + return t +} + +func (t *TaskStatus) getJudge() *TaskStatus { + if t.Verdict != VerdictAccepted { + return t + } + + b := bytes.NewReader([]byte(t.judgeText)) + d := xml.NewDecoder(b) + d.CharsetReader = func(charset string, input io.Reader) (io.Reader, error) { + switch charset { + case "windows-1251": + return charmap.Windows1251.NewDecoder().Reader(input), nil + default: + return nil, fmt.Errorf("unknown charset: %s", charset) + } + } + + err := d.Decode(&t.judge) + if err != nil { + t.Verdict = VerdictSystemError + t.Message = "cannot parse judge file" + } + + return t +} + +func (t *TaskStatus) checkJudge(pts *map[int]int32) *TaskStatus { + if t.Verdict != VerdictAccepted { + return t + } + + mp := map[string]int{ + "accepted": VerdictAccepted, + "wrong-answer": VerdictWrongAnswer, + "presentation-error": VerdictWrongAnswer, + "points": VerdictPartialCorrect, + "relative-scoring": VerdictPartialCorrect, + } + + if v, ok := mp[t.judge.Outcome]; ok { + t.Verdict = v + t.Message = t.judge.Result + if v == VerdictAccepted { + t.Points = (*pts)[t.Id] + } else if v == VerdictPartialCorrect { + t.Points = int32(t.judge.Points) + int32(t.judge.PCType) + } + } else { + t.Verdict = VerdictJuryFailed + t.Message = fmt.Sprintf("unknown outcome: %v, result: %v", t.judge.Outcome, t.judge.Result) + } + + return t +} + +func (s *service) checkResults(user string, config *Config) (JudgeStatus, int32) { + // CE will be processed in phase compile + + pts := map[int]int32{} + for _, task := range config.Tasks { + pts[task.Id] = task.Points + } + + var results []TaskStatus + dir := filepath.Join(UserDir, fmt.Sprintf("%s", user)) + var sum int32 = 0 + + for i := 1; i <= len(config.Tasks); i++ { + result := TaskStatus{Id: i, Verdict: VerdictAccepted, Points: 0} + + info := filepath.Join(dir, fmt.Sprintf("%d.info", i)) + judge := filepath.Join(dir, fmt.Sprintf("%d.judge", i)) + + result.getInfoText(info). + getInfo(). + checkExit(). + checkTime(config). + checkMemory(config). + getJudgeText(judge). + getJudge(). + checkJudge(&pts) + + sum += result.Points + } + + return JudgeStatus{Message: "", Tasks: results}, sum +} diff --git a/internal/service/status/create.go b/internal/service/status/create.go new file mode 100644 index 0000000..ad8093f --- /dev/null +++ b/internal/service/status/create.go @@ -0,0 +1,33 @@ +package status + +import ( + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/internal/model" + "github.com/jackc/pgtype" + "go.uber.org/zap" +) + +type CreateData struct { + SubmissionID uint + ProblemVersionID uint + Context pgtype.JSON + Point int32 +} + +func (s service) Create(data *model.Status) (*model.Status, e.Status) { + status := &model.Status{ + SubmissionID: data.SubmissionID, + ProblemVersionID: data.ProblemVersionID, + Context: data.Context, + Point: data.Point, + IsEnabled: true, + } + + err := s.db.Create(status).Error + if err != nil { + s.log.Warn("DatabaseError", zap.Error(err), zap.Any("status", status)) + return nil, e.DatabaseError + } + + return status, e.Success +} diff --git a/internal/service/status/query.go b/internal/service/status/query.go new file mode 100644 index 0000000..dd474b5 --- /dev/null +++ b/internal/service/status/query.go @@ -0,0 +1,56 @@ +package status + +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" +) + +func (s service) Query(sid uint, associations bool) (*model.Status, e.Status) { + + status := &model.Status{ + SubmissionID: sid, + IsEnabled: true, + } + + query := s.db + if associations { + query = query.Preload(clause.Associations) + } + + err := query. + Where(status). + Last(&status).Error + + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, e.StatusNotFound + } + + if err != nil { + s.log.Warn("DatabaseError", zap.Error(err), zap.Any("status", status)) + return nil, e.DatabaseError + } + return status, e.Success +} + +func (s service) QueryByVersion(pvid uint, offset int, limit int) ([]*model.Status, e.Status) { + var statuses []*model.Status + status := &model.Status{ + ProblemVersionID: pvid, + IsEnabled: true, + } + + err := s.db.Preload(clause.Associations). + Where(status). + Limit(limit). + Offset(offset). + Find(&statuses).Error + if err != nil { + s.log.Warn("DatabaseError", zap.Error(err), zap.Any("status", status)) + return nil, e.DatabaseError + } + return statuses, e.Success +} diff --git a/internal/service/status/rejudge.go b/internal/service/status/rejudge.go new file mode 100644 index 0000000..919b0db --- /dev/null +++ b/internal/service/status/rejudge.go @@ -0,0 +1,11 @@ +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") +} diff --git a/internal/service/status/service.go b/internal/service/status/service.go new file mode 100644 index 0000000..221b83f --- /dev/null +++ b/internal/service/status/service.go @@ -0,0 +1,30 @@ +package status + +import ( + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/internal/global" + "github.com/WHUPRJ/woj-server/internal/model" + "go.uber.org/zap" + "gorm.io/gorm" +) + +var _ Service = (*service)(nil) + +type Service interface { + Create(*model.Status) (*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 { + log *zap.Logger + db *gorm.DB +} + +func NewService(g *global.Global) Service { + return &service{ + log: g.Log, + db: g.Db.Get().(*gorm.DB), + } +} diff --git a/internal/service/submission/create.go b/internal/service/submission/create.go new file mode 100644 index 0000000..2c919d6 --- /dev/null +++ b/internal/service/submission/create.go @@ -0,0 +1,31 @@ +package submission + +import ( + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/internal/model" + "go.uber.org/zap" +) + +type CreateData struct { + ProblemID uint + UserID uint + Language string + Code string +} + +func (s *service) Create(data *CreateData) (*model.Submission, e.Status) { + submission := &model.Submission{ + ProblemID: data.ProblemID, + UserID: data.UserID, + Language: data.Language, + Code: data.Code, + } + + err := s.db.Create(submission).Error + if err != nil { + s.log.Warn("DatabaseError", zap.Error(err), zap.Any("submission", submission)) + return nil, e.DatabaseError + } + + return submission, e.Success +} diff --git a/internal/service/submission/query.go b/internal/service/submission/query.go new file mode 100644 index 0000000..1a3838b --- /dev/null +++ b/internal/service/submission/query.go @@ -0,0 +1,33 @@ +package submission + +import ( + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/internal/model" + "go.uber.org/zap" + "gorm.io/gorm/clause" +) + +func (s *service) Query(pid uint, uid uint, offset int, limit int) ([]*model.Submission, e.Status) { + submissions := make([]*model.Submission, 0) + + submission := &model.Submission{ + ProblemID: pid, + UserID: uid, + } + + err := s.db.Preload(clause.Associations). + Where(submission). + Limit(limit). + Offset(offset). + Find(&submissions).Error + + //if errors.Is(err, gorm.ErrRecordNotFound) { + // return nil, e.ProblemNotFound + //} + + if err != nil { + s.log.Warn("DatabaseError", zap.Error(err), zap.Any("pid", pid), zap.Any("uid", uid)) + return nil, e.DatabaseError + } + return submissions, e.Success +} diff --git a/internal/service/submission/service.go b/internal/service/submission/service.go new file mode 100644 index 0000000..4c8de37 --- /dev/null +++ b/internal/service/submission/service.go @@ -0,0 +1,28 @@ +package submission + +import ( + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/internal/global" + "github.com/WHUPRJ/woj-server/internal/model" + "go.uber.org/zap" + "gorm.io/gorm" +) + +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) +} + +type service struct { + log *zap.Logger + db *gorm.DB +} + +func NewService(g *global.Global) Service { + return &service{ + log: g.Log, + db: g.Db.Get().(*gorm.DB), + } +} diff --git a/internal/service/task/common.go b/internal/service/task/common.go index 3629330..b626d6b 100644 --- a/internal/service/task/common.go +++ b/internal/service/task/common.go @@ -6,10 +6,10 @@ import ( "go.uber.org/zap" ) -func (s *service) submit(typename string, payload []byte) (*asynq.TaskInfo, e.Status) { +func (s *service) submit(typename string, payload []byte, queue string) (*asynq.TaskInfo, e.Status) { task := asynq.NewTask(typename, payload) - info, err := s.queue.Enqueue(task) + info, err := s.queue.Enqueue(task, asynq.Queue(queue)) if err != nil { s.log.Warn("failed to enqueue task", zap.Error(err), zap.Any("task", task)) return nil, e.TaskEnqueueFailed @@ -20,8 +20,8 @@ func (s *service) submit(typename string, payload []byte) (*asynq.TaskInfo, e.St return info, e.Success } -func (s *service) GetTaskInfo(id string) (*asynq.TaskInfo, e.Status) { - task, err := s.inspector.GetTaskInfo("default", id) +func (s *service) GetTaskInfo(id string, queue string) (*asynq.TaskInfo, e.Status) { + task, err := s.inspector.GetTaskInfo(queue, id) if err != nil { s.log.Debug("get task info failed", zap.Error(err), zap.String("id", id)) return nil, e.TaskGetInfoFailed diff --git a/internal/service/task/judge.go b/internal/service/task/judge.go deleted file mode 100644 index 057c150..0000000 --- a/internal/service/task/judge.go +++ /dev/null @@ -1,20 +0,0 @@ -package task - -import ( - "encoding/json" - "github.com/WHUPRJ/woj-server/internal/e" - "github.com/WHUPRJ/woj-server/internal/model" - "go.uber.org/zap" -) - -func (s *service) NewJudge(submission model.Submission) (string, e.Status) { - payload, err := json.Marshal(model.SubmitJudge{Submission: submission}) - if err != nil { - s.log.Warn("json marshal error", zap.Error(err), zap.Any("payload", submission)) - return "", e.InternalError - } - - info, status := s.submit(model.TypeSubmitJudge, payload) - - return info.ID, status -} diff --git a/internal/service/task/problem.go b/internal/service/task/problem.go new file mode 100644 index 0000000..818934a --- /dev/null +++ b/internal/service/task/problem.go @@ -0,0 +1,46 @@ +package task + +import ( + "encoding/json" + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/internal/model" + "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, + }) + if err != nil { + s.log.Warn("json marshal error", + zap.Error(err), + zap.Any("ProblemVersionID", pvId), + zap.String("ProblemFile", file)) + return "", e.InternalError + } + + info, status := s.submit(model.TypeProblemBuild, payload, model.QueueRunner) + + 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, + }) + if err != nil { + s.log.Warn("json marshal error", + zap.Error(err), + zap.Any("Status", status), + zap.Any("ProblemVersionID", pvId), + zap.Any("Context", ctx)) + return "", e.InternalError + } + + info, status := s.submit(model.TypeProblemUpdate, payload, model.QueueServer) + + return info.ID, status +} diff --git a/internal/service/task/push.go b/internal/service/task/push.go deleted file mode 100644 index e252789..0000000 --- a/internal/service/task/push.go +++ /dev/null @@ -1,23 +0,0 @@ -package task - -import ( - "encoding/json" - "github.com/WHUPRJ/woj-server/internal/e" - "github.com/WHUPRJ/woj-server/internal/model" - "go.uber.org/zap" -) - -func (s *service) PushProblem(id uint, file string) (string, e.Status) { - payload, err := json.Marshal(model.ProblemPushPayload{ - ProblemID: id, - ProblemFile: file, - }) - if err != nil { - s.log.Warn("json marshal error", zap.Error(err), zap.Any("id", id), zap.String("file", file)) - return "", e.InternalError - } - - info, status := s.submit(model.TypeSubmitJudge, payload) - - return info.ID, status -} diff --git a/internal/service/task/service.go b/internal/service/task/service.go index 66d48ff..af64354 100644 --- a/internal/service/task/service.go +++ b/internal/service/task/service.go @@ -4,6 +4,7 @@ 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/runner" "github.com/hibiken/asynq" "go.uber.org/zap" ) @@ -11,10 +12,12 @@ import ( var _ Service = (*service)(nil) type Service interface { - NewJudge(submission model.Submission) (string, e.Status) - PushProblem(id uint, file string) (string, e.Status) - GetTaskInfo(id string) (*asynq.TaskInfo, e.Status) - submit(typename string, payload []byte) (*asynq.TaskInfo, e.Status) + 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) + + GetTaskInfo(string, string) (*asynq.TaskInfo, e.Status) } type service struct { diff --git a/internal/service/task/submit.go b/internal/service/task/submit.go new file mode 100644 index 0000000..4c3e0e7 --- /dev/null +++ b/internal/service/task/submit.go @@ -0,0 +1,55 @@ +package task + +import ( + "encoding/json" + "github.com/WHUPRJ/woj-server/internal/e" + "github.com/WHUPRJ/woj-server/internal/model" + "github.com/WHUPRJ/woj-server/internal/service/runner" + "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, + }) + if err != nil { + s.log.Warn("json marshal error", zap.Error(err), zap.Any("Submission", submission)) + return "", e.InternalError + } + + info, status := s.submit(model.TypeSubmitJudge, payload, model.QueueRunner) + + return info.ID, status +} + +func (s *service) SubmitUpdate(status e.Status, sid uint, point int32, ctx runner.JudgeStatus) (string, e.Status) { + ctxText, err := json.Marshal(ctx) + if err != nil { + s.log.Warn("json marshal error", + zap.Error(err), + zap.Any("ctx", ctx)) + return "", e.InternalError + } + + payload, err := json.Marshal(model.SubmitUpdatePayload{ + Status: status, + Sid: sid, + Point: point, + Context: string(ctxText), + }) + if err != nil { + s.log.Warn("json marshal error", + zap.Error(err), + zap.Any("Status", status), + zap.Int32("Point", point), + zap.Any("Context", ctx)) + return "", e.InternalError + } + + info, status := s.submit(model.TypeSubmitUpdate, payload, model.QueueServer) + + return info.ID, status +} diff --git a/internal/service/user/create.go b/internal/service/user/create.go index faf5e16..f91cd53 100644 --- a/internal/service/user/create.go +++ b/internal/service/user/create.go @@ -9,31 +9,32 @@ import ( ) type CreateData struct { - Username string - Nickname string + UserName string Password string + NickName string } func (s *service) Create(data *CreateData) (*model.User, e.Status) { hashed, err := bcrypt.GenerateFromPassword([]byte(data.Password), bcrypt.DefaultCost) if err != nil { - s.log.Debug("bcrypt error", zap.Error(err), zap.String("password", data.Password)) + s.log.Warn("BcryptError", zap.Error(err), zap.String("password", data.Password)) return nil, e.InternalError } user := &model.User{ - UserName: data.Username, + UserName: data.UserName, Password: hashed, - NickName: data.Nickname, + NickName: data.NickName, Role: model.RoleGeneral, IsEnabled: true, } - if err := s.db.Create(user).Error; err != nil { - if strings.Contains(err.Error(), "duplicate key") { - return nil, e.UserDuplicated - } - s.log.Debug("create user error", zap.Error(err), zap.Any("data", data)) + err = s.db.Create(user).Error + if err != nil && strings.Contains(err.Error(), "duplicate key") { + return nil, e.UserDuplicated + } + if err != nil { + s.log.Warn("DatabaseError", zap.Error(err), zap.Any("user", user)) return nil, e.DatabaseError } return user, e.Success diff --git a/internal/service/user/login.go b/internal/service/user/login.go index 77824e5..d80acbd 100644 --- a/internal/service/user/login.go +++ b/internal/service/user/login.go @@ -4,11 +4,17 @@ import ( "errors" "github.com/WHUPRJ/woj-server/internal/e" "github.com/WHUPRJ/woj-server/internal/model" + "go.uber.org/zap" "golang.org/x/crypto/bcrypt" "gorm.io/gorm" ) -func (s *service) Login(data *model.User) (*model.User, e.Status) { +type LoginData struct { + UserName string + Password string +} + +func (s *service) Login(data *LoginData) (*model.User, e.Status) { user := &model.User{UserName: data.UserName} err := s.db.Where(user).First(&user).Error @@ -16,6 +22,7 @@ func (s *service) Login(data *model.User) (*model.User, e.Status) { return nil, e.UserNotFound } if err != nil { + s.log.Warn("DatabaseError", zap.Error(err), zap.Any("user", user)) return nil, e.DatabaseError } @@ -23,7 +30,7 @@ func (s *service) Login(data *model.User) (*model.User, e.Status) { return nil, e.UserDisabled } - err = bcrypt.CompareHashAndPassword(user.Password, data.Password) + err = bcrypt.CompareHashAndPassword(user.Password, []byte(data.Password)) if err != nil { return nil, e.UserWrongPassword } diff --git a/internal/service/user/profile.go b/internal/service/user/profile.go index 4705f31..7cd706c 100644 --- a/internal/service/user/profile.go +++ b/internal/service/user/profile.go @@ -4,17 +4,19 @@ import ( "errors" "github.com/WHUPRJ/woj-server/internal/e" "github.com/WHUPRJ/woj-server/internal/model" + "go.uber.org/zap" "gorm.io/gorm" ) -func (s *service) Profile(id uint) (*model.User, e.Status) { +func (s *service) Profile(uid uint) (*model.User, e.Status) { user := new(model.User) - err := s.db.First(&user, id).Error + err := s.db.First(&user, uid).Error if errors.Is(err, gorm.ErrRecordNotFound) { return nil, e.UserNotFound } if err != nil { + s.log.Warn("DatabaseError", zap.Error(err), zap.Any("uid", uid)) return nil, e.DatabaseError } diff --git a/internal/service/user/service.go b/internal/service/user/service.go index e5e398a..4baeffd 100644 --- a/internal/service/user/service.go +++ b/internal/service/user/service.go @@ -13,9 +13,9 @@ var _ Service = (*service)(nil) type Service interface { Create(data *CreateData) (*model.User, e.Status) - Login(data *model.User) (*model.User, e.Status) - IncrVersion(id uint) (int64, e.Status) - Profile(id uint) (*model.User, e.Status) + Login(data *LoginData) (*model.User, e.Status) + IncrVersion(uid uint) (int64, e.Status) + Profile(uid uint) (*model.User, e.Status) } type service struct { diff --git a/internal/service/user/version.go b/internal/service/user/version.go index a342668..8303af8 100644 --- a/internal/service/user/version.go +++ b/internal/service/user/version.go @@ -7,11 +7,12 @@ import ( "go.uber.org/zap" ) -func (s *service) IncrVersion(id uint) (int64, e.Status) { - version, err := s.redis.Incr(context.Background(), fmt.Sprintf("Version:%d", id)).Result() +func (s *service) IncrVersion(uid uint) (int64, e.Status) { + version, err := s.redis.Incr(context.Background(), fmt.Sprintf("Version:%d", uid)).Result() if err != nil { - s.log.Debug("redis.Incr error", zap.Error(err)) + s.log.Warn("RedisError", zap.Error(err), zap.Any("uid", uid)) return -1, e.RedisError } + return version, e.Success } diff --git a/pkg/down/down.go b/pkg/down/down.go new file mode 100644 index 0000000..d4b30b9 --- /dev/null +++ b/pkg/down/down.go @@ -0,0 +1,55 @@ +package down + +import ( + "fmt" + "github.com/WHUPRJ/woj-server/pkg/utils" + "io" + "net/http" + "os" + "path/filepath" +) + +func Down(dest string, url string) error { + dir := filepath.Dir(dest) + err := os.MkdirAll(dir, 0755) + if err != nil { + return err + } + + tmp := fmt.Sprintf("%s.%s", dest, utils.RandomString(5)) + f, err := os.Create(tmp) + if err != nil { + return err + } + + resp, err := http.Get(url) + if err != nil { + return err + } + defer func(Body io.ReadCloser) { + _ = Body.Close() + }(resp.Body) + + if resp.StatusCode != http.StatusOK { + _ = os.Remove(tmp) + return fmt.Errorf("bad status %s when accessing %s", resp.Status, url) + } + + _, err = io.Copy(f, resp.Body) + if err != nil { + return err + } + + err = f.Close() + if err != nil { + return err + } + + err = os.Rename(tmp, dest) + if err != nil { + _ = os.Remove(dest) + return err + } + + return nil +} diff --git a/pkg/unzip/unzip.go b/pkg/unzip/unzip.go new file mode 100644 index 0000000..98bac57 --- /dev/null +++ b/pkg/unzip/unzip.go @@ -0,0 +1,60 @@ +package unzip + +import ( + "archive/zip" + "io" + "os" + "path/filepath" +) + +func Unzip(src, dest string) error { + r, err := zip.OpenReader(src) + if err != nil { + return err + } + defer func(r *zip.ReadCloser) { + _ = r.Close() + }(r) + + handler := func(f *zip.File, dest string) error { + rc, err := f.Open() + if err != nil { + return err + } + defer func(rc io.ReadCloser) { + _ = rc.Close() + }(rc) + + path := filepath.Join(dest, f.Name) + if f.FileInfo().IsDir() { + _ = os.MkdirAll(path, 0755) + } else { + _ = os.MkdirAll(filepath.Dir(path), 0755) + + file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + return err + } + defer func(file *os.File) { + _ = file.Close() + }(file) + + _, err = io.Copy(file, rc) + if err != nil { + return err + } + } + + return nil + } + + for _, f := range r.File { + err := handler(f, dest) + if err != nil { + _ = os.RemoveAll(dest) + return err + } + } + + return nil +} diff --git a/pkg/utils/file.go b/pkg/utils/file.go index c05458c..5857aac 100644 --- a/pkg/utils/file.go +++ b/pkg/utils/file.go @@ -3,6 +3,7 @@ package utils import ( "io" "os" + "path/filepath" ) func FileRead(filePath string) ([]byte, error) { @@ -23,6 +24,8 @@ func FileExist(filePath string) bool { } func FileTouch(filePath string) bool { + base := filepath.Dir(filePath) + _ = os.MkdirAll(base, 0755) _, err := os.OpenFile(filePath, os.O_RDONLY|os.O_CREATE, 0644) return If(err == nil, true, false).(bool) } diff --git a/pkg/zapasynq/logger.go b/pkg/zapasynq/logger.go new file mode 100644 index 0000000..f23f8b3 --- /dev/null +++ b/pkg/zapasynq/logger.go @@ -0,0 +1,25 @@ +package zapasynq + +import ( + "go.uber.org/zap" +) + +type Logger struct { + logger *zap.Logger +} + +func New(zapLogger *zap.Logger) Logger { + return Logger{ + logger: zapLogger, + } +} + +func (l Logger) Debug(args ...interface{}) { l.logger.Sugar().Debugf(args[0].(string), args[1:]...) } + +func (l Logger) Info(args ...interface{}) { l.logger.Sugar().Infof(args[0].(string), args[1:]...) } + +func (l Logger) Warn(args ...interface{}) { l.logger.Sugar().Warnf(args[0].(string), args[1:]...) } + +func (l Logger) Error(args ...interface{}) { l.logger.Sugar().Errorf(args[0].(string), args[1:]...) } + +func (l Logger) Fatal(args ...interface{}) { l.logger.Sugar().Fatalf(args[0].(string), args[1:]...) } diff --git a/resource/runner/framework/scripts/setup.sh b/resource/runner/framework/scripts/setup.sh index 5b60d8b..b93b328 100755 --- a/resource/runner/framework/scripts/setup.sh +++ b/resource/runner/framework/scripts/setup.sh @@ -5,7 +5,7 @@ rm -rf woj-sandbox git clone https://github.com/WHUPRJ/woj-sandbox.git >/dev/null 2>&1 || exit 1 cd woj-sandbox && ./build_libseccomp.sh || exit 1 -mkdir -p build && cd build +mkdir -p build && cd build || exit 1 cmake .. -DCMAKE_BUILD_TYPE=Release || exit 1 make -j || exit 1 diff --git a/resource/runner/framework/template/default/c.Makefile b/resource/runner/framework/template/default/c.Makefile index 4f22696..25be642 100644 --- a/resource/runner/framework/template/default/c.Makefile +++ b/resource/runner/framework/template/default/c.Makefile @@ -1,7 +1,7 @@ include ${TEMPLATE}/c.mk ${TEMPLATE}/Judger.mk compile: - $(CC) $(CFLAGS) -o $(PREFIX)/user/$(USER_PROG).out $(PREFIX)/user/$(USER_PROG).$(LANG) + @$(CC) $(CFLAGS) -o $(PREFIX)/user/$(USER_PROG).out $(PREFIX)/user/$(USER_PROG).$(LANG) judge: - $($(CMP)) $(PREFIX)/data/input/$(TEST_NUM).input $(PREFIX)/user/$(TEST_NUM).out.usr $(PREFIX)/data/output/$(TEST_NUM).output > $(PREFIX)/user/$(TEST_NUM).judge 2>&1 + $($(CMP)) $(PREFIX)/data/input/$(TEST_NUM).input $(PREFIX)/user/$(TEST_NUM).out.usr $(PREFIX)/data/output/$(TEST_NUM).output $(PREFIX)/user/$(TEST_NUM).judge -appes diff --git a/resource/runner/framework/template/default/cpp.Makefile b/resource/runner/framework/template/default/cpp.Makefile index bcb89be..ea648d2 100644 --- a/resource/runner/framework/template/default/cpp.Makefile +++ b/resource/runner/framework/template/default/cpp.Makefile @@ -1,7 +1,7 @@ include ${TEMPLATE}/cpp.mk ${TEMPLATE}/Judger.mk compile: - $(CXX) $(CFLAGS) -o $(PREFIX)/user/$(USER_PROG).out $(PREFIX)/user/$(USER_PROG).$(LANG) + @$(CXX) $(CFLAGS) -o $(PREFIX)/user/$(USER_PROG).out $(PREFIX)/user/$(USER_PROG).$(LANG) judge: - $($(CMP)) $(PREFIX)/data/input/$(TEST_NUM).input $(PREFIX)/user/$(TEST_NUM).out.usr $(PREFIX)/data/output/$(TEST_NUM).output > $(PREFIX)/user/$(TEST_NUM).judge 2>&1 + $($(CMP)) $(PREFIX)/data/input/$(TEST_NUM).input $(PREFIX)/user/$(TEST_NUM).out.usr $(PREFIX)/data/output/$(TEST_NUM).output $(PREFIX)/user/$(TEST_NUM).judge -appes diff --git a/resource/runner/framework/template/setup.sh b/resource/runner/framework/template/setup.sh index 5576343..59c117f 100755 --- a/resource/runner/framework/template/setup.sh +++ b/resource/runner/framework/template/setup.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -x rm -rf testlib -git clone https://github.com/MikeMirzayanov/testlib.git >/dev/null 2>&1 || exit 1 +git clone --depth=1 https://github.com/MikeMirzayanov/testlib.git >/dev/null 2>&1 || exit 1 rm -rf testlib/.git rm -rf testlib/tests cd testlib/checkers || exit 1 diff --git a/resource/runner/problem/example/judge/XYZ.Makefile b/resource/runner/problem/example/judge/XYZ.Makefile index 32b8f1c..d15d183 100644 --- a/resource/runner/problem/example/judge/XYZ.Makefile +++ b/resource/runner/problem/example/judge/XYZ.Makefile @@ -19,4 +19,4 @@ compile: judge: # Rename on *.out.usr or *.judge is not allowed sed '/gadgets/d' $(PREFIX)/user/$(TEST_NUM).out.usr > $(PREFIX)/user/$(TEST_NUM).out.usr1 - $(NCMP) $(PREFIX)/data/input/$(TEST_NUM).input $(PREFIX)/user/$(TEST_NUM).out.usr1 $(PREFIX)/data/output/$(TEST_NUM).output > $(PREFIX)/user/$(TEST_NUM).judge 2>&1 + $(NCMP) $(PREFIX)/data/input/$(TEST_NUM).input $(PREFIX)/user/$(TEST_NUM).out.usr1 $(PREFIX)/data/output/$(TEST_NUM).output $(PREFIX)/user/$(TEST_NUM).judge -appes diff --git a/resource/runner/scripts/prepare_container.sh b/resource/runner/scripts/prepare_container.sh index af10035..e02c989 100755 --- a/resource/runner/scripts/prepare_container.sh +++ b/resource/runner/scripts/prepare_container.sh @@ -6,7 +6,7 @@ cd "$(dirname "$0")"/../ || exit 1 if [ -f ./.mark.docker ]; then log_warn "Docker containers already prepared" - log_warn "If you want to re-prepare the containers, please remove the file `pwd`/.mark.docker" + log_warn "If you want to re-prepare the containers, please remove the file $(pwd)/.mark.docker" exit 1 fi diff --git a/resource/runner/scripts/problem_compile.sh b/resource/runner/scripts/problem_compile.sh index 874dbb8..8f35ac6 100755 --- a/resource/runner/scripts/problem_compile.sh +++ b/resource/runner/scripts/problem_compile.sh @@ -14,7 +14,7 @@ get_problem_info "$WORKSPACE" "$1" "$3" SRC_FILE="$WORKSPACE"/user/"$2"/"$2"."$3" EXE_FILE="$WORKSPACE"/user/"$2"/"$2".out -LOG_FILE="$WORKSPACE"/user/"$2"/"$2".compile.log +export LOG_FILE="$WORKSPACE"/user/"$2"/"$2".compile.log rm -f "$EXE_FILE" && touch "$EXE_FILE" diff --git a/resource/runner/scripts/problem_judge.sh b/resource/runner/scripts/problem_judge.sh index 9a743aa..0142ee2 100755 --- a/resource/runner/scripts/problem_judge.sh +++ b/resource/runner/scripts/problem_judge.sh @@ -12,7 +12,7 @@ fi get_problem_info "$WORKSPACE" "$1" "$3" -TIMEOUT=${4:-60} +export TIMEOUT=${4:-60} for test_num in $(seq "$Info_Num"); do std_file="$WORKSPACE/problem/$1/data/output/$test_num.output" ans_file="$WORKSPACE/user/$2/$test_num.out.usr" diff --git a/resource/runner/scripts/problem_prebuild.sh b/resource/runner/scripts/problem_prebuild.sh index de3b45d..3b97164 100755 --- a/resource/runner/scripts/problem_prebuild.sh +++ b/resource/runner/scripts/problem_prebuild.sh @@ -21,7 +21,7 @@ if [ ! -f "$WORKSPACE/problem/$1/judge/prebuild.Makefile" ]; then exit 0 fi -TIMEOUT=${2:-300} +export TIMEOUT=${2:-300} docker_run \ -v "$WORKSPACE/problem/$1/data":/woj/problem/data \ -v "$WORKSPACE/problem/$1/judge":/woj/problem/judge \ From 26a81652b387671b7e3c8b38fc4232a96a8a3eaa Mon Sep 17 00:00:00 2001 From: Paul Pan Date: Sun, 23 Oct 2022 17:29:35 +0800 Subject: [PATCH 19/19] feat: another big update 1. add consumer 2. add createVersion 3. add upload 4. fix runner 5. add rejudge Co-authored-by: cxy004 Co-authored-by: wzt --- .gitignore | 1 + config.yaml | 7 ++ go.mod | 13 +++- go.sum | 28 +++++++- internal/api/consumer/handler.go | 36 ++++++++++ internal/api/consumer/problemUpdate.go | 40 +++++++++++ internal/api/consumer/submitUpdate.go | 37 ++++++++++ internal/api/problem/createVersion.go | 67 +++++++++++++++++ internal/api/problem/details.go | 8 ++- internal/api/problem/handler.go | 11 ++- internal/api/problem/search.go | 8 +-- internal/api/problem/upload.go | 44 ++++++++++++ internal/api/runner/build.go | 36 +++++++--- internal/api/runner/handler.go | 15 ++-- internal/api/runner/judge.go | 88 ++++++++++++----------- internal/api/status/query.go | 11 ++- internal/api/status/queryByVersion.go | 17 +++-- internal/api/submission/create.go | 19 ++++- internal/api/submission/handler.go | 2 + internal/api/submission/query.go | 14 ++-- internal/api/submission/rejudge.go | 63 ++++++++++++++++ internal/api/user/create.go | 1 - internal/api/user/logout.go | 1 - internal/api/user/profile.go | 4 +- internal/app/server/server.go | 9 ++- internal/e/code.go | 11 +++ internal/e/resp.go | 9 +++ internal/global/config.go | 9 +++ internal/model/Task.go | 13 ++-- internal/repo/postgresql/postgresql.go | 2 + internal/service/problem/createVersion.go | 2 + internal/service/problem/service.go | 2 +- internal/service/problem/updateVersion.go | 10 +-- internal/service/runner/common.go | 12 ++-- internal/service/runner/compile.go | 10 +-- internal/service/runner/newProblem.go | 19 +++-- internal/service/runner/service.go | 4 +- internal/service/runner/status.go | 5 +- internal/service/status/create.go | 13 ++-- internal/service/status/rejudge.go | 11 --- internal/service/status/service.go | 3 +- internal/service/storage/get.go | 30 ++++++++ internal/service/storage/service.go | 41 +++++++++++ internal/service/storage/upload.go | 28 ++++++++ internal/service/submission/query.go | 24 +++++++ internal/service/submission/service.go | 1 + internal/service/task/common.go | 2 - internal/service/task/problem.go | 24 +++---- internal/service/task/service.go | 8 +-- internal/service/task/submit.go | 30 ++++---- pkg/utils/file.go | 8 +++ resource/runner/scripts/problem_run.sh | 2 +- 52 files changed, 729 insertions(+), 184 deletions(-) create mode 100644 internal/api/consumer/handler.go create mode 100644 internal/api/consumer/problemUpdate.go create mode 100644 internal/api/consumer/submitUpdate.go create mode 100644 internal/api/problem/createVersion.go create mode 100644 internal/api/problem/upload.go create mode 100644 internal/api/submission/rejudge.go delete mode 100644 internal/service/status/rejudge.go create mode 100644 internal/service/storage/get.go create mode 100644 internal/service/storage/service.go create mode 100644 internal/service/storage/upload.go diff --git a/.gitignore b/.gitignore index 75afa1a..8826357 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ ### Project +/tmp /server /runner my.secrets diff --git a/config.yaml b/config.yaml index 7ff81ec..206536b 100644 --- a/config.yaml +++ b/config.yaml @@ -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' diff --git a/go.mod b/go.mod index f3d872a..b42a8c2 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index d858d78..28e388a 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/internal/api/consumer/handler.go b/internal/api/consumer/handler.go new file mode 100644 index 0000000..3c8b840 --- /dev/null +++ b/internal/api/consumer/handler.go @@ -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 +} diff --git a/internal/api/consumer/problemUpdate.go b/internal/api/consumer/problemUpdate.go new file mode 100644 index 0000000..d5187d0 --- /dev/null +++ b/internal/api/consumer/problemUpdate.go @@ -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 +} diff --git a/internal/api/consumer/submitUpdate.go b/internal/api/consumer/submitUpdate.go new file mode 100644 index 0000000..8b9c64b --- /dev/null +++ b/internal/api/consumer/submitUpdate.go @@ -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 +} diff --git a/internal/api/problem/createVersion.go b/internal/api/problem/createVersion.go new file mode 100644 index 0000000..2456abc --- /dev/null +++ b/internal/api/problem/createVersion.go @@ -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) +} diff --git a/internal/api/problem/details.go b/internal/api/problem/details.go index f9c42b3..3df21e6 100644 --- a/internal/api/problem/details.go +++ b/internal/api/problem/details.go @@ -37,9 +37,11 @@ func (h *handler) Details(c *gin.Context) { } pv, status := h.problemService.QueryLatestVersion(req.Pid) - e.Pong(c, status, gin.H{ + if status != e.Success { + e.Pong(c, status, nil) + } + e.Pong(c, e.Success, gin.H{ "problem": p, - "context": pv.Context, + "context": pv.Context.Get(), }) - return } diff --git a/internal/api/problem/handler.go b/internal/api/problem/handler.go index 4e3a9a2..53bdda2 100644 --- a/internal/api/problem/handler.go +++ b/internal/api/problem/handler.go @@ -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) } diff --git a/internal/api/problem/search.go b/internal/api/problem/search.go index 4384647..9baeab1 100644 --- a/internal/api/problem/search.go +++ b/internal/api/problem/search.go @@ -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 } } diff --git a/internal/api/problem/upload.go b/internal/api/problem/upload.go new file mode 100644 index 0000000..a83edc3 --- /dev/null +++ b/internal/api/problem/upload.go @@ -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, + }) +} diff --git a/internal/api/runner/build.go b/internal/api/runner/build.go index 1f7f74a..58d9463 100644 --- a/internal/api/runner/build.go +++ b/internal/api/runner/build.go @@ -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,16 +19,32 @@ 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, "{}" + } - for i := range config.Languages { - config.Languages[i].Type = "" - config.Languages[i].Script = "" - config.Languages[i].Cmp = "" - } + config, status := h.runnerService.NewProblem(p.ProblemVersionID, url, true) + if status != e.Success { + return e.InternalError, "{}" + } - b, _ := json.Marshal(config) - h.taskService.ProblemUpdate(status, p.ProblemVersionID, string(b)) + for i := range config.Languages { + config.Languages[i].Type = "" + config.Languages[i].Script = "" + config.Languages[i].Cmp = "" + } + + b, _ := json.Marshal(config) + return e.Success, string(b) + }() + + h.taskService.ProblemUpdate(&model.ProblemUpdatePayload{ + Status: status, + ProblemVersionID: p.ProblemVersionID, + Context: ctx, + }) return nil } diff --git a/internal/api/runner/handler.go b/internal/api/runner/handler.go index d8ddbff..ad89428 100644 --- a/internal/api/runner/handler.go +++ b/internal/api/runner/handler.go @@ -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" @@ -19,16 +20,18 @@ type Handler interface { } type handler struct { - log *zap.Logger - runnerService runner.Service - taskService task.Service + log *zap.Logger + runnerService runner.Service + taskService task.Service + storageService storage.Service } func NewRunner(g *global.Global) (Handler, error) { hnd := &handler{ - log: g.Log, - runnerService: runner.NewService(g), - taskService: task.NewService(g), + log: g.Log, + runnerService: runner.NewService(g), + taskService: task.NewService(g), + storageService: storage.NewService(g), } status := hnd.runnerService.EnsureDeps(false) diff --git a/internal/api/runner/judge.go b/internal/api/runner/judge.go index 594af4d..1a8e563 100644 --- a/internal/api/runner/judge.go +++ b/internal/api/runner/judge.go @@ -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 - systemError := runner.JudgeStatus{Message: "System Error"} + status, point, ctx := func() (e.Status, int32, runner.JudgeStatus) { + systemError := runner.JudgeStatus{Message: "System Error"} - // write 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 - } - 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 - } - - // compile - result, status := h.runnerService.Compile(p.ProblemVersionId, user, p.Submission.Language) - if status == e.RunnerProblemNotExist { - _, status := h.runnerService.NewProblem(p.ProblemVersionId, p.StorageKey) - 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 + // 1. write user code + userCode := filepath.Join(runner.UserDir, user, fmt.Sprintf("%s.%s", user, p.Submission.Language)) + if !utils.FileTouch(userCode) { + return e.InternalError, 0, systemError + } + err := utils.FileWrite(userCode, []byte(p.Submission.Code)) + if err != nil { + return e.InternalError, 0, systemError } - } else if status != e.Success { - h.taskService.SubmitUpdate(status, p.Submission.ID, 0, result) - return nil - } - // 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 - } + // 2. check problem + if !h.runnerService.ProblemExists(p.ProblemVersionID) { + url, status := h.storageService.Get(p.StorageKey, time.Second*60*5) + if status != e.Success { + 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) + _, 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 { + return e.InternalError, 0, systemError + } + + // 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 } diff --git a/internal/api/status/query.go b/internal/api/status/query.go index 54b353d..13faf0f 100644 --- a/internal/api/status/query.go +++ b/internal/api/status/query.go @@ -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 } diff --git a/internal/api/status/queryByVersion.go b/internal/api/status/queryByVersion.go index d7e1728..3a738c0 100644 --- a/internal/api/status/queryByVersion.go +++ b/internal/api/status/queryByVersion.go @@ -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 } diff --git a/internal/api/submission/create.go b/internal/api/submission/create.go index a49fea4..5743285 100644 --- a/internal/api/submission/create.go +++ b/internal/api/submission/create.go @@ -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) + payload := &model.SubmitJudgePayload{ + ProblemVersionID: pv.ID, + StorageKey: pv.StorageKey, + Submission: *s, + } + _, status = h.taskService.SubmitJudge(payload) + e.Pong(c, status, nil) - return } diff --git a/internal/api/submission/handler.go b/internal/api/submission/handler.go index 9a0c9c3..a23aff5 100644 --- a/internal/api/submission/handler.go +++ b/internal/api/submission/handler.go @@ -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) } diff --git a/internal/api/submission/query.go b/internal/api/submission/query.go index 7259fdf..e3e6126 100644 --- a/internal/api/submission/query.go +++ b/internal/api/submission/query.go @@ -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) } diff --git a/internal/api/submission/rejudge.go b/internal/api/submission/rejudge.go new file mode 100644 index 0000000..7d87250 --- /dev/null +++ b/internal/api/submission/rejudge.go @@ -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) +} diff --git a/internal/api/user/create.go b/internal/api/user/create.go index f8c8fb0..ba224e1 100644 --- a/internal/api/user/create.go +++ b/internal/api/user/create.go @@ -55,5 +55,4 @@ func (h *handler) Create(c *gin.Context) { } token, status := h.jwtService.SignClaim(claim) e.Pong(c, status, token) - return } diff --git a/internal/api/user/logout.go b/internal/api/user/logout.go index c2554ec..f9a288c 100644 --- a/internal/api/user/logout.go +++ b/internal/api/user/logout.go @@ -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 } diff --git a/internal/api/user/profile.go b/internal/api/user/profile.go index 072ed3a..bc23f9e 100644 --- a/internal/api/user/profile.go +++ b/internal/api/user/profile.go @@ -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 } diff --git a/internal/app/server/server.go b/internal/app/server/server.go index ed7d4a9..c46b94a 100644 --- a/internal/app/server/server.go +++ b/internal/app/server/server.go @@ -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, diff --git a/internal/e/code.go b/internal/e/code.go index 9161c73..df0f552 100644 --- a/internal/e/code.go +++ b/internal/e/code.go @@ -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", } diff --git a/internal/e/resp.go b/internal/e/resp.go index b3a8e4c..4ea2ae5 100644 --- a/internal/e/resp.go +++ b/internal/e/resp.go @@ -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) + } +} diff --git a/internal/global/config.go b/internal/global/config.go index e478dee..0eaaa19 100644 --- a/internal/global/config.go +++ b/internal/global/config.go @@ -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"` } diff --git a/internal/model/Task.go b/internal/model/Task.go index ad2096a..fc8a529 100644 --- a/internal/model/Task.go +++ b/internal/model/Task.go @@ -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 - Point int32 - Context string + Status e.Status + SubmissionID uint + ProblemVersionID uint + Point int32 + Context string } diff --git a/internal/repo/postgresql/postgresql.go b/internal/repo/postgresql/postgresql.go index 15b0226..4a5ae90 100644 --- a/internal/repo/postgresql/postgresql.go +++ b/internal/repo/postgresql/postgresql.go @@ -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 diff --git a/internal/service/problem/createVersion.go b/internal/service/problem/createVersion.go index f355c6a..b63a9d0 100644 --- a/internal/service/problem/createVersion.go +++ b/internal/service/problem/createVersion.go @@ -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, } diff --git a/internal/service/problem/service.go b/internal/service/problem/service.go index 8e6df02..0dd05af 100644 --- a/internal/service/problem/service.go +++ b/internal/service/problem/service.go @@ -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) } diff --git a/internal/service/problem/updateVersion.go b/internal/service/problem/updateVersion.go index c5e360c..de8230c 100644 --- a/internal/service/problem/updateVersion.go +++ b/internal/service/problem/updateVersion.go @@ -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 } diff --git a/internal/service/runner/common.go b/internal/service/runner/common.go index 01260c7..24eba47 100644 --- a/internal/service/runner/common.go +++ b/internal/service/runner/common.go @@ -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) } diff --git a/internal/service/runner/compile.go b/internal/service/runner/compile.go index 2b0b270..62ad6c0 100644 --- a/internal/service/runner/compile.go +++ b/internal/service/runner/compile.go @@ -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 } diff --git a/internal/service/runner/newProblem.go b/internal/service/runner/newProblem.go index 6c9c91a..79f969f 100644 --- a/internal/service/runner/newProblem.go +++ b/internal/service/runner/newProblem.go @@ -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,10 +52,17 @@ func (s *service) prebuild(version uint, force bool) e.Status { return e.Success } -func (s *service) NewProblem(version uint, url string) (Config, e.Status) { - status := s.download(version, url) - if status != e.Success { - return Config{}, 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) @@ -63,7 +70,7 @@ func (s *service) NewProblem(version uint, url string) (Config, e.Status) { return Config{}, e.RunnerProblemParseFailed } - status = s.prebuild(version, true) + status := s.prebuild(version, true) if status != e.Success { return Config{}, status } diff --git a/internal/service/runner/service.go b/internal/service/runner/service.go index b4fd57b..d9322ac 100644 --- a/internal/service/runner/service.go +++ b/internal/service/runner/service.go @@ -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 { diff --git a/internal/service/runner/status.go b/internal/service/runner/status.go index 8c9ddbe..6336b95 100644 --- a/internal/service/runner/status.go +++ b/internal/service/runner/status.go @@ -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 diff --git a/internal/service/status/create.go b/internal/service/status/create.go index ad8093f..4c863d6 100644 --- a/internal/service/status/create.go +++ b/internal/service/status/create.go @@ -10,17 +10,20 @@ 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, - Point: data.Point, - IsEnabled: true, + Context: pgtype.JSON{ + Bytes: []byte(data.Context), + Status: pgtype.Present, + }, + Point: data.Point, + IsEnabled: true, } err := s.db.Create(status).Error diff --git a/internal/service/status/rejudge.go b/internal/service/status/rejudge.go deleted file mode 100644 index 919b0db..0000000 --- a/internal/service/status/rejudge.go +++ /dev/null @@ -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") -} diff --git a/internal/service/status/service.go b/internal/service/status/service.go index 221b83f..70a95b8 100644 --- a/internal/service/status/service.go +++ b/internal/service/status/service.go @@ -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 { diff --git a/internal/service/storage/get.go b/internal/service/storage/get.go new file mode 100644 index 0000000..c65f3cd --- /dev/null +++ b/internal/service/storage/get.go @@ -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 +} diff --git a/internal/service/storage/service.go b/internal/service/storage/service.go new file mode 100644 index 0000000..633b336 --- /dev/null +++ b/internal/service/storage/service.go @@ -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, + } +} diff --git a/internal/service/storage/upload.go b/internal/service/storage/upload.go new file mode 100644 index 0000000..69e7627 --- /dev/null +++ b/internal/service/storage/upload.go @@ -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 +} diff --git a/internal/service/submission/query.go b/internal/service/submission/query.go index 1a3838b..7dc6e7f 100644 --- a/internal/service/submission/query.go +++ b/internal/service/submission/query.go @@ -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 +} diff --git a/internal/service/submission/service.go b/internal/service/submission/service.go index 4c8de37..b5e8095 100644 --- a/internal/service/submission/service.go +++ b/internal/service/submission/service.go @@ -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 { diff --git a/internal/service/task/common.go b/internal/service/task/common.go index b626d6b..e88844c 100644 --- a/internal/service/task/common.go +++ b/internal/service/task/common.go @@ -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 } diff --git a/internal/service/task/problem.go b/internal/service/task/problem.go index 818934a..327b003 100644 --- a/internal/service/task/problem.go +++ b/internal/service/task/problem.go @@ -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 } diff --git a/internal/service/task/service.go b/internal/service/task/service.go index af64354..f1931bd 100644 --- a/internal/service/task/service.go +++ b/internal/service/task/service.go @@ -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) } diff --git a/internal/service/task/submit.go b/internal/service/task/submit.go index 4c3e0e7..c588c6b 100644 --- a/internal/service/task/submit.go +++ b/internal/service/task/submit.go @@ -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 } diff --git a/pkg/utils/file.go b/pkg/utils/file.go index 5857aac..9ae7939 100644 --- a/pkg/utils/file.go +++ b/pkg/utils/file.go @@ -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) diff --git a/resource/runner/scripts/problem_run.sh b/resource/runner/scripts/problem_run.sh index 8168fd4..3fb4ae3 100755 --- a/resource/runner/scripts/problem_run.sh +++ b/resource/runner/scripts/problem_run.sh @@ -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