feat: a big update

1. merge woj-runner scripts into woj-server
2. add woj-runner app
3. refactor submission status problem ...
4. jwt middleware update

Co-authored-by: cxy004 <cxy004@qq.com>
Co-authored-by: wzt <w.zhongtao@qq.com>
This commit is contained in:
Paul Pan 2022-10-22 17:38:39 +08:00
parent 062c5ef964
commit d42ee0ce54
91 changed files with 2257 additions and 406 deletions

View File

@ -18,14 +18,15 @@ jobs:
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Docker meta - name: Server Meta
id: meta id: server_meta
uses: docker/metadata-action@v4 uses: docker/metadata-action@v4
with: with:
images: panpaul/woj-server images: panpaul/woj-server
- name: Build and Push the Docker Image - name: Build and Push the Server Image
uses: docker/build-push-action@v3 uses: docker/build-push-action@v3
with: with:
push: ${{ github.event_name != 'pull_request' }} push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.server_meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} file: ./Dockerfile.server
labels: ${{ steps.server_meta.outputs.labels }}

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
### Project ### Project
/server /server
/runner
my.secrets my.secrets
### JetBrains template ### JetBrains template

11
Dockerfile.runner Normal file
View 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"]

View File

@ -1,12 +1,12 @@
FROM golang:latest AS builder FROM golang:latest AS builder
WORKDIR /builder WORKDIR /builder
COPY . /builder COPY . /builder
RUN make build RUN make server
FROM alpine:latest FROM alpine:latest
WORKDIR /app WORKDIR /app
RUN apk --no-cache add tzdata ca-certificates libc6-compat RUN apk --no-cache add tzdata ca-certificates libc6-compat
COPY --from=builder /builder/server /app COPY --from=builder /builder/server /app
COPY --from=builder /builder/resource /app/resource COPY --from=builder /builder/resource/frontend /app/resource/frontend
EXPOSE 8000 EXPOSE 8000
ENTRYPOINT ["/app/server"] ENTRYPOINT ["/app/server"]

View File

@ -1,28 +1,29 @@
PROJECT=server
GO := go GO := go
LDFLAGS += -X main.BuildTime=$(shell date -u '+%Y-%m-%d-%I-%M-%S') LDFLAGS += -X cmd.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.Version=$(shell cat VERSION)+$(shell git rev-parse HEAD)
LDFLAGS += -s -w LDFLAGS += -s -w
GOBUILD := $(GO) build -o $(PROJECT) -ldflags '$(LDFLAGS)' ./cmd/app GOBUILD := $(GO) build -ldflags '$(LDFLAGS)'
GOBIN := $(shell go env GOPATH)/bin GOBIN := $(shell go env GOPATH)/bin
.PHONY: all build clean run dep swagger fmt .PHONY: all server runner build clean dep swagger fmt
default: all default: all
all: clean build all: clean build
build: swagger dep server: swagger dep
$(GOBUILD) $(GOBUILD) -o server ./cmd/server
runner: dep
$(GOBUILD) -o runner ./cmd/runner
build: runner server
clean: clean:
rm -f $(PROJECT) rm -f runner
rm -f server
run: clean swagger dep build
./$(PROJECT) run
dep: dep:
go mod tidy && go mod download go mod tidy && go mod download

View File

@ -1,68 +0,0 @@
package main
import (
"github.com/urfave/cli/v2"
"log"
"os"
"time"
)
func main() {
a := &cli.App{
Name: "OJ",
Usage: "woj-server",
Compiled: getBuildTime(),
Version: Version,
EnableBashCompletion: true,
Authors: []*cli.Author{
{
Name: "Paul",
Email: "i@0x7f.app",
},
{
Name: "cxy004",
Email: "cxy004@qq.com",
},
{
Name: "wzt",
Email: "w.zhongtao@qq.com",
},
},
Flags: []cli.Flag{
&cli.StringFlag{
Name: "config",
Aliases: []string{"c"},
Usage: "path to the config file",
Value: "config.yaml",
EnvVars: []string{"APP_CONFIG"},
},
},
Commands: []*cli.Command{
{
Name: "run",
Aliases: []string{"r"},
Usage: "start the server",
Action: run,
},
},
}
err := a.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
var (
BuildTime = "2022-09-06-01-00-00"
Version = "1.0.0+None"
)
func getBuildTime() time.Time {
build, err := time.Parse("2006-01-02-15-04-05", BuildTime)
if err != nil {
log.Printf("failed to parse build time: %v", err)
build = time.Now()
}
return build
}

View File

@ -1,21 +0,0 @@
package main
import (
"github.com/WHUPRJ/woj-server/internal/app"
"github.com/WHUPRJ/woj-server/internal/global"
"github.com/urfave/cli/v2"
"math/rand"
"time"
)
func run(c *cli.Context) error {
rand.Seed(time.Now().Unix())
g := new(global.Global)
g.SetupConfig(c.String("config"))
g.SetupZap()
defer func() { _ = g.Log.Sync() }()
g.Log.Info("starting server...")
return app.Run(g)
}

75
cmd/common.go Normal file
View 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
View 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
View 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)
}

View File

@ -6,7 +6,7 @@ WebServer:
Redis: Redis:
Db: 0 Db: 0
QueueDb: 0 QueueDb: 1
Address: '127.0.0.1:6379' Address: '127.0.0.1:6379'
Password: '' Password: ''

5
go.mod
View File

