commit
72c89f2e43
15
.github/workflows/docker-image.yml
vendored
15
.github/workflows/docker-image.yml
vendored
@ -2,10 +2,10 @@ name: Docker Image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
branches: [ "master", "develop" ]
|
||||
tags: [ "v*" ]
|
||||
pull_request:
|
||||
branches: [ "master" ]
|
||||
branches: [ "master", "develop" ]
|
||||
|
||||
|
||||
jobs:
|
||||
@ -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 }}
|
||||
|
4
.github/workflows/golangci-lint.yml
vendored
4
.github/workflows/golangci-lint.yml
vendored
@ -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
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,5 +1,7 @@
|
||||
### Project
|
||||
/tmp
|
||||
/server
|
||||
/runner
|
||||
my.secrets
|
||||
|
||||
### JetBrains template
|
||||
|
11
Dockerfile.runner
Normal file
11
Dockerfile.runner
Normal file
@ -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"]
|
@ -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"]
|
31
Makefile
31
Makefile
@ -1,31 +1,36 @@
|
||||
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
|
||||
.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
|
||||
|
||||
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 ./...
|
||||
|
@ -1,69 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/WHUPRJ/woj-server/internal/app"
|
||||
"github.com/WHUPRJ/woj-server/internal/global"
|
||||
"github.com/urfave/cli/v2"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
a := &cli.App{
|
||||
Name: "OJ",
|
||||
Compiled: getBuildTime(),
|
||||
Version: Version,
|
||||
EnableBashCompletion: true,
|
||||
Authors: []*cli.Author{
|
||||
{
|
||||
Name: "Paul",
|
||||
Email: "i@0x7f.app",
|
||||
},
|
||||
},
|
||||
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: 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)
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
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
|
||||
}
|
75
cmd/common.go
Normal file
75
cmd/common.go
Normal file
@ -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
|
||||
}
|
34
cmd/runner/main.go
Normal file
34
cmd/runner/main.go
Normal file
@ -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)
|
||||
}
|
34
cmd/server/main.go
Normal file
34
cmd/server/main.go
Normal file
@ -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)
|
||||
}
|
21
config.yaml
21
config.yaml
@ -1,12 +1,33 @@
|
||||
WebServer:
|
||||
Address: 0.0.0.0
|
||||
Port: 8000
|
||||
JwtSigningKey: 'rq67SdQIRABhHq40'
|
||||
JwtExpireHour: 12
|
||||
|
||||
Redis:
|
||||
Db: 0
|
||||
QueueDb: 1
|
||||
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
|
||||
|
||||
Storage:
|
||||
Endpoint: '127.0.0.1:9000'
|
||||
UseSSL: false
|
||||
AccessKey: 'EHd5Zj56QrTivhFI'
|
||||
SecretKey: 'FUHy4RW1mn0Kbr5pibDZ6R2F9116FZKY'
|
||||
Bucket: 'woj'
|
||||
|
||||
Metrics:
|
||||
Namespace: 'OJ'
|
||||
Subsystem: 'server'
|
||||
|
40
go.mod
40
go.mod
@ -7,13 +7,23 @@ 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/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-20220722155217-630584e8d5aa
|
||||
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
|
||||
moul.io/zapgorm2 v1.1.3
|
||||
)
|
||||
|
||||
require (
|
||||
@ -23,6 +33,8 @@ 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/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
|
||||
@ -33,28 +45,46 @@ 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/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/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/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
|
||||
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/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.6.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // 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
|
||||
go.uber.org/multierr v1.7.0 // 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
|
||||
)
|
||||
|
210
go.sum
210
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,17 +62,30 @@ 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=
|
||||
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=
|
||||
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=
|
||||
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=
|
||||
@ -117,10 +132,17 @@ 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=
|
||||
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-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=
|
||||
@ -160,6 +182,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=
|
||||
@ -172,12 +195,69 @@ 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=
|
||||
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=
|
||||
@ -193,7 +273,14 @@ 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=
|
||||
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,21 +288,37 @@ 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=
|
||||
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=
|
||||
@ -226,6 +329,16 @@ 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=
|
||||
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
|
||||
@ -265,25 +378,44 @@ 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=
|
||||
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=
|
||||
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/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=
|
||||
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=
|
||||
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=
|
||||
@ -309,30 +441,53 @@ 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=
|
||||
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 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/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/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=
|
||||
@ -367,6 +522,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=
|
||||
@ -379,6 +535,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=
|
||||
@ -389,10 +546,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=
|
||||
@ -400,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=
|
||||
@ -417,13 +578,17 @@ 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=
|
||||
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 +596,12 @@ 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-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=
|
||||
@ -451,7 +621,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=
|
||||
@ -464,8 +636,11 @@ 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-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=
|
||||
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,12 +648,15 @@ 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=
|
||||
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=
|
||||
@ -487,14 +665,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 +684,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=
|
||||
@ -520,10 +703,13 @@ 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=
|
||||
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 +801,12 @@ 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/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=
|
||||
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 +821,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 +834,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=
|
||||
|
36
internal/api/consumer/handler.go
Normal file
36
internal/api/consumer/handler.go
Normal file
@ -0,0 +1,36 @@
|
||||
package consumer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/WHUPRJ/woj-server/internal/global"
|
||||
"github.com/WHUPRJ/woj-server/internal/service/problem"
|
||||
"github.com/WHUPRJ/woj-server/internal/service/status"
|
||||
"github.com/WHUPRJ/woj-server/internal/service/task"
|
||||
"github.com/hibiken/asynq"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var _ Handler = (*handler)(nil)
|
||||
|
||||
type Handler interface {
|
||||
ProblemUpdate(_ context.Context, t *asynq.Task) error
|
||||
SubmitUpdate(_ context.Context, t *asynq.Task) error
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
log *zap.Logger
|
||||
problemService problem.Service
|
||||
statusService status.Service
|
||||
taskService task.Service
|
||||
}
|
||||
|
||||
func NewConsumer(g *global.Global) Handler {
|
||||
hnd := &handler{
|
||||
log: g.Log,
|
||||
problemService: problem.NewService(g),
|
||||
statusService: status.NewService(g),
|
||||
taskService: task.NewService(g),
|
||||
}
|
||||
|
||||
return hnd
|
||||
}
|
40
internal/api/consumer/problemUpdate.go
Normal file
40
internal/api/consumer/problemUpdate.go
Normal file
@ -0,0 +1,40 @@
|
||||
package consumer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/WHUPRJ/woj-server/internal/e"
|
||||
"github.com/WHUPRJ/woj-server/internal/model"
|
||||
"github.com/hibiken/asynq"
|
||||
"github.com/jackc/pgtype"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func (h *handler) ProblemUpdate(_ context.Context, t *asynq.Task) error {
|
||||
p := new(model.ProblemUpdatePayload)
|
||||
if err := json.Unmarshal(t.Payload(), &p); err != nil {
|
||||
return fmt.Errorf("json.Unmarshal failed: %v: %w", err, asynq.SkipRetry)
|
||||
}
|
||||
|
||||
if p.Status != e.Success {
|
||||
h.log.Warn("RunnerError", zap.Any("payload", p))
|
||||
return nil
|
||||
}
|
||||
|
||||
status := h.problemService.UpdateVersion(
|
||||
p.ProblemVersionID,
|
||||
map[string]interface{}{
|
||||
"Context": pgtype.JSON{
|
||||
Bytes: []byte(p.Context),
|
||||
Status: pgtype.Present,
|
||||
},
|
||||
"IsEnabled": true,
|
||||
},
|
||||
)
|
||||
|
||||
if status != e.Success {
|
||||
return fmt.Errorf(status.String())
|
||||
}
|
||||
return nil
|
||||
}
|
37
internal/api/consumer/submitUpdate.go
Normal file
37
internal/api/consumer/submitUpdate.go
Normal file
@ -0,0 +1,37 @@
|
||||
package consumer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/WHUPRJ/woj-server/internal/e"
|
||||
"github.com/WHUPRJ/woj-server/internal/model"
|
||||
"github.com/WHUPRJ/woj-server/internal/service/status"
|
||||
"github.com/hibiken/asynq"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func (h *handler) SubmitUpdate(_ context.Context, t *asynq.Task) error {
|
||||
p := new(model.SubmitUpdatePayload)
|
||||
if err := json.Unmarshal(t.Payload(), &p); err != nil {
|
||||
return fmt.Errorf("json.Unmarshal failed: %v: %w", err, asynq.SkipRetry)
|
||||
}
|
||||
|
||||
if p.Status != e.Success {
|
||||
h.log.Warn("RunnerError", zap.Any("payload", p))
|
||||
return nil
|
||||
}
|
||||
|
||||
createData := &status.CreateData{
|
||||
SubmissionID: p.SubmissionID,
|
||||
ProblemVersionID: p.ProblemVersionID,
|
||||
Context: p.Context,
|
||||
Point: p.Point,
|
||||
}
|
||||
_, eStatus := h.statusService.Create(createData)
|
||||
|
||||
if eStatus != e.Success {
|
||||
return fmt.Errorf(eStatus.String())
|
||||
}
|
||||
return nil
|
||||
}
|
@ -7,7 +7,7 @@ import (
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// randomString godoc
|
||||
// randomString
|
||||
// @Summary random string
|
||||
// @Description generate random string with length = 32
|
||||
// @Tags debug
|
67
internal/api/problem/createVersion.go
Normal file
67
internal/api/problem/createVersion.go
Normal file
@ -0,0 +1,67 @@
|
||||
package problem
|
||||
|
||||
import (
|
||||
"github.com/WHUPRJ/woj-server/internal/e"
|
||||
"github.com/WHUPRJ/woj-server/internal/global"
|
||||
"github.com/WHUPRJ/woj-server/internal/model"
|
||||
"github.com/WHUPRJ/woj-server/internal/service/problem"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type createVersionRequest struct {
|
||||
ProblemID uint `form:"pid" binding:"required"`
|
||||
StorageKey string `form:"storage_key" binding:"required"`
|
||||
}
|
||||
|
||||
// CreateVersion
|
||||
// @Summary create a problem version
|
||||
// @Description create a problem version
|
||||
// @Accept application/x-www-form-urlencoded
|
||||
// @Produce json
|
||||
// @Param pid formData int true "problem id"
|
||||
// @Param storage_key formData string true "storage key"
|
||||
// @Response 200 {object} e.Response ""
|
||||
// @Security Authentication
|
||||
// @Router /v1/problem/create_version [post]
|
||||
func (h *handler) CreateVersion(c *gin.Context) {
|
||||
claim, exist := c.Get("claim")
|
||||
if !exist {
|
||||
e.Pong(c, e.UserUnauthenticated, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// uid := claim.(*global.Claim).UID
|
||||
|
||||
role := claim.(*global.Claim).Role
|
||||
req := new(createVersionRequest)
|
||||
|
||||
if err := c.ShouldBind(req); err != nil {
|
||||
e.Pong(c, e.InvalidParameter, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// guest can not submit
|
||||
if role < model.RoleAdmin {
|
||||
e.Pong(c, e.UserUnauthorized, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: check pid exist
|
||||
|
||||
createVersionData := &problem.CreateVersionData{
|
||||
ProblemID: req.ProblemID,
|
||||
StorageKey: req.StorageKey,
|
||||
}
|
||||
pv, status := h.problemService.CreateVersion(createVersionData)
|
||||
if status != e.Success {
|
||||
e.Pong(c, status, nil)
|
||||
return
|
||||
}
|
||||
|
||||
payload := &model.ProblemBuildPayload{
|
||||
ProblemVersionID: pv.ID,
|
||||
StorageKey: pv.StorageKey,
|
||||
}
|
||||
_, status = h.taskService.ProblemBuild(payload)
|
||||
e.Pong(c, status, nil)
|
||||
}
|
47
internal/api/problem/details.go
Normal file
47
internal/api/problem/details.go
Normal file
@ -0,0 +1,47 @@
|
||||
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)
|
||||
if status != e.Success {
|
||||
e.Pong(c, status, nil)
|
||||
}
|
||||
e.Pong(c, e.Success, gin.H{
|
||||
"problem": p,
|
||||
"context": pv.Context.Get(),
|
||||
})
|
||||
}
|
43
internal/api/problem/handler.go
Normal file
43
internal/api/problem/handler.go
Normal file
@ -0,0 +1,43 @@
|
||||
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"
|
||||
)
|
||||
|
||||
var _ Handler = (*handler)(nil)
|
||||
|
||||
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) {
|
||||
app := &handler{
|
||||
log: g.Log,
|
||||
jwtService: g.Jwt,
|
||||
problemService: problem.NewService(g),
|
||||
taskService: task.NewService(g),
|
||||
storageService: storage.NewService(g),
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
39
internal/api/problem/search.go
Normal file
39
internal/api/problem/search.go
Normal file
@ -0,0 +1,39 @@
|
||||
package problem
|
||||
|
||||
import (
|
||||
"github.com/WHUPRJ/woj-server/internal/e"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type searchRequest struct {
|
||||
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 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)
|
||||
|
||||
if err := c.ShouldBind(req); err != nil {
|
||||
e.Pong(c, e.InvalidParameter, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: pagination
|
||||
if req.Search == "" {
|
||||
// TODO: query without LIKE
|
||||
problems, status := h.problemService.QueryFuzz(req.Search, true, true)
|
||||
e.Pong(c, status, problems)
|
||||
return
|
||||
} else {
|
||||
problems, status := h.problemService.QueryFuzz(req.Search, true, true)
|
||||
e.Pong(c, status, problems)
|
||||
return
|
||||
}
|
||||
}
|
79
internal/api/problem/update.go
Normal file
79
internal/api/problem/update.go
Normal file
@ -0,0 +1,79 @@
|
||||
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 updateRequest struct {
|
||||
Pid uint `form:"pid"`
|
||||
Title string `form:"title" binding:"required"`
|
||||
Statement string `form:"statement" 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 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
|
||||
// @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
|
||||
}
|
||||
|
||||
if req.Pid == 0 {
|
||||
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 {
|
||||
p, status := h.problemService.Query(req.Pid, true, false)
|
||||
if status != e.Success {
|
||||
e.Pong(c, status, nil)
|
||||
return
|
||||
}
|
||||
if p.ProviderID != uid {
|
||||
e.Pong(c, e.UserUnauthorized, nil)
|
||||
return
|
||||
}
|
||||
|
||||
p.Title = req.Title
|
||||
p.Statement = req.Statement
|
||||
p.IsEnabled = req.IsEnabled
|
||||
|
||||
p, status = h.problemService.Update(p)
|
||||
e.Pong(c, status, p)
|
||||
return
|
||||
}
|
||||
}
|
44
internal/api/problem/upload.go
Normal file
44
internal/api/problem/upload.go
Normal file
@ -0,0 +1,44 @@
|
||||
package problem
|
||||
|
||||
import (
|
||||
"github.com/WHUPRJ/woj-server/internal/e"
|
||||
"github.com/WHUPRJ/woj-server/internal/global"
|
||||
"github.com/WHUPRJ/woj-server/internal/model"
|
||||
"github.com/WHUPRJ/woj-server/pkg/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Upload
|
||||
// @Summary get upload url
|
||||
// @Description get upload url
|
||||
// @Produce json
|
||||
// @Response 200 {object} e.Response "upload url and key"
|
||||
// @Security Authentication
|
||||
// @Router /v1/problem/upload [post]
|
||||
func (h *handler) Upload(c *gin.Context) {
|
||||
claim, exist := c.Get("claim")
|
||||
if !exist {
|
||||
e.Pong(c, e.UserUnauthenticated, nil)
|
||||
return
|
||||
}
|
||||
|
||||
role := claim.(*global.Claim).Role
|
||||
if role < model.RoleAdmin {
|
||||
e.Pong(c, e.UserUnauthorized, nil)
|
||||
return
|
||||
}
|
||||
|
||||
key := utils.RandomString(16)
|
||||
url, status := h.storageService.Upload(key, time.Second*60*60)
|
||||
|
||||
if status != e.Success {
|
||||
e.Pong(c, status, nil)
|
||||
return
|
||||
}
|
||||
|
||||
e.Pong(c, e.Success, gin.H{
|
||||
"key": key,
|
||||
"url": url,
|
||||
})
|
||||
}
|
50
internal/api/runner/build.go
Normal file
50
internal/api/runner/build.go
Normal file
@ -0,0 +1,50 @@
|
||||
package runner
|
||||
|
||||
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 {
|
||||
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))
|
||||
|
||||
status, ctx := func() (e.Status, string) {
|
||||
url, status := h.storageService.Get(p.StorageKey, time.Second*60*5)
|
||||
if status != e.Success {
|
||||
return e.InternalError, "{}"
|
||||
}
|
||||
|
||||
config, status := h.runnerService.NewProblem(p.ProblemVersionID, url, true)
|
||||
if status != e.Success {
|
||||
return e.InternalError, "{}"
|
||||
}
|
||||
|
||||
for i := range config.Languages {
|
||||
config.Languages[i].Type = ""
|
||||
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
|
||||
}
|
44
internal/api/runner/handler.go
Normal file
44
internal/api/runner/handler.go
Normal file
@ -0,0 +1,44 @@
|
||||
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/storage"
|
||||
"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
|
||||
storageService storage.Service
|
||||
}
|
||||
|
||||
func NewRunner(g *global.Global) (Handler, error) {
|
||||
hnd := &handler{
|
||||
log: g.Log,
|
||||
runnerService: runner.NewService(g),
|
||||
taskService: task.NewService(g),
|
||||
storageService: storage.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
|
||||
}
|
77
internal/api/runner/judge.go
Normal file
77
internal/api/runner/judge.go
Normal file
@ -0,0 +1,77 @@
|
||||
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"
|
||||
"time"
|
||||
)
|
||||
|
||||
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))
|
||||
|
||||
status, point, ctx := func() (e.Status, int32, runner.JudgeStatus) {
|
||||
systemError := runner.JudgeStatus{Message: "System Error"}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
_, 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
|
||||
}
|
32
internal/api/status/handler.go
Normal file
32
internal/api/status/handler.go
Normal file
@ -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)
|
||||
}
|
31
internal/api/status/query.go
Normal file
31
internal/api/status/query.go
Normal file
@ -0,0 +1,31 @@
|
||||
package status
|
||||
|
||||
import (
|
||||
"github.com/WHUPRJ/woj-server/internal/e"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type queryRequest struct {
|
||||
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)
|
||||
|
||||
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)
|
||||
}
|
51
internal/api/status/queryByVersion.go
Normal file
51
internal/api/status/queryByVersion.go
Normal file
@ -0,0 +1,51 @@
|
||||
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" binding:"required"`
|
||||
Offset int `form:"offset"`
|
||||
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")
|
||||
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, err.Error())
|
||||
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)
|
||||
}
|
77
internal/api/submission/create.go
Normal file
77
internal/api/submission/create.go
Normal file
@ -0,0 +1,77 @@
|
||||
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"
|
||||
)
|
||||
|
||||
type createRequest struct {
|
||||
Pid uint `form:"pid" binding:"required"`
|
||||
Language string `form:"language" binding:"required"`
|
||||
Code string `form:"code" 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
|
||||
|
||||
role := claim.(*global.Claim).Role
|
||||
req := new(createRequest)
|
||||
|
||||
if err := c.ShouldBind(req); err != nil {
|
||||
e.Pong(c, e.InvalidParameter, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// guest can not submit
|
||||
if role < model.RoleGeneral {
|
||||
e.Pong(c, e.UserUnauthorized, nil)
|
||||
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
|
||||
}
|
||||
|
||||
payload := &model.SubmitJudgePayload{
|
||||
ProblemVersionID: pv.ID,
|
||||
StorageKey: pv.StorageKey,
|
||||
Submission: *s,
|
||||
}
|
||||
_, status = h.taskService.SubmitJudge(payload)
|
||||
|
||||
e.Pong(c, status, nil)
|
||||
}
|
43
internal/api/submission/handler.go
Normal file
43
internal/api/submission/handler.go
Normal file
@ -0,0 +1,43 @@
|
||||
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)
|
||||
Rejudge(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)
|
||||
group.POST("/rejudge", app.jwtService.Handler(true), app.Rejudge)
|
||||
}
|
71
internal/api/submission/query.go
Normal file
71
internal/api/submission/query.go
Normal file
@ -0,0 +1,71 @@
|
||||
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" binding:"required"`
|
||||
}
|
||||
|
||||
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 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]
|
||||
|
||||
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, response)
|
||||
}
|
63
internal/api/submission/rejudge.go
Normal file
63
internal/api/submission/rejudge.go
Normal file
@ -0,0 +1,63 @@
|
||||
package submission
|
||||
|
||||
import (
|
||||
"github.com/WHUPRJ/woj-server/internal/e"
|
||||
"github.com/WHUPRJ/woj-server/internal/global"
|
||||
"github.com/WHUPRJ/woj-server/internal/model"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type rejudgeRequest struct {
|
||||
Sid uint `form:"sid" binding:"required"`
|
||||
}
|
||||
|
||||
// Rejudge
|
||||
// @Summary rejudge a submission
|
||||
// @Description rejudge a submission
|
||||
// @Accept application/x-www-form-urlencoded
|
||||
// @Produce json
|
||||
// @Param sid formData int true "submission id"
|
||||
// @Response 200 {object} e.Response ""
|
||||
// @Security Authentication
|
||||
// @Router /v1/submission/rejudge [post]
|
||||
func (h *handler) Rejudge(c *gin.Context) {
|
||||
claim, exist := c.Get("claim")
|
||||
if !exist {
|
||||
e.Pong(c, e.UserUnauthenticated, nil)
|
||||
return
|
||||
}
|
||||
|
||||
role := claim.(*global.Claim).Role
|
||||
req := new(rejudgeRequest)
|
||||
|
||||
if err := c.ShouldBind(req); err != nil {
|
||||
e.Pong(c, e.InvalidParameter, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// only admin can rejudge
|
||||
if role < model.RoleAdmin {
|
||||
e.Pong(c, e.UserUnauthorized, nil)
|
||||
return
|
||||
}
|
||||
|
||||
s, status := h.submissionService.QueryBySid(req.Sid, false)
|
||||
if status != e.Success {
|
||||
e.Pong(c, status, nil)
|
||||
return
|
||||
}
|
||||
|
||||
pv, status := h.problemService.QueryLatestVersion(s.ProblemID)
|
||||
if status != e.Success {
|
||||
e.Pong(c, status, nil)
|
||||
return
|
||||
}
|
||||
|
||||
_, status = h.taskService.SubmitJudge(&model.SubmitJudgePayload{
|
||||
ProblemVersionID: pv.ID,
|
||||
StorageKey: pv.StorageKey,
|
||||
Submission: *s,
|
||||
})
|
||||
|
||||
e.Pong(c, status, nil)
|
||||
}
|
58
internal/api/user/create.go
Normal file
58
internal/api/user/create.go
Normal file
@ -0,0 +1,58 @@
|
||||
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"
|
||||
)
|
||||
|
||||
type createRequest struct {
|
||||
UserName string `form:"username" binding:"required"`
|
||||
Password string `form:"password" binding:"required"`
|
||||
NickName string `form:"nickname" binding:"required"`
|
||||
}
|
||||
|
||||
// Create
|
||||
// @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 "jwt token"
|
||||
// @Router /v1/user/create [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
|
||||
}
|
||||
|
||||
createData := &user.CreateData{
|
||||
UserName: req.UserName,
|
||||
Password: req.Password,
|
||||
NickName: req.NickName,
|
||||
}
|
||||
u, status := h.userService.Create(createData)
|
||||
if status != e.Success {
|
||||
e.Pong(c, status, nil)
|
||||
return
|
||||
}
|
||||
|
||||
version, status := h.userService.IncrVersion(u.ID)
|
||||
if status != e.Success {
|
||||
e.Pong(c, status, nil)
|
||||
return
|
||||
}
|
||||
|
||||
claim := &global.Claim{
|
||||
UID: u.ID,
|
||||
Role: u.Role,
|
||||
Version: version,
|
||||
}
|
||||
token, status := h.jwtService.SignClaim(claim)
|
||||
e.Pong(c, status, token)
|
||||
}
|
36
internal/api/user/handler.go
Normal file
36
internal/api/user/handler.go
Normal file
@ -0,0 +1,36 @@
|
||||
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)
|
||||
Login(c *gin.Context)
|
||||
Logout(c *gin.Context)
|
||||
Profile(c *gin.Context)
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
log *zap.Logger
|
||||
jwtService global.JwtService
|
||||
userService user.Service
|
||||
}
|
||||
|
||||
func RouteRegister(g *global.Global, group *gin.RouterGroup) {
|
||||
app := &handler{
|
||||
log: g.Log,
|
||||
jwtService: g.Jwt,
|
||||
userService: user.NewService(g),
|
||||
}
|
||||
|
||||
group.POST("/create", app.Create)
|
||||
group.POST("/login", app.Login)
|
||||
group.POST("/logout", app.jwtService.Handler(true), app.Logout)
|
||||
group.POST("/profile", app.jwtService.Handler(true), app.Profile)
|
||||
}
|
59
internal/api/user/login.go
Normal file
59
internal/api/user/login.go
Normal file
@ -0,0 +1,59 @@
|
||||
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"
|
||||
)
|
||||
|
||||
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 "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, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// check password
|
||||
loginData := &user.LoginData{
|
||||
UserName: req.UserName,
|
||||
Password: req.Password,
|
||||
}
|
||||
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(u.ID)
|
||||
if status != e.Success {
|
||||
e.Pong(c, status, nil)
|
||||
return
|
||||
}
|
||||
claim := &global.Claim{
|
||||
UID: u.ID,
|
||||
Role: u.Role,
|
||||
Version: version,
|
||||
}
|
||||
token, status := h.jwtService.SignClaim(claim)
|
||||
e.Pong(c, status, gin.H{
|
||||
"token": token,
|
||||
"nickname": u.NickName,
|
||||
})
|
||||
}
|
26
internal/api/user/logout.go
Normal file
26
internal/api/user/logout.go
Normal file
@ -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
|
||||
}
|
||||
|
||||
_, status := h.userService.IncrVersion(claim.(*global.Claim).UID)
|
||||
e.Pong(c, status, nil)
|
||||
}
|
54
internal/api/user/profile.go
Normal file
54
internal/api/user/profile.go
Normal file
@ -0,0 +1,54 @@
|
||||
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/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 int 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 {
|
||||
e.Pong(c, e.InvalidParameter, nil)
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
// TODO: >= admin can see is_enable
|
||||
|
||||
e.Pong(c, status, user)
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/WHUPRJ/woj-server/internal/global"
|
||||
"github.com/WHUPRJ/woj-server/internal/router"
|
||||
"go.uber.org/zap"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Run(g *global.Global) error {
|
||||
routersInit := router.InitRouters(g)
|
||||
|
||||
addr := fmt.Sprintf("%s:%d", g.Conf.WebServer.Address, g.Conf.WebServer.Port)
|
||||
|
||||
server := &http.Server{
|
||||
Addr: addr,
|
||||
Handler: routersInit,
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
g.Log.Fatal("ListenAndServe 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 ...")
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
43
internal/app/runner/runner.go
Normal file
43
internal/app/runner/runner.go
Normal file
@ -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
|
||||
}
|
103
internal/app/server/server.go
Normal file
103
internal/app/server/server.go
Normal file
@ -0,0 +1,103 @@
|
||||
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"
|
||||
"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 RunServer(g *global.Global) error {
|
||||
// Setup Database
|
||||
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)
|
||||
|
||||
// Prepare Router
|
||||
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: routers,
|
||||
}
|
||||
|
||||
// Run Server
|
||||
go func() {
|
||||
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
g.Log.Fatal("ListenAndServe Failed", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
// Create Queue
|
||||
queueMux := asynq.NewServeMux()
|
||||
{
|
||||
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,
|
||||
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 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))
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
@ -1,16 +1,117 @@
|
||||
package e
|
||||
|
||||
const (
|
||||
Fallback Err = 0
|
||||
Success Err = 1
|
||||
InternalError Err = 100
|
||||
InvalidParameter Err = 101
|
||||
NotFound Err = 102
|
||||
Success Status = iota
|
||||
Unknown
|
||||
)
|
||||
|
||||
var msgText = map[Err]string{
|
||||
const (
|
||||
InternalError Status = 100 + iota
|
||||
InvalidParameter
|
||||
NotFound
|
||||
DatabaseError
|
||||
RedisError
|
||||
)
|
||||
|
||||
const (
|
||||
TokenUnknown Status = 200 + iota
|
||||
TokenEmpty
|
||||
TokenMalformed
|
||||
TokenTimeError
|
||||
TokenInvalid
|
||||
TokenSignError
|
||||
TokenRevoked
|
||||
)
|
||||
|
||||
const (
|
||||
UserNotFound Status = 300 + iota
|
||||
UserWrongPassword
|
||||
UserDuplicated
|
||||
UserUnauthenticated
|
||||
UserUnauthorized
|
||||
UserDisabled
|
||||
)
|
||||
|
||||
const (
|
||||
ProblemNotFound Status = 500 + iota
|
||||
ProblemNotAvailable
|
||||
ProblemVersionNotFound
|
||||
ProblemVersionNotAvailable
|
||||
SubmissionNotFound
|
||||
StatusNotFound
|
||||
)
|
||||
|
||||
const (
|
||||
TaskEnqueueFailed Status = 600 + iota
|
||||
TaskGetInfoFailed
|
||||
)
|
||||
|
||||
const (
|
||||
RunnerDepsBuildFailed Status = 700 + iota
|
||||
RunnerDownloadFailed
|
||||
RunnerUnzipFailed
|
||||
RunnerProblemNotExist
|
||||
RunnerProblemPrebuildFailed
|
||||
RunnerProblemParseFailed
|
||||
RunnerUserNotExist
|
||||
RunnerUserCompileFailed
|
||||
RunnerRunFailed
|
||||
RunnerJudgeFailed
|
||||
)
|
||||
|
||||
const (
|
||||
StorageUploadFailed Status = 800 + iota
|
||||
StorageGetFailed
|
||||
)
|
||||
|
||||
var msgText = map[Status]string{
|
||||
Success: "Success",
|
||||
Unknown: "Unknown error",
|
||||
|
||||
InternalError: "Internal Error",
|
||||
InvalidParameter: "Invalid Parameter",
|
||||
NotFound: "Not Found",
|
||||
DatabaseError: "Database Error",
|
||||
RedisError: "Redis 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",
|
||||
|
||||
UserNotFound: "User Not Found",
|
||||
UserWrongPassword: "User Wrong Password",
|
||||
UserDuplicated: "User Duplicated",
|
||||
UserUnauthenticated: "User Unauthenticated",
|
||||
UserUnauthorized: "User Unauthorized",
|
||||
UserDisabled: "User Disabled",
|
||||
|
||||
ProblemNotFound: "Problem Not Found",
|
||||
ProblemNotAvailable: "Problem Not Available",
|
||||
ProblemVersionNotFound: "Problem Version Not Found",
|
||||
ProblemVersionNotAvailable: "Problem Version Not Available",
|
||||
|
||||
SubmissionNotFound: "Submission Not Found",
|
||||
|
||||
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",
|
||||
|
||||
StorageUploadFailed: "Storage Upload Failed",
|
||||
StorageGetFailed: "Storage Get Failed",
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -12,14 +12,24 @@ 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.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)
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,39 @@
|
||||
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"`
|
||||
JwtSigningKey string `yaml:"JwtSigningKey"`
|
||||
JwtExpireHour int `yaml:"JwtExpireHour"`
|
||||
}
|
||||
|
||||
type ConfigRedis struct {
|
||||
Db int `yaml:"Db"`
|
||||
QueueDb int `yaml:"QueueDb"`
|
||||
Address string `yaml:"Address"`
|
||||
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 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"`
|
||||
@ -25,18 +42,8 @@ type ConfigMetrics struct {
|
||||
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"`
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
@ -9,5 +9,7 @@ type Global struct {
|
||||
Log *zap.Logger
|
||||
Conf *Config
|
||||
Stat *metrics.Metrics
|
||||
// Redis *redis.Client
|
||||
Db Repo
|
||||
Redis Repo
|
||||
Jwt JwtService
|
||||
}
|
23
internal/global/jwt.go
Normal file
23
internal/global/jwt.go
Normal file
@ -0,0 +1,23 @@
|
||||
package global
|
||||
|
||||
import (
|
||||
"github.com/WHUPRJ/woj-server/internal/e"
|
||||
"github.com/WHUPRJ/woj-server/internal/model"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
)
|
||||
|
||||
type Claim struct {
|
||||
UID uint `json:"id"`
|
||||
Role model.Role `json:"role"`
|
||||
Version int64 `json:"version"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
type JwtService interface {
|
||||
ParseToken(tokenText string) (*Claim, e.Status)
|
||||
SignClaim(claim *Claim) (string, e.Status)
|
||||
Validate(claim *Claim) bool
|
||||
|
||||
Handler(forced bool) gin.HandlerFunc
|
||||
}
|
@ -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())
|
||||
}
|
||||
}
|
7
internal/global/repo.go
Normal file
7
internal/global/repo.go
Normal file
@ -0,0 +1,7 @@
|
||||
package global
|
||||
|
||||
type Repo interface {
|
||||
Setup(*Global)
|
||||
Get() interface{}
|
||||
Close() error
|
||||
}
|
11
internal/global/router.go
Normal file
11
internal/global/router.go
Normal file
@ -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)
|
||||
}
|
@ -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())
|
||||
|
||||
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",
|
||||
})
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
23
internal/model/Problem.go
Normal file
23
internal/model/Problem.go
Normal file
@ -0,0 +1,23 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/jackc/pgtype"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Problem struct {
|
||||
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"`
|
||||
}
|
9
internal/model/Role.go
Normal file
9
internal/model/Role.go
Normal file
@ -0,0 +1,9 @@
|
||||
package model
|
||||
|
||||
type Role int
|
||||
|
||||
const (
|
||||
RoleGuest Role = 10
|
||||
RoleGeneral Role = 20
|
||||
RoleAdmin Role = 30
|
||||
)
|
16
internal/model/Status.go
Normal file
16
internal/model/Status.go
Normal file
@ -0,0 +1,16 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/jackc/pgtype"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Status struct {
|
||||
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"`
|
||||
}
|
12
internal/model/Submission.go
Normal file
12
internal/model/Submission.go
Normal file
@ -0,0 +1,12 @@
|
||||
package model
|
||||
|
||||
import "gorm.io/gorm"
|
||||
|
||||
type Submission struct {
|
||||
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"`
|
||||
}
|
42
internal/model/Task.go
Normal file
42
internal/model/Task.go
Normal file
@ -0,0 +1,42 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/WHUPRJ/woj-server/internal/e"
|
||||
)
|
||||
|
||||
const (
|
||||
TypeProblemBuild = "problem:build"
|
||||
TypeProblemUpdate = "problem:update"
|
||||
TypeSubmitJudge = "submit:judge"
|
||||
TypeSubmitUpdate = "submit:update"
|
||||
)
|
||||
|
||||
const (
|
||||
QueueServer = "server"
|
||||
QueueRunner = "runner"
|
||||
)
|
||||
|
||||
type ProblemBuildPayload struct {
|
||||
ProblemVersionID uint
|
||||
StorageKey string
|
||||
}
|
||||
|
||||
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
|
||||
SubmissionID uint
|
||||
ProblemVersionID uint
|
||||
Point int32
|
||||
Context string
|
||||
}
|
14
internal/model/User.go
Normal file
14
internal/model/User.go
Normal file
@ -0,0 +1,14 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
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"`
|
||||
Password []byte `json:"-" gorm:"not null"`
|
||||
IsEnabled bool `json:"is_enabled" gorm:"not null;index"`
|
||||
}
|
@ -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)
|
||||
}
|
@ -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()
|
||||
|
@ -30,10 +30,12 @@ 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.Fallback
|
||||
err = e.Unknown
|
||||
} else if err != e.Success {
|
||||
success = false
|
||||
}
|
||||
|
||||
m.Record(method, url, success, status, err, elapsed)
|
||||
|
@ -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,
|
||||
|
113
internal/repo/postgresql/postgresql.go
Normal file
113
internal/repo/postgresql/postgresql.go
Normal file
@ -0,0 +1,113 @@
|
||||
package postgresql
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"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"
|
||||
"gorm.io/gorm/schema"
|
||||
"moul.io/zapgorm2"
|
||||
"time"
|
||||
)
|
||||
|
||||
var _ global.Repo = (*Repo)(nil)
|
||||
|
||||
type Repo struct {
|
||||
db *gorm.DB
|
||||
log *zap.Logger
|
||||
}
|
||||
|
||||
func (r *Repo) Get() interface{} {
|
||||
return r.db
|
||||
}
|
||||
|
||||
func (r *Repo) Close() error {
|
||||
db, err := r.db.DB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return db.Close()
|
||||
}
|
||||
|
||||
func (r *Repo) 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 *Repo) migrateDatabase() {
|
||||
r.log.Info("Auto Migrating database...")
|
||||
|
||||
_ = 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
|
||||
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")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
39
internal/repo/redis/redis.go
Normal file
39
internal/repo/redis/redis.go
Normal file
@ -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()
|
||||
}
|
@ -1,15 +1,21 @@
|
||||
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/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/WHUPRJ/woj-server/internal/model"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @title OJ Server API Documentation
|
||||
// @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)
|
||||
@ -17,6 +23,10 @@ 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},
|
||||
{Version: "/v1", Path: "/problem", Register: problem.RouteRegister},
|
||||
{Version: "/v1", Path: "/submission", Register: submission.RouteRegister},
|
||||
{Version: "/v1", Path: "/status", Register: status.RouteRegister},
|
||||
}
|
||||
|
@ -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
|
||||
|
41
internal/service/jwt/middleware.go
Normal file
41
internal/service/jwt/middleware.go
Normal file
@ -0,0 +1,41 @@
|
||||
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(forced bool) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
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
|
||||
}
|
||||
|
||||
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()
|
||||
} else {
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
}
|
25
internal/service/jwt/service.go
Normal file
25
internal/service/jwt/service.go
Normal file
@ -0,0 +1,25 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"github.com/WHUPRJ/woj-server/internal/global"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var _ global.JwtService = (*service)(nil)
|
||||
|
||||
type service struct {
|
||||
log *zap.Logger
|
||||
redis *redis.Client
|
||||
SigningKey []byte
|
||||
ExpireHour int
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
76
internal/service/jwt/token.go
Normal file
76
internal/service/jwt/token.go
Normal file
@ -0,0 +1,76 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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) (*global.Claim, e.Status) {
|
||||
if tokenText == "" {
|
||||
return nil, e.TokenEmpty
|
||||
}
|
||||
|
||||
token, err := jwt.ParseWithClaims(
|
||||
tokenText,
|
||||
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"])
|
||||
}
|
||||
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.(*global.Claim)
|
||||
return c, e.Success
|
||||
}
|
||||
|
||||
return nil, e.TokenInvalid
|
||||
}
|
||||
|
||||
func (s *service) SignClaim(claim *global.Claim) (string, e.Status) {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
31
internal/service/problem/create.go
Normal file
31
internal/service/problem/create.go
Normal file
@ -0,0 +1,31 @@
|
||||
package problem
|
||||
|
||||
import (
|
||||
"github.com/WHUPRJ/woj-server/internal/e"
|
||||
"github.com/WHUPRJ/woj-server/internal/model"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type CreateData struct {
|
||||
Title string
|
||||
Statement string
|
||||
ProviderID uint
|
||||
IsEnabled bool
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
return problem, e.Success
|
||||
}
|
29
internal/service/problem/createVersion.go
Normal file
29
internal/service/problem/createVersion.go
Normal file
@ -0,0 +1,29 @@
|
||||
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"
|
||||
)
|
||||
|
||||
type CreateVersionData struct {
|
||||
ProblemID uint
|
||||
StorageKey string
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
32
internal/service/problem/query.go
Normal file
32
internal/service/problem/query.go
Normal file
@ -0,0 +1,32 @@
|
||||
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"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
func (s *service) Query(pid uint, associations bool, shouldEnable bool) (*model.Problem, e.Status) {
|
||||
problem := new(model.Problem)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if shouldEnable && !problem.IsEnabled {
|
||||
return nil, e.ProblemNotAvailable
|
||||
}
|
||||
return problem, e.Success
|
||||
}
|
30
internal/service/problem/queryFuzz.go
Normal file
30
internal/service/problem/queryFuzz.go
Normal file
@ -0,0 +1,30 @@
|
||||
package problem
|
||||
|
||||
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) QueryFuzz(search string, associations bool, shouldEnable bool) ([]*model.Problem, e.Status) {
|
||||
problems := make([]*model.Problem, 0)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
return problems, e.Success
|
||||
}
|
29
internal/service/problem/queryLatestVersion.go
Normal file
29
internal/service/problem/queryLatestVersion.go
Normal file
@ -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
|
||||
}
|
27
internal/service/problem/queryVersion.go
Normal file
27
internal/service/problem/queryVersion.go
Normal file
@ -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
|
||||
}
|
35
internal/service/problem/service.go
Normal file
35
internal/service/problem/service.go
Normal file
@ -0,0 +1,35 @@
|
||||
package problem
|
||||
|
||||
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.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(pvid uint, values interface{}) e.Status
|
||||
QueryVersion(pvid uint, shouldEnable bool) (*model.ProblemVersion, e.Status)
|
||||
QueryLatestVersion(pid uint) (*model.ProblemVersion, 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),
|
||||
}
|
||||
}
|
17
internal/service/problem/update.go
Normal file
17
internal/service/problem/update.go
Normal file
@ -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) 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
|
||||
}
|
||||
|
||||
return problem, e.Success
|
||||
}
|
17
internal/service/problem/updateVersion.go
Normal file
17
internal/service/problem/updateVersion.go
Normal file
@ -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(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("pvid", pvid), zap.Any("values", values))
|
||||
return e.DatabaseError
|
||||
}
|
||||
|
||||
return e.Success
|
||||
}
|
75
internal/service/runner/common.go
Normal file
75
internal/service/runner/common.go
Normal file
@ -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 = "./problem/"
|
||||
ScriptsDir = "./scripts/"
|
||||
UserDir = "./user/"
|
||||
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), 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, user, file)
|
||||
return utils.FileExist(userPath)
|
||||
}
|
30
internal/service/runner/compile.go
Normal file
30
internal/service/runner/compile.go
Normal file
@ -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, user, fmt.Sprintf("%s.out", user))
|
||||
|
||||
_ = os.Remove(target)
|
||||
status := s.checkAndExecute(version, user, lang, "problem_compile.sh", e.RunnerUserCompileFailed)
|
||||
|
||||
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) || 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
|
||||
}
|
125
internal/service/runner/config.go
Normal file
125
internal/service/runner/config.go
Normal file
@ -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
|
||||
}
|
31
internal/service/runner/deps.go
Normal file
31
internal/service/runner/deps.go
Normal file
@ -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
|
||||
}
|
79
internal/service/runner/newProblem.go
Normal file
79
internal/service/runner/newProblem.go
Normal file
@ -0,0 +1,79 @@
|
||||
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, force bool) (Config, e.Status) {
|
||||
if force {
|
||||
problemPath := filepath.Join(ProblemDir, fmt.Sprintf("%d", version))
|
||||
_ = os.RemoveAll(problemPath)
|
||||
}
|
||||
|
||||
if !s.ProblemExists(version) {
|
||||
status := s.download(version, url)
|
||||
if status != e.Success {
|
||||
return Config{}, status
|
||||
}
|
||||
}
|
||||
|
||||
cfg, err := s.ParseConfig(version, false)
|
||||
if err != nil {
|
||||
return Config{}, e.RunnerProblemParseFailed
|
||||
}
|
||||
|
||||
status := s.prebuild(version, true)
|
||||
if status != e.Success {
|
||||
return Config{}, status
|
||||
}
|
||||
|
||||
return cfg, e.Success
|
||||
}
|
22
internal/service/runner/runAndJudge.go
Normal file
22
internal/service/runner/runAndJudge.go
Normal file
@ -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
|
||||
}
|
36
internal/service/runner/service.go
Normal file
36
internal/service/runner/service.go
Normal file
@ -0,0 +1,36 @@
|
||||
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, force bool) (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)
|
||||
// ProblemExists check if problem exists
|
||||
ProblemExists(version uint) bool
|
||||
}
|
||||
|
||||
type service struct {
|
||||
log *zap.Logger
|
||||
}
|
||||
|
||||
func NewService(g *global.Global) Service {
|
||||
return &service{
|
||||
log: g.Log,
|
||||
}
|
||||
}
|
228
internal/service/runner/status.go
Normal file
228
internal/service/runner/status.go
Normal file
@ -0,0 +1,228 @@
|
||||
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, 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().
|
||||
checkTime(config).
|
||||
checkMemory(config).
|
||||
checkExit().
|
||||
getJudgeText(judge).
|
||||
getJudge().
|
||||
checkJudge(&pts)
|
||||
|
||||
sum += result.Points
|
||||
results = append(results, result)
|
||||
}
|
||||
|
||||
return JudgeStatus{Message: "", Tasks: results}, sum
|
||||
}
|
36
internal/service/status/create.go
Normal file
36
internal/service/status/create.go
Normal file
@ -0,0 +1,36 @@
|
||||
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 string
|
||||
Point int32
|
||||
}
|
||||
|
||||
func (s service) Create(data *CreateData) (*model.Status, e.Status) {
|
||||
status := &model.Status{
|
||||
SubmissionID: data.SubmissionID,
|
||||
ProblemVersionID: data.ProblemVersionID,
|
||||
Context: pgtype.JSON{
|
||||
Bytes: []byte(data.Context),
|
||||
Status: pgtype.Present,
|
||||
},
|
||||
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
|
||||
}
|
56
internal/service/status/query.go
Normal file
56
internal/service/status/query.go
Normal file
@ -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
|
||||
}
|
29
internal/service/status/service.go
Normal file
29
internal/service/status/service.go
Normal file
@ -0,0 +1,29 @@
|
||||
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(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)
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
}
|
30
internal/service/storage/get.go
Normal file
30
internal/service/storage/get.go
Normal file
@ -0,0 +1,30 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/WHUPRJ/woj-server/internal/e"
|
||||
"go.uber.org/zap"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (s *service) Get(objectName string, expiry time.Duration) (string, e.Status) {
|
||||
preSignedURL, err := s.client.PresignedGetObject(
|
||||
context.Background(),
|
||||
s.bucket,
|
||||
objectName,
|
||||
expiry,
|
||||
url.Values{},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
s.log.Warn("failed to generate pre-signed get url",
|
||||
zap.Error(err),
|
||||
zap.String("objectName", objectName),
|
||||
zap.Duration("expiry", expiry),
|
||||
)
|
||||
return "", e.StorageGetFailed
|
||||
}
|
||||
|
||||
return preSignedURL.String(), e.Success
|
||||
}
|
41
internal/service/storage/service.go
Normal file
41
internal/service/storage/service.go
Normal file
@ -0,0 +1,41 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"github.com/WHUPRJ/woj-server/internal/e"
|
||||
"github.com/WHUPRJ/woj-server/internal/global"
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
"go.uber.org/zap"
|
||||
"time"
|
||||
)
|
||||
|
||||
var _ Service = (*service)(nil)
|
||||
|
||||
type Service interface {
|
||||
Upload(objectName string, expiry time.Duration) (string, e.Status)
|
||||
Get(objectName string, expiry time.Duration) (string, e.Status)
|
||||
}
|
||||
|
||||
type service struct {
|
||||
log *zap.Logger
|
||||
client *minio.Client
|
||||
bucket string
|
||||
}
|
||||
|
||||
func NewService(g *global.Global) Service {
|
||||
minioClient, err := minio.New(g.Conf.Storage.Endpoint, &minio.Options{
|
||||
Creds: credentials.NewStaticV4(g.Conf.Storage.AccessKey, g.Conf.Storage.SecretKey, ""),
|
||||
Secure: g.Conf.Storage.UseSSL,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
g.Log.Fatal("failed to create minio client", zap.Error(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
return &service{
|
||||
log: g.Log,
|
||||
client: minioClient,
|
||||
bucket: g.Conf.Storage.Bucket,
|
||||
}
|
||||
}
|
28
internal/service/storage/upload.go
Normal file
28
internal/service/storage/upload.go
Normal file
@ -0,0 +1,28 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/WHUPRJ/woj-server/internal/e"
|
||||
"go.uber.org/zap"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (s *service) Upload(objectName string, expiry time.Duration) (string, e.Status) {
|
||||
preSignedURL, err := s.client.PresignedPutObject(
|
||||
context.Background(),
|
||||
s.bucket,
|
||||
objectName,
|
||||
expiry,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
s.log.Warn("failed to generate pre-signed upload url",
|
||||
zap.Error(err),
|
||||
zap.String("objectName", objectName),
|
||||
zap.Duration("expiry", expiry),
|
||||
)
|
||||
return "", e.StorageUploadFailed
|
||||
}
|
||||
|
||||
return preSignedURL.String(), e.Success
|
||||
}
|
31
internal/service/submission/create.go
Normal file
31
internal/service/submission/create.go
Normal file
@ -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
|
||||
}
|
57
internal/service/submission/query.go
Normal file
57
internal/service/submission/query.go
Normal file
@ -0,0 +1,57 @@
|
||||
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"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
29
internal/service/submission/service.go
Normal file
29
internal/service/submission/service.go
Normal file
@ -0,0 +1,29 @@
|
||||
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)
|
||||
QueryBySid(sid uint, associations bool) (*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),
|
||||
}
|
||||
}
|
28
internal/service/task/common.go
Normal file
28
internal/service/task/common.go
Normal file
@ -0,0 +1,28 @@
|
||||
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, queue string) (*asynq.TaskInfo, e.Status) {
|
||||
task := asynq.NewTask(typename, payload)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
return info, e.Success
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
return task, e.Success
|
||||
}
|
38
internal/service/task/problem.go
Normal file
38
internal/service/task/problem.go
Normal file
@ -0,0 +1,38 @@
|
||||
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(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("data", data),
|
||||
)
|
||||
return "", e.InternalError
|
||||
}
|
||||
|
||||
info, status := s.submit(model.TypeProblemBuild, payload, model.QueueRunner)
|
||||
|
||||
return info.ID, status
|
||||
}
|
||||
|
||||
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("data", data),
|
||||
)
|
||||
return "", e.InternalError
|
||||
}
|
||||
|
||||
info, status := s.submit(model.TypeProblemUpdate, payload, model.QueueServer)
|
||||
|
||||
return info.ID, status
|
||||
}
|
40
internal/service/task/service.go
Normal file
40
internal/service/task/service.go
Normal file
@ -0,0 +1,40 @@
|
||||
package task
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
var _ Service = (*service)(nil)
|
||||
|
||||
type Service interface {
|
||||
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)
|
||||
}
|
||||
|
||||
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.QueueDb,
|
||||
}
|
||||
return &service{
|
||||
log: g.Log,
|
||||
queue: asynq.NewClient(redisOpt),
|
||||
inspector: asynq.NewInspector(redisOpt),
|
||||
}
|
||||
}
|
49
internal/service/task/submit.go
Normal file
49
internal/service/task/submit.go
Normal file
@ -0,0 +1,49 @@
|
||||
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(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("data", data),
|
||||
)
|
||||
return "", e.InternalError
|
||||
}
|
||||
|
||||
info, status := s.submit(model.TypeSubmitJudge, payload, model.QueueRunner)
|
||||
|
||||
return info.ID, 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",
|
||||
zap.Error(err),
|
||||
zap.Any("ctx", ctx))
|
||||
return "", e.InternalError
|
||||
}
|
||||
|
||||
data.Context = string(ctxText)
|
||||
payload, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
s.log.Warn("json marshal error",
|
||||
zap.Error(err),
|
||||
zap.Any("data", data),
|
||||
zap.Any("Context", ctx),
|
||||
)
|
||||
return "", e.InternalError
|
||||
}
|
||||
|
||||
info, status := s.submit(model.TypeSubmitUpdate, payload, model.QueueServer)
|
||||
|
||||
return info.ID, status
|
||||
}
|
41
internal/service/user/create.go
Normal file
41
internal/service/user/create.go
Normal file
@ -0,0 +1,41 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"github.com/WHUPRJ/woj-server/internal/e"
|
||||
"github.com/WHUPRJ/woj-server/internal/model"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type CreateData struct {
|
||||
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.Warn("BcryptError", zap.Error(err), zap.String("password", data.Password))
|
||||
return nil, e.InternalError
|
||||
}
|
||||
|
||||
user := &model.User{
|
||||
UserName: data.UserName,
|
||||
Password: hashed,
|
||||
NickName: data.NickName,
|
||||
Role: model.RoleGeneral,
|
||||
IsEnabled: true,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user