@ -10,7 +10,7 @@ require (
github.com/go-redis/redis/v8 v8.11.5 github.com/go-redis/redis/v8 v8.11.5
github.com/golang-jwt/jwt/v4 v4.4.2 github.com/golang-jwt/jwt/v4 v4.4.2
github.com/hibiken/asynq v0.23.0 github.com/hibiken/asynq v0.23.0
github.com/lib/pq v1.10.2 github.com/jackc/pgtype v1.11.0
github.com/prometheus/client_golang v1.13.0 github.com/prometheus/client_golang v1.13.0
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a
github.com/swaggo/gin-swagger v1.5.3 github.com/swaggo/gin-swagger v1.5.3
@ -18,6 +18,7 @@ require (
github.com/urfave/cli/v2 v2.14.1 github.com/urfave/cli/v2 v2.14.1
go.uber.org/zap v1.23.0 go.uber.org/zap v1.23.0
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
golang.org/x/text v0.3.7
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
gorm.io/driver/postgres v1.3.9 gorm.io/driver/postgres v1.3.9
gorm.io/gorm v1.23.8 gorm.io/gorm v1.23.8
@ -49,7 +50,6 @@ require (
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgproto3/v2 v2.3.0 // indirect github.com/jackc/pgproto3/v2 v2.3.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
github.com/jackc/pgtype v1.11.0 // indirect
github.com/jackc/pgx/v4 v4.16.1 // indirect github.com/jackc/pgx/v4 v4.16.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.4 // indirect github.com/jinzhu/now v1.1.4 // indirect
@ -74,7 +74,6 @@ require (
go.uber.org/multierr v1.7.0 // indirect go.uber.org/multierr v1.7.0 // indirect
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
golang.org/x/tools v0.1.10 // indirect golang.org/x/tools v0.1.10 // indirect
google.golang.org/protobuf v1.28.1 // indirect google.golang.org/protobuf v1.28.1 // indirect

View File

@ -0,0 +1,45 @@
package problem
import (
"github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/global"
"github.com/WHUPRJ/woj-server/internal/model"
"github.com/gin-gonic/gin"
)
type detailsRequest struct {
Pid uint `form:"pid"`
}
// Details
// @Summary get details of a problem
// @Description get details of a problem
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param pid formData int true "problem id"
// @Response 200 {object} e.Response "problem details"
// @Router /v1/problem/details [post]
func (h *handler) Details(c *gin.Context) {
req := new(detailsRequest)
if err := c.ShouldBind(req); err != nil {
e.Pong(c, e.InvalidParameter, err.Error())
return
}
claim, exist := c.Get("claim")
shouldEnable := !exist || claim.(*global.Claim).Role < model.RoleAdmin
p, status := h.problemService.Query(req.Pid, true, shouldEnable)
if status != e.Success {
e.Pong(c, status, nil)
return
}
pv, status := h.problemService.QueryLatestVersion(req.Pid)
e.Pong(c, status, gin.H{
"problem": p,
"context": pv.Context,
})
return
}

View File

@ -10,23 +10,25 @@ import (
var _ Handler = (*handler)(nil) var _ Handler = (*handler)(nil)
type Handler interface { type Handler interface {
Update(c *gin.Context) Details(c *gin.Context)
Search(c *gin.Context) Search(c *gin.Context)
Update(c *gin.Context)
} }
type handler struct { type handler struct {
log *zap.Logger log *zap.Logger
problemService problem.Service
jwtService global.JwtService jwtService global.JwtService
problemService problem.Service
} }
func RouteRegister(g *global.Global, group *gin.RouterGroup) { func RouteRegister(g *global.Global, group *gin.RouterGroup) {
app := &handler{ app := &handler{
log: g.Log, log: g.Log,
problemService: problem.NewService(g),
jwtService: g.Jwt, jwtService: g.Jwt,
problemService: problem.NewService(g),
} }
group.POST("/details", app.jwtService.Handler(false), app.Details)
group.POST("/search", app.Search) group.POST("/search", app.Search)
group.POST("/update", app.jwtService.Handler(), app.Update) group.POST("/update", app.jwtService.Handler(true), app.Update)
} }

View File

@ -6,7 +6,6 @@ import (
) )
type searchRequest struct { type searchRequest struct {
Pid uint `form:"pid"`
Search string `form:"search"` Search string `form:"search"`
} }
@ -15,9 +14,8 @@ type searchRequest struct {
// @Description get detail of a problem // @Description get detail of a problem
// @Accept application/x-www-form-urlencoded // @Accept application/x-www-form-urlencoded
// @Produce json // @Produce json
// @Param pid formData int false "problem id" // @Param search formData string false "word search"
// @Param search formData string false "search problem" // @Response 200 {object} e.Response "problemset"
// @Response 200 {object} e.Response "problem info"
// @Router /v1/problem/search [post] // @Router /v1/problem/search [post]
func (h *handler) Search(c *gin.Context) { func (h *handler) Search(c *gin.Context) {
req := new(searchRequest) req := new(searchRequest)
@ -27,17 +25,14 @@ func (h *handler) Search(c *gin.Context) {
return return
} }
if req.Pid == 0 && req.Search == "" { // TODO: pagination
e.Pong(c, e.InvalidParameter, nil) if req.Search == "" {
return // TODO: query without LIKE
} problem, status := h.problemService.QueryFuzz(req.Search, true, true)
if req.Pid != 0 {
problem, status := h.problemService.Query(req.Pid)
e.Pong(c, status, problem) e.Pong(c, status, problem)
return return
} else { } else {
problem, status := h.problemService.QueryFuzz(req.Search) problem, status := h.problemService.QueryFuzz(req.Search, true, true)
e.Pong(c, status, problem) e.Pong(c, status, problem)
return return
} }

View File

@ -4,15 +4,14 @@ import (
"github.com/WHUPRJ/woj-server/internal/e" "github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/global" "github.com/WHUPRJ/woj-server/internal/global"
"github.com/WHUPRJ/woj-server/internal/model" "github.com/WHUPRJ/woj-server/internal/model"
"github.com/WHUPRJ/woj-server/internal/service/problem"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
type updateRequest struct { type updateRequest struct {
Pid uint `form:"pid"` Pid uint `form:"pid"`
Title string `form:"title" binding:"required"` Title string `form:"title" binding:"required"`
Content string `form:"content" binding:"required"` Statement string `form:"statement" binding:"required"`
TimeLimit uint `form:"time_limit" binding:"required"`
MemoryLimit uint `form:"memory_limit" binding:"required"`
IsEnabled bool `form:"is_enabled"` IsEnabled bool `form:"is_enabled"`
} }
@ -23,9 +22,7 @@ type updateRequest struct {
// @Produce json // @Produce json
// @Param pid formData int false "problem id, 0 for create" // @Param pid formData int false "problem id, 0 for create"
// @Param title formData string true "title" // @Param title formData string true "title"
// @Param content formData string true "content" // @Param statement formData string true "statement"
// @Param time_limit formData int true "time limit in ms"
// @Param memory_limit formData int true "memory limit in kb"
// @Param is_enabled formData bool false "is enabled" // @Param is_enabled formData bool false "is enabled"
// @Response 200 {object} e.Response "problem info without provider information" // @Response 200 {object} e.Response "problem info without provider information"
// @Security Authentication // @Security Authentication
@ -50,32 +47,33 @@ func (h *handler) Update(c *gin.Context) {
return return
} }
problem := &model.Problem{
Title: req.Title,
Content: req.Content,
TimeLimit: req.TimeLimit,
MemoryLimit: req.MemoryLimit,
IsEnabled: req.IsEnabled,
}
if req.Pid == 0 { if req.Pid == 0 {
problem, status := h.problemService.Create(uid, problem) createData := &problem.CreateData{
e.Pong(c, status, problem) Title: req.Title,
Statement: req.Statement,
ProviderID: uid,
IsEnabled: false,
}
p, status := h.problemService.Create(createData)
e.Pong(c, status, p)
return return
} else { } else {
inDb, status := h.problemService.Query(req.Pid) p, status := h.problemService.Query(req.Pid, true, false)
if status != e.Success && status != e.ProblemNotAvailable { if status != e.Success {
e.Pong(c, status, nil) e.Pong(c, status, nil)
return return
} }
if p.ProviderID != uid {
if inDb.ProviderID != uid {
e.Pong(c, e.UserUnauthorized, nil) e.Pong(c, e.UserUnauthorized, nil)
return return
} }
problem, status := h.problemService.Update(req.Pid, problem) p.Title = req.Title
e.Pong(c, status, problem) p.Statement = req.Statement
p.IsEnabled = req.IsEnabled
p, status = h.problemService.Update(p)
e.Pong(c, status, p)
return return
} }
} }

View File

@ -0,0 +1,34 @@
package runner
import (
"context"
"encoding/json"
"fmt"
"github.com/WHUPRJ/woj-server/internal/model"
"github.com/hibiken/asynq"
"go.uber.org/zap"
)
func (h *handler) Build(_ context.Context, t *asynq.Task) error {
// TODO: configure timeout with context
var p model.ProblemBuildPayload
if err := json.Unmarshal(t.Payload(), &p); err != nil {
return fmt.Errorf("json.Unmarshal failed: %v: %w", err, asynq.SkipRetry)
}
h.log.Info("build", zap.Any("payload", p))
config, status := h.runnerService.NewProblem(p.ProblemVersionID, p.ProblemFile)
for i := range config.Languages {
config.Languages[i].Type = ""
config.Languages[i].Script = ""
config.Languages[i].Cmp = ""
}
b, _ := json.Marshal(config)
h.taskService.ProblemUpdate(status, p.ProblemVersionID, string(b))
return nil
}

View File

@ -0,0 +1,41 @@
package runner
import (
"context"
"errors"
"github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/global"
"github.com/WHUPRJ/woj-server/internal/service/runner"
"github.com/WHUPRJ/woj-server/internal/service/task"
"github.com/hibiken/asynq"
"go.uber.org/zap"
)
var _ Handler = (*handler)(nil)
type Handler interface {
Build(_ context.Context, t *asynq.Task) error
Judge(_ context.Context, t *asynq.Task) error
}
type handler struct {
log *zap.Logger
runnerService runner.Service
taskService task.Service
}
func NewRunner(g *global.Global) (Handler, error) {
hnd := &handler{
log: g.Log,
runnerService: runner.NewService(g),
taskService: task.NewService(g),
}
status := hnd.runnerService.EnsureDeps(false)
if status != e.Success {
g.Log.Error("failed to ensure runner dependencies", zap.String("status", status.String()))
return nil, errors.New("failed to ensure dependencies")
}
return hnd, nil
}

View File

@ -0,0 +1,73 @@
package runner
import (
"context"
"encoding/json"
"fmt"
"github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/model"
"github.com/WHUPRJ/woj-server/internal/service/runner"
"github.com/WHUPRJ/woj-server/pkg/utils"
"github.com/hibiken/asynq"
"go.uber.org/zap"
"path/filepath"
)
func (h *handler) Judge(_ context.Context, t *asynq.Task) error {
var p model.SubmitJudgePayload
if err := json.Unmarshal(t.Payload(), &p); err != nil {
return fmt.Errorf("json.Unmarshal failed: %v: %w", err, asynq.SkipRetry)
}
user := utils.RandomString(16)
h.log.Info("judge", zap.Any("payload", p), zap.String("user", user))
// common
systemError := runner.JudgeStatus{Message: "System Error"}
// write code
userCode := filepath.Join(runner.UserDir, user, fmt.Sprintf("%s.%s", user, p.Submission.Language))
if !utils.FileTouch(userCode) {
h.log.Info("Touch file failed", zap.String("userCode", userCode))
h.taskService.SubmitUpdate(e.InternalError, p.ProblemVersionId, 0, systemError)
return nil
}
err := utils.FileWrite(userCode, []byte(p.Submission.Code))
if err != nil {
h.log.Info("Write file failed", zap.String("code", p.Submission.Code))
h.taskService.SubmitUpdate(e.InternalError, p.ProblemVersionId, 0, systemError)
return nil
}
// compile
result, status := h.runnerService.Compile(p.ProblemVersionId, user, p.Submission.Language)
if status == e.RunnerProblemNotExist {
_, status := h.runnerService.NewProblem(p.ProblemVersionId, p.StorageKey)
if status != e.Success {
h.log.Warn("download problem failed",
zap.Any("status", status),
zap.Uint("pvid", p.ProblemVersionId),
zap.String("storageKey", p.StorageKey))
h.taskService.SubmitUpdate(status, p.ProblemVersionId, 0, systemError)
return nil
}
} else if status != e.Success {
h.taskService.SubmitUpdate(status, p.Submission.ID, 0, result)
return nil
}
// config
config, err := h.runnerService.ParseConfig(p.ProblemVersionId, true)
if err != nil {
h.log.Info("parse config failed", zap.Error(err), zap.Uint("pvid", p.ProblemVersionId))
h.taskService.SubmitUpdate(e.InternalError, p.ProblemVersionId, 0, systemError)
return nil
}
// run
var points int32
result, points, status = h.runnerService.RunAndJudge(p.ProblemVersionId, user, p.Submission.Language, &config)
h.taskService.SubmitUpdate(status, p.Submission.ID, points, result)
return nil
}

View 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)
}

View File

@ -0,0 +1,24 @@
package status
import (
"github.com/WHUPRJ/woj-server/internal/e"
"github.com/gin-gonic/gin"
)
type queryRequest struct {
SubmissionID uint `form:"sid"`
}
func (h *handler) Query(c *gin.Context) {
req := new(queryRequest)
if err := c.ShouldBind(req); err != nil {
e.Pong(c, e.InvalidParameter, err.Error())
return
}
status, eStatus := h.statusService.Query(req.SubmissionID, true)
e.Pong(c, eStatus, status)
return
}

View File

@ -0,0 +1,42 @@
package status
import (
"github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/global"
"github.com/WHUPRJ/woj-server/internal/model"
"github.com/gin-gonic/gin"
)
type queryByVersionRequest struct {
ProblemVersionID uint `form:"pvid"`
Offset int `form:"offset"`
Limit int `form:"limit"`
}
func (h *handler) QueryByProblemVersion(c *gin.Context) {
claim, exist := c.Get("claim")
if !exist {
e.Pong(c, e.UserUnauthenticated, nil)
return
}
role := claim.(*global.Claim).Role
req := new(queryByVersionRequest)
if err := c.ShouldBind(req); err != nil {
e.Pong(c, e.InvalidParameter, nil)
return
}
if role < model.RoleAdmin {
e.Pong(c, e.UserUnauthorized, nil)
return
}
statuses, eStatus := h.statusService.QueryByVersion(req.ProblemVersionID, req.Offset, req.Limit)
e.Pong(c, eStatus, statuses)
return
}

View File

@ -0,0 +1,64 @@
package submission
import (
"github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/global"
"github.com/WHUPRJ/woj-server/internal/service/submission"
"github.com/gin-gonic/gin"
)
type createRequest struct {
Pid uint `form:"pid" binding:"required"`
Language string `form:"language" binding:"required"`
Code string `form:"statement" binding:"required"`
}
// Create
// @Summary create a submission
// @Description create a submission
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param pid formData int true "problem id"
// @Param language formData string true "language"
// @Param code formData string true "code"
// @Response 200 {object} e.Response ""
// @Security Authentication
// @Router /v1/submission/create [post]
func (h *handler) Create(c *gin.Context) {
claim, exist := c.Get("claim")
if !exist {
e.Pong(c, e.UserUnauthenticated, nil)
return
}
uid := claim.(*global.Claim).UID
req := new(createRequest)
if err := c.ShouldBind(req); err != nil {
e.Pong(c, e.InvalidParameter, err.Error())
return
}
createData := &submission.CreateData{
ProblemID: req.Pid,
UserID: uid,
Language: req.Language,
Code: req.Code,
}
s, status := h.submissionService.Create(createData)
if status != e.Success {
e.Pong(c, status, nil)
return
}
pv, status := h.problemService.QueryLatestVersion(req.Pid)
if status != e.Success {
e.Pong(c, status, nil)
return
}
_, status = h.taskService.SubmitJudge(pv.ID, pv.StorageKey, *s)
e.Pong(c, status, nil)
return
}

View File

@ -0,0 +1,41 @@
package submission
import (
"github.com/WHUPRJ/woj-server/internal/global"
"github.com/WHUPRJ/woj-server/internal/service/problem"
"github.com/WHUPRJ/woj-server/internal/service/status"
"github.com/WHUPRJ/woj-server/internal/service/submission"
"github.com/WHUPRJ/woj-server/internal/service/task"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
var _ Handler = (*handler)(nil)
type Handler interface {
Create(c *gin.Context)
Query(c *gin.Context)
}
type handler struct {
log *zap.Logger
jwtService global.JwtService
problemService problem.Service
statusService status.Service
submissionService submission.Service
taskService task.Service
}
func RouteRegister(g *global.Global, group *gin.RouterGroup) {
app := &handler{
log: g.Log,
jwtService: g.Jwt,
problemService: problem.NewService(g),
statusService: status.NewService(g),
submissionService: submission.NewService(g),
taskService: task.NewService(g),
}
group.POST("/create", app.jwtService.Handler(true), app.Create)
group.POST("/query", app.Query)
}

View File

@ -0,0 +1,73 @@
package submission
import (
"github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/model"
"github.com/gin-gonic/gin"
)
type queryRequest struct {
Pid uint `form:"pid"`
Uid uint `form:"uid"`
Offset int `form:"offset"`
Limit int `form:"limit"`
}
type queryResponse struct {
Submission model.Submission `json:"submission"`
Point int32 `json:"point"`
}
// Query
// @Summary Query submissions
// @Description Query submissions
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param pid formData uint false "problem id"
// @Param uid formData uint false "user id"
// @Param offset formData int false "start position"
// @Param limit formData int false "limit number of records"
// @Response 200 {object} e.Response "queryResponse"
// @Router /v1/submission/query [post]
func (h *handler) Query(c *gin.Context) {
req := new(queryRequest)
if err := c.ShouldBind(req); err != nil {
e.Pong(c, e.InvalidParameter, err.Error())
return
}
if req.Pid == 0 && req.Uid == 0 {
e.Pong(c, e.InvalidParameter, nil)
return
}
submissions, status := h.submissionService.Query(req.Pid, req.Uid, req.Offset, req.Limit)
var response []*queryResponse
for _, submission := range submissions {
currentStatus, _ := h.statusService.Query(submission.ID, false)
var currentPoint int32
if currentStatus == nil {
currentPoint = -1
} else {
currentPoint = currentStatus.Point
}
newResponse := &queryResponse{
Submission: *submission,
Point: currentPoint,
}
newResponse.Submission.Code = ""
response = append(response, newResponse)
}
e.Pong(c, status, submissions)
return
}

View File

@ -8,9 +8,9 @@ import (
) )
type createRequest struct { type createRequest struct {
Username string `form:"username" binding:"required"` UserName string `form:"username" binding:"required"`
Nickname string `form:"nickname" binding:"required"`
Password string `form:"password" binding:"required"` Password string `form:"password" binding:"required"`
NickName string `form:"nickname" binding:"required"`
} }
// Create // Create
@ -32,11 +32,10 @@ func (h *handler) Create(c *gin.Context) {
} }
createData := &user.CreateData{ createData := &user.CreateData{
Username: req.Username, UserName: req.UserName,
Nickname: req.Nickname,
Password: req.Password, Password: req.Password,
NickName: req.NickName,
} }
u, status := h.userService.Create(createData) u, status := h.userService.Create(createData)
if status != e.Success { if status != e.Success {
e.Pong(c, status, nil) e.Pong(c, status, nil)
@ -48,6 +47,7 @@ func (h *handler) Create(c *gin.Context) {
e.Pong(c, status, nil) e.Pong(c, status, nil)
return return
} }
claim := &global.Claim{ claim := &global.Claim{
UID: u.ID, UID: u.ID,
Role: u.Role, Role: u.Role,
@ -55,4 +55,5 @@ func (h *handler) Create(c *gin.Context) {
} }
token, status := h.jwtService.SignClaim(claim) token, status := h.jwtService.SignClaim(claim)
e.Pong(c, status, token) e.Pong(c, status, token)
return
} }

View File

@ -18,19 +18,19 @@ type Handler interface {
type handler struct { type handler struct {
log *zap.Logger log *zap.Logger
userService user.Service
jwtService global.JwtService jwtService global.JwtService
userService user.Service
} }
func RouteRegister(g *global.Global, group *gin.RouterGroup) { func RouteRegister(g *global.Global, group *gin.RouterGroup) {
app := &handler{ app := &handler{
log: g.Log, log: g.Log,
userService: user.NewService(g),
jwtService: g.Jwt, jwtService: g.Jwt,
userService: user.NewService(g),
} }
group.POST("/login", app.Login)
group.POST("/create", app.Create) group.POST("/create", app.Create)
group.POST("/logout", app.jwtService.Handler(), app.Logout) group.POST("/login", app.Login)
group.POST("/profile", app.jwtService.Handler(), app.Profile) group.POST("/logout", app.jwtService.Handler(true), app.Logout)
group.POST("/profile", app.jwtService.Handler(true), app.Profile)
} }

View File

@ -3,12 +3,12 @@ package user
import ( import (
"github.com/WHUPRJ/woj-server/internal/e" "github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/global" "github.com/WHUPRJ/woj-server/internal/global"
"github.com/WHUPRJ/woj-server/internal/model" "github.com/WHUPRJ/woj-server/internal/service/user"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
type loginRequest struct { type loginRequest struct {
Username string `form:"username" binding:"required"` UserName string `form:"username" binding:"required"`
Password string `form:"password" binding:"required"` Password string `form:"password" binding:"required"`
} }
@ -30,30 +30,30 @@ func (h *handler) Login(c *gin.Context) {
} }
// check password // check password
userData := &model.User{ loginData := &user.LoginData{
UserName: req.Username, UserName: req.UserName,
Password: []byte(req.Password), Password: req.Password,
} }
user, status := h.userService.Login(userData) u, status := h.userService.Login(loginData)
if status != e.Success { if status != e.Success {
e.Pong(c, status, nil) e.Pong(c, status, nil)
return return
} }
// sign and return token // sign and return token
version, status := h.userService.IncrVersion(user.ID) version, status := h.userService.IncrVersion(u.ID)
if status != e.Success { if status != e.Success {
e.Pong(c, status, nil) e.Pong(c, status, nil)
return return
} }
claim := &global.Claim{ claim := &global.Claim{
UID: user.ID, UID: u.ID,
Role: user.Role, Role: u.Role,
Version: version, Version: version,
} }
token, status := h.jwtService.SignClaim(claim) token, status := h.jwtService.SignClaim(claim)
e.Pong(c, status, gin.H{ e.Pong(c, status, gin.H{
"token": token, "token": token,
"nickname": user.NickName, "nickname": u.NickName,
}) })
} }

View File

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

View File

@ -31,18 +31,22 @@ func (h *handler) Profile(c *gin.Context) {
uid := claim.(*global.Claim).UID uid := claim.(*global.Claim).UID
role := claim.(*global.Claim).Role role := claim.(*global.Claim).Role
req := new(profileRequest) req := new(profileRequest)
if err := c.ShouldBind(req); err == nil {
if req.UID != 0 && req.UID != uid { if err := c.ShouldBind(req); err != nil {
if role >= model.RoleGeneral { e.Pong(c, e.InvalidParameter, nil)
uid = req.UID return
} else { }
if req.UID == 0 {
req.UID = uid
} else if req.UID != uid && role < model.RoleGeneral {
e.Pong(c, e.UserUnauthorized, nil) e.Pong(c, e.UserUnauthorized, nil)
return return
} }
}
}
user, status := h.userService.Profile(uid) user, status := h.userService.Profile(req.UID)
e.Pong(c, status, user) e.Pong(c, status, user)
return
} }

View 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
}

View File

@ -1,22 +1,27 @@
package app package server
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/WHUPRJ/woj-server/internal/global" "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/postgresql"
"github.com/WHUPRJ/woj-server/internal/repo/redis" "github.com/WHUPRJ/woj-server/internal/repo/redis"
"github.com/WHUPRJ/woj-server/internal/router" "github.com/WHUPRJ/woj-server/internal/router"
"github.com/WHUPRJ/woj-server/internal/service/jwt" "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" "go.uber.org/zap"
"net/http" "net/http"
"os" "os"
"os/signal" "os/signal"
"runtime"
"syscall" "syscall"
"time" "time"
) )
func Run(g *global.Global) error { func RunServer(g *global.Global) error {
// Setup Database // Setup Database
g.Db = new(postgresql.Repo) g.Db = new(postgresql.Repo)
g.Db.Setup(g) g.Db.Setup(g)
@ -29,13 +34,13 @@ func Run(g *global.Global) error {
g.Jwt = jwt.NewJwtService(g) g.Jwt = jwt.NewJwtService(g)
// Prepare Router // Prepare Router
handler := router.InitRouters(g) routers := router.InitRouters(g)
// Create Server // Create Server
addr := fmt.Sprintf("%s:%d", g.Conf.WebServer.Address, g.Conf.WebServer.Port) addr := fmt.Sprintf("%s:%d", g.Conf.WebServer.Address, g.Conf.WebServer.Port)
server := &http.Server{ server := &http.Server{
Addr: addr, Addr: addr,
Handler: handler, Handler: routers,
} }
// Run Server // Run Server
@ -45,19 +50,47 @@ func Run(g *global.Global) error {
} }
}() }()
// Create Queue
queueMux := asynq.NewServeMux()
// TODO: fill
queueMux.HandleFunc(model.TypeProblemUpdate, func(ctx context.Context, t *asynq.Task) error { return nil })
queueMux.HandleFunc(model.TypeSubmitUpdate, func(ctx context.Context, t *asynq.Task) error { return nil })
queueSrv := asynq.NewServer(
asynq.RedisClientOpt{
Addr: g.Conf.Redis.Address,
Password: g.Conf.Redis.Password,
DB: g.Conf.Redis.QueueDb,
},
asynq.Config{
Concurrency: utils.If(runtime.NumCPU() > 1, runtime.NumCPU()-1, 1).(int),
Logger: zapasynq.New(g.Log),
Queues: map[string]int{model.QueueServer: 1},
},
)
// Run Queue
if err := queueSrv.Start(queueMux); err != nil {
g.Log.Fatal("queueSrv.Start Failed", zap.Error(err))
}
// Handle SIGINT and SIGTERM. // Handle SIGINT and SIGTERM.
quit := make(chan os.Signal, 1) quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit <-quit
g.Log.Info("Shutting down server ...") g.Log.Info("Shutting down server ...")
// Graceful Shutdown // Graceful Shutdown Server
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
err := server.Shutdown(ctx) err := server.Shutdown(ctx)
if err != nil { if err != nil {
g.Log.Warn("Server Shutdown Failed", zap.Error(err)) g.Log.Warn("Server Shutdown Failed", zap.Error(err))
} }
// Graceful Shutdown Queue
queueSrv.Shutdown()
// Graceful Shutdown Database
err = g.Db.Close() err = g.Db.Close()
if err != nil { if err != nil {
g.Log.Warn("Database Close Failed", zap.Error(err)) g.Log.Warn("Database Close Failed", zap.Error(err))

View File

@ -1,35 +1,61 @@
package e package e
const ( const (
Success Status = 0 Success Status = iota
Unknown Status = 1 Unknown
)
InternalError Status = 100 const (
InvalidParameter Status = 101 InternalError Status = 100 + iota
NotFound Status = 102 InvalidParameter
DatabaseError Status = 103 NotFound
RedisError Status = 104 DatabaseError
RedisError
)
TokenUnknown Status = 200 const (
TokenEmpty Status = 201 TokenUnknown Status = 200 + iota
TokenMalformed Status = 202 TokenEmpty
TokenTimeError Status = 203 TokenMalformed
TokenInvalid Status = 204 TokenTimeError
TokenSignError Status = 205 TokenInvalid
TokenRevoked Status = 206 TokenSignError
TokenRevoked
)
UserNotFound Status = 300 const (
UserWrongPassword Status = 301 UserNotFound Status = 300 + iota
UserDuplicated Status = 302 UserWrongPassword
UserUnauthenticated Status = 303 UserDuplicated
UserUnauthorized Status = 304 UserUnauthenticated
UserDisabled Status = 305 UserUnauthorized
UserDisabled
)
ProblemNotFound Status = 500 const (
ProblemNotAvailable Status = 501 ProblemNotFound Status = 500 + iota
ProblemNotAvailable
ProblemVersionNotFound
ProblemVersionNotAvailable
StatusNotFound
)
TaskEnqueueFailed Status = 600 const (
TaskGetInfoFailed Status = 601 TaskEnqueueFailed Status = 600 + iota
TaskGetInfoFailed
)
const (
RunnerDepsBuildFailed Status = 700 + iota
RunnerDownloadFailed
RunnerUnzipFailed
RunnerProblemNotExist
RunnerProblemPrebuildFailed
RunnerProblemParseFailed
RunnerUserNotExist
RunnerUserCompileFailed
RunnerRunFailed
RunnerJudgeFailed
) )
var msgText = map[Status]string{ var msgText = map[Status]string{
@ -59,7 +85,22 @@ var msgText = map[Status]string{
ProblemNotFound: "Problem Not Found", ProblemNotFound: "Problem Not Found",
ProblemNotAvailable: "Problem Not Available", ProblemNotAvailable: "Problem Not Available",
ProblemVersionNotFound: "Problem Version Not Found",
ProblemVersionNotAvailable: "Problem Version Not Available",
StatusNotFound: "Status Not Found",
TaskEnqueueFailed: "Task Enqueue Failed", TaskEnqueueFailed: "Task Enqueue Failed",
TaskGetInfoFailed: "Task Get Info 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",
} }

View File

@ -19,5 +19,5 @@ type JwtService interface {
SignClaim(claim *Claim) (string, e.Status) SignClaim(claim *Claim) (string, e.Status)
Validate(claim *Claim) bool Validate(claim *Claim) bool
Handler() gin.HandlerFunc Handler(forced bool) gin.HandlerFunc
} }

View File

@ -1,6 +0,0 @@
package model
const (
LangC int32 = iota
LangCPP
)

View File

@ -1,20 +1,23 @@
package model package model
import ( import (
"github.com/lib/pq" "github.com/jackc/pgtype"
"gorm.io/gorm" "gorm.io/gorm"
) )
type Problem struct { type Problem struct {
gorm.Model `json:"meta"` gorm.Model `json:"meta"`
Title string `json:"title" gorm:"not null"` Title string `json:"title" gorm:"not null"`
Content string `json:"content" gorm:"not null"` Statement string `json:"statement" gorm:"not null"`
TimeLimit uint `json:"time_limit" gorm:"not null"` ProviderID uint `json:"-" gorm:"not null;index"`
MemoryLimit uint `json:"memory_limit" gorm:"not null"` Provider User `json:"provider" gorm:"foreignKey:ProviderID"`
ProviderID uint `json:"provider_id" gorm:"not null;index"` IsEnabled bool `json:"is_enabled" gorm:"not null;index"`
Provider User `json:"-" gorm:"foreignKey:ProviderID"` }
Languages pq.Int32Array `json:"languages" gorm:"type:int[]"`
Points pq.Int32Array `json:"points" gorm:"type:int[]"` type ProblemVersion struct {
StorageKey string `json:"storage_key" gorm:"not null"` 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"` IsEnabled bool `json:"is_enabled" gorm:"not null;index"`
} }

View File

@ -1,11 +1,16 @@
package model package model
import "gorm.io/gorm" import (
"github.com/jackc/pgtype"
"gorm.io/gorm"
)
type Status struct { type Status struct {
gorm.Model `json:"-"` gorm.Model `json:"meta"`
SubmissionID uint `json:"submission_id" gorm:"not null;index"` SubmissionID uint `json:"-" gorm:"not null;index"`
Submission Submission `json:"-" gorm:"foreignKey:SubmissionID"` Submission Submission `json:"submission" gorm:"foreignKey:SubmissionID"`
Verdict Verdict `json:"verdict" gorm:"not null"` 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"` Point int32 `json:"point" gorm:"not null"`
IsEnabled bool `json:"is_enabled" gorm:"not null;index"`
} }

View File

@ -3,11 +3,10 @@ package model
import "gorm.io/gorm" import "gorm.io/gorm"
type Submission struct { type Submission struct {
gorm.Model `json:"-"` gorm.Model `json:"meta"`
ProblemID uint `json:"problem_id" gorm:"not null;index"` ProblemID uint `json:"problem_id" gorm:"not null;index"`
Problem Problem `json:"-" gorm:"foreignKey:ProblemID"` UserID uint `json:"-" gorm:"not null;index"`
UserID uint `json:"user_id" gorm:"not null;index"` User User `json:"user" gorm:"foreignKey:UserID"`
User User `json:"-" gorm:"foreignKey:UserID"` Language string `json:"language" gorm:"not null"`
Language int32 `json:"language" gorm:"not null"`
Code string `json:"code" gorm:"not null"` Code string `json:"code" gorm:"not null"`
} }

View File

@ -1,15 +1,41 @@
package model package model
const ( import (
TypeProblemPush = "problem:push" "github.com/WHUPRJ/woj-server/internal/e"
TypeSubmitJudge = "submit:judge"
) )
type ProblemPushPayload struct { const (
ProblemID uint TypeProblemBuild = "problem:build"
TypeProblemUpdate = "problem:update"
TypeSubmitJudge = "submit:judge"
TypeSubmitUpdate = "submit:update"
)
const (
QueueServer = "server"
QueueRunner = "runner"
)
type ProblemBuildPayload struct {
ProblemVersionID uint
ProblemFile string ProblemFile string
} }
type SubmitJudge struct { type ProblemUpdatePayload struct {
Status e.Status
ProblemVersionID uint
Context string
}
type SubmitJudgePayload struct {
ProblemVersionId uint
StorageKey string
Submission Submission Submission Submission
} }
type SubmitUpdatePayload struct {
Status e.Status
Sid uint
Point int32
Context string
}

View File

@ -5,7 +5,7 @@ import (
) )
type User struct { type User struct {
gorm.Model `json:"-"` gorm.Model `json:"meta"`
UserName string `json:"user_name" gorm:"not null;uniqueIndex"` UserName string `json:"user_name" gorm:"not null;uniqueIndex"`
NickName string `json:"nick_name" gorm:"not null"` NickName string `json:"nick_name" gorm:"not null"`
Role Role `json:"role" gorm:"not null"` Role Role `json:"role" gorm:"not null"`

View File

@ -1,17 +0,0 @@
package model
type Verdict int
const (
VerdictJudging Verdict = iota
VerdictAccepted
VerdictWrongAnswer
VerdictTimeLimitExceeded
VerdictMemoryLimitExceeded
VerdictRuntimeError
VerdictCompileError
VerdictSystemError
VerdictJuryFailed
VerdictSkipped
VerdictPartiallyCorrect
)

View File

@ -3,6 +3,8 @@ package router
import ( import (
"github.com/WHUPRJ/woj-server/internal/api/debug" "github.com/WHUPRJ/woj-server/internal/api/debug"
"github.com/WHUPRJ/woj-server/internal/api/problem" "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/api/user"
"github.com/WHUPRJ/woj-server/internal/global" "github.com/WHUPRJ/woj-server/internal/global"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -25,4 +27,6 @@ var endpoints = []global.EndpointInfo{
{Version: "", Path: "/debug", Register: debug.RouteRegister}, {Version: "", Path: "/debug", Register: debug.RouteRegister},
{Version: "/v1", Path: "/user", Register: user.RouteRegister}, {Version: "/v1", Path: "/user", Register: user.RouteRegister},
{Version: "/v1", Path: "/problem", Register: problem.RouteRegister}, {Version: "/v1", Path: "/problem", Register: problem.RouteRegister},
{Version: "/v1", Path: "/submission", Register: submission.RouteRegister},
{Version: "/v1", Path: "/status", Register: status.RouteRegister},
} }

View File

@ -2,35 +2,40 @@ package jwt
import ( import (
"github.com/WHUPRJ/woj-server/internal/e" "github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/global"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"strings" "strings"
) )
func (s *service) Handler() gin.HandlerFunc { func (s *service) Handler(forced bool) gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
claim, status := func() (*global.Claim, e.Status) {
const tokenPrefix = "bearer " const tokenPrefix = "bearer "
tokenHeader := c.GetHeader("Authorization") tokenHeader := c.GetHeader("Authorization")
if tokenHeader == "" || !strings.HasPrefix(strings.ToLower(tokenHeader), tokenPrefix) { if tokenHeader == "" || !strings.HasPrefix(strings.ToLower(tokenHeader), tokenPrefix) {
e.Pong(c, e.TokenEmpty, nil) return nil, e.TokenEmpty
c.Abort()
return
} }
token := tokenHeader[len(tokenPrefix):]
token := tokenHeader[len(tokenPrefix):]
claim, status := s.ParseToken(token) claim, status := s.ParseToken(token)
if status != e.Success { if status != e.Success {
e.Pong(c, status, nil) return nil, status
c.Abort()
return
} }
if !s.Validate(claim) { if !s.Validate(claim) {
e.Pong(c, e.TokenRevoked, nil) return nil, e.TokenRevoked
c.Abort()
return
} }
return claim, e.Success
}()
if status == e.Success {
c.Set("claim", claim) c.Set("claim", claim)
}
if forced && status != e.Success {
e.Pong(c, status, nil)
c.Abort()
} else {
c.Next() c.Next()
} }
}
} }

View File

@ -6,12 +6,24 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
) )
func (s *service) Create(uid uint, problem *model.Problem) (*model.Problem, e.Status) { type CreateData struct {
problem.ProviderID = uid Title string
problem.IsEnabled = true Statement string
ProviderID uint
IsEnabled bool
}
if err := s.db.Create(problem).Error; err != nil { func (s *service) Create(data *CreateData) (*model.Problem, e.Status) {
s.log.Debug("create problem error", zap.Error(err), zap.Any("problem", problem)) 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 nil, e.DatabaseError
} }

View File

@ -0,0 +1,27 @@
package problem
import (
"github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/model"
"go.uber.org/zap"
)
type CreateVersionData struct {
ProblemID uint
StorageKey string
}
func (s *service) CreateVersion(data *CreateVersionData) (*model.ProblemVersion, e.Status) {
problemVersion := &model.ProblemVersion{
ProblemID: data.ProblemID,
StorageKey: data.StorageKey,
}
err := s.db.Create(problemVersion).Error
if err != nil {
s.log.Warn("DatabaseError", zap.Error(err), zap.Any("problemVersion", problemVersion))
return nil, e.DatabaseError
}
return problemVersion, e.Success
}

View File

@ -4,21 +4,29 @@ import (
"errors" "errors"
"github.com/WHUPRJ/woj-server/internal/e" "github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/model" "github.com/WHUPRJ/woj-server/internal/model"
"github.com/WHUPRJ/woj-server/pkg/utils" "go.uber.org/zap"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/clause" "gorm.io/gorm/clause"
) )
func (s *service) Query(problemId uint) (*model.Problem, e.Status) { func (s *service) Query(pid uint, associations bool, shouldEnable bool) (*model.Problem, e.Status) {
problem := new(model.Problem) problem := new(model.Problem)
err := s.db.Preload(clause.Associations).First(&problem, problemId).Error query := s.db
if associations {
query = query.Preload(clause.Associations)
}
err := query.First(&problem, pid).Error
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, e.ProblemNotFound return nil, e.ProblemNotFound
} }
if err != nil { if err != nil {
s.log.Warn("DatabaseError", zap.Error(err), zap.Any("pid", pid))
return nil, e.DatabaseError return nil, e.DatabaseError
} }
return problem, utils.If(problem.IsEnabled, e.Success, e.ProblemNotAvailable).(e.Status) if shouldEnable && !problem.IsEnabled {
return nil, e.ProblemNotAvailable
}
return problem, e.Success
} }

View File

@ -1,25 +1,28 @@
package problem package problem
import ( import (
"errors"
"github.com/WHUPRJ/woj-server/internal/e" "github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/model" "github.com/WHUPRJ/woj-server/internal/model"
"gorm.io/gorm" "go.uber.org/zap"
"gorm.io/gorm/clause" "gorm.io/gorm/clause"
) )
func (s *service) QueryFuzz(search string) ([]*model.Problem, e.Status) { func (s *service) QueryFuzz(search string, associations bool, shouldEnable bool) ([]*model.Problem, e.Status) {
var problems []*model.Problem problems := make([]*model.Problem, 0)
err := s.db.Preload(clause.Associations). query := s.db
Where("is_enabled = true"). if associations {
Where(s.db.Where("title LIKE ?", "%"+search+"%"). query = query.Preload(clause.Associations)
Or("content LIKE ?", "%"+search+"%")).
Find(&problems).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, e.ProblemNotFound
} }
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 { if err != nil {
s.log.Warn("DatabaseError", zap.Error(err), zap.Any("search", search))
return nil, e.DatabaseError return nil, e.DatabaseError
} }

View 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
}

View 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
}

View File

@ -11,10 +11,15 @@ import (
var _ Service = (*service)(nil) var _ Service = (*service)(nil)
type Service interface { type Service interface {
Create(uint, *model.Problem) (*model.Problem, e.Status) Create(data *CreateData) (*model.Problem, e.Status)
Update(uint, *model.Problem) (*model.Problem, e.Status) Update(problem *model.Problem) (*model.Problem, e.Status)
Query(uint) (*model.Problem, e.Status) Query(pid uint, associations bool, shouldEnable bool) (*model.Problem, e.Status)
QueryFuzz(string) ([]*model.Problem, e.Status) QueryFuzz(search string, associations bool, shouldEnable bool) ([]*model.Problem, e.Status)
CreateVersion(data *CreateVersionData) (*model.ProblemVersion, e.Status)
UpdateVersion(problemVersion *model.ProblemVersion) (*model.ProblemVersion, e.Status)
QueryVersion(pvid uint, shouldEnable bool) (*model.ProblemVersion, e.Status)
QueryLatestVersion(pid uint) (*model.ProblemVersion, e.Status)
} }
type service struct { type service struct {

View File

@ -4,15 +4,12 @@ import (
"github.com/WHUPRJ/woj-server/internal/e" "github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/model" "github.com/WHUPRJ/woj-server/internal/model"
"go.uber.org/zap" "go.uber.org/zap"
"gorm.io/gorm/clause"
) )
func (s *service) Update(pid uint, problem *model.Problem) (*model.Problem, e.Status) { func (s *service) Update(problem *model.Problem) (*model.Problem, e.Status) {
if err := s.db.Clauses(clause.Returning{}).Model(problem). err := s.db.Save(problem).Error
Where("ID = (?)", pid). if err != nil {
Select("Title", "Content", "TimeLimit", "MemoryLimit", "IsEnabled"). s.log.Warn("DatabaseError", zap.Error(err), zap.Any("problem", problem))
Updates(problem).Error; err != nil {
s.log.Debug("update problem error", zap.Error(err), zap.Any("problem", problem))
return nil, e.DatabaseError return nil, e.DatabaseError
} }

View 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(problemVersion *model.ProblemVersion) (*model.ProblemVersion, e.Status) {
err := s.db.Save(problemVersion).Error
if err != nil {
s.log.Warn("DatabaseError", zap.Error(err), zap.Any("problemVersion", problemVersion))
return nil, e.DatabaseError
}
return problemVersion, e.Success
}

View 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 = "./problems/"
ScriptsDir = "./scripts/"
UserDir = "./users/"
TmpDir = "./tmp/"
)
func init() {
wd, err := os.Getwd()
if err != nil {
panic(err)
}
Prefix = path.Join(wd, Prefix)
ProblemDir = path.Join(Prefix, ProblemDir)
ScriptsDir = path.Join(Prefix, ScriptsDir)
UserDir = path.Join(Prefix, UserDir)
TmpDir = path.Join(Prefix, TmpDir)
}
func (s *service) execute(script string, args ...string) error {
p := filepath.Join(ScriptsDir, script)
cmd := exec.Command(p, args...)
cmd.Dir = ScriptsDir
return cmd.Run()
}
func (s *service) checkAndExecute(version uint, user string, lang string, script string, fail e.Status) e.Status {
if !s.problemExists(version) {
s.log.Info("problem not exists", zap.Uint("version", version))
return e.RunnerProblemNotExist
}
if !s.userExists(user, fmt.Sprintf("%s.%s", user, lang)) {
s.log.Info("user program not exists", zap.String("user", user), zap.String("lang", lang))
return e.RunnerUserNotExist
}
err := s.execute(script, fmt.Sprintf("%d", version), fmt.Sprintf("%s", user), lang)
if err != nil {
s.log.Info("execute failed",
zap.Error(err),
zap.Uint("version", version),
zap.String("user", user),
zap.String("lang", lang))
return fail
}
return e.Success
}
func (s *service) problemExists(version uint) bool {
problemPath := filepath.Join(ProblemDir, fmt.Sprintf("%d", version))
return utils.FileExist(problemPath)
}
func (s *service) userExists(user string, file string) bool {
userPath := filepath.Join(UserDir, fmt.Sprintf("%s", user), file)
return utils.FileExist(userPath)
}

View 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, fmt.Sprintf("%s", user), fmt.Sprintf("%s.out", user))
_ = os.Remove(target)
status := s.checkAndExecute(version, user, lang, "problem_compile.sh", e.RunnerUserCompileFailed)
log := filepath.Join(UserDir, fmt.Sprintf("%s.compile.log", user))
msg, err := utils.FileRead(log)
msg = utils.If(err == nil, msg, nil).([]byte)
msgText := string(msg)
if utils.FileExist(target) {
return JudgeStatus{}, e.Success
} else {
return JudgeStatus{
Message: "compile failed",
Tasks: []TaskStatus{{Verdict: VerdictCompileError, Message: msgText}}},
utils.If(status == e.Success, e.RunnerUserCompileFailed, status).(e.Status)
}
}

View 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
}

View 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
}

View File

@ -0,0 +1,72 @@
package runner
import (
"fmt"
"github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/pkg/down"
"github.com/WHUPRJ/woj-server/pkg/unzip"
"github.com/WHUPRJ/woj-server/pkg/utils"
"go.uber.org/zap"
"os"
"path/filepath"
)
func (s *service) download(version uint, url string) e.Status {
zipPath := filepath.Join(TmpDir, fmt.Sprintf("%d.zip", version))
problemPath := filepath.Join(ProblemDir, fmt.Sprintf("%d", version))
err := down.Down(zipPath, url)
if err != nil {
s.log.Error("download problem failed", zap.Error(err))
return e.RunnerDownloadFailed
}
err = unzip.Unzip(zipPath, problemPath)
if err != nil {
s.log.Warn("unzip problem failed", zap.Error(err))
return e.RunnerUnzipFailed
}
return e.Success
}
func (s *service) prebuild(version uint, force bool) e.Status {
if !s.problemExists(version) {
return e.RunnerProblemNotExist
}
mark := filepath.Join(ProblemDir, fmt.Sprintf("%d", version), ".mark.prebuild")
if force {
_ = os.Remove(mark)
} else if utils.FileExist(mark) {
return e.Success
}
err := s.execute("problem_prebuild.sh", fmt.Sprintf("%d", version))
if err != nil {
s.log.Warn("prebuild problem failed", zap.Error(err), zap.Uint("version", version))
return e.RunnerProblemPrebuildFailed
}
return e.Success
}
func (s *service) NewProblem(version uint, url string) (Config, e.Status) {
status := s.download(version, url)
if status != e.Success {
return Config{}, status
}
cfg, err := s.ParseConfig(version, false)
if err != nil {
return Config{}, e.RunnerProblemParseFailed
}
status = s.prebuild(version, true)
if status != e.Success {
return Config{}, status
}
return cfg, e.Success
}

View 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
}

View File

@ -0,0 +1,34 @@
package runner
import (
"github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/global"
"go.uber.org/zap"
)
var _ Service = (*service)(nil)
type Service interface {
// EnsureDeps build docker images
EnsureDeps(force bool) e.Status
// NewProblem = Download + Parse + Prebuild
NewProblem(version uint, url string) (Config, e.Status)
// Compile compile user submission
Compile(version uint, user string, lang string) (JudgeStatus, e.Status)
// RunAndJudge execute user program
RunAndJudge(version uint, user string, lang string, config *Config) (JudgeStatus, int32, e.Status)
// ParseConfig parse config file
ParseConfig(version uint, skipCheck bool) (Config, error)
}
type service struct {
log *zap.Logger
}
func NewService(g *global.Global) Service {
return &service{
log: g.Log,
}
}

View File

@ -0,0 +1,227 @@
package runner
import (
"bytes"
"encoding/json"
"encoding/xml"
"fmt"
"github.com/WHUPRJ/woj-server/pkg/utils"
"golang.org/x/text/encoding/charmap"
"io"
"path/filepath"
)
const (
VerdictAccepted = iota
VerdictWrongAnswer
VerdictJuryFailed
VerdictPartialCorrect
VerdictTimeLimitExceeded
VerdictMemoryLimitExceeded
VerdictRuntimeError
VerdictCompileError
VerdictSystemError
)
type TestLibReport struct {
XMLName xml.Name `xml:"result"`
Outcome string `xml:"outcome,attr"`
PCType int `xml:"pctype,attr"`
Points float64 `xml:"points,attr"`
Result string `xml:",chardata"`
}
type TaskStatus struct {
Id int `json:"id"`
Points int32 `json:"points"`
RealTime int `json:"real_time"`
CpuTime int `json:"cpu_time"`
Memory int `json:"memory"`
Verdict int `json:"verdict"`
Message string `json:"message"`
infoText []byte
info map[string]interface{}
judgeText string
judge TestLibReport
}
type JudgeStatus struct {
Message string `json:"message"`
Tasks []TaskStatus `json:"tasks"`
}
func (t *TaskStatus) getInfoText(infoFile string) *TaskStatus {
if t.Verdict != VerdictAccepted {
return t
}
var err error
t.infoText, err = utils.FileRead(infoFile)
if err != nil {
t.Verdict = VerdictSystemError
t.Message = "cannot read info file"
}
return t
}
func (t *TaskStatus) getInfo() *TaskStatus {
if t.Verdict != VerdictAccepted {
return t
}
err := json.Unmarshal(t.infoText, &t.info)
if err != nil {
t.Verdict = VerdictSystemError
t.Message = "cannot parse info file"
} else {
t.RealTime = int(t.info["real_time"].(float64))
t.CpuTime = int(t.info["cpu_time"].(float64))
t.Memory = int(t.info["memory"].(float64))
}
return t
}
func (t *TaskStatus) checkExit() *TaskStatus {
if t.Verdict != VerdictAccepted {
return t
}
if t.info["status"] != "exited" || t.info["code"] != 0.0 {
t.Verdict = VerdictRuntimeError
t.Message = fmt.Sprintf("status: %v, code: %v", t.info["status"], t.info["code"])
}
return t
}
func (t *TaskStatus) checkTime(config *Config) *TaskStatus {
if t.Verdict != VerdictAccepted {
return t
}
if t.info["real_time"].(float64) > float64(config.Runtime.TimeLimit)+5 {
t.Verdict = VerdictTimeLimitExceeded
t.Message = fmt.Sprintf("real_time: %v cpu_time: %v", t.info["real_time"], t.info["cpu_time"])
}
return t
}
func (t *TaskStatus) checkMemory(config *Config) *TaskStatus {
if t.Verdict != VerdictAccepted {
return t
}
if t.info["memory"].(float64) > float64((config.Runtime.MemoryLimit+1)*1024) {
t.Verdict = VerdictMemoryLimitExceeded
t.Message = fmt.Sprintf("memory: %v", t.info["memory"])
}
return t
}
func (t *TaskStatus) getJudgeText(judgeFile string) *TaskStatus {
if t.Verdict != VerdictAccepted {
return t
}
j, err := utils.FileRead(judgeFile)
if err != nil {
t.Verdict = VerdictSystemError
t.Message = "cannot read judge file"
} else {
t.judgeText = string(j)
}
return t
}
func (t *TaskStatus) getJudge() *TaskStatus {
if t.Verdict != VerdictAccepted {
return t
}
b := bytes.NewReader([]byte(t.judgeText))
d := xml.NewDecoder(b)
d.CharsetReader = func(charset string, input io.Reader) (io.Reader, error) {
switch charset {
case "windows-1251":
return charmap.Windows1251.NewDecoder().Reader(input), nil
default:
return nil, fmt.Errorf("unknown charset: %s", charset)
}
}
err := d.Decode(&t.judge)
if err != nil {
t.Verdict = VerdictSystemError
t.Message = "cannot parse judge file"
}
return t
}
func (t *TaskStatus) checkJudge(pts *map[int]int32) *TaskStatus {
if t.Verdict != VerdictAccepted {
return t
}
mp := map[string]int{
"accepted": VerdictAccepted,
"wrong-answer": VerdictWrongAnswer,
"presentation-error": VerdictWrongAnswer,
"points": VerdictPartialCorrect,
"relative-scoring": VerdictPartialCorrect,
}
if v, ok := mp[t.judge.Outcome]; ok {
t.Verdict = v
t.Message = t.judge.Result
if v == VerdictAccepted {
t.Points = (*pts)[t.Id]
} else if v == VerdictPartialCorrect {
t.Points = int32(t.judge.Points) + int32(t.judge.PCType)
}
} else {
t.Verdict = VerdictJuryFailed
t.Message = fmt.Sprintf("unknown outcome: %v, result: %v", t.judge.Outcome, t.judge.Result)
}
return t
}
func (s *service) checkResults(user string, config *Config) (JudgeStatus, int32) {
// CE will be processed in phase compile
pts := map[int]int32{}
for _, task := range config.Tasks {
pts[task.Id] = task.Points
}
var results []TaskStatus
dir := filepath.Join(UserDir, fmt.Sprintf("%s", user))
var sum int32 = 0
for i := 1; i <= len(config.Tasks); i++ {
result := TaskStatus{Id: i, Verdict: VerdictAccepted, Points: 0}
info := filepath.Join(dir, fmt.Sprintf("%d.info", i))
judge := filepath.Join(dir, fmt.Sprintf("%d.judge", i))
result.getInfoText(info).
getInfo().
checkExit().
checkTime(config).
checkMemory(config).
getJudgeText(judge).
getJudge().
checkJudge(&pts)
sum += result.Points
}
return JudgeStatus{Message: "", Tasks: results}, sum
}

View File

@ -0,0 +1,33 @@
package status
import (
"github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/model"
"github.com/jackc/pgtype"
"go.uber.org/zap"
)
type CreateData struct {
SubmissionID uint
ProblemVersionID uint
Context pgtype.JSON
Point int32
}
func (s service) Create(data *model.Status) (*model.Status, e.Status) {
status := &model.Status{
SubmissionID: data.SubmissionID,
ProblemVersionID: data.ProblemVersionID,
Context: data.Context,
Point: data.Point,
IsEnabled: true,
}
err := s.db.Create(status).Error
if err != nil {
s.log.Warn("DatabaseError", zap.Error(err), zap.Any("status", status))
return nil, e.DatabaseError
}
return status, e.Success
}

View 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
}

View File

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

View File

@ -0,0 +1,30 @@
package status
import (
"github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/global"
"github.com/WHUPRJ/woj-server/internal/model"
"go.uber.org/zap"
"gorm.io/gorm"
)
var _ Service = (*service)(nil)
type Service interface {
Create(*model.Status) (*model.Status, e.Status)
Query(sid uint, associations bool) (*model.Status, e.Status)
QueryByVersion(pvid uint, offset int, limit int) ([]*model.Status, e.Status)
Rejudge(statusID uint) ([]*model.Status, e.Status)
}
type service struct {
log *zap.Logger
db *gorm.DB
}
func NewService(g *global.Global) Service {
return &service{
log: g.Log,
db: g.Db.Get().(*gorm.DB),
}
}

View 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
}

View File

@ -0,0 +1,33 @@
package submission
import (
"github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/model"
"go.uber.org/zap"
"gorm.io/gorm/clause"
)
func (s *service) Query(pid uint, uid uint, offset int, limit int) ([]*model.Submission, e.Status) {
submissions := make([]*model.Submission, 0)
submission := &model.Submission{
ProblemID: pid,
UserID: uid,
}
err := s.db.Preload(clause.Associations).
Where(submission).
Limit(limit).
Offset(offset).
Find(&submissions).Error
//if errors.Is(err, gorm.ErrRecordNotFound) {
// return nil, e.ProblemNotFound
//}
if err != nil {
s.log.Warn("DatabaseError", zap.Error(err), zap.Any("pid", pid), zap.Any("uid", uid))
return nil, e.DatabaseError
}
return submissions, e.Success
}

View File

@ -0,0 +1,28 @@
package submission
import (
"github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/global"
"github.com/WHUPRJ/woj-server/internal/model"
"go.uber.org/zap"
"gorm.io/gorm"
)
var _ Service = (*service)(nil)
type Service interface {
Create(data *CreateData) (*model.Submission, e.Status)
Query(pid uint, uid uint, offset int, limit int) ([]*model.Submission, e.Status)
}
type service struct {
log *zap.Logger
db *gorm.DB
}
func NewService(g *global.Global) Service {
return &service{
log: g.Log,
db: g.Db.Get().(*gorm.DB),
}
}

View File

@ -6,10 +6,10 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
) )
func (s *service) submit(typename string, payload []byte) (*asynq.TaskInfo, e.Status) { func (s *service) submit(typename string, payload []byte, queue string) (*asynq.TaskInfo, e.Status) {
task := asynq.NewTask(typename, payload) task := asynq.NewTask(typename, payload)
info, err := s.queue.Enqueue(task) info, err := s.queue.Enqueue(task, asynq.Queue(queue))
if err != nil { if err != nil {
s.log.Warn("failed to enqueue task", zap.Error(err), zap.Any("task", task)) s.log.Warn("failed to enqueue task", zap.Error(err), zap.Any("task", task))
return nil, e.TaskEnqueueFailed return nil, e.TaskEnqueueFailed
@ -20,8 +20,8 @@ func (s *service) submit(typename string, payload []byte) (*asynq.TaskInfo, e.St
return info, e.Success return info, e.Success
} }
func (s *service) GetTaskInfo(id string) (*asynq.TaskInfo, e.Status) { func (s *service) GetTaskInfo(id string, queue string) (*asynq.TaskInfo, e.Status) {
task, err := s.inspector.GetTaskInfo("default", id) task, err := s.inspector.GetTaskInfo(queue, id)
if err != nil { if err != nil {
s.log.Debug("get task info failed", zap.Error(err), zap.String("id", id)) s.log.Debug("get task info failed", zap.Error(err), zap.String("id", id))
return nil, e.TaskGetInfoFailed return nil, e.TaskGetInfoFailed

View File

@ -1,20 +0,0 @@
package task
import (
"encoding/json"
"github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/model"
"go.uber.org/zap"
)
func (s *service) NewJudge(submission model.Submission) (string, e.Status) {
payload, err := json.Marshal(model.SubmitJudge{Submission: submission})
if err != nil {
s.log.Warn("json marshal error", zap.Error(err), zap.Any("payload", submission))
return "", e.InternalError
}
info, status := s.submit(model.TypeSubmitJudge, payload)
return info.ID, status
}

View File

@ -0,0 +1,46 @@
package task
import (
"encoding/json"
"github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/model"
"go.uber.org/zap"
)
func (s *service) ProblemBuild(pvId uint, file string) (string, e.Status) {
payload, err := json.Marshal(model.ProblemBuildPayload{
ProblemVersionID: pvId,
ProblemFile: file,
})
if err != nil {
s.log.Warn("json marshal error",
zap.Error(err),
zap.Any("ProblemVersionID", pvId),
zap.String("ProblemFile", file))
return "", e.InternalError
}
info, status := s.submit(model.TypeProblemBuild, payload, model.QueueRunner)
return info.ID, status
}
func (s *service) ProblemUpdate(status e.Status, pvId uint, ctx string) (string, e.Status) {
payload, err := json.Marshal(model.ProblemUpdatePayload{
Status: status,
ProblemVersionID: pvId,
Context: ctx,
})
if err != nil {
s.log.Warn("json marshal error",
zap.Error(err),
zap.Any("Status", status),
zap.Any("ProblemVersionID", pvId),
zap.Any("Context", ctx))
return "", e.InternalError
}
info, status := s.submit(model.TypeProblemUpdate, payload, model.QueueServer)
return info.ID, status
}

View File

@ -1,23 +0,0 @@
package task
import (
"encoding/json"
"github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/model"
"go.uber.org/zap"
)
func (s *service) PushProblem(id uint, file string) (string, e.Status) {
payload, err := json.Marshal(model.ProblemPushPayload{
ProblemID: id,
ProblemFile: file,
})
if err != nil {
s.log.Warn("json marshal error", zap.Error(err), zap.Any("id", id), zap.String("file", file))
return "", e.InternalError
}
info, status := s.submit(model.TypeSubmitJudge, payload)
return info.ID, status
}

View File

@ -4,6 +4,7 @@ import (
"github.com/WHUPRJ/woj-server/internal/e" "github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/global" "github.com/WHUPRJ/woj-server/internal/global"
"github.com/WHUPRJ/woj-server/internal/model" "github.com/WHUPRJ/woj-server/internal/model"
"github.com/WHUPRJ/woj-server/internal/service/runner"
"github.com/hibiken/asynq" "github.com/hibiken/asynq"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -11,10 +12,12 @@ import (
var _ Service = (*service)(nil) var _ Service = (*service)(nil)
type Service interface { type Service interface {
NewJudge(submission model.Submission) (string, e.Status) ProblemBuild(pvId uint, file string) (string, e.Status)
PushProblem(id uint, file string) (string, e.Status) ProblemUpdate(status e.Status, pvId uint, ctx string) (string, e.Status)
GetTaskInfo(id string) (*asynq.TaskInfo, e.Status) SubmitJudge(pvid uint, storageKey string, submission model.Submission) (string, e.Status)
submit(typename string, payload []byte) (*asynq.TaskInfo, e.Status) SubmitUpdate(status e.Status, sid uint, point int32, ctx runner.JudgeStatus) (string, e.Status)
GetTaskInfo(string, string) (*asynq.TaskInfo, e.Status)
} }
type service struct { type service struct {

View File

@ -0,0 +1,55 @@
package task
import (
"encoding/json"
"github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/model"
"github.com/WHUPRJ/woj-server/internal/service/runner"
"go.uber.org/zap"
)
func (s *service) SubmitJudge(pvid uint, storageKey string, submission model.Submission) (string, e.Status) {
payload, err := json.Marshal(
model.SubmitJudgePayload{
ProblemVersionId: pvid,
StorageKey: storageKey,
Submission: submission,
})
if err != nil {
s.log.Warn("json marshal error", zap.Error(err), zap.Any("Submission", submission))
return "", e.InternalError
}
info, status := s.submit(model.TypeSubmitJudge, payload, model.QueueRunner)
return info.ID, status
}
func (s *service) SubmitUpdate(status e.Status, sid uint, point int32, ctx runner.JudgeStatus) (string, e.Status) {
ctxText, err := json.Marshal(ctx)
if err != nil {
s.log.Warn("json marshal error",
zap.Error(err),
zap.Any("ctx", ctx))
return "", e.InternalError
}
payload, err := json.Marshal(model.SubmitUpdatePayload{
Status: status,
Sid: sid,
Point: point,
Context: string(ctxText),
})
if err != nil {
s.log.Warn("json marshal error",
zap.Error(err),
zap.Any("Status", status),
zap.Int32("Point", point),
zap.Any("Context", ctx))
return "", e.InternalError
}
info, status := s.submit(model.TypeSubmitUpdate, payload, model.QueueServer)
return info.ID, status
}

View File

@ -9,31 +9,32 @@ import (
) )
type CreateData struct { type CreateData struct {
Username string UserName string
Nickname string
Password string Password string
NickName string
} }
func (s *service) Create(data *CreateData) (*model.User, e.Status) { func (s *service) Create(data *CreateData) (*model.User, e.Status) {
hashed, err := bcrypt.GenerateFromPassword([]byte(data.Password), bcrypt.DefaultCost) hashed, err := bcrypt.GenerateFromPassword([]byte(data.Password), bcrypt.DefaultCost)
if err != nil { if err != nil {
s.log.Debug("bcrypt error", zap.Error(err), zap.String("password", data.Password)) s.log.Warn("BcryptError", zap.Error(err), zap.String("password", data.Password))
return nil, e.InternalError return nil, e.InternalError
} }
user := &model.User{ user := &model.User{
UserName: data.Username, UserName: data.UserName,
Password: hashed, Password: hashed,
NickName: data.Nickname, NickName: data.NickName,
Role: model.RoleGeneral, Role: model.RoleGeneral,
IsEnabled: true, IsEnabled: true,
} }
if err := s.db.Create(user).Error; err != nil { err = s.db.Create(user).Error
if strings.Contains(err.Error(), "duplicate key") { if err != nil && strings.Contains(err.Error(), "duplicate key") {
return nil, e.UserDuplicated return nil, e.UserDuplicated
} }
s.log.Debug("create user error", zap.Error(err), zap.Any("data", data)) if err != nil {
s.log.Warn("DatabaseError", zap.Error(err), zap.Any("user", user))
return nil, e.DatabaseError return nil, e.DatabaseError
} }
return user, e.Success return user, e.Success

View File

@ -4,11 +4,17 @@ import (
"errors" "errors"
"github.com/WHUPRJ/woj-server/internal/e" "github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/model" "github.com/WHUPRJ/woj-server/internal/model"
"go.uber.org/zap"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
"gorm.io/gorm" "gorm.io/gorm"
) )
func (s *service) Login(data *model.User) (*model.User, e.Status) { type LoginData struct {
UserName string
Password string
}
func (s *service) Login(data *LoginData) (*model.User, e.Status) {
user := &model.User{UserName: data.UserName} user := &model.User{UserName: data.UserName}
err := s.db.Where(user).First(&user).Error err := s.db.Where(user).First(&user).Error
@ -16,6 +22,7 @@ func (s *service) Login(data *model.User) (*model.User, e.Status) {
return nil, e.UserNotFound return nil, e.UserNotFound
} }
if err != nil { if err != nil {
s.log.Warn("DatabaseError", zap.Error(err), zap.Any("user", user))
return nil, e.DatabaseError return nil, e.DatabaseError
} }
@ -23,7 +30,7 @@ func (s *service) Login(data *model.User) (*model.User, e.Status) {
return nil, e.UserDisabled return nil, e.UserDisabled
} }
err = bcrypt.CompareHashAndPassword(user.Password, data.Password) err = bcrypt.CompareHashAndPassword(user.Password, []byte(data.Password))
if err != nil { if err != nil {
return nil, e.UserWrongPassword return nil, e.UserWrongPassword
} }

View File

@ -4,17 +4,19 @@ import (
"errors" "errors"
"github.com/WHUPRJ/woj-server/internal/e" "github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/model" "github.com/WHUPRJ/woj-server/internal/model"
"go.uber.org/zap"
"gorm.io/gorm" "gorm.io/gorm"
) )
func (s *service) Profile(id uint) (*model.User, e.Status) { func (s *service) Profile(uid uint) (*model.User, e.Status) {
user := new(model.User) user := new(model.User)
err := s.db.First(&user, id).Error err := s.db.First(&user, uid).Error
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, e.UserNotFound return nil, e.UserNotFound
} }
if err != nil { if err != nil {
s.log.Warn("DatabaseError", zap.Error(err), zap.Any("uid", uid))
return nil, e.DatabaseError return nil, e.DatabaseError
} }

View File

@ -13,9 +13,9 @@ var _ Service = (*service)(nil)
type Service interface { type Service interface {
Create(data *CreateData) (*model.User, e.Status) Create(data *CreateData) (*model.User, e.Status)
Login(data *model.User) (*model.User, e.Status) Login(data *LoginData) (*model.User, e.Status)
IncrVersion(id uint) (int64, e.Status) IncrVersion(uid uint) (int64, e.Status)
Profile(id uint) (*model.User, e.Status) Profile(uid uint) (*model.User, e.Status)
} }
type service struct { type service struct {

View File

@ -7,11 +7,12 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
) )
func (s *service) IncrVersion(id uint) (int64, e.Status) { func (s *service) IncrVersion(uid uint) (int64, e.Status) {
version, err := s.redis.Incr(context.Background(), fmt.Sprintf("Version:%d", id)).Result() version, err := s.redis.Incr(context.Background(), fmt.Sprintf("Version:%d", uid)).Result()
if err != nil { if err != nil {
s.log.Debug("redis.Incr error", zap.Error(err)) s.log.Warn("RedisError", zap.Error(err), zap.Any("uid", uid))
return -1, e.RedisError return -1, e.RedisError
} }
return version, e.Success return version, e.Success
} }

55
pkg/down/down.go Normal file
View File

@ -0,0 +1,55 @@
package down
import (
"fmt"
"github.com/WHUPRJ/woj-server/pkg/utils"
"io"
"net/http"
"os"
"path/filepath"
)
func Down(dest string, url string) error {
dir := filepath.Dir(dest)
err := os.MkdirAll(dir, 0755)
if err != nil {
return err
}
tmp := fmt.Sprintf("%s.%s", dest, utils.RandomString(5))
f, err := os.Create(tmp)
if err != nil {
return err
}
resp, err := http.Get(url)
if err != nil {
return err
}
defer func(Body io.ReadCloser) {
_ = Body.Close()
}(resp.Body)
if resp.StatusCode != http.StatusOK {
_ = os.Remove(tmp)
return fmt.Errorf("bad status %s when accessing %s", resp.Status, url)
}
_, err = io.Copy(f, resp.Body)
if err != nil {
return err
}
err = f.Close()
if err != nil {
return err
}
err = os.Rename(tmp, dest)
if err != nil {
_ = os.Remove(dest)
return err
}
return nil
}

60
pkg/unzip/unzip.go Normal file
View File

@ -0,0 +1,60 @@
package unzip
import (
"archive/zip"
"io"
"os"
"path/filepath"
)
func Unzip(src, dest string) error {
r, err := zip.OpenReader(src)
if err != nil {
return err
}
defer func(r *zip.ReadCloser) {
_ = r.Close()
}(r)
handler := func(f *zip.File, dest string) error {
rc, err := f.Open()
if err != nil {
return err
}
defer func(rc io.ReadCloser) {
_ = rc.Close()
}(rc)
path := filepath.Join(dest, f.Name)
if f.FileInfo().IsDir() {
_ = os.MkdirAll(path, 0755)
} else {
_ = os.MkdirAll(filepath.Dir(path), 0755)
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return err
}
defer func(file *os.File) {
_ = file.Close()
}(file)
_, err = io.Copy(file, rc)
if err != nil {
return err
}
}
return nil
}
for _, f := range r.File {
err := handler(f, dest)
if err != nil {
_ = os.RemoveAll(dest)
return err
}
}
return nil
}

View File

@ -3,6 +3,7 @@ package utils
import ( import (
"io" "io"
"os" "os"
"path/filepath"
) )
func FileRead(filePath string) ([]byte, error) { func FileRead(filePath string) ([]byte, error) {
@ -23,6 +24,8 @@ func FileExist(filePath string) bool {
} }
func FileTouch(filePath string) bool { func FileTouch(filePath string) bool {
base := filepath.Dir(filePath)
_ = os.MkdirAll(base, 0755)
_, err := os.OpenFile(filePath, os.O_RDONLY|os.O_CREATE, 0644) _, err := os.OpenFile(filePath, os.O_RDONLY|os.O_CREATE, 0644)
return If(err == nil, true, false).(bool) return If(err == nil, true, false).(bool)
} }

25
pkg/zapasynq/logger.go Normal file
View File

@ -0,0 +1,25 @@
package zapasynq
import (
"go.uber.org/zap"
)
type Logger struct {
logger *zap.Logger
}
func New(zapLogger *zap.Logger) Logger {
return Logger{
logger: zapLogger,
}
}
func (l Logger) Debug(args ...interface{}) { l.logger.Sugar().Debugf(args[0].(string), args[1:]...) }
func (l Logger) Info(args ...interface{}) { l.logger.Sugar().Infof(args[0].(string), args[1:]...) }
func (l Logger) Warn(args ...interface{}) { l.logger.Sugar().Warnf(args[0].(string), args[1:]...) }
func (l Logger) Error(args ...interface{}) { l.logger.Sugar().Errorf(args[0].(string), args[1:]...) }
func (l Logger) Fatal(args ...interface{}) { l.logger.Sugar().Fatalf(args[0].(string), args[1:]...) }

View File

@ -5,7 +5,7 @@ rm -rf woj-sandbox
git clone https://github.com/WHUPRJ/woj-sandbox.git >/dev/null 2>&1 || exit 1 git clone https://github.com/WHUPRJ/woj-sandbox.git >/dev/null 2>&1 || exit 1
cd woj-sandbox && ./build_libseccomp.sh || exit 1 cd woj-sandbox && ./build_libseccomp.sh || exit 1
mkdir -p build && cd build mkdir -p build && cd build || exit 1
cmake .. -DCMAKE_BUILD_TYPE=Release || exit 1 cmake .. -DCMAKE_BUILD_TYPE=Release || exit 1
make -j || exit 1 make -j || exit 1

View File

@ -1,7 +1,7 @@
include ${TEMPLATE}/c.mk ${TEMPLATE}/Judger.mk include ${TEMPLATE}/c.mk ${TEMPLATE}/Judger.mk
compile: compile:
$(CC) $(CFLAGS) -o $(PREFIX)/user/$(USER_PROG).out $(PREFIX)/user/$(USER_PROG).$(LANG) @$(CC) $(CFLAGS) -o $(PREFIX)/user/$(USER_PROG).out $(PREFIX)/user/$(USER_PROG).$(LANG)
judge: judge:
$($(CMP)) $(PREFIX)/data/input/$(TEST_NUM).input $(PREFIX)/user/$(TEST_NUM).out.usr $(PREFIX)/data/output/$(TEST_NUM).output > $(PREFIX)/user/$(TEST_NUM).judge 2>&1 $($(CMP)) $(PREFIX)/data/input/$(TEST_NUM).input $(PREFIX)/user/$(TEST_NUM).out.usr $(PREFIX)/data/output/$(TEST_NUM).output $(PREFIX)/user/$(TEST_NUM).judge -appes

View File

@ -1,7 +1,7 @@
include ${TEMPLATE}/cpp.mk ${TEMPLATE}/Judger.mk include ${TEMPLATE}/cpp.mk ${TEMPLATE}/Judger.mk
compile: compile:
$(CXX) $(CFLAGS) -o $(PREFIX)/user/$(USER_PROG).out $(PREFIX)/user/$(USER_PROG).$(LANG) @$(CXX) $(CFLAGS) -o $(PREFIX)/user/$(USER_PROG).out $(PREFIX)/user/$(USER_PROG).$(LANG)
judge: judge:
$($(CMP)) $(PREFIX)/data/input/$(TEST_NUM).input $(PREFIX)/user/$(TEST_NUM).out.usr $(PREFIX)/data/output/$(TEST_NUM).output > $(PREFIX)/user/$(TEST_NUM).judge 2>&1 $($(CMP)) $(PREFIX)/data/input/$(TEST_NUM).input $(PREFIX)/user/$(TEST_NUM).out.usr $(PREFIX)/data/output/$(TEST_NUM).output $(PREFIX)/user/$(TEST_NUM).judge -appes

View File

@ -1,7 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -x set -x
rm -rf testlib rm -rf testlib
git clone https://github.com/MikeMirzayanov/testlib.git >/dev/null 2>&1 || exit 1 git clone --depth=1 https://github.com/MikeMirzayanov/testlib.git >/dev/null 2>&1 || exit 1
rm -rf testlib/.git rm -rf testlib/.git
rm -rf testlib/tests rm -rf testlib/tests
cd testlib/checkers || exit 1 cd testlib/checkers || exit 1

View File

@ -19,4 +19,4 @@ compile:
judge: judge:
# Rename on *.out.usr or *.judge is not allowed # Rename on *.out.usr or *.judge is not allowed
sed '/gadgets/d' $(PREFIX)/user/$(TEST_NUM).out.usr > $(PREFIX)/user/$(TEST_NUM).out.usr1 sed '/gadgets/d' $(PREFIX)/user/$(TEST_NUM).out.usr > $(PREFIX)/user/$(TEST_NUM).out.usr1
$(NCMP) $(PREFIX)/data/input/$(TEST_NUM).input $(PREFIX)/user/$(TEST_NUM).out.usr1 $(PREFIX)/data/output/$(TEST_NUM).output > $(PREFIX)/user/$(TEST_NUM).judge 2>&1 $(NCMP) $(PREFIX)/data/input/$(TEST_NUM).input $(PREFIX)/user/$(TEST_NUM).out.usr1 $(PREFIX)/data/output/$(TEST_NUM).output $(PREFIX)/user/$(TEST_NUM).judge -appes

View File

@ -6,7 +6,7 @@ cd "$(dirname "$0")"/../ || exit 1
if [ -f ./.mark.docker ]; then if [ -f ./.mark.docker ]; then
log_warn "Docker containers already prepared" log_warn "Docker containers already prepared"
log_warn "If you want to re-prepare the containers, please remove the file `pwd`/.mark.docker" log_warn "If you want to re-prepare the containers, please remove the file $(pwd)/.mark.docker"
exit 1 exit 1
fi fi

View File

@ -14,7 +14,7 @@ get_problem_info "$WORKSPACE" "$1" "$3"
SRC_FILE="$WORKSPACE"/user/"$2"/"$2"."$3" SRC_FILE="$WORKSPACE"/user/"$2"/"$2"."$3"
EXE_FILE="$WORKSPACE"/user/"$2"/"$2".out EXE_FILE="$WORKSPACE"/user/"$2"/"$2".out
LOG_FILE="$WORKSPACE"/user/"$2"/"$2".compile.log export LOG_FILE="$WORKSPACE"/user/"$2"/"$2".compile.log
rm -f "$EXE_FILE" && touch "$EXE_FILE" rm -f "$EXE_FILE" && touch "$EXE_FILE"

View File

@ -12,7 +12,7 @@ fi
get_problem_info "$WORKSPACE" "$1" "$3" get_problem_info "$WORKSPACE" "$1" "$3"
TIMEOUT=${4:-60} export TIMEOUT=${4:-60}
for test_num in $(seq "$Info_Num"); do for test_num in $(seq "$Info_Num"); do
std_file="$WORKSPACE/problem/$1/data/output/$test_num.output" std_file="$WORKSPACE/problem/$1/data/output/$test_num.output"
ans_file="$WORKSPACE/user/$2/$test_num.out.usr" ans_file="$WORKSPACE/user/$2/$test_num.out.usr"

View File

@ -21,7 +21,7 @@ if [ ! -f "$WORKSPACE/problem/$1/judge/prebuild.Makefile" ]; then
exit 0 exit 0
fi fi
TIMEOUT=${2:-300} export TIMEOUT=${2:-300}
docker_run \ docker_run \
-v "$WORKSPACE/problem/$1/data":/woj/problem/data \ -v "$WORKSPACE/problem/$1/data":/woj/problem/data \
-v "$WORKSPACE/problem/$1/judge":/woj/problem/judge \ -v "$WORKSPACE/problem/$1/judge":/woj/problem/judge \