Merge branch 'develop'
version 1.2.0
This commit is contained in:
commit
8a22bb3cb5
8
.dockerignore
Normal file
8
.dockerignore
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# top
|
||||||
|
/woj
|
||||||
|
|
||||||
|
# runner
|
||||||
|
resource/runner/.mark.image
|
||||||
|
resource/runner/problem/*
|
||||||
|
resource/runner/tmp/*
|
||||||
|
resource/runner/user/*
|
32
.github/workflows/docker-image.yml
vendored
32
.github/workflows/docker-image.yml
vendored
@ -1,32 +0,0 @@
|
|||||||
name: Docker Image
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "master", "develop" ]
|
|
||||||
tags: [ "v*" ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ "master", "develop" ]
|
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Login to Docker Hub
|
|
||||||
uses: docker/login-action@v2
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
- name: Server Meta
|
|
||||||
id: server_meta
|
|
||||||
uses: docker/metadata-action@v4
|
|
||||||
with:
|
|
||||||
images: panpaul/woj-server
|
|
||||||
- name: Build and Push the Server Image
|
|
||||||
uses: docker/build-push-action@v3
|
|
||||||
with:
|
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
|
||||||
tags: ${{ steps.server_meta.outputs.tags }}
|
|
||||||
file: ./Dockerfile.server
|
|
||||||
labels: ${{ steps.server_meta.outputs.labels }}
|
|
29
.github/workflows/golangci-lint.yml
vendored
29
.github/workflows/golangci-lint.yml
vendored
@ -1,29 +0,0 @@
|
|||||||
name: Lint
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "master", "develop" ]
|
|
||||||
tags: [ "v*" ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ "master", "develop" ]
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
golangci:
|
|
||||||
name: lint
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/setup-go@v3
|
|
||||||
with:
|
|
||||||
go-version: 1.19
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Generate Swagger Docs
|
|
||||||
run: make swagger
|
|
||||||
- name: golangci-lint
|
|
||||||
uses: golangci/golangci-lint-action@v3
|
|
||||||
with:
|
|
||||||
version: latest
|
|
||||||
only-new-issues: true
|
|
99
.gitignore
vendored
99
.gitignore
vendored
@ -1,96 +1,3 @@
|
|||||||
### Project
|
/woj
|
||||||
/tmp
|
/config.yaml
|
||||||
/server
|
/dsn.txt
|
||||||
/runner
|
|
||||||
my.secrets
|
|
||||||
|
|
||||||
### JetBrains template
|
|
||||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
|
||||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
|
||||||
|
|
||||||
# User-specific stuff
|
|
||||||
.idea/**/workspace.xml
|
|
||||||
.idea/**/tasks.xml
|
|
||||||
.idea/**/usage.statistics.xml
|
|
||||||
.idea/**/dictionaries
|
|
||||||
.idea/**/shelf
|
|
||||||
|
|
||||||
# Generated files
|
|
||||||
.idea/**/contentModel.xml
|
|
||||||
|
|
||||||
# Sensitive or high-churn files
|
|
||||||
.idea/**/dataSources/
|
|
||||||
.idea/**/dataSources.ids
|
|
||||||
.idea/**/dataSources.local.xml
|
|
||||||
.idea/**/sqlDataSources.xml
|
|
||||||
.idea/**/dynamic.xml
|
|
||||||
.idea/**/uiDesigner.xml
|
|
||||||
.idea/**/dbnavigator.xml
|
|
||||||
|
|
||||||
# Gradle
|
|
||||||
.idea/**/gradle.xml
|
|
||||||
.idea/**/libraries
|
|
||||||
|
|
||||||
# Gradle and Maven with auto-import
|
|
||||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
|
||||||
# since they will be recreated, and may cause churn. Uncomment if using
|
|
||||||
# auto-import.
|
|
||||||
# .idea/artifacts
|
|
||||||
# .idea/compiler.xml
|
|
||||||
# .idea/jarRepositories.xml
|
|
||||||
# .idea/modules.xml
|
|
||||||
# .idea/*.iml
|
|
||||||
# .idea/modules
|
|
||||||
# *.iml
|
|
||||||
# *.ipr
|
|
||||||
|
|
||||||
# CMake
|
|
||||||
cmake-build-*/
|
|
||||||
|
|
||||||
# Mongo Explorer plugin
|
|
||||||
.idea/**/mongoSettings.xml
|
|
||||||
|
|
||||||
# File-based project format
|
|
||||||
*.iws
|
|
||||||
|
|
||||||
# IntelliJ
|
|
||||||
out/
|
|
||||||
|
|
||||||
# mpeltonen/sbt-idea plugin
|
|
||||||
.idea_modules/
|
|
||||||
|
|
||||||
# JIRA plugin
|
|
||||||
atlassian-ide-plugin.xml
|
|
||||||
|
|
||||||
# Cursive Clojure plugin
|
|
||||||
.idea/replstate.xml
|
|
||||||
|
|
||||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
|
||||||
com_crashlytics_export_strings.xml
|
|
||||||
crashlytics.properties
|
|
||||||
crashlytics-build.properties
|
|
||||||
fabric.properties
|
|
||||||
|
|
||||||
# Editor-based Rest Client
|
|
||||||
.idea/httpRequests
|
|
||||||
|
|
||||||
# Android studio 3.1+ serialized cache file
|
|
||||||
.idea/caches/build_file_checksums.ser
|
|
||||||
|
|
||||||
### Go template
|
|
||||||
# Binaries for programs and plugins
|
|
||||||
*.exe
|
|
||||||
*.exe~
|
|
||||||
*.dll
|
|
||||||
*.so
|
|
||||||
*.dylib
|
|
||||||
|
|
||||||
# Test binary, built with `go test -c`
|
|
||||||
*.test
|
|
||||||
|
|
||||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
|
||||||
*.out
|
|
||||||
|
|
||||||
# Dependency directories (remove the comment below to include it)
|
|
||||||
# vendor/
|
|
||||||
|
|
||||||
|
25
.idea/jsonSchemas.xml
Normal file
25
.idea/jsonSchemas.xml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="JsonSchemaMappingsProjectConfiguration">
|
||||||
|
<state>
|
||||||
|
<map>
|
||||||
|
<entry key="docker-compose.yml">
|
||||||
|
<value>
|
||||||
|
<SchemaInfo>
|
||||||
|
<option name="name" value="docker-compose.yml" />
|
||||||
|
<option name="relativePathToSchema" value="https://raw.githubusercontent.com/compose-spec/compose-spec/master/schema/compose-spec.json" />
|
||||||
|
<option name="applicationDefined" value="true" />
|
||||||
|
<option name="patterns">
|
||||||
|
<list>
|
||||||
|
<Item>
|
||||||
|
<option name="path" value="docker-compose.yml" />
|
||||||
|
</Item>
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</SchemaInfo>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</state>
|
||||||
|
</component>
|
||||||
|
</project>
|
6
.idea/swagger-settings.xml
Normal file
6
.idea/swagger-settings.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="SwaggerSettings">
|
||||||
|
<option name="defaultPreviewType" value="SWAGGER_UI" />
|
||||||
|
</component>
|
||||||
|
</project>
|
@ -1,11 +0,0 @@
|
|||||||
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 +0,0 @@
|
|||||||
FROM golang:latest AS builder
|
|
||||||
WORKDIR /builder
|
|
||||||
COPY . /builder
|
|
||||||
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/frontend /app/resource/frontend
|
|
||||||
EXPOSE 8000
|
|
||||||
ENTRYPOINT ["/app/server"]
|
|
29
Makefile
29
Makefile
@ -1,36 +1,37 @@
|
|||||||
GO := go
|
GO := go
|
||||||
|
|
||||||
LDFLAGS += -X cmd.BuildTime=$(shell date -u '+%Y-%m-%d-%I-%M-%S')
|
PKG_BASE := $(shell head -n 1 go.mod | awk '{print $$2}')
|
||||||
LDFLAGS += -X cmd.Version=$(shell cat VERSION)+$(shell git rev-parse HEAD)
|
BUILD_TIME := $(shell date -u '+%Y%m%d-%I%M%S')
|
||||||
|
VERSION := $(shell cat VERSION)
|
||||||
|
GIT_COMMIT := $(shell git rev-parse HEAD)
|
||||||
|
|
||||||
|
LDFLAGS += -X $(PKG_BASE)/cmd.BuildTime=$(BUILD_TIME)
|
||||||
|
LDFLAGS += -X $(PKG_BASE)/cmd.Version=$(VERSION)
|
||||||
|
LDFLAGS += -X $(PKG_BASE)/cmd.GitCommit=$(GIT_COMMIT)
|
||||||
|
LDFLAGS += -X $(PKG_BASE)/cmd.SentryDSN=$(shell cat dsn.txt)
|
||||||
LDFLAGS += -s -w
|
LDFLAGS += -s -w
|
||||||
|
|
||||||
GOBUILD := $(GO) build -ldflags '$(LDFLAGS)'
|
GOBUILD := $(GO) build -ldflags '$(LDFLAGS)'
|
||||||
GOBIN := $(shell go env GOPATH)/bin
|
GOBIN := $(shell go env GOPATH)/bin
|
||||||
|
|
||||||
.PHONY: all server runner build clean dep swagger fmt
|
.PHONY: all build clean dep swagger fmt
|
||||||
|
|
||||||
default: all
|
default: all
|
||||||
|
|
||||||
all: clean build
|
all: clean build
|
||||||
|
|
||||||
server: swagger dep
|
build: swagger dep
|
||||||
$(GOBUILD) -o server ./cmd/server
|
$(GOBUILD) -o woj ./cmd/woj
|
||||||
|
|
||||||
runner: dep
|
|
||||||
$(GOBUILD) -o runner ./cmd/runner
|
|
||||||
|
|
||||||
build: runner server
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f runner
|
rm -f woj
|
||||||
rm -f server
|
|
||||||
|
|
||||||
dep:
|
dep:
|
||||||
go mod tidy && go mod download
|
go mod download
|
||||||
|
|
||||||
swagger:
|
swagger:
|
||||||
go install github.com/swaggo/swag/cmd/swag@latest
|
go install github.com/swaggo/swag/cmd/swag@latest
|
||||||
$(GOBIN)/swag init -g internal/router/api.go -o internal/router/docs
|
$(GOBIN)/swag init -g internal/web/router/api.go -d .,./internal/e,./internal/model --pdl 1 -o internal/web/router/docs
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
go fmt ./...
|
go fmt ./...
|
||||||
|
35
Runner.Dockerfile
Normal file
35
Runner.Dockerfile
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# builder
|
||||||
|
FROM docker.io/library/golang:alpine AS builder
|
||||||
|
|
||||||
|
ENV GOPROXY=https://goproxy.cn
|
||||||
|
WORKDIR /builder
|
||||||
|
|
||||||
|
RUN apk add --no-cache git make
|
||||||
|
RUN go install github.com/swaggo/swag/cmd/swag@latest
|
||||||
|
|
||||||
|
COPY go.mod /builder/go.mod
|
||||||
|
COPY go.sum /builder/go.sum
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
COPY . /builder
|
||||||
|
RUN make build
|
||||||
|
|
||||||
|
|
||||||
|
# main image
|
||||||
|
FROM quay.io/podman/stable
|
||||||
|
|
||||||
|
# pkill
|
||||||
|
RUN yum -y install jq procps-ng && yum -y clean all && rm -rf /var/cache
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# prepare images
|
||||||
|
COPY --from=builder /builder/resource/runner /app/resource/runner
|
||||||
|
RUN bash -c "cd /app/resource/runner/scripts && ./prepare_images.sh save"
|
||||||
|
|
||||||
|
# sources
|
||||||
|
COPY --from=builder /builder/config.docker.yaml /app
|
||||||
|
COPY --from=builder /builder/docker-entrypoint.sh /app
|
||||||
|
COPY --from=builder /builder/woj /app
|
||||||
|
|
||||||
|
ENTRYPOINT ["/app/docker-entrypoint.sh"]
|
29
Server.Dockerfile
Normal file
29
Server.Dockerfile
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# builder
|
||||||
|
FROM docker.io/library/golang:alpine AS builder
|
||||||
|
|
||||||
|
ENV GOPROXY=https://goproxy.cn
|
||||||
|
WORKDIR /builder
|
||||||
|
|
||||||
|
RUN apk add --no-cache git make
|
||||||
|
RUN go install github.com/swaggo/swag/cmd/swag@latest
|
||||||
|
|
||||||
|
COPY go.mod /builder/go.mod
|
||||||
|
COPY go.sum /builder/go.sum
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
COPY . /builder
|
||||||
|
RUN make build
|
||||||
|
|
||||||
|
|
||||||
|
# main image
|
||||||
|
FROM docker.io/library/alpine
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
RUN apk --no-cache add tzdata ca-certificates libc6-compat bash
|
||||||
|
|
||||||
|
COPY --from=builder /builder/config.docker.yaml /app
|
||||||
|
COPY --from=builder /builder/docker-entrypoint.sh /app
|
||||||
|
COPY --from=builder /builder/resource/frontend /app/resource/frontend
|
||||||
|
COPY --from=builder /builder/woj /app
|
||||||
|
|
||||||
|
ENTRYPOINT ["/app/docker-entrypoint.sh"]
|
61
build_image.sh
Executable file
61
build_image.sh
Executable file
@ -0,0 +1,61 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
. resource/runner/scripts/common.sh
|
||||||
|
|
||||||
|
# version
|
||||||
|
VERSION="$(cat VERSION)"
|
||||||
|
log_info "VERSION: $VERSION"
|
||||||
|
|
||||||
|
function build_base() {
|
||||||
|
log_info "[+] Building Base Images"
|
||||||
|
pushd resource/runner || exit 1
|
||||||
|
$DOCKER build -t git.0x7f.app/woj/ubuntu-full:latest -f scripts/ubuntu-full.Dockerfile . ||
|
||||||
|
(log_error "Build Full Image failed" && exit 1)
|
||||||
|
$DOCKER build -t git.0x7f.app/woj/ubuntu-run:latest -f scripts/ubuntu-run.Dockerfile . ||
|
||||||
|
(log_error "Build Tiny Image failed" && exit 1)
|
||||||
|
popd
|
||||||
|
}
|
||||||
|
|
||||||
|
function push_base() {
|
||||||
|
log_info "[+] Pushing Base Images"
|
||||||
|
$DOCKER push "git.0x7f.app/woj/ubuntu-full:latest"
|
||||||
|
$DOCKER push "git.0x7f.app/woj/ubuntu-run:latest"
|
||||||
|
}
|
||||||
|
|
||||||
|
function build_server() {
|
||||||
|
log_info "[+] Building Server"
|
||||||
|
$DOCKER build -t "git.0x7f.app/woj/woj-server:latest" -f Server.Dockerfile . ||
|
||||||
|
(log_error "[!] Failed to build Server" && exit 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
function build_runner() {
|
||||||
|
log_info "[+] Building Runner"
|
||||||
|
$DOCKER build \
|
||||||
|
--cap-add=sys_admin,mknod \
|
||||||
|
--device=/dev/fuse \
|
||||||
|
--security-opt label=disable \
|
||||||
|
-t "git.0x7f.app/woj/woj-runner:latest" \
|
||||||
|
-f Runner.Dockerfile . ||
|
||||||
|
(log_error "[!] Failed to build Runner" && exit 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
function push_server() {
|
||||||
|
log_info "[+] Pushing Server Images"
|
||||||
|
$DOCKER push "git.0x7f.app/woj/woj-server:latest"
|
||||||
|
$DOCKER tag "git.0x7f.app/woj/woj-server:latest" "git.0x7f.app/woj/woj-server:$VERSION"
|
||||||
|
$DOCKER push "git.0x7f.app/woj/woj-server:$VERSION"
|
||||||
|
}
|
||||||
|
|
||||||
|
function push_runner() {
|
||||||
|
log_info "[+] Pushing Runner Images"
|
||||||
|
$DOCKER push "git.0x7f.app/woj/woj-runner:latest"
|
||||||
|
$DOCKER tag "git.0x7f.app/woj/woj-runner:latest" "git.0x7f.app/woj/woj-runner:$VERSION"
|
||||||
|
$DOCKER push "git.0x7f.app/woj/woj-runner:$VERSION"
|
||||||
|
}
|
||||||
|
|
||||||
|
build_base
|
||||||
|
push_base
|
||||||
|
build_server
|
||||||
|
push_server
|
||||||
|
build_runner
|
||||||
|
push_runner
|
@ -1,10 +1,9 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/internal/global"
|
"github.com/getsentry/sentry-go"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"log"
|
"log"
|
||||||
"math/rand"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -34,27 +33,39 @@ var App = &cli.App{
|
|||||||
EnvVars: []string{"APP_CONFIG"},
|
EnvVars: []string{"APP_CONFIG"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
After: cleanupSentry,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
BuildTime string
|
BuildTime string
|
||||||
Version string
|
Version string
|
||||||
|
GitCommit string
|
||||||
|
SentryDSN string
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if BuildTime == "" {
|
if BuildTime == "" {
|
||||||
BuildTime = "2022-09-06-01-00-00"
|
// First Commit
|
||||||
|
BuildTime = "20220907-153437"
|
||||||
}
|
}
|
||||||
App.Compiled = getBuildTime()
|
App.Compiled = getBuildTime()
|
||||||
|
|
||||||
if Version == "" {
|
if Version == "" {
|
||||||
Version = "0.0.0+None"
|
Version = "0.0.0"
|
||||||
}
|
}
|
||||||
App.Version = Version
|
App.Version = Version
|
||||||
|
|
||||||
|
if GitCommit == "" {
|
||||||
|
GitCommit = "out-of-tree"
|
||||||
|
}
|
||||||
|
|
||||||
|
if SentryDSN != "" {
|
||||||
|
setupSentry()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getBuildTime() time.Time {
|
func getBuildTime() time.Time {
|
||||||
build, err := time.Parse("2006-01-02-15-04-05", BuildTime)
|
build, err := time.Parse("20060102-150405", BuildTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("failed to parse build time: %v", err)
|
log.Printf("failed to parse build time: %v", err)
|
||||||
build = time.Now()
|
build = time.Now()
|
||||||
@ -62,14 +73,22 @@ func getBuildTime() time.Time {
|
|||||||
return build
|
return build
|
||||||
}
|
}
|
||||||
|
|
||||||
func CommonSetup(c *cli.Context) *global.Global {
|
func setupSentry() {
|
||||||
rand.Seed(time.Now().Unix())
|
err := sentry.Init(sentry.ClientOptions{
|
||||||
|
Dsn: SentryDSN,
|
||||||
g := new(global.Global)
|
EnableTracing: true,
|
||||||
g.SetupConfig(c.String("config"))
|
TracesSampleRate: 1.0,
|
||||||
g.SetupZap()
|
SendDefaultPII: true,
|
||||||
|
Release: GitCommit,
|
||||||
g.Log.Info("starting...")
|
})
|
||||||
|
if err != nil {
|
||||||
return g
|
log.Fatalf("sentry.Init: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanupSentry(*cli.Context) error {
|
||||||
|
if SentryDSN != "" {
|
||||||
|
sentry.Flush(time.Second * 2)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
115
cmd/woj/woj.go
Normal file
115
cmd/woj/woj.go
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.0x7f.app/WOJ/woj-server/cmd"
|
||||||
|
appRunner "git.0x7f.app/WOJ/woj-server/internal/app/runner"
|
||||||
|
appServer "git.0x7f.app/WOJ/woj-server/internal/app/server"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/misc/config"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/misc/log"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/repo/cache"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/repo/db"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/service/problem"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/service/runner"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/service/status"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/service/storage"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/service/submission"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/service/task"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/service/user"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/web/jwt"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/web/metrics"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/web/router"
|
||||||
|
"github.com/getsentry/sentry-go"
|
||||||
|
"github.com/samber/do"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
slog "log"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
a := cmd.App
|
||||||
|
a.Usage = "woj-server"
|
||||||
|
a.Commands = []*cli.Command{
|
||||||
|
{
|
||||||
|
Name: "server",
|
||||||
|
Aliases: []string{"s"},
|
||||||
|
Usage: "start web api server",
|
||||||
|
Action: wrap(appServer.RunServer),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "migrate",
|
||||||
|
Aliases: []string{"m"},
|
||||||
|
Usage: "migrate database",
|
||||||
|
Action: wrap(appServer.RunServerMigrate),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "runner",
|
||||||
|
Aliases: []string{"r"},
|
||||||
|
Usage: "start runner",
|
||||||
|
Action: wrap(appRunner.RunRunner),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := a.Run(os.Args)
|
||||||
|
if err != nil {
|
||||||
|
slog.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareServices(c *cli.Context) *do.Injector {
|
||||||
|
injector := do.New()
|
||||||
|
|
||||||
|
// cli context
|
||||||
|
do.ProvideValue(injector, c)
|
||||||
|
|
||||||
|
{ // basic services
|
||||||
|
do.Provide(injector, config.NewService)
|
||||||
|
do.Provide(injector, log.NewService)
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // repo services
|
||||||
|
do.Provide(injector, db.NewService)
|
||||||
|
do.Provide(injector, cache.NewService)
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // web helper services
|
||||||
|
do.Provide(injector, metrics.NewService)
|
||||||
|
do.Provide(injector, jwt.NewService)
|
||||||
|
do.Provide(injector, router.NewService)
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // core services
|
||||||
|
do.Provide(injector, problem.NewService)
|
||||||
|
do.Provide(injector, runner.NewService)
|
||||||
|
do.Provide(injector, status.NewService)
|
||||||
|
do.Provide(injector, storage.NewService)
|
||||||
|
do.Provide(injector, submission.NewService)
|
||||||
|
do.Provide(injector, task.NewService)
|
||||||
|
do.Provide(injector, user.NewService)
|
||||||
|
}
|
||||||
|
|
||||||
|
return injector
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrap(f func(i *do.Injector) error) func(*cli.Context) error {
|
||||||
|
return func(c *cli.Context) error {
|
||||||
|
defer func() {
|
||||||
|
if cmd.SentryDSN != "" {
|
||||||
|
// only recover when sentry is enabled
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
sentry.CaptureException(r.(error))
|
||||||
|
sentry.Flush(time.Second * 2)
|
||||||
|
slog.Printf("Panic Captured: %v", r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
injector := prepareServices(c)
|
||||||
|
|
||||||
|
logger := do.MustInvoke[log.Service](injector)
|
||||||
|
defer func() { _ = logger.GetRawLogger().Sync() }()
|
||||||
|
logger.GetRawLogger().Info("starting...")
|
||||||
|
|
||||||
|
return f(injector)
|
||||||
|
}
|
||||||
|
}
|
37
config.docker.yaml
Normal file
37
config.docker.yaml
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
WebServer:
|
||||||
|
Address: ${WEB_SERVER_ADDRESS}
|
||||||
|
Port: ${WEB_SERVER_PORT}
|
||||||
|
JwtSigningKey: ${WEB_SERVER_JWT_SIGNING_KEY}
|
||||||
|
JwtExpireHour: ${WEB_SERVER_JWT_EXPIRE_HOUR}
|
||||||
|
|
||||||
|
Redis:
|
||||||
|
Db: ${REDIS_DB}
|
||||||
|
QueueDb: ${REDIS_QUEUE_DB}
|
||||||
|
Address: ${REDIS_ADDRESS}
|
||||||
|
Port: ${REDIS_PORT}
|
||||||
|
Password: ${REDIS_PASSWORD}
|
||||||
|
|
||||||
|
Database:
|
||||||
|
Host: ${DATABASE_HOST}
|
||||||
|
Port: ${DATABASE_PORT}
|
||||||
|
User: ${DATABASE_USER}
|
||||||
|
Password: ${DATABASE_PASSWORD}
|
||||||
|
Database: ${DATABASE_NAME}
|
||||||
|
Prefix: ${DATABASE_PREFIX}
|
||||||
|
MaxOpenConns: ${DATABASE_MAX_OPEN_CONNS}
|
||||||
|
MaxIdleConns: ${DATABASE_MAX_IDLE_CONNS}
|
||||||
|
ConnMaxLifetime: ${DATABASE_CONN_MAX_LIFETIME}
|
||||||
|
TimeZone: ${DATABASE_TIMEZONE}
|
||||||
|
|
||||||
|
Storage:
|
||||||
|
Endpoint: ${STORAGE_ENDPOINT}
|
||||||
|
UseSSL: ${STORAGE_USE_SSL}
|
||||||
|
AccessKey: ${STORAGE_ACCESS_KEY}
|
||||||
|
SecretKey: ${STORAGE_SECRET_KEY}
|
||||||
|
Bucket: ${STORAGE_BUCKET}
|
||||||
|
|
||||||
|
Metrics:
|
||||||
|
Namespace: ${METRICS_NAMESPACE}
|
||||||
|
Subsystem: ${METRICS_SUBSYSTEM}
|
||||||
|
|
||||||
|
Development: ${DEVELOPMENT}
|
35
config.yaml
35
config.yaml
@ -1,35 +0,0 @@
|
|||||||
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'
|
|
||||||
|
|
||||||
Development: true
|
|
103
docker-compose.yml
Normal file
103
docker-compose.yml
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
services:
|
||||||
|
server:
|
||||||
|
image: git.0x7f.app/woj/woj-server:1.1.0
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: [ "CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:8000/health" ]
|
||||||
|
interval: 5s
|
||||||
|
command: server
|
||||||
|
environment:
|
||||||
|
- REDIS_ADDRESS=cache
|
||||||
|
- DATABASE_HOST=db
|
||||||
|
- DATABASE_USER=dev
|
||||||
|
- DATABASE_PASSWORD=password
|
||||||
|
- DATABASE_NAME=dev
|
||||||
|
- STORAGE_ENDPOINT=storage:9000
|
||||||
|
- STORAGE_ACCESS_KEY=access_key
|
||||||
|
- STORAGE_SECRET_KEY=secret_key
|
||||||
|
- STORAGE_BUCKET=woj
|
||||||
|
- DEVELOPMENT=true
|
||||||
|
volumes:
|
||||||
|
- /etc/timezone:/etc/timezone:ro
|
||||||
|
- /etc/localtime:/etc/localtime:ro
|
||||||
|
depends_on:
|
||||||
|
runner:
|
||||||
|
condition: service_started
|
||||||
|
storage:
|
||||||
|
condition: service_healthy
|
||||||
|
cache:
|
||||||
|
condition: service_healthy
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
|
||||||
|
runner:
|
||||||
|
image: git.0x7f.app/woj/woj-runner:1.1.0
|
||||||
|
restart: unless-stopped
|
||||||
|
command: runner
|
||||||
|
security_opt:
|
||||||
|
- "label=disable"
|
||||||
|
cap_add:
|
||||||
|
- SYS_ADMIN
|
||||||
|
- MKNOD
|
||||||
|
devices:
|
||||||
|
- "/dev/fuse"
|
||||||
|
environment:
|
||||||
|
- REDIS_ADDRESS=cache
|
||||||
|
- STORAGE_ENDPOINT=storage:9000
|
||||||
|
- STORAGE_ACCESS_KEY=access_key
|
||||||
|
- STORAGE_SECRET_KEY=secret_key
|
||||||
|
- STORAGE_BUCKET=woj
|
||||||
|
- DEVELOPMENT=true
|
||||||
|
volumes:
|
||||||
|
- runner:/app/resource/runner/user
|
||||||
|
- /etc/timezone:/etc/timezone:ro
|
||||||
|
- /etc/localtime:/etc/localtime:ro
|
||||||
|
depends_on:
|
||||||
|
storage:
|
||||||
|
condition: service_healthy
|
||||||
|
cache:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
storage:
|
||||||
|
image: quay.io/minio/minio:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: [ "CMD", "curl", "-f", "http://127.0.0.1:9000/minio/health/live" ]
|
||||||
|
interval: 5s
|
||||||
|
entrypoint: sh
|
||||||
|
command: -c 'mkdir -p /data/woj && minio server /data'
|
||||||
|
environment:
|
||||||
|
MINIO_ROOT_USER: "access_key"
|
||||||
|
MINIO_ROOT_PASSWORD: "secret_key"
|
||||||
|
volumes:
|
||||||
|
- storage:/data
|
||||||
|
|
||||||
|
cache:
|
||||||
|
image: docker.io/library/redis:alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: [ "CMD", "redis-cli", "ping" ]
|
||||||
|
interval: 5s
|
||||||
|
volumes:
|
||||||
|
- cache:/data
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: docker.io/library/postgres:alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: [ "CMD", "pg_isready", "-U", "dev" ]
|
||||||
|
interval: 5s
|
||||||
|
environment:
|
||||||
|
- POSTGRES_USER=dev
|
||||||
|
- POSTGRES_PASSWORD=password
|
||||||
|
- POSTGRES_DB=dev
|
||||||
|
volumes:
|
||||||
|
- db:/var/lib/postgresql/data
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
runner:
|
||||||
|
storage:
|
||||||
|
cache:
|
||||||
|
db:
|
86
docker-entrypoint.sh
Executable file
86
docker-entrypoint.sh
Executable file
@ -0,0 +1,86 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -eo pipefail
|
||||||
|
|
||||||
|
COLOR_RED="\e[0;31m"
|
||||||
|
COLOR_GREEN="\e[0;32m"
|
||||||
|
COLOR_YELLOW="\e[0;33m"
|
||||||
|
COLOR_NONE="\e[0m"
|
||||||
|
function log_info() { echo -e "${COLOR_GREEN}$*${COLOR_NONE}" 1>&2; }
|
||||||
|
function log_warn() { echo -e "${COLOR_YELLOW}$*${COLOR_NONE}" 1>&2; }
|
||||||
|
function log_error() { echo -e "${COLOR_RED}$*${COLOR_NONE}" 1>&2; }
|
||||||
|
|
||||||
|
log_info "extracting env vars"
|
||||||
|
|
||||||
|
function check_env() {
|
||||||
|
# $1 -> var name
|
||||||
|
# $2 -> default value
|
||||||
|
# $3 -> quota or not
|
||||||
|
val=$(eval "echo -n \${$1}")
|
||||||
|
if test -z "$val"; then
|
||||||
|
log_warn "Environment variable $1 is not set, using default value \"$2\""
|
||||||
|
export "$1"="$2"
|
||||||
|
else
|
||||||
|
log_info "Using $1=$val"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if "$3"; then
|
||||||
|
val=$(eval "echo -n \${$1}")
|
||||||
|
# shellcheck disable=SC2140
|
||||||
|
export "$1"="'$val'"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# echo -n ">>>>> "
|
||||||
|
# eval "echo -n \${$1}"
|
||||||
|
# echo " <<<<<"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_env "WEB_SERVER_ADDRESS" "0.0.0.0" true
|
||||||
|
check_env "WEB_SERVER_PORT" 8000 false
|
||||||
|
check_env "WEB_SERVER_JWT_SIGNING_KEY" "$(head -n 10 /dev/urandom | md5sum | cut -c 1-32)" true
|
||||||
|
check_env "WEB_SERVER_JWT_EXPIRE_HOUR" 12 false
|
||||||
|
|
||||||
|
check_env "REDIS_DB" 0 false
|
||||||
|
check_env "REDIS_QUEUE_DB" 1 false
|
||||||
|
check_env "REDIS_ADDRESS" "redis" true
|
||||||
|
check_env "REDIS_PORT" 6379 false
|
||||||
|
check_env "REDIS_PASSWORD" "" true
|
||||||
|
|
||||||
|
check_env "DATABASE_HOST" "postgres" true
|
||||||
|
check_env "DATABASE_PORT" 5432 false
|
||||||
|
check_env "DATABASE_USER" "dev" true
|
||||||
|
check_env "DATABASE_PASSWORD" "password" true
|
||||||
|
check_env "DATABASE_NAME" "dev" true
|
||||||
|
check_env "DATABASE_PREFIX" "oj_" true
|
||||||
|
check_env "DATABASE_MAX_OPEN_CONNS" 100 false
|
||||||
|
check_env "DATABASE_MAX_IDLE_CONNS" 60 false
|
||||||
|
check_env "DATABASE_CONN_MAX_LIFETIME" 60 false
|
||||||
|
check_env "DATABASE_TIMEZONE" "Asia/Shanghai" true
|
||||||
|
|
||||||
|
check_env "STORAGE_ENDPOINT" "minio:9000" true
|
||||||
|
check_env "STORAGE_USE_SSL" "false" false
|
||||||
|
check_env "STORAGE_ACCESS_KEY" "access_key" true
|
||||||
|
check_env "STORAGE_SECRET_KEY" "secret_key" true
|
||||||
|
check_env "STORAGE_BUCKET" "woj" true
|
||||||
|
|
||||||
|
check_env "METRICS_NAMESPACE" "woj" true
|
||||||
|
check_env "METRICS_SUBSYSTEM" "server" true
|
||||||
|
|
||||||
|
check_env "DEVELOPMENT" false false
|
||||||
|
|
||||||
|
rm -f /tmp/tmp.yaml
|
||||||
|
(
|
||||||
|
echo "cat <<EOF >/app/config.yaml"
|
||||||
|
cat /app/config.docker.yaml
|
||||||
|
echo "EOF"
|
||||||
|
) >/tmp/tmp.yaml
|
||||||
|
|
||||||
|
if [ -f '/app/config.yaml' ]; then
|
||||||
|
log_info "config.yaml already exists, skip"
|
||||||
|
else
|
||||||
|
log_info "creating config.yaml"
|
||||||
|
. /tmp/tmp.yaml || (log_error "failed to create config.yaml" && exit 1)
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "starting woj"
|
||||||
|
#cat /app/config.yaml
|
||||||
|
exec /app/woj "$@"
|
140
go.mod
140
go.mod
@ -1,90 +1,96 @@
|
|||||||
module github.com/WHUPRJ/woj-server
|
module git.0x7f.app/WOJ/woj-server
|
||||||
|
|
||||||
go 1.19
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gin-contrib/cors v1.4.0
|
github.com/TheZeroSlave/zapsentry v1.20.0
|
||||||
|
github.com/getsentry/sentry-go v0.25.0
|
||||||
|
github.com/gin-contrib/cors v1.5.0
|
||||||
github.com/gin-contrib/pprof v1.4.0
|
github.com/gin-contrib/pprof v1.4.0
|
||||||
github.com/gin-contrib/zap v0.0.2
|
github.com/gin-contrib/zap v0.2.0
|
||||||
github.com/gin-gonic/gin v1.8.1
|
github.com/gin-gonic/gin v1.9.1
|
||||||
github.com/go-redis/redis/v8 v8.11.5
|
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||||
github.com/golang-jwt/jwt/v4 v4.4.2
|
github.com/hibiken/asynq v0.24.1
|
||||||
github.com/hibiken/asynq v0.23.0
|
github.com/jackc/pgtype v1.14.0
|
||||||
github.com/jackc/pgtype v1.11.0
|
github.com/minio/minio-go/v7 v7.0.66
|
||||||
github.com/minio/minio-go/v7 v7.0.42
|
github.com/prometheus/client_golang v1.17.0
|
||||||
github.com/prometheus/client_golang v1.13.0
|
github.com/redis/go-redis/v9 v9.3.1
|
||||||
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a
|
github.com/samber/do v1.6.0
|
||||||
github.com/swaggo/gin-swagger v1.5.3
|
github.com/swaggo/files v1.0.1
|
||||||
github.com/swaggo/swag v1.8.5
|
github.com/swaggo/gin-swagger v1.6.0
|
||||||
github.com/urfave/cli/v2 v2.14.1
|
github.com/swaggo/swag v1.16.2
|
||||||
go.uber.org/zap v1.23.0
|
github.com/urfave/cli/v2 v2.26.0
|
||||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
|
go.uber.org/zap v1.26.0
|
||||||
golang.org/x/text v0.3.7
|
golang.org/x/crypto v0.17.0
|
||||||
|
golang.org/x/text v0.14.0
|
||||||
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.5.4
|
||||||
gorm.io/gorm v1.23.8
|
gorm.io/gorm v1.25.5
|
||||||
moul.io/zapgorm2 v1.1.3
|
moul.io/zapgorm2 v1.3.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
github.com/bytedance/sonic v1.10.2 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||||
|
github.com/chenzhuoyu/iasm v0.9.1 // indirect
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
github.com/go-openapi/jsonpointer v0.20.2 // indirect
|
||||||
github.com/go-openapi/jsonreference v0.19.6 // indirect
|
github.com/go-openapi/jsonreference v0.20.3 // indirect
|
||||||
github.com/go-openapi/spec v0.20.4 // indirect
|
github.com/go-openapi/spec v0.20.12 // indirect
|
||||||
github.com/go-openapi/swag v0.19.15 // indirect
|
github.com/go-openapi/swag v0.22.5 // indirect
|
||||||
github.com/go-playground/locales v0.14.0 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.10.0 // indirect
|
github.com/go-playground/validator/v10 v10.16.0 // indirect
|
||||||
github.com/goccy/go-json v0.9.7 // indirect
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
github.com/golang/protobuf v1.5.2 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
github.com/google/uuid v1.3.0 // indirect
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
github.com/google/uuid v1.5.0 // indirect
|
||||||
github.com/jackc/pgconn v1.12.1 // indirect
|
|
||||||
github.com/jackc/pgio v1.0.0 // indirect
|
github.com/jackc/pgio v1.0.0 // indirect
|
||||||
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/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
|
github.com/jackc/pgx/v4 v4.18.1 // indirect
|
||||||
github.com/jackc/pgx/v4 v4.16.1 // indirect
|
github.com/jackc/pgx/v5 v5.5.1 // indirect
|
||||||
|
github.com/jackc/puddle/v2 v2.2.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.5 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/compress v1.15.9 // indirect
|
github.com/klauspost/compress v1.17.4 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.1.0 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
||||||
github.com/leodido/go-urn v1.2.1 // indirect
|
github.com/leodido/go-urn v1.2.4 // indirect
|
||||||
github.com/mailru/easyjson v0.7.6 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
|
||||||
github.com/minio/md5-simd v1.1.2 // indirect
|
github.com/minio/md5-simd v1.1.2 // indirect
|
||||||
github.com/minio/sha256-simd v1.0.0 // indirect
|
github.com/minio/sha256-simd v1.0.1 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
|
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
|
||||||
github.com/prometheus/client_model v0.2.0 // indirect
|
github.com/prometheus/client_model v0.5.0 // indirect
|
||||||
github.com/prometheus/common v0.37.0 // indirect
|
github.com/prometheus/common v0.45.0 // indirect
|
||||||
github.com/prometheus/procfs v0.8.0 // indirect
|
github.com/prometheus/procfs v0.12.0 // indirect
|
||||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||||
github.com/rs/xid v1.4.0 // indirect
|
github.com/rs/xid v1.5.0 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||||
github.com/spf13/cast v1.5.0 // indirect
|
github.com/spf13/cast v1.6.0 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.7 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
go.uber.org/atomic v1.7.0 // indirect
|
github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e // indirect
|
||||||
go.uber.org/multierr v1.7.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
|
golang.org/x/arch v0.6.0 // indirect
|
||||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect
|
golang.org/x/net v0.19.0 // indirect
|
||||||
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
|
golang.org/x/sync v0.5.0 // indirect
|
||||||
golang.org/x/tools v0.1.10 // indirect
|
golang.org/x/sys v0.15.0 // indirect
|
||||||
google.golang.org/protobuf v1.28.1 // indirect
|
golang.org/x/time v0.5.0 // indirect
|
||||||
gopkg.in/ini.v1 v1.66.6 // indirect
|
golang.org/x/tools v0.16.1 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
google.golang.org/protobuf v1.31.0 // indirect
|
||||||
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
)
|
)
|
||||||
|
@ -2,11 +2,12 @@ package consumer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/WHUPRJ/woj-server/internal/global"
|
"git.0x7f.app/WOJ/woj-server/internal/misc/log"
|
||||||
"github.com/WHUPRJ/woj-server/internal/service/problem"
|
"git.0x7f.app/WOJ/woj-server/internal/service/problem"
|
||||||
"github.com/WHUPRJ/woj-server/internal/service/status"
|
"git.0x7f.app/WOJ/woj-server/internal/service/status"
|
||||||
"github.com/WHUPRJ/woj-server/internal/service/task"
|
"git.0x7f.app/WOJ/woj-server/internal/service/task"
|
||||||
"github.com/hibiken/asynq"
|
"github.com/hibiken/asynq"
|
||||||
|
"github.com/samber/do"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -24,12 +25,12 @@ type handler struct {
|
|||||||
taskService task.Service
|
taskService task.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConsumer(g *global.Global) Handler {
|
func NewConsumer(i *do.Injector) Handler {
|
||||||
hnd := &handler{
|
hnd := &handler{
|
||||||
log: g.Log,
|
log: do.MustInvoke[log.Service](i).GetLogger("api.consumer"),
|
||||||
problemService: problem.NewService(g),
|
problemService: do.MustInvoke[problem.Service](i),
|
||||||
statusService: status.NewService(g),
|
statusService: do.MustInvoke[status.Service](i),
|
||||||
taskService: task.NewService(g),
|
taskService: do.MustInvoke[task.Service](i),
|
||||||
}
|
}
|
||||||
|
|
||||||
return hnd
|
return hnd
|
||||||
|
@ -4,8 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/internal/model"
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
"github.com/hibiken/asynq"
|
"github.com/hibiken/asynq"
|
||||||
"github.com/jackc/pgtype"
|
"github.com/jackc/pgtype"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
@ -4,9 +4,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/internal/model"
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
"github.com/WHUPRJ/woj-server/internal/service/status"
|
"git.0x7f.app/WOJ/woj-server/internal/service/status"
|
||||||
"github.com/hibiken/asynq"
|
"github.com/hibiken/asynq"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
@ -1,22 +1,25 @@
|
|||||||
package debug
|
package debug
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/internal/global"
|
"git.0x7f.app/WOJ/woj-server/internal/misc/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/samber/do"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ Handler = (*handler)(nil)
|
var _ Handler = (*handler)(nil)
|
||||||
|
|
||||||
type Handler interface {
|
type Handler interface {
|
||||||
randomString(c *gin.Context)
|
RandomString(c *gin.Context)
|
||||||
}
|
}
|
||||||
|
|
||||||
type handler struct {
|
type handler struct {
|
||||||
log *zap.Logger
|
log *zap.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func RouteRegister(g *global.Global, group *gin.RouterGroup) {
|
func RouteRegister(rg *gin.RouterGroup, i *do.Injector) {
|
||||||
app := &handler{g.Log}
|
app := &handler{}
|
||||||
group.GET("/random", app.randomString)
|
app.log = do.MustInvoke[log.Service](i).GetLogger("api.debug")
|
||||||
|
|
||||||
|
rg.GET("/random", app.RandomString)
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
package debug
|
package debug
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/pkg/utils"
|
"git.0x7f.app/WOJ/woj-server/pkg/utils"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
// randomString
|
// RandomString
|
||||||
// @Summary random string
|
// @Summary generate random string
|
||||||
// @Description generate random string with length = 32
|
// @Description Generate random string with length = 32.
|
||||||
// @Tags debug
|
// @Tags debug
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Response 200 {object} e.Response "random string"
|
// @Response 200 {object} e.Response[string] "random string"
|
||||||
// @Router /debug/random [get]
|
// @Router /debug/random [get]
|
||||||
func (h *handler) randomString(c *gin.Context) {
|
func (h *handler) RandomString(c *gin.Context) {
|
||||||
str := utils.RandomString(32)
|
str := utils.RandomString(32)
|
||||||
h.log.Info("random string", zap.String("str", str))
|
h.log.Info("random string", zap.String("str", str))
|
||||||
e.Pong(c, e.Success, str)
|
e.Pong(c, e.Success, str)
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
package problem
|
package problem
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/internal/global"
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
"github.com/WHUPRJ/woj-server/internal/model"
|
"git.0x7f.app/WOJ/woj-server/internal/service/problem"
|
||||||
"github.com/WHUPRJ/woj-server/internal/service/problem"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -14,54 +13,61 @@ type createVersionRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateVersion
|
// CreateVersion
|
||||||
// @Summary create a problem version
|
// @Summary [admin] create a problem version
|
||||||
// @Description create a problem version
|
// @Description Create a problem version associated with `pid`.
|
||||||
|
// @Tags problem,admin
|
||||||
// @Accept application/x-www-form-urlencoded
|
// @Accept application/x-www-form-urlencoded
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param pid formData int true "problem id"
|
// @Param pid formData int true "problem id"
|
||||||
// @Param storage_key formData string true "storage key"
|
// @Param storage_key formData string true "storage key, zip file containing problem data"
|
||||||
// @Response 200 {object} e.Response ""
|
// @Response 200 {object} e.Response[any] "nothing"
|
||||||
// @Security Authentication
|
// @Security Authentication
|
||||||
// @Router /v1/problem/create_version [post]
|
// @Router /v1/problem/create_version [post]
|
||||||
func (h *handler) CreateVersion(c *gin.Context) {
|
func (h *handler) CreateVersion(c *gin.Context) {
|
||||||
claim, exist := c.Get("claim")
|
claim, exist := c.Get("claim")
|
||||||
if !exist {
|
if !exist {
|
||||||
e.Pong(c, e.UserUnauthenticated, nil)
|
e.Pong[any](c, e.UserUnauthenticated, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// uid := claim.(*global.Claim).UID
|
|
||||||
|
|
||||||
role := claim.(*global.Claim).Role
|
|
||||||
req := new(createVersionRequest)
|
req := new(createVersionRequest)
|
||||||
|
|
||||||
if err := c.ShouldBind(req); err != nil {
|
if err := c.ShouldBind(req); err != nil {
|
||||||
e.Pong(c, e.InvalidParameter, err.Error())
|
e.Pong(c, e.InvalidParameter, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// guest can not submit
|
// only admin can create problem version
|
||||||
|
role := claim.(*model.Claim).Role
|
||||||
if role < model.RoleAdmin {
|
if role < model.RoleAdmin {
|
||||||
e.Pong(c, e.UserUnauthorized, nil)
|
e.Pong[any](c, e.UserUnauthorized, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: check pid exist
|
// make sure problem exists
|
||||||
|
_, status := h.problemService.Query(req.ProblemID, false, false)
|
||||||
|
if status != e.Success {
|
||||||
|
e.Pong[any](c, status, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// create problem version
|
||||||
createVersionData := &problem.CreateVersionData{
|
createVersionData := &problem.CreateVersionData{
|
||||||
ProblemID: req.ProblemID,
|
ProblemID: req.ProblemID,
|
||||||
StorageKey: req.StorageKey,
|
StorageKey: req.StorageKey,
|
||||||
}
|
}
|
||||||
pv, status := h.problemService.CreateVersion(createVersionData)
|
pv, status := h.problemService.CreateVersion(createVersionData)
|
||||||
if status != e.Success {
|
if status != e.Success {
|
||||||
e.Pong(c, status, nil)
|
e.Pong[any](c, status, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// enqueue task: runner build problem
|
||||||
payload := &model.ProblemBuildPayload{
|
payload := &model.ProblemBuildPayload{
|
||||||
ProblemVersionID: pv.ID,
|
ProblemVersionID: pv.ID,
|
||||||
StorageKey: pv.StorageKey,
|
StorageKey: pv.StorageKey,
|
||||||
}
|
}
|
||||||
_, status = h.taskService.ProblemBuild(payload)
|
_, status = h.taskService.ProblemBuild(payload)
|
||||||
e.Pong(c, status, nil)
|
e.Pong[any](c, status, nil)
|
||||||
|
|
||||||
|
// TODO: if failed, delete problem version
|
||||||
}
|
}
|
@ -1,9 +1,8 @@
|
|||||||
package problem
|
package problem
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/internal/global"
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
"github.com/WHUPRJ/woj-server/internal/model"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -11,37 +10,44 @@ type detailsRequest struct {
|
|||||||
Pid uint `form:"pid"`
|
Pid uint `form:"pid"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type problemDetailsResponse struct {
|
||||||
|
Problem *model.Problem `json:"problem"`
|
||||||
|
Context interface{} `json:"context"`
|
||||||
|
}
|
||||||
|
|
||||||
// Details
|
// Details
|
||||||
// @Summary get details of a problem
|
// @Summary get details of a problem
|
||||||
// @Description get details of a problem
|
// @Description Get details of a problem.
|
||||||
|
// @Tags problem
|
||||||
// @Accept application/x-www-form-urlencoded
|
// @Accept application/x-www-form-urlencoded
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param pid formData int true "problem id"
|
// @Param pid formData int true "problem id"
|
||||||
// @Response 200 {object} e.Response "problem details"
|
// @Response 200 {object} e.Response[problemDetailsResponse] "problem details"
|
||||||
// @Router /v1/problem/details [post]
|
// @Router /v1/problem/details [post]
|
||||||
func (h *handler) Details(c *gin.Context) {
|
func (h *handler) Details(c *gin.Context) {
|
||||||
req := new(detailsRequest)
|
req := new(detailsRequest)
|
||||||
|
|
||||||
if err := c.ShouldBind(req); err != nil {
|
if err := c.ShouldBind(req); err != nil {
|
||||||
e.Pong(c, e.InvalidParameter, err.Error())
|
e.Pong(c, e.InvalidParameter, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
claim, exist := c.Get("claim")
|
claim, exist := c.Get("claim")
|
||||||
shouldEnable := !exist || claim.(*global.Claim).Role < model.RoleAdmin
|
shouldEnable := !exist || claim.(*model.Claim).Role < model.RoleAdmin
|
||||||
|
|
||||||
p, status := h.problemService.Query(req.Pid, true, shouldEnable)
|
p, status := h.problemService.Query(req.Pid, true, shouldEnable)
|
||||||
if status != e.Success {
|
if status != e.Success {
|
||||||
e.Pong(c, status, nil)
|
e.Pong[any](c, status, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pv, status := h.problemService.QueryLatestVersion(req.Pid)
|
pv, status := h.problemService.QueryLatestVersion(req.Pid)
|
||||||
if status != e.Success {
|
if status != e.Success {
|
||||||
e.Pong(c, status, nil)
|
e.Pong[any](c, status, nil)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
e.Pong(c, e.Success, gin.H{
|
|
||||||
"problem": p,
|
e.Pong(c, e.Success, problemDetailsResponse{
|
||||||
"context": pv.Context.Get(),
|
Problem: p,
|
||||||
|
Context: pv.Context.Get(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
package problem
|
package problem
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/internal/global"
|
"git.0x7f.app/WOJ/woj-server/internal/misc/log"
|
||||||
"github.com/WHUPRJ/woj-server/internal/service/problem"
|
"git.0x7f.app/WOJ/woj-server/internal/service/problem"
|
||||||
"github.com/WHUPRJ/woj-server/internal/service/storage"
|
"git.0x7f.app/WOJ/woj-server/internal/service/storage"
|
||||||
"github.com/WHUPRJ/woj-server/internal/service/task"
|
"git.0x7f.app/WOJ/woj-server/internal/service/task"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/web/jwt"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/samber/do"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -16,28 +18,29 @@ type Handler interface {
|
|||||||
Search(c *gin.Context)
|
Search(c *gin.Context)
|
||||||
Update(c *gin.Context)
|
Update(c *gin.Context)
|
||||||
Upload(c *gin.Context)
|
Upload(c *gin.Context)
|
||||||
|
CreateVersion(c *gin.Context)
|
||||||
}
|
}
|
||||||
|
|
||||||
type handler struct {
|
type handler struct {
|
||||||
log *zap.Logger
|
log *zap.Logger
|
||||||
jwtService global.JwtService
|
jwtService jwt.Service
|
||||||
problemService problem.Service
|
problemService problem.Service
|
||||||
taskService task.Service
|
taskService task.Service
|
||||||
storageService storage.Service
|
storageService storage.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func RouteRegister(g *global.Global, group *gin.RouterGroup) {
|
func RouteRegister(rg *gin.RouterGroup, i *do.Injector) {
|
||||||
app := &handler{
|
app := &handler{
|
||||||
log: g.Log,
|
log: do.MustInvoke[log.Service](i).GetLogger("api.problem"),
|
||||||
jwtService: g.Jwt,
|
jwtService: do.MustInvoke[jwt.Service](i),
|
||||||
problemService: problem.NewService(g),
|
problemService: do.MustInvoke[problem.Service](i),
|
||||||
taskService: task.NewService(g),
|
taskService: do.MustInvoke[task.Service](i),
|
||||||
storageService: storage.NewService(g),
|
storageService: do.MustInvoke[storage.Service](i),
|
||||||
}
|
}
|
||||||
|
|
||||||
group.POST("/search", app.Search)
|
rg.POST("/search", app.Search)
|
||||||
group.POST("/details", app.jwtService.Handler(false), app.Details)
|
rg.POST("/details", app.jwtService.Handler(false), app.Details)
|
||||||
group.POST("/update", app.jwtService.Handler(true), app.Update)
|
rg.POST("/update", app.jwtService.Handler(true), app.Update)
|
||||||
group.POST("/upload", app.jwtService.Handler(true), app.Upload)
|
rg.POST("/upload", app.jwtService.Handler(true), app.Upload)
|
||||||
group.POST("/create_version", app.jwtService.Handler(true), app.CreateVersion)
|
rg.POST("/create_version", app.jwtService.Handler(true), app.CreateVersion)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
package problem
|
package problem
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
|
_ "git.0x7f.app/WOJ/woj-server/internal/model" // swag requires this
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -10,16 +11,16 @@ type searchRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Search
|
// Search
|
||||||
// @Summary get detail of a problem
|
// @Summary search for problems
|
||||||
// @Description get detail of a problem
|
// @Description Search for problems based on keywords. If the keyword is empty, return all problems.
|
||||||
|
// @Tags problem
|
||||||
// @Accept application/x-www-form-urlencoded
|
// @Accept application/x-www-form-urlencoded
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param search formData string false "word search"
|
// @Param search formData string false "keyword"
|
||||||
// @Response 200 {object} e.Response "problemset"
|
// @Response 200 {object} e.Response[[]model.Problem] "problems found"
|
||||||
// @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)
|
||||||
|
|
||||||
if err := c.ShouldBind(req); err != nil {
|
if err := c.ShouldBind(req); err != nil {
|
||||||
e.Pong(c, e.InvalidParameter, err.Error())
|
e.Pong(c, e.InvalidParameter, err.Error())
|
||||||
return
|
return
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
package problem
|
package problem
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/internal/global"
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
"github.com/WHUPRJ/woj-server/internal/model"
|
"git.0x7f.app/WOJ/woj-server/internal/service/problem"
|
||||||
"github.com/WHUPRJ/woj-server/internal/service/problem"
|
"git.0x7f.app/WOJ/woj-server/pkg/utils"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -16,28 +16,31 @@ type updateRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update
|
// Update
|
||||||
// @Summary create or update a problem
|
// @Summary [admin] create or update a problem
|
||||||
// @Description create or update a problem
|
// @Description Create or update a problem.
|
||||||
|
// @Tags problem,admin
|
||||||
// @Accept application/x-www-form-urlencoded
|
// @Accept application/x-www-form-urlencoded
|
||||||
// @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 statement formData string true "statement"
|
// @Param statement formData string true "statement"
|
||||||
// @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[model.Problem] "problem info without provider information"
|
||||||
// @Security Authentication
|
// @Security Authentication
|
||||||
// @Router /v1/problem/update [post]
|
// @Router /v1/problem/update [post]
|
||||||
func (h *handler) Update(c *gin.Context) {
|
func (h *handler) Update(c *gin.Context) {
|
||||||
claim, exist := c.Get("claim")
|
claim, exist := c.Get("claim")
|
||||||
if !exist {
|
if !exist {
|
||||||
e.Pong(c, e.UserUnauthenticated, nil)
|
e.Pong[any](c, e.UserUnauthenticated, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
uid := claim.(*global.Claim).UID
|
uid := claim.(*model.Claim).UID
|
||||||
role := claim.(*global.Claim).Role
|
role := claim.(*model.Claim).Role
|
||||||
|
|
||||||
|
// only admin can modify problem
|
||||||
if role < model.RoleAdmin {
|
if role < model.RoleAdmin {
|
||||||
e.Pong(c, e.UserUnauthorized, nil)
|
e.Pong[any](c, e.UserUnauthorized, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,6 +51,7 @@ func (h *handler) Update(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if req.Pid == 0 {
|
if req.Pid == 0 {
|
||||||
|
// create problem
|
||||||
createData := &problem.CreateData{
|
createData := &problem.CreateData{
|
||||||
Title: req.Title,
|
Title: req.Title,
|
||||||
Statement: req.Statement,
|
Statement: req.Statement,
|
||||||
@ -58,18 +62,24 @@ func (h *handler) Update(c *gin.Context) {
|
|||||||
e.Pong(c, status, p)
|
e.Pong(c, status, p)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
|
// update problem
|
||||||
|
|
||||||
|
// check if problem exists
|
||||||
p, status := h.problemService.Query(req.Pid, true, false)
|
p, status := h.problemService.Query(req.Pid, true, false)
|
||||||
if status != e.Success {
|
if status != e.Success {
|
||||||
e.Pong(c, status, nil)
|
e.Pong[any](c, status, nil)
|
||||||
return
|
|
||||||
}
|
|
||||||
if p.ProviderID != uid {
|
|
||||||
e.Pong(c, e.UserUnauthorized, nil)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
p.Title = req.Title
|
// check if user is the provider of the problem
|
||||||
p.Statement = req.Statement
|
if p.ProviderID != uid {
|
||||||
|
e.Pong[any](c, e.UserUnauthorized, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// update problem
|
||||||
|
p.Title = utils.If(req.Title != "", req.Title, p.Title)
|
||||||
|
p.Statement = utils.If(req.Statement != "", req.Statement, p.Statement)
|
||||||
p.IsEnabled = req.IsEnabled
|
p.IsEnabled = req.IsEnabled
|
||||||
|
|
||||||
p, status = h.problemService.Update(p)
|
p, status = h.problemService.Update(p)
|
||||||
|
@ -1,44 +1,47 @@
|
|||||||
package problem
|
package problem
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/internal/global"
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
"github.com/WHUPRJ/woj-server/internal/model"
|
"git.0x7f.app/WOJ/woj-server/pkg/utils"
|
||||||
"github.com/WHUPRJ/woj-server/pkg/utils"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type uploadResponse struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
// Upload
|
// Upload
|
||||||
// @Summary get upload url
|
// @Summary [admin] get upload url
|
||||||
// @Description get upload url
|
// @Description Retrieve a pre-signed upload URL from the object storage
|
||||||
|
// @Tags problem,admin
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Response 200 {object} e.Response "upload url and key"
|
// @Response 200 {object} e.Response[uploadResponse] "upload url and key"
|
||||||
// @Security Authentication
|
// @Security Authentication
|
||||||
// @Router /v1/problem/upload [post]
|
// @Router /v1/problem/upload [post]
|
||||||
func (h *handler) Upload(c *gin.Context) {
|
func (h *handler) Upload(c *gin.Context) {
|
||||||
claim, exist := c.Get("claim")
|
claim, exist := c.Get("claim")
|
||||||
if !exist {
|
if !exist {
|
||||||
e.Pong(c, e.UserUnauthenticated, nil)
|
e.Pong[any](c, e.UserUnauthenticated, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
role := claim.(*global.Claim).Role
|
// only admin can upload
|
||||||
|
role := claim.(*model.Claim).Role
|
||||||
if role < model.RoleAdmin {
|
if role < model.RoleAdmin {
|
||||||
e.Pong(c, e.UserUnauthorized, nil)
|
e.Pong[any](c, e.UserUnauthorized, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// generate random key
|
||||||
key := utils.RandomString(16)
|
key := utils.RandomString(16)
|
||||||
url, status := h.storageService.Upload(key, time.Second*60*60)
|
url, status := h.storageService.Upload(key, time.Second*60*60)
|
||||||
|
|
||||||
if status != e.Success {
|
if status != e.Success {
|
||||||
e.Pong(c, status, nil)
|
e.Pong[any](c, status, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
e.Pong(c, e.Success, gin.H{
|
e.Pong(c, e.Success, uploadResponse{Key: key, URL: url})
|
||||||
"key": key,
|
|
||||||
"url": url,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/internal/model"
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
"github.com/hibiken/asynq"
|
"github.com/hibiken/asynq"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"time"
|
"time"
|
||||||
@ -31,6 +31,7 @@ func (h *handler) Build(_ context.Context, t *asynq.Task) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i := range config.Languages {
|
for i := range config.Languages {
|
||||||
|
// do not store in db
|
||||||
config.Languages[i].Type = ""
|
config.Languages[i].Type = ""
|
||||||
config.Languages[i].Script = ""
|
config.Languages[i].Script = ""
|
||||||
config.Languages[i].Cmp = ""
|
config.Languages[i].Cmp = ""
|
||||||
|
@ -3,12 +3,13 @@ package runner
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/internal/global"
|
"git.0x7f.app/WOJ/woj-server/internal/misc/log"
|
||||||
"github.com/WHUPRJ/woj-server/internal/service/runner"
|
"git.0x7f.app/WOJ/woj-server/internal/service/runner"
|
||||||
"github.com/WHUPRJ/woj-server/internal/service/storage"
|
"git.0x7f.app/WOJ/woj-server/internal/service/storage"
|
||||||
"github.com/WHUPRJ/woj-server/internal/service/task"
|
"git.0x7f.app/WOJ/woj-server/internal/service/task"
|
||||||
"github.com/hibiken/asynq"
|
"github.com/hibiken/asynq"
|
||||||
|
"github.com/samber/do"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -26,17 +27,17 @@ type handler struct {
|
|||||||
storageService storage.Service
|
storageService storage.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRunner(g *global.Global) (Handler, error) {
|
func NewRunner(i *do.Injector) (Handler, error) {
|
||||||
hnd := &handler{
|
hnd := &handler{
|
||||||
log: g.Log,
|
log: do.MustInvoke[log.Service](i).GetLogger("api.runner"),
|
||||||
runnerService: runner.NewService(g),
|
runnerService: do.MustInvoke[runner.Service](i),
|
||||||
taskService: task.NewService(g),
|
taskService: do.MustInvoke[task.Service](i),
|
||||||
storageService: storage.NewService(g),
|
storageService: do.MustInvoke[storage.Service](i),
|
||||||
}
|
}
|
||||||
|
|
||||||
status := hnd.runnerService.EnsureDeps(false)
|
status := hnd.runnerService.EnsureDeps(false)
|
||||||
if status != e.Success {
|
if status != e.Success {
|
||||||
g.Log.Error("failed to ensure runner dependencies", zap.String("status", status.String()))
|
hnd.log.Error("failed to ensure runner dependencies", zap.String("status", status.String()))
|
||||||
return nil, errors.New("failed to ensure dependencies")
|
return nil, errors.New("failed to ensure dependencies")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,10 +4,10 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/internal/model"
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
"github.com/WHUPRJ/woj-server/internal/service/runner"
|
"git.0x7f.app/WOJ/woj-server/internal/service/runner"
|
||||||
"github.com/WHUPRJ/woj-server/pkg/utils"
|
"git.0x7f.app/WOJ/woj-server/pkg/utils"
|
||||||
"github.com/hibiken/asynq"
|
"github.com/hibiken/asynq"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -63,7 +63,7 @@ func (h *handler) Judge(_ context.Context, t *asynq.Task) error {
|
|||||||
|
|
||||||
// 5. run and judge
|
// 5. run and judge
|
||||||
result, point, status := h.runnerService.RunAndJudge(p.ProblemVersionID, user, p.Submission.Language, &config)
|
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
|
return utils.If(status != e.Success, e.InternalError, e.Success), point, result
|
||||||
}()
|
}()
|
||||||
|
|
||||||
h.taskService.SubmitUpdate(&model.SubmitUpdatePayload{
|
h.taskService.SubmitUpdate(&model.SubmitUpdatePayload{
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
package status
|
package status
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/internal/global"
|
"git.0x7f.app/WOJ/woj-server/internal/misc/log"
|
||||||
"github.com/WHUPRJ/woj-server/internal/service/status"
|
"git.0x7f.app/WOJ/woj-server/internal/service/status"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/service/submission"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/web/jwt"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/samber/do"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -11,22 +14,26 @@ var _ Handler = (*handler)(nil)
|
|||||||
|
|
||||||
type Handler interface {
|
type Handler interface {
|
||||||
Query(c *gin.Context)
|
Query(c *gin.Context)
|
||||||
|
QueryBySubmissionID(c *gin.Context)
|
||||||
QueryByProblemVersion(c *gin.Context)
|
QueryByProblemVersion(c *gin.Context)
|
||||||
}
|
}
|
||||||
|
|
||||||
type handler struct {
|
type handler struct {
|
||||||
log *zap.Logger
|
log *zap.Logger
|
||||||
statusService status.Service
|
statusService status.Service
|
||||||
jwtService global.JwtService
|
submissionService submission.Service
|
||||||
|
jwtService jwt.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func RouteRegister(g *global.Global, group *gin.RouterGroup) {
|
func RouteRegister(rg *gin.RouterGroup, i *do.Injector) {
|
||||||
app := &handler{
|
app := &handler{
|
||||||
log: g.Log,
|
log: do.MustInvoke[log.Service](i).GetLogger("api.status"),
|
||||||
statusService: status.NewService(g),
|
submissionService: do.MustInvoke[submission.Service](i),
|
||||||
jwtService: g.Jwt,
|
statusService: do.MustInvoke[status.Service](i),
|
||||||
|
jwtService: do.MustInvoke[jwt.Service](i),
|
||||||
}
|
}
|
||||||
|
|
||||||
group.POST("/query", app.Query)
|
rg.POST("/query", app.jwtService.Handler(true), app.Query)
|
||||||
group.POST("/query/problem_version", app.jwtService.Handler(true), app.QueryByProblemVersion)
|
rg.POST("/query/submission", app.jwtService.Handler(true), app.QueryBySubmissionID)
|
||||||
|
rg.POST("/query/version", app.jwtService.Handler(true), app.QueryByProblemVersion)
|
||||||
}
|
}
|
||||||
|
@ -1,31 +1,75 @@
|
|||||||
package status
|
package status
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/pkg/utils"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type queryRequest struct {
|
type queryRequest struct {
|
||||||
SubmissionID uint `form:"sid" binding:"required"`
|
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
|
// Query
|
||||||
// @Summary query submissions by via submission id
|
// @Summary query status via problem id or user id
|
||||||
// @Description query submissions by via submission id
|
// @Description Batch query judgement status based on either the question or user.
|
||||||
|
// @Tags status
|
||||||
// @Accept application/x-www-form-urlencoded
|
// @Accept application/x-www-form-urlencoded
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param sid formData uint true "submission id"
|
// @Param pid formData uint false "problem id"
|
||||||
// @Response 200 {object} e.Response "model.status"
|
// @Param uid formData uint false "user id"
|
||||||
|
// @Param offset formData int false "start position"
|
||||||
|
// @Param limit formData int true "limit number of records"
|
||||||
|
// @Response 200 {object} e.Response[[]queryResponse] "queryResponse"
|
||||||
// @Router /v1/status/query [post]
|
// @Router /v1/status/query [post]
|
||||||
func (h *handler) Query(c *gin.Context) {
|
func (h *handler) Query(c *gin.Context) {
|
||||||
req := new(queryRequest)
|
claim, exist := c.Get("claim")
|
||||||
|
if !exist {
|
||||||
|
e.Pong[any](c, e.UserUnauthenticated, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req := new(queryRequest)
|
||||||
if err := c.ShouldBind(req); err != nil {
|
if err := c.ShouldBind(req); err != nil {
|
||||||
e.Pong(c, e.InvalidParameter, err.Error())
|
e.Pong(c, e.InvalidParameter, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
status, eStatus := h.statusService.Query(req.SubmissionID, true)
|
if req.Pid == 0 && req.Uid == 0 {
|
||||||
|
e.Pong[any](c, e.InvalidParameter, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
e.Pong(c, eStatus, status)
|
submissions, status := h.submissionService.Query(req.Pid, req.Uid, req.Offset, req.Limit)
|
||||||
|
|
||||||
|
uid := claim.(*model.Claim).UID
|
||||||
|
role := claim.(*model.Claim).Role
|
||||||
|
var response []*queryResponse
|
||||||
|
|
||||||
|
for _, submission := range submissions {
|
||||||
|
cur, _ := h.statusService.Query(submission.ID, false)
|
||||||
|
point := utils.If(cur == nil, -1, cur.Point)
|
||||||
|
resp := &queryResponse{
|
||||||
|
Submission: *submission,
|
||||||
|
Point: point,
|
||||||
|
}
|
||||||
|
|
||||||
|
if role < model.RoleAdmin || uid != submission.UserID {
|
||||||
|
// strip out code
|
||||||
|
resp.Submission.Code = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
response = append(response, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Pong(c, status, response)
|
||||||
}
|
}
|
||||||
|
@ -1,51 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
50
internal/api/status/query_one.go
Normal file
50
internal/api/status/query_one.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package status
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type queryOneRequest struct {
|
||||||
|
SubmissionID uint `form:"sid" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryBySubmissionID
|
||||||
|
// @Summary query status via submission id
|
||||||
|
// @Description Query the detailed results of the judgement based on the submission ID.
|
||||||
|
// @Tags status
|
||||||
|
// @Accept application/x-www-form-urlencoded
|
||||||
|
// @Produce json
|
||||||
|
// @Param sid formData uint true "submission id"
|
||||||
|
// @Response 200 {object} e.Response[model.Status] "submission status"
|
||||||
|
// @Router /v1/status/query/submission [post]
|
||||||
|
func (h *handler) QueryBySubmissionID(c *gin.Context) {
|
||||||
|
claim, exist := c.Get("claim")
|
||||||
|
if !exist {
|
||||||
|
e.Pong[any](c, e.UserUnauthenticated, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req := new(queryOneRequest)
|
||||||
|
if err := c.ShouldBind(req); err != nil {
|
||||||
|
e.Pong(c, e.InvalidParameter, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// query status
|
||||||
|
submitStatus, status := h.statusService.Query(req.SubmissionID, true)
|
||||||
|
|
||||||
|
// check permission
|
||||||
|
role := claim.(*model.Claim).Role
|
||||||
|
if role >= model.RoleAdmin || submitStatus.Submission.UserID == claim.(*model.Claim).UID {
|
||||||
|
// full status
|
||||||
|
e.Pong(c, status, submitStatus)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
// strip out code
|
||||||
|
submitStatus.Submission.Code = ""
|
||||||
|
e.Pong(c, status, submitStatus)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
49
internal/api/status/query_version.go
Normal file
49
internal/api/status/query_version.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package status
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
|
"git.0x7f.app/WOJ/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 [admin] query status by problem version
|
||||||
|
// @Description Retrieve all judgement results corresponding to the problem version.
|
||||||
|
// @Tags status,admin
|
||||||
|
// @Accept application/x-www-form-urlencoded
|
||||||
|
// @Produce json
|
||||||
|
// @Param pvid formData uint true "problem version"
|
||||||
|
// @Param offset formData int false "start position"
|
||||||
|
// @Param limit formData int true "max number of results"
|
||||||
|
// @Response 200 {object} e.Response[[]model.Status] "submission status array"
|
||||||
|
// @Security Authentication
|
||||||
|
// @Router /v1/status/query/version [post]
|
||||||
|
func (h *handler) QueryByProblemVersion(c *gin.Context) {
|
||||||
|
claim, exist := c.Get("claim")
|
||||||
|
if !exist {
|
||||||
|
e.Pong[any](c, e.UserUnauthenticated, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req := new(queryByVersionRequest)
|
||||||
|
if err := c.ShouldBind(req); err != nil {
|
||||||
|
e.Pong(c, e.InvalidParameter, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// check permission
|
||||||
|
role := claim.(*model.Claim).Role
|
||||||
|
if role < model.RoleAdmin {
|
||||||
|
e.Pong[any](c, e.UserUnauthorized, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
submitStatus, status := h.statusService.QueryByVersion(req.ProblemVersionID, req.Offset, req.Limit)
|
||||||
|
e.Pong(c, status, submitStatus)
|
||||||
|
}
|
@ -1,10 +1,9 @@
|
|||||||
package submission
|
package submission
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/internal/global"
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
"github.com/WHUPRJ/woj-server/internal/model"
|
"git.0x7f.app/WOJ/woj-server/internal/service/submission"
|
||||||
"github.com/WHUPRJ/woj-server/internal/service/submission"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,63 +14,66 @@ type createRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create
|
// Create
|
||||||
// @Summary create a submission
|
// @Summary submit for judgement
|
||||||
// @Description create a submission
|
// @Description Submit the code for judgement.
|
||||||
|
// @Tags submission
|
||||||
// @Accept application/x-www-form-urlencoded
|
// @Accept application/x-www-form-urlencoded
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param pid formData int true "problem id"
|
// @Param pid formData int true "problem id"
|
||||||
// @Param language formData string true "language"
|
// @Param language formData string true "language"
|
||||||
// @Param code formData string true "code"
|
// @Param code formData string true "code"
|
||||||
// @Response 200 {object} e.Response ""
|
// @Response 200 {object} e.Response[uint] "submission id"
|
||||||
// @Security Authentication
|
// @Security Authentication
|
||||||
// @Router /v1/submission/create [post]
|
// @Router /v1/submission/create [post]
|
||||||
func (h *handler) Create(c *gin.Context) {
|
func (h *handler) Create(c *gin.Context) {
|
||||||
claim, exist := c.Get("claim")
|
claim, exist := c.Get("claim")
|
||||||
if !exist {
|
if !exist {
|
||||||
e.Pong(c, e.UserUnauthenticated, nil)
|
e.Pong[any](c, e.UserUnauthenticated, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
uid := claim.(*global.Claim).UID
|
uid := claim.(*model.Claim).UID
|
||||||
|
role := claim.(*model.Claim).Role
|
||||||
|
|
||||||
|
// guest can not submit
|
||||||
|
if role < model.RoleGeneral {
|
||||||
|
e.Pong[any](c, e.UserUnauthorized, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
role := claim.(*global.Claim).Role
|
|
||||||
req := new(createRequest)
|
req := new(createRequest)
|
||||||
|
|
||||||
if err := c.ShouldBind(req); err != nil {
|
if err := c.ShouldBind(req); err != nil {
|
||||||
e.Pong(c, e.InvalidParameter, err.Error())
|
e.Pong(c, e.InvalidParameter, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// guest can not submit
|
// create submission
|
||||||
if role < model.RoleGeneral {
|
|
||||||
e.Pong(c, e.UserUnauthorized, nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
createData := &submission.CreateData{
|
createData := &submission.CreateData{
|
||||||
ProblemID: req.Pid,
|
ProblemID: req.Pid,
|
||||||
UserID: uid,
|
UserID: uid,
|
||||||
Language: req.Language,
|
Language: req.Language,
|
||||||
Code: req.Code,
|
Code: req.Code,
|
||||||
}
|
}
|
||||||
s, status := h.submissionService.Create(createData)
|
res, status := h.submissionService.Create(createData)
|
||||||
if status != e.Success {
|
if status != e.Success {
|
||||||
e.Pong(c, status, nil)
|
e.Pong[any](c, status, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// query latest version
|
||||||
pv, status := h.problemService.QueryLatestVersion(req.Pid)
|
pv, status := h.problemService.QueryLatestVersion(req.Pid)
|
||||||
if status != e.Success {
|
if status != e.Success {
|
||||||
e.Pong(c, status, nil)
|
e.Pong[any](c, status, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// submit judge
|
||||||
payload := &model.SubmitJudgePayload{
|
payload := &model.SubmitJudgePayload{
|
||||||
ProblemVersionID: pv.ID,
|
ProblemVersionID: pv.ID,
|
||||||
StorageKey: pv.StorageKey,
|
StorageKey: pv.StorageKey,
|
||||||
Submission: *s,
|
Submission: *res,
|
||||||
}
|
}
|
||||||
_, status = h.taskService.SubmitJudge(payload)
|
_, status = h.taskService.SubmitJudge(payload)
|
||||||
|
|
||||||
e.Pong(c, status, nil)
|
e.Pong[any](c, status, res.ID)
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
package submission
|
package submission
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/internal/global"
|
"git.0x7f.app/WOJ/woj-server/internal/misc/log"
|
||||||
"github.com/WHUPRJ/woj-server/internal/service/problem"
|
"git.0x7f.app/WOJ/woj-server/internal/service/problem"
|
||||||
"github.com/WHUPRJ/woj-server/internal/service/status"
|
"git.0x7f.app/WOJ/woj-server/internal/service/status"
|
||||||
"github.com/WHUPRJ/woj-server/internal/service/submission"
|
"git.0x7f.app/WOJ/woj-server/internal/service/submission"
|
||||||
"github.com/WHUPRJ/woj-server/internal/service/task"
|
"git.0x7f.app/WOJ/woj-server/internal/service/task"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/web/jwt"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/samber/do"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -14,30 +16,28 @@ var _ Handler = (*handler)(nil)
|
|||||||
|
|
||||||
type Handler interface {
|
type Handler interface {
|
||||||
Create(c *gin.Context)
|
Create(c *gin.Context)
|
||||||
Query(c *gin.Context)
|
|
||||||
Rejudge(c *gin.Context)
|
Rejudge(c *gin.Context)
|
||||||
}
|
}
|
||||||
|
|
||||||
type handler struct {
|
type handler struct {
|
||||||
log *zap.Logger
|
log *zap.Logger
|
||||||
jwtService global.JwtService
|
jwtService jwt.Service
|
||||||
problemService problem.Service
|
problemService problem.Service
|
||||||
statusService status.Service
|
statusService status.Service
|
||||||
submissionService submission.Service
|
submissionService submission.Service
|
||||||
taskService task.Service
|
taskService task.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func RouteRegister(g *global.Global, group *gin.RouterGroup) {
|
func RouteRegister(rg *gin.RouterGroup, i *do.Injector) {
|
||||||
app := &handler{
|
app := &handler{
|
||||||
log: g.Log,
|
log: do.MustInvoke[log.Service](i).GetLogger("api.submission"),
|
||||||
jwtService: g.Jwt,
|
jwtService: do.MustInvoke[jwt.Service](i),
|
||||||
problemService: problem.NewService(g),
|
problemService: do.MustInvoke[problem.Service](i),
|
||||||
statusService: status.NewService(g),
|
statusService: do.MustInvoke[status.Service](i),
|
||||||
submissionService: submission.NewService(g),
|
submissionService: do.MustInvoke[submission.Service](i),
|
||||||
taskService: task.NewService(g),
|
taskService: do.MustInvoke[task.Service](i),
|
||||||
}
|
}
|
||||||
|
|
||||||
group.POST("/create", app.jwtService.Handler(true), app.Create)
|
rg.POST("/create", app.jwtService.Handler(true), app.Create)
|
||||||
group.POST("/query", app.Query)
|
rg.POST("/rejudge", app.jwtService.Handler(true), app.Rejudge)
|
||||||
group.POST("/rejudge", app.jwtService.Handler(true), app.Rejudge)
|
|
||||||
}
|
}
|
||||||
|
@ -1,71 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
@ -1,9 +1,8 @@
|
|||||||
package submission
|
package submission
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/internal/global"
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
"github.com/WHUPRJ/woj-server/internal/model"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -12,52 +11,55 @@ type rejudgeRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Rejudge
|
// Rejudge
|
||||||
// @Summary rejudge a submission
|
// @Summary [admin] rejudge a specific submission
|
||||||
// @Description rejudge a submission
|
// @Description rejudge a specific submission
|
||||||
|
// @Tags submission,admin
|
||||||
// @Accept application/x-www-form-urlencoded
|
// @Accept application/x-www-form-urlencoded
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param sid formData int true "submission id"
|
// @Param sid formData int true "submission id"
|
||||||
// @Response 200 {object} e.Response ""
|
// @Response 200 {object} e.Response[any] "nothing"
|
||||||
// @Security Authentication
|
// @Security Authentication
|
||||||
// @Router /v1/submission/rejudge [post]
|
// @Router /v1/submission/rejudge [post]
|
||||||
func (h *handler) Rejudge(c *gin.Context) {
|
func (h *handler) Rejudge(c *gin.Context) {
|
||||||
claim, exist := c.Get("claim")
|
claim, exist := c.Get("claim")
|
||||||
if !exist {
|
if !exist {
|
||||||
e.Pong(c, e.UserUnauthenticated, nil)
|
e.Pong[any](c, e.UserUnauthenticated, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
role := claim.(*global.Claim).Role
|
|
||||||
req := new(rejudgeRequest)
|
req := new(rejudgeRequest)
|
||||||
|
|
||||||
if err := c.ShouldBind(req); err != nil {
|
if err := c.ShouldBind(req); err != nil {
|
||||||
e.Pong(c, e.InvalidParameter, err.Error())
|
e.Pong(c, e.InvalidParameter, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// only admin can rejudge
|
// only admin can rejudge
|
||||||
|
role := claim.(*model.Claim).Role
|
||||||
if role < model.RoleAdmin {
|
if role < model.RoleAdmin {
|
||||||
e.Pong(c, e.UserUnauthorized, nil)
|
e.Pong[any](c, e.UserUnauthorized, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// query submission
|
||||||
s, status := h.submissionService.QueryBySid(req.Sid, false)
|
s, status := h.submissionService.QueryBySid(req.Sid, false)
|
||||||
if status != e.Success {
|
if status != e.Success {
|
||||||
e.Pong(c, status, nil)
|
e.Pong[any](c, status, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// query latest problem version
|
||||||
pv, status := h.problemService.QueryLatestVersion(s.ProblemID)
|
pv, status := h.problemService.QueryLatestVersion(s.ProblemID)
|
||||||
if status != e.Success {
|
if status != e.Success {
|
||||||
e.Pong(c, status, nil)
|
e.Pong[any](c, status, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// submit judge
|
||||||
_, status = h.taskService.SubmitJudge(&model.SubmitJudgePayload{
|
_, status = h.taskService.SubmitJudge(&model.SubmitJudgePayload{
|
||||||
ProblemVersionID: pv.ID,
|
ProblemVersionID: pv.ID,
|
||||||
StorageKey: pv.StorageKey,
|
StorageKey: pv.StorageKey,
|
||||||
Submission: *s,
|
Submission: *s,
|
||||||
})
|
})
|
||||||
|
|
||||||
e.Pong(c, status, nil)
|
e.Pong[any](c, status, nil)
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package user
|
package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/internal/global"
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
"github.com/WHUPRJ/woj-server/internal/service/user"
|
"git.0x7f.app/WOJ/woj-server/internal/service/user"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -16,21 +16,22 @@ type createRequest struct {
|
|||||||
// Create
|
// Create
|
||||||
// @Summary create a new user
|
// @Summary create a new user
|
||||||
// @Description create a new user
|
// @Description create a new user
|
||||||
|
// @Tags user
|
||||||
// @Accept application/x-www-form-urlencoded
|
// @Accept application/x-www-form-urlencoded
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param username formData string true "username"
|
// @Param username formData string true "username"
|
||||||
// @Param nickname formData string true "nickname"
|
// @Param nickname formData string true "nickname"
|
||||||
// @Param password formData string true "password"
|
// @Param password formData string true "password"
|
||||||
// @Response 200 {object} e.Response "jwt token"
|
// @Response 200 {object} e.Response[string] "jwt token"
|
||||||
// @Router /v1/user/create [post]
|
// @Router /v1/user/create [post]
|
||||||
func (h *handler) Create(c *gin.Context) {
|
func (h *handler) Create(c *gin.Context) {
|
||||||
req := new(createRequest)
|
req := new(createRequest)
|
||||||
|
|
||||||
if err := c.ShouldBind(req); err != nil {
|
if err := c.ShouldBind(req); err != nil {
|
||||||
e.Pong(c, e.InvalidParameter, err.Error())
|
e.Pong(c, e.InvalidParameter, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// create user
|
||||||
createData := &user.CreateData{
|
createData := &user.CreateData{
|
||||||
UserName: req.UserName,
|
UserName: req.UserName,
|
||||||
Password: req.Password,
|
Password: req.Password,
|
||||||
@ -38,17 +39,19 @@ func (h *handler) Create(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
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[any](c, status, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update version in cache
|
||||||
version, status := h.userService.IncrVersion(u.ID)
|
version, status := h.userService.IncrVersion(u.ID)
|
||||||
if status != e.Success {
|
if status != e.Success {
|
||||||
e.Pong(c, status, nil)
|
e.Pong[any](c, status, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
claim := &global.Claim{
|
// sign jwt token
|
||||||
|
claim := &model.Claim{
|
||||||
UID: u.ID,
|
UID: u.ID,
|
||||||
Role: u.Role,
|
Role: u.Role,
|
||||||
Version: version,
|
Version: version,
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
package user
|
package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/internal/global"
|
"git.0x7f.app/WOJ/woj-server/internal/misc/log"
|
||||||
"github.com/WHUPRJ/woj-server/internal/service/user"
|
"git.0x7f.app/WOJ/woj-server/internal/service/user"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/web/jwt"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/samber/do"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -18,19 +20,19 @@ type Handler interface {
|
|||||||
|
|
||||||
type handler struct {
|
type handler struct {
|
||||||
log *zap.Logger
|
log *zap.Logger
|
||||||
jwtService global.JwtService
|
jwtService jwt.Service
|
||||||
userService user.Service
|
userService user.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func RouteRegister(g *global.Global, group *gin.RouterGroup) {
|
func RouteRegister(rg *gin.RouterGroup, i *do.Injector) {
|
||||||
app := &handler{
|
app := &handler{
|
||||||
log: g.Log,
|
log: do.MustInvoke[log.Service](i).GetLogger("api.user"),
|
||||||
jwtService: g.Jwt,
|
jwtService: do.MustInvoke[jwt.Service](i),
|
||||||
userService: user.NewService(g),
|
userService: do.MustInvoke[user.Service](i),
|
||||||
}
|
}
|
||||||
|
|
||||||
group.POST("/create", app.Create)
|
rg.POST("/create", app.Create)
|
||||||
group.POST("/login", app.Login)
|
rg.POST("/login", app.Login)
|
||||||
group.POST("/logout", app.jwtService.Handler(true), app.Logout)
|
rg.POST("/logout", app.jwtService.Handler(true), app.Logout)
|
||||||
group.POST("/profile", app.jwtService.Handler(true), app.Profile)
|
rg.POST("/profile", app.jwtService.Handler(true), app.Profile)
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package user
|
package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/internal/global"
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
"github.com/WHUPRJ/woj-server/internal/service/user"
|
"git.0x7f.app/WOJ/woj-server/internal/service/user"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -12,20 +12,25 @@ type loginRequest struct {
|
|||||||
Password string `form:"password" binding:"required"`
|
Password string `form:"password" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type loginResponse struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
NickName string `json:"nickname"`
|
||||||
|
}
|
||||||
|
|
||||||
// Login
|
// Login
|
||||||
// @Summary login
|
// @Summary login
|
||||||
// @Description login and return token
|
// @Description login and return token
|
||||||
|
// @Tags user
|
||||||
// @Accept application/x-www-form-urlencoded
|
// @Accept application/x-www-form-urlencoded
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param username formData string true "username"
|
// @Param username formData string true "username"
|
||||||
// @Param password formData string true "password"
|
// @Param password formData string true "password"
|
||||||
// @Response 200 {object} e.Response "jwt token and user nickname"
|
// @Response 200 {object} e.Response[loginResponse] "jwt token and user's nickname"
|
||||||
// @Router /v1/user/login [post]
|
// @Router /v1/user/login [post]
|
||||||
func (h *handler) Login(c *gin.Context) {
|
func (h *handler) Login(c *gin.Context) {
|
||||||
req := new(loginRequest)
|
req := new(loginRequest)
|
||||||
|
|
||||||
if err := c.ShouldBind(req); err != nil {
|
if err := c.ShouldBind(req); err != nil {
|
||||||
e.Pong(c, e.InvalidParameter, nil)
|
e.Pong(c, e.InvalidParameter, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,24 +41,21 @@ func (h *handler) Login(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
u, status := h.userService.Login(loginData)
|
u, status := h.userService.Login(loginData)
|
||||||
if status != e.Success {
|
if status != e.Success {
|
||||||
e.Pong(c, status, nil)
|
e.Pong[any](c, status, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// sign and return token
|
// sign and return token
|
||||||
version, status := h.userService.IncrVersion(u.ID)
|
version, status := h.userService.IncrVersion(u.ID)
|
||||||
if status != e.Success {
|
if status != e.Success {
|
||||||
e.Pong(c, status, nil)
|
e.Pong[any](c, status, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
claim := &global.Claim{
|
claim := &model.Claim{
|
||||||
UID: u.ID,
|
UID: u.ID,
|
||||||
Role: u.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, loginResponse{Token: token, NickName: u.NickName})
|
||||||
"token": token,
|
|
||||||
"nickname": u.NickName,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,27 @@
|
|||||||
package user
|
package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/internal/global"
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Logout
|
// Logout
|
||||||
// @Summary logout
|
// @Summary logout
|
||||||
// @Description logout
|
// @Description logout
|
||||||
|
// @Tags user
|
||||||
// @Accept application/x-www-form-urlencoded
|
// @Accept application/x-www-form-urlencoded
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Response 200 {object} e.Response "nil"
|
// @Response 200 {object} e.Response[any] "nothing"
|
||||||
// @Security Authentication
|
// @Security Authentication
|
||||||
// @Router /v1/user/logout [post]
|
// @Router /v1/user/logout [post]
|
||||||
func (h *handler) Logout(c *gin.Context) {
|
func (h *handler) Logout(c *gin.Context) {
|
||||||
claim, exist := c.Get("claim")
|
claim, exist := c.Get("claim")
|
||||||
if !exist {
|
if !exist {
|
||||||
e.Pong(c, e.UserUnauthenticated, nil)
|
e.Pong[any](c, e.UserUnauthenticated, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, status := h.userService.IncrVersion(claim.(*global.Claim).UID)
|
_, status := h.userService.IncrVersion(claim.(*model.Claim).UID)
|
||||||
e.Pong(c, status, nil)
|
e.Pong[any](c, status, nil)
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package user
|
package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/internal/global"
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
"github.com/WHUPRJ/woj-server/internal/model"
|
"git.0x7f.app/WOJ/woj-server/pkg/utils"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -14,41 +14,39 @@ type profileRequest struct {
|
|||||||
// Profile
|
// Profile
|
||||||
// @Summary profile
|
// @Summary profile
|
||||||
// @Description fetch user profile
|
// @Description fetch user profile
|
||||||
|
// @Tags user
|
||||||
// @Accept application/x-www-form-urlencoded
|
// @Accept application/x-www-form-urlencoded
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param uid formData int false "user id"
|
// @Param uid formData int false "user id"
|
||||||
// @Response 200 {object} e.Response "user info"
|
// @Response 200 {object} e.Response[model.User] "user info"
|
||||||
// @Security Authentication
|
// @Security Authentication
|
||||||
// @Router /v1/user/profile [post]
|
// @Router /v1/user/profile [post]
|
||||||
func (h *handler) Profile(c *gin.Context) {
|
func (h *handler) Profile(c *gin.Context) {
|
||||||
// TODO: create a new struct for profile (user info & solve info)
|
|
||||||
|
|
||||||
claim, exist := c.Get("claim")
|
claim, exist := c.Get("claim")
|
||||||
if !exist {
|
if !exist {
|
||||||
e.Pong(c, e.UserUnauthenticated, nil)
|
e.Pong[any](c, e.UserUnauthenticated, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
uid := claim.(*global.Claim).UID
|
uid := claim.(*model.Claim).UID
|
||||||
role := claim.(*global.Claim).Role
|
role := claim.(*model.Claim).Role
|
||||||
|
|
||||||
req := new(profileRequest)
|
req := new(profileRequest)
|
||||||
|
|
||||||
if err := c.ShouldBind(req); err != nil {
|
if err := c.ShouldBind(req); err != nil {
|
||||||
e.Pong(c, e.InvalidParameter, nil)
|
e.Pong(c, e.InvalidParameter, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.UID == 0 {
|
user, status := h.userService.Profile(utils.If(req.UID == 0, uid, req.UID))
|
||||||
req.UID = uid
|
if status != e.Success {
|
||||||
} else if req.UID != uid && role < model.RoleGeneral {
|
e.Pong[any](c, status, nil)
|
||||||
e.Pong(c, e.UserUnauthorized, nil)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, status := h.userService.Profile(req.UID)
|
if role < model.RoleAdmin && user.ID != uid {
|
||||||
|
e.Pong[any](c, e.UserUnauthorized, nil)
|
||||||
// TODO: >= admin can see is_enable
|
return
|
||||||
|
}
|
||||||
|
|
||||||
e.Pong(c, status, user)
|
e.Pong(c, status, user)
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,24 @@
|
|||||||
package runner
|
package runner
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/internal/api/runner"
|
"fmt"
|
||||||
"github.com/WHUPRJ/woj-server/internal/global"
|
"git.0x7f.app/WOJ/woj-server/internal/api/runner"
|
||||||
"github.com/WHUPRJ/woj-server/internal/model"
|
"git.0x7f.app/WOJ/woj-server/internal/misc/config"
|
||||||
"github.com/WHUPRJ/woj-server/pkg/utils"
|
"git.0x7f.app/WOJ/woj-server/internal/misc/log"
|
||||||
"github.com/WHUPRJ/woj-server/pkg/zapasynq"
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/pkg/utils"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/pkg/zapasynq"
|
||||||
"github.com/hibiken/asynq"
|
"github.com/hibiken/asynq"
|
||||||
|
"github.com/samber/do"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"runtime"
|
"runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RunRunner(g *global.Global) error {
|
func RunRunner(i *do.Injector) error {
|
||||||
hnd, err := runner.NewRunner(g)
|
conf := do.MustInvoke[config.Service](i).GetConfig()
|
||||||
|
rlog := do.MustInvoke[log.Service](i).GetLogger("app.runner")
|
||||||
|
|
||||||
|
hnd, err := runner.NewRunner(i)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -23,19 +29,19 @@ func RunRunner(g *global.Global) error {
|
|||||||
|
|
||||||
srv := asynq.NewServer(
|
srv := asynq.NewServer(
|
||||||
asynq.RedisClientOpt{
|
asynq.RedisClientOpt{
|
||||||
Addr: g.Conf.Redis.Address,
|
Addr: fmt.Sprintf("%s:%d", conf.Redis.Address, conf.Redis.Port),
|
||||||
Password: g.Conf.Redis.Password,
|
Password: conf.Redis.Password,
|
||||||
DB: g.Conf.Redis.QueueDb,
|
DB: conf.Redis.QueueDb,
|
||||||
},
|
},
|
||||||
asynq.Config{
|
asynq.Config{
|
||||||
Concurrency: utils.If(runtime.NumCPU() > 1, runtime.NumCPU()-1, 1).(int),
|
Concurrency: utils.If(runtime.NumCPU() > 1, runtime.NumCPU()-1, 1),
|
||||||
Logger: zapasynq.New(g.Log),
|
Logger: zapasynq.New(rlog),
|
||||||
Queues: map[string]int{model.QueueRunner: 1},
|
Queues: map[string]int{model.QueueRunner: 1},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := srv.Run(mux); err != nil {
|
if err := srv.Run(mux); err != nil {
|
||||||
g.Log.Warn("could not run server", zap.Error(err))
|
rlog.Warn("could not run server", zap.Error(err))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,17 +2,18 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/WHUPRJ/woj-server/internal/api/consumer"
|
"git.0x7f.app/WOJ/woj-server/internal/api/consumer"
|
||||||
"github.com/WHUPRJ/woj-server/internal/global"
|
"git.0x7f.app/WOJ/woj-server/internal/misc/config"
|
||||||
"github.com/WHUPRJ/woj-server/internal/model"
|
"git.0x7f.app/WOJ/woj-server/internal/misc/log"
|
||||||
"github.com/WHUPRJ/woj-server/internal/repo/postgresql"
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
"github.com/WHUPRJ/woj-server/internal/repo/redis"
|
"git.0x7f.app/WOJ/woj-server/internal/repo/db"
|
||||||
"github.com/WHUPRJ/woj-server/internal/router"
|
"git.0x7f.app/WOJ/woj-server/internal/web/router"
|
||||||
"github.com/WHUPRJ/woj-server/internal/service/jwt"
|
"git.0x7f.app/WOJ/woj-server/pkg/utils"
|
||||||
"github.com/WHUPRJ/woj-server/pkg/utils"
|
"git.0x7f.app/WOJ/woj-server/pkg/zapasynq"
|
||||||
"github.com/WHUPRJ/woj-server/pkg/zapasynq"
|
|
||||||
"github.com/hibiken/asynq"
|
"github.com/hibiken/asynq"
|
||||||
|
"github.com/samber/do"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@ -22,23 +23,27 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RunServer(g *global.Global) error {
|
func RunServerMigrate(i *do.Injector) error {
|
||||||
// Setup Database
|
slog := do.MustInvoke[log.Service](i).GetLogger("app.server")
|
||||||
g.Db = new(postgresql.Repo)
|
|
||||||
g.Db.Setup(g)
|
|
||||||
|
|
||||||
// Setup Redis
|
// Migrate and shutdown database
|
||||||
g.Redis = new(redis.Repo)
|
err := do.MustInvoke[db.Service](i).Close()
|
||||||
g.Redis.Setup(g)
|
if err != nil {
|
||||||
|
slog.Warn("Database Close Failed", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
// Setup JWT
|
return err
|
||||||
g.Jwt = jwt.NewJwtService(g)
|
}
|
||||||
|
|
||||||
|
func RunServer(i *do.Injector) error {
|
||||||
|
conf := do.MustInvoke[config.Service](i).GetConfig()
|
||||||
|
slog := do.MustInvoke[log.Service](i).GetLogger("app.server")
|
||||||
|
|
||||||
// Prepare Router
|
// Prepare Router
|
||||||
routers := router.InitRouters(g)
|
routers := do.MustInvoke[router.Service](i).GetRouter()
|
||||||
|
|
||||||
// Create Server
|
// Create Server
|
||||||
addr := fmt.Sprintf("%s:%d", g.Conf.WebServer.Address, g.Conf.WebServer.Port)
|
addr := fmt.Sprintf("%s:%d", conf.WebServer.Address, conf.WebServer.Port)
|
||||||
server := &http.Server{
|
server := &http.Server{
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
Handler: routers,
|
Handler: routers,
|
||||||
@ -46,57 +51,57 @@ func RunServer(g *global.Global) error {
|
|||||||
|
|
||||||
// Run Server
|
// Run Server
|
||||||
go func() {
|
go func() {
|
||||||
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
g.Log.Fatal("ListenAndServe Failed", zap.Error(err))
|
slog.Fatal("ListenAndServe Failed", zap.Error(err))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Create Queue
|
// Create Queue
|
||||||
queueMux := asynq.NewServeMux()
|
queueMux := asynq.NewServeMux()
|
||||||
{
|
{
|
||||||
handler := consumer.NewConsumer(g)
|
handler := consumer.NewConsumer(i)
|
||||||
queueMux.HandleFunc(model.TypeProblemUpdate, handler.ProblemUpdate)
|
queueMux.HandleFunc(model.TypeProblemUpdate, handler.ProblemUpdate)
|
||||||
queueMux.HandleFunc(model.TypeSubmitUpdate, handler.SubmitUpdate)
|
queueMux.HandleFunc(model.TypeSubmitUpdate, handler.SubmitUpdate)
|
||||||
}
|
}
|
||||||
queueSrv := asynq.NewServer(
|
queueSrv := asynq.NewServer(
|
||||||
asynq.RedisClientOpt{
|
asynq.RedisClientOpt{
|
||||||
Addr: g.Conf.Redis.Address,
|
Addr: fmt.Sprintf("%s:%d", conf.Redis.Address, conf.Redis.Port),
|
||||||
Password: g.Conf.Redis.Password,
|
Password: conf.Redis.Password,
|
||||||
DB: g.Conf.Redis.QueueDb,
|
DB: conf.Redis.QueueDb,
|
||||||
},
|
},
|
||||||
asynq.Config{
|
asynq.Config{
|
||||||
Concurrency: utils.If(runtime.NumCPU() > 1, runtime.NumCPU()-1, 1).(int),
|
Concurrency: utils.If(runtime.NumCPU() > 1, runtime.NumCPU()-1, 1),
|
||||||
Logger: zapasynq.New(g.Log),
|
Logger: zapasynq.New(slog),
|
||||||
Queues: map[string]int{model.QueueServer: 1},
|
Queues: map[string]int{model.QueueServer: 1},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
// Run Queue
|
// Run Queue
|
||||||
if err := queueSrv.Start(queueMux); err != nil {
|
if err := queueSrv.Start(queueMux); err != nil {
|
||||||
g.Log.Fatal("queueSrv.Start Failed", zap.Error(err))
|
slog.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 ...")
|
slog.Info("Shutting down server ...")
|
||||||
|
|
||||||
// Graceful Shutdown Server
|
// 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))
|
slog.Warn("Server Shutdown Failed", zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Graceful Shutdown Queue
|
// Graceful Shutdown Queue
|
||||||
queueSrv.Shutdown()
|
queueSrv.Shutdown()
|
||||||
|
|
||||||
// Graceful Shutdown Database
|
// Graceful Shutdown Database
|
||||||
err = g.Db.Close()
|
err = do.MustInvoke[db.Service](i).Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
g.Log.Warn("Database Close Failed", zap.Error(err))
|
slog.Warn("Database Close Failed", zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
@ -1,35 +1,26 @@
|
|||||||
package e
|
package e
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/pkg/utils"
|
"git.0x7f.app/WOJ/woj-server/pkg/utils"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Response struct {
|
type Response[T any] struct {
|
||||||
Code int `json:"code"`
|
Code int `json:"code"`
|
||||||
Msg string `json:"msg"`
|
Msg string `json:"msg"`
|
||||||
Body interface{} `json:"body"`
|
Body T `json:"body"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func Wrap(status Status, body interface{}) interface{} {
|
func wrap[T any](status Status, body T) Response[interface{}] {
|
||||||
return Response{
|
return Response[interface{}]{
|
||||||
Code: int(status),
|
Code: int(status),
|
||||||
Msg: status.String(),
|
Msg: status.String(),
|
||||||
Body: utils.If(status == Success, body, nil),
|
Body: utils.If[interface{}](status == Success, body, nil),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Pong(c *gin.Context, status Status, body interface{}) {
|
func Pong[T any](c *gin.Context, status Status, body T) {
|
||||||
c.Set("err", status)
|
c.Set("err", status)
|
||||||
c.JSON(http.StatusOK, Wrap(status, body))
|
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,15 +0,0 @@
|
|||||||
package global
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/WHUPRJ/woj-server/internal/pkg/metrics"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Global struct {
|
|
||||||
Log *zap.Logger
|
|
||||||
Conf *Config
|
|
||||||
Stat *metrics.Metrics
|
|
||||||
Db Repo
|
|
||||||
Redis Repo
|
|
||||||
Jwt JwtService
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
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,7 +0,0 @@
|
|||||||
package global
|
|
||||||
|
|
||||||
type Repo interface {
|
|
||||||
Setup(*Global)
|
|
||||||
Get() interface{}
|
|
||||||
Close() error
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
package global
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/WHUPRJ/woj-server/pkg/utils"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"go.uber.org/zap/zapcore"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
"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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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())
|
|
||||||
}
|
|
||||||
}
|
|
48
internal/misc/config/conf.go
Normal file
48
internal/misc/config/conf.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/pkg/utils"
|
||||||
|
"github.com/samber/do"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Service = (*service)(nil)
|
||||||
|
|
||||||
|
type Service interface {
|
||||||
|
GetConfig() *model.Config
|
||||||
|
HealthCheck() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(i *do.Injector) (Service, error) {
|
||||||
|
cliCtx := do.MustInvoke[*cli.Context](i)
|
||||||
|
|
||||||
|
data, err := utils.FileRead(cliCtx.String("config"))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to setup config: %s\n", err.Error())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
srv := &service{}
|
||||||
|
err = yaml.Unmarshal(data, &srv.conf)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to setup config: %s\n", err.Error())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return srv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type service struct {
|
||||||
|
conf model.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) GetConfig() *model.Config {
|
||||||
|
return &s.conf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) HealthCheck() error {
|
||||||
|
return nil
|
||||||
|
}
|
91
internal/misc/log/zap.go
Normal file
91
internal/misc/log/zap.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.0x7f.app/WOJ/woj-server/cmd"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/misc/config"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/pkg/utils"
|
||||||
|
"github.com/TheZeroSlave/zapsentry"
|
||||||
|
"github.com/getsentry/sentry-go"
|
||||||
|
"github.com/samber/do"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Service = (*service)(nil)
|
||||||
|
|
||||||
|
type Service interface {
|
||||||
|
GetRawLogger() *zap.Logger
|
||||||
|
GetLogger(domain string) *zap.Logger
|
||||||
|
HealthCheck() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(i *do.Injector) (Service, error) {
|
||||||
|
srv := &service{}
|
||||||
|
srv.confService = do.MustInvoke[config.Service](i)
|
||||||
|
|
||||||
|
c := srv.confService.GetConfig()
|
||||||
|
cfg := zap.Config{
|
||||||
|
Level: zap.NewAtomicLevelAt(utils.If(
|
||||||
|
c.Development,
|
||||||
|
zapcore.DebugLevel,
|
||||||
|
zapcore.InfoLevel,
|
||||||
|
)),
|
||||||
|
Development: c.Development,
|
||||||
|
Encoding: "console", // or json
|
||||||
|
EncoderConfig: utils.If(
|
||||||
|
c.Development,
|
||||||
|
zap.NewDevelopmentEncoderConfig(),
|
||||||
|
zap.NewProductionEncoderConfig(),
|
||||||
|
),
|
||||||
|
OutputPaths: []string{"stdout"},
|
||||||
|
ErrorOutputPaths: []string{"stderr"},
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
srv.logger, err = cfg.Build()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to setup Zap: %s\n", err.Error())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd.SentryDSN != "" {
|
||||||
|
srv.logger = attachSentry(srv.logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
return srv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func attachSentry(log *zap.Logger) *zap.Logger {
|
||||||
|
cfg := zapsentry.Configuration{
|
||||||
|
Level: zapcore.ErrorLevel,
|
||||||
|
EnableBreadcrumbs: true,
|
||||||
|
BreadcrumbLevel: zapcore.InfoLevel,
|
||||||
|
}
|
||||||
|
|
||||||
|
core, err := zapsentry.NewCore(cfg, zapsentry.NewSentryClientFromClient(sentry.CurrentHub().Client()))
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("failed to init zap", zap.Error(err))
|
||||||
|
return log
|
||||||
|
}
|
||||||
|
|
||||||
|
log = zapsentry.AttachCoreToLogger(core, log)
|
||||||
|
return log.With(zapsentry.NewScope())
|
||||||
|
}
|
||||||
|
|
||||||
|
type service struct {
|
||||||
|
confService config.Service
|
||||||
|
logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) GetRawLogger() *zap.Logger {
|
||||||
|
return s.logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) GetLogger(domain string) *zap.Logger {
|
||||||
|
return s.logger.Named(domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) HealthCheck() error {
|
||||||
|
return nil
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package global
|
package model
|
||||||
|
|
||||||
type ConfigWebServer struct {
|
type ConfigWebServer struct {
|
||||||
Address string `yaml:"Address"`
|
Address string `yaml:"Address"`
|
||||||
@ -11,6 +11,7 @@ type ConfigRedis struct {
|
|||||||
Db int `yaml:"Db"`
|
Db int `yaml:"Db"`
|
||||||
QueueDb int `yaml:"QueueDb"`
|
QueueDb int `yaml:"QueueDb"`
|
||||||
Address string `yaml:"Address"`
|
Address string `yaml:"Address"`
|
||||||
|
Port int `yaml:"Port"`
|
||||||
Password string `yaml:"Password"`
|
Password string `yaml:"Password"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,6 +25,7 @@ type ConfigDatabase struct {
|
|||||||
MaxOpenConns int `yaml:"MaxOpenConns"`
|
MaxOpenConns int `yaml:"MaxOpenConns"`
|
||||||
MaxIdleConns int `yaml:"MaxIdleConns"`
|
MaxIdleConns int `yaml:"MaxIdleConns"`
|
||||||
ConnMaxLifetime int `yaml:"ConnMaxLifetime"`
|
ConnMaxLifetime int `yaml:"ConnMaxLifetime"`
|
||||||
|
TimeZone string `yaml:"TimeZone"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConfigStorage struct {
|
type ConfigStorage struct {
|
12
internal/model/jwt.go
Normal file
12
internal/model/jwt.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/golang-jwt/jwt/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Claim struct {
|
||||||
|
UID uint `json:"id"`
|
||||||
|
Role Role `json:"role"`
|
||||||
|
Version int64 `json:"version"`
|
||||||
|
jwt.RegisteredClaims
|
||||||
|
}
|
@ -1,11 +1,12 @@
|
|||||||
package global
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/samber/do"
|
||||||
)
|
)
|
||||||
|
|
||||||
type EndpointInfo struct {
|
type EndpointInfo struct {
|
||||||
Version string
|
Version string
|
||||||
Path string
|
Path string
|
||||||
Register func(g *Global, group *gin.RouterGroup)
|
Register func(rg *gin.RouterGroup, i *do.Injector)
|
||||||
}
|
}
|
@ -2,7 +2,7 @@ package cast
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,60 +0,0 @@
|
|||||||
package metrics
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
|
||||||
"github.com/WHUPRJ/woj-server/internal/pkg/cast"
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Metrics struct {
|
|
||||||
namespace string
|
|
||||||
subsystem string
|
|
||||||
|
|
||||||
counter *prometheus.CounterVec
|
|
||||||
hist *prometheus.HistogramVec
|
|
||||||
|
|
||||||
logPaths []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Metrics) Setup(namespace string, subsystem string) {
|
|
||||||
m.namespace = namespace
|
|
||||||
m.subsystem = subsystem
|
|
||||||
|
|
||||||
m.counter = prometheus.NewCounterVec(
|
|
||||||
prometheus.CounterOpts{
|
|
||||||
Namespace: namespace,
|
|
||||||
Subsystem: subsystem,
|
|
||||||
Name: "requests_total",
|
|
||||||
Help: "Total number of requests",
|
|
||||||
},
|
|
||||||
[]string{"method", "url"},
|
|
||||||
)
|
|
||||||
|
|
||||||
m.hist = prometheus.NewHistogramVec(
|
|
||||||
prometheus.HistogramOpts{
|
|
||||||
Namespace: namespace,
|
|
||||||
Subsystem: subsystem,
|
|
||||||
Name: "requests_details",
|
|
||||||
Help: "Details of each request",
|
|
||||||
Buckets: []float64{0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 25, 50, 100, 250, 500, 1000},
|
|
||||||
},
|
|
||||||
[]string{"method", "url", "success", "http_code", "err_code"},
|
|
||||||
)
|
|
||||||
|
|
||||||
prometheus.MustRegister(m.counter, m.hist)
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
}).Inc()
|
|
||||||
|
|
||||||
m.hist.With(prometheus.Labels{
|
|
||||||
"method": method,
|
|
||||||
"url": url,
|
|
||||||
"success": cast.ToString(success),
|
|
||||||
"http_code": cast.ToString(httpCode),
|
|
||||||
"err_code": cast.ToString(errCode),
|
|
||||||
}).Observe(elapsed)
|
|
||||||
}
|
|
60
internal/repo/cache/redis.go
vendored
Normal file
60
internal/repo/cache/redis.go
vendored
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/misc/config"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/misc/log"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
"github.com/samber/do"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Service = (*service)(nil)
|
||||||
|
|
||||||
|
type Service interface {
|
||||||
|
Get() *redis.Client
|
||||||
|
Close() error
|
||||||
|
HealthCheck() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(i *do.Injector) (Service, error) {
|
||||||
|
srv := &service{}
|
||||||
|
srv.log = do.MustInvoke[log.Service](i).GetLogger("redis")
|
||||||
|
|
||||||
|
conf := do.MustInvoke[config.Service](i).GetConfig()
|
||||||
|
srv.setup(fmt.Sprintf("%s:%d", conf.Redis.Address, conf.Redis.Port), conf.Redis.Password, conf.Redis.Db)
|
||||||
|
return srv, srv.err
|
||||||
|
}
|
||||||
|
|
||||||
|
type service struct {
|
||||||
|
log *zap.Logger
|
||||||
|
client *redis.Client
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) Get() *redis.Client {
|
||||||
|
return s.client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) Close() error {
|
||||||
|
return s.client.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) HealthCheck() error {
|
||||||
|
return s.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) setup(addr string, password string, db int) {
|
||||||
|
s.client = redis.NewClient(&redis.Options{
|
||||||
|
Addr: addr,
|
||||||
|
Password: password,
|
||||||
|
DB: db,
|
||||||
|
})
|
||||||
|
|
||||||
|
_, s.err = s.client.Ping(context.Background()).Result()
|
||||||
|
if s.err != nil {
|
||||||
|
s.log.Error("Redis ping failed", zap.Error(s.err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
165
internal/repo/db/pg.go
Normal file
165
internal/repo/db/pg.go
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/misc/config"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/misc/log"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/pkg/utils"
|
||||||
|
"github.com/samber/do"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"gorm.io/driver/postgres"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/schema"
|
||||||
|
"hash/fnv"
|
||||||
|
"moul.io/zapgorm2"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Service = (*service)(nil)
|
||||||
|
|
||||||
|
type Service interface {
|
||||||
|
Get() *gorm.DB
|
||||||
|
Close() error
|
||||||
|
HealthCheck() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(i *do.Injector) (Service, error) {
|
||||||
|
srv := &service{}
|
||||||
|
srv.log = do.MustInvoke[log.Service](i).GetLogger("postgresql")
|
||||||
|
|
||||||
|
conf := do.MustInvoke[config.Service](i).GetConfig()
|
||||||
|
srv.setup(conf)
|
||||||
|
return srv, srv.err
|
||||||
|
}
|
||||||
|
|
||||||
|
type service struct {
|
||||||
|
log *zap.Logger
|
||||||
|
db *gorm.DB
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) Get() *gorm.DB {
|
||||||
|
return s.db
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) Close() error {
|
||||||
|
var db *sql.DB
|
||||||
|
db, s.err = s.db.DB()
|
||||||
|
if s.err != nil {
|
||||||
|
return s.err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.err = db.Close()
|
||||||
|
return s.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) HealthCheck() error {
|
||||||
|
return s.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) setup(conf *model.Config) {
|
||||||
|
s.log.Info("Connecting to database...")
|
||||||
|
|
||||||
|
logger := zapgorm2.New(s.log)
|
||||||
|
logger.IgnoreRecordNotFoundError = true
|
||||||
|
|
||||||
|
tz := utils.If(conf.Database.TimeZone == "", "Asia/Shanghai", conf.Database.TimeZone)
|
||||||
|
dsn := fmt.Sprintf(
|
||||||
|
"user=%s password=%s dbname=%s host=%s port=%d sslmode=disable TimeZone=%s",
|
||||||
|
conf.Database.User,
|
||||||
|
conf.Database.Password,
|
||||||
|
conf.Database.Database,
|
||||||
|
conf.Database.Host,
|
||||||
|
conf.Database.Port,
|
||||||
|
tz,
|
||||||
|
)
|
||||||
|
|
||||||
|
s.db, s.err = gorm.Open(
|
||||||
|
postgres.Open(dsn),
|
||||||
|
&gorm.Config{
|
||||||
|
NamingStrategy: schema.NamingStrategy{
|
||||||
|
SingularTable: true,
|
||||||
|
TablePrefix: conf.Database.Prefix,
|
||||||
|
},
|
||||||
|
PrepareStmt: true,
|
||||||
|
Logger: logger,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if s.err != nil {
|
||||||
|
s.log.Error("Failed to connect to database", zap.Error(s.err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var db *sql.DB
|
||||||
|
db, s.err = s.checkAlive(3)
|
||||||
|
if s.err != nil {
|
||||||
|
s.log.Error("Database is not alive", zap.Error(s.err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
db.SetMaxOpenConns(conf.Database.MaxOpenConns)
|
||||||
|
db.SetMaxIdleConns(conf.Database.MaxIdleConns)
|
||||||
|
db.SetConnMaxLifetime(time.Duration(conf.Database.ConnMaxLifetime) * time.Minute)
|
||||||
|
|
||||||
|
s.migrateDatabase()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) migrateDatabase() {
|
||||||
|
s.log.Info("Auto Migrating database...")
|
||||||
|
|
||||||
|
// Running AutoMigrate concurrently on the same model fails with various race conditions
|
||||||
|
// https://github.com/go-gorm/gorm/pull/6680
|
||||||
|
// https://github.com/go-gorm/postgres/pull/224
|
||||||
|
|
||||||
|
// Obtain a lock to prevent concurrent AutoMigrate
|
||||||
|
|
||||||
|
lockID := func(s string) int64 {
|
||||||
|
h := fnv.New64a()
|
||||||
|
_, err := h.Write([]byte(s))
|
||||||
|
return utils.If(err != nil, int64(0x4242AA55), int64(h.Sum64()))
|
||||||
|
}("gorm:migrator")
|
||||||
|
|
||||||
|
s.err = s.db.Exec("SELECT pg_advisory_lock(?)", lockID).Error
|
||||||
|
if s.err != nil {
|
||||||
|
s.log.Error("Failed to obtain lock", zap.Error(s.err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = s.db.AutoMigrate(&model.User{})
|
||||||
|
_ = s.db.AutoMigrate(&model.Problem{})
|
||||||
|
_ = s.db.AutoMigrate(&model.ProblemVersion{})
|
||||||
|
_ = s.db.AutoMigrate(&model.Submission{})
|
||||||
|
_ = s.db.AutoMigrate(&model.Status{})
|
||||||
|
|
||||||
|
s.err = s.db.Exec("SELECT pg_advisory_unlock(?)", lockID).Error
|
||||||
|
if s.err != nil {
|
||||||
|
s.log.Error("Failed to release lock", zap.Error(s.err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) 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 := s.db.DB()
|
||||||
|
if err != nil {
|
||||||
|
s.log.Warn("failed to get sql.DB instance", zap.Error(err))
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
return s.checkAlive(retry - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.Ping()
|
||||||
|
if err != nil {
|
||||||
|
s.log.Warn("failed to ping database", zap.Error(err))
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
return s.checkAlive(retry - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.log.Info("database connect established")
|
||||||
|
return db, nil
|
||||||
|
}
|
@ -1,113 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
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,81 +0,0 @@
|
|||||||
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"
|
|
||||||
"github.com/gin-contrib/pprof"
|
|
||||||
ginZap "github.com/gin-contrib/zap"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
||||||
swaggerFiles "github.com/swaggo/files"
|
|
||||||
"github.com/swaggo/gin-swagger"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func InitRouters(g *global.Global) *gin.Engine {
|
|
||||||
gin.SetMode(utils.If(g.Conf.Development, gin.DebugMode, gin.ReleaseMode).(string))
|
|
||||||
|
|
||||||
r := gin.New()
|
|
||||||
r.MaxMultipartMemory = 8 << 20
|
|
||||||
|
|
||||||
// Logger middleware and debug
|
|
||||||
if g.Conf.Development {
|
|
||||||
// Gin's default logger is pretty enough
|
|
||||||
r.Use(gin.Logger())
|
|
||||||
r.Use(gin.Recovery())
|
|
||||||
// add prof
|
|
||||||
pprof.Register(r)
|
|
||||||
} else {
|
|
||||||
r.Use(ginZap.Ginzap(g.Log, time.RFC3339, false))
|
|
||||||
r.Use(ginZap.RecoveryWithZap(g.Log, true))
|
|
||||||
}
|
|
||||||
|
|
||||||
// CORS middleware
|
|
||||||
r.Use(cors.New(cors.Config{
|
|
||||||
AllowAllOrigins: true,
|
|
||||||
AllowMethods: []string{"GET", "POST", "PUT", "OPTIONS"},
|
|
||||||
AllowHeaders: []string{"Origin", "Content-Length", "Content-Type"},
|
|
||||||
AllowCredentials: true,
|
|
||||||
}))
|
|
||||||
|
|
||||||
// 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
|
|
||||||
r.GET("/metrics", gin.WrapH(promhttp.Handler()))
|
|
||||||
|
|
||||||
// swagger
|
|
||||||
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
|
||||||
|
|
||||||
// health
|
|
||||||
r.GET("/health", func(c *gin.Context) {
|
|
||||||
resp := &struct {
|
|
||||||
Timestamp time.Time `json:"timestamp"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
}{
|
|
||||||
Timestamp: time.Now(),
|
|
||||||
Status: "ok",
|
|
||||||
}
|
|
||||||
c.JSON(http.StatusOK, resp)
|
|
||||||
})
|
|
||||||
|
|
||||||
// api
|
|
||||||
api := r.Group("/api/")
|
|
||||||
setupApi(g, api)
|
|
||||||
|
|
||||||
// static files
|
|
||||||
r.Static("/static", "./resource/frontend/static")
|
|
||||||
r.StaticFile("/", "./resource/frontend/index.html")
|
|
||||||
r.NoRoute(func(c *gin.Context) {
|
|
||||||
c.File("./resource/frontend/index.html")
|
|
||||||
})
|
|
||||||
|
|
||||||
return r
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +1,8 @@
|
|||||||
package problem
|
package problem
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/internal/model"
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -21,7 +21,7 @@ func (s *service) Create(data *CreateData) (*model.Problem, e.Status) {
|
|||||||
IsEnabled: data.IsEnabled,
|
IsEnabled: data.IsEnabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.db.Create(problem).Error
|
err := s.db.Get().Create(problem).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Warn("DatabaseError", zap.Error(err), zap.Any("problem", problem))
|
s.log.Warn("DatabaseError", zap.Error(err), zap.Any("problem", problem))
|
||||||
return nil, e.DatabaseError
|
return nil, e.DatabaseError
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package problem
|
package problem
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/internal/model"
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
"github.com/jackc/pgtype"
|
"github.com/jackc/pgtype"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
@ -19,7 +19,7 @@ func (s *service) CreateVersion(data *CreateVersionData) (*model.ProblemVersion,
|
|||||||
StorageKey: data.StorageKey,
|
StorageKey: data.StorageKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.db.Create(problemVersion).Error
|
err := s.db.Get().Create(problemVersion).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Warn("DatabaseError", zap.Error(err), zap.Any("problemVersion", problemVersion))
|
s.log.Warn("DatabaseError", zap.Error(err), zap.Any("problemVersion", problemVersion))
|
||||||
return nil, e.DatabaseError
|
return nil, e.DatabaseError
|
||||||
|
@ -2,8 +2,8 @@ package problem
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/internal/model"
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/clause"
|
"gorm.io/gorm/clause"
|
||||||
@ -12,7 +12,7 @@ import (
|
|||||||
func (s *service) Query(pid uint, associations bool, shouldEnable bool) (*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)
|
||||||
|
|
||||||
query := s.db
|
query := s.db.Get()
|
||||||
if associations {
|
if associations {
|
||||||
query = query.Preload(clause.Associations)
|
query = query.Preload(clause.Associations)
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package problem
|
package problem
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/internal/model"
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"gorm.io/gorm/clause"
|
"gorm.io/gorm/clause"
|
||||||
)
|
)
|
||||||
@ -10,7 +10,7 @@ import (
|
|||||||
func (s *service) QueryFuzz(search string, associations bool, shouldEnable bool) ([]*model.Problem, e.Status) {
|
func (s *service) QueryFuzz(search string, associations bool, shouldEnable bool) ([]*model.Problem, e.Status) {
|
||||||
problems := make([]*model.Problem, 0)
|
problems := make([]*model.Problem, 0)
|
||||||
|
|
||||||
query := s.db
|
query := s.db.Get()
|
||||||
if associations {
|
if associations {
|
||||||
query = query.Preload(clause.Associations)
|
query = query.Preload(clause.Associations)
|
||||||
}
|
}
|
||||||
@ -18,7 +18,7 @@ func (s *service) QueryFuzz(search string, associations bool, shouldEnable bool)
|
|||||||
query = query.Where("is_enabled = true")
|
query = query.Where("is_enabled = true")
|
||||||
}
|
}
|
||||||
query = query.
|
query = query.
|
||||||
Where(s.db.Where("title LIKE ?", "%"+search+"%").
|
Where(s.db.Get().Where("title LIKE ?", "%"+search+"%").
|
||||||
Or("statement LIKE ?", "%"+search+"%"))
|
Or("statement LIKE ?", "%"+search+"%"))
|
||||||
err := query.Find(&problems).Error
|
err := query.Find(&problems).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -2,8 +2,8 @@ package problem
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/internal/model"
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
@ -14,7 +14,7 @@ func (s *service) QueryLatestVersion(pid uint) (*model.ProblemVersion, e.Status)
|
|||||||
IsEnabled: true,
|
IsEnabled: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.db.
|
err := s.db.Get().
|
||||||
Where(problemVersion).
|
Where(problemVersion).
|
||||||
Last(&problemVersion).Error
|
Last(&problemVersion).Error
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
@ -2,8 +2,8 @@ package problem
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/internal/model"
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
@ -11,7 +11,7 @@ import (
|
|||||||
func (s *service) QueryVersion(pvid uint, shouldEnable bool) (*model.ProblemVersion, e.Status) {
|
func (s *service) QueryVersion(pvid uint, shouldEnable bool) (*model.ProblemVersion, e.Status) {
|
||||||
problemVersion := new(model.ProblemVersion)
|
problemVersion := new(model.ProblemVersion)
|
||||||
|
|
||||||
err := s.db.First(&problemVersion, pvid).Error
|
err := s.db.Get().First(&problemVersion, pvid).Error
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return nil, e.ProblemVersionNotFound
|
return nil, e.ProblemVersionNotFound
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
package problem
|
package problem
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/internal/global"
|
"git.0x7f.app/WOJ/woj-server/internal/misc/log"
|
||||||
"github.com/WHUPRJ/woj-server/internal/model"
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/repo/db"
|
||||||
|
"github.com/samber/do"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ Service = (*service)(nil)
|
var _ Service = (*service)(nil)
|
||||||
@ -20,16 +21,22 @@ type Service interface {
|
|||||||
UpdateVersion(pvid uint, values interface{}) e.Status
|
UpdateVersion(pvid uint, values interface{}) e.Status
|
||||||
QueryVersion(pvid uint, shouldEnable bool) (*model.ProblemVersion, e.Status)
|
QueryVersion(pvid uint, shouldEnable bool) (*model.ProblemVersion, e.Status)
|
||||||
QueryLatestVersion(pid uint) (*model.ProblemVersion, e.Status)
|
QueryLatestVersion(pid uint) (*model.ProblemVersion, e.Status)
|
||||||
|
|
||||||
|
HealthCheck() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(i *do.Injector) (Service, error) {
|
||||||
|
return &service{
|
||||||
|
log: do.MustInvoke[log.Service](i).GetLogger("problem"),
|
||||||
|
db: do.MustInvoke[db.Service](i),
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type service struct {
|
type service struct {
|
||||||
log *zap.Logger
|
log *zap.Logger
|
||||||
db *gorm.DB
|
db db.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(g *global.Global) Service {
|
func (s *service) HealthCheck() error {
|
||||||
return &service{
|
return nil
|
||||||
log: g.Log,
|
|
||||||
db: g.Db.Get().(*gorm.DB),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
package problem
|
package problem
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/internal/model"
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *service) Update(problem *model.Problem) (*model.Problem, e.Status) {
|
func (s *service) Update(problem *model.Problem) (*model.Problem, e.Status) {
|
||||||
err := s.db.Save(problem).Error
|
err := s.db.Get().Save(problem).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Warn("DatabaseError", zap.Error(err), zap.Any("problem", problem))
|
s.log.Warn("DatabaseError", zap.Error(err), zap.Any("problem", problem))
|
||||||
return nil, e.DatabaseError
|
return nil, e.DatabaseError
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
package problem
|
package problem
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/internal/model"
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *service) UpdateVersion(pvid uint, values interface{}) e.Status {
|
func (s *service) UpdateVersion(pvid uint, values interface{}) e.Status {
|
||||||
err := s.db.Model(&model.ProblemVersion{}).Where("id = ?", pvid).Updates(values).Error
|
err := s.db.Get().Model(&model.ProblemVersion{}).Where("id = ?", pvid).Updates(values).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Warn("DatabaseError", zap.Error(err), zap.Any("pvid", pvid), zap.Any("values", values))
|
s.log.Warn("DatabaseError", zap.Error(err), zap.Any("pvid", pvid), zap.Any("values", values))
|
||||||
return e.DatabaseError
|
return e.DatabaseError
|
||||||
|
@ -2,8 +2,8 @@ package runner
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/pkg/utils"
|
"git.0x7f.app/WOJ/woj-server/pkg/utils"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@ -36,6 +36,10 @@ func (s *service) execute(script string, args ...string) error {
|
|||||||
p := filepath.Join(ScriptsDir, script)
|
p := filepath.Join(ScriptsDir, script)
|
||||||
cmd := exec.Command(p, args...)
|
cmd := exec.Command(p, args...)
|
||||||
cmd.Dir = ScriptsDir
|
cmd.Dir = ScriptsDir
|
||||||
|
if s.verbose {
|
||||||
|
cmd.Stdout = os.Stderr
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
}
|
||||||
return cmd.Run()
|
return cmd.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,8 +2,8 @@ package runner
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/pkg/utils"
|
"git.0x7f.app/WOJ/woj-server/pkg/utils"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
@ -16,14 +16,14 @@ func (s *service) Compile(version uint, user string, lang string) (JudgeStatus,
|
|||||||
|
|
||||||
log := filepath.Join(UserDir, user, fmt.Sprintf("%s.compile.log", user))
|
log := filepath.Join(UserDir, user, fmt.Sprintf("%s.compile.log", user))
|
||||||
msg, err := utils.FileRead(log)
|
msg, err := utils.FileRead(log)
|
||||||
msg = utils.If(err == nil, msg, nil).([]byte)
|
msg = utils.If(err == nil, msg, nil)
|
||||||
msgText := string(msg)
|
msgText := string(msg)
|
||||||
|
|
||||||
if !utils.FileExist(target) || utils.FileEmpty(target) {
|
if !utils.FileExist(target) || utils.FileEmpty(target) {
|
||||||
return JudgeStatus{
|
return JudgeStatus{
|
||||||
Message: "compile failed",
|
Message: "compile failed",
|
||||||
Tasks: []TaskStatus{{Verdict: VerdictCompileError, Message: msgText}}},
|
Tasks: []TaskStatus{{Verdict: VerdictCompileError, Message: msgText}}},
|
||||||
utils.If(status == e.Success, e.RunnerUserCompileFailed, status).(e.Status)
|
utils.If(status == e.Success, e.RunnerUserCompileFailed, status)
|
||||||
}
|
}
|
||||||
|
|
||||||
return JudgeStatus{}, e.Success
|
return JudgeStatus{}, e.Success
|
||||||
|
@ -16,9 +16,9 @@ type Config struct {
|
|||||||
} `json:"Runtime"`
|
} `json:"Runtime"`
|
||||||
Languages []struct {
|
Languages []struct {
|
||||||
Lang string `json:"Lang"`
|
Lang string `json:"Lang"`
|
||||||
Type string `json:"Type"`
|
Type string `json:"Type,omitempty"`
|
||||||
Script string `json:"Script"`
|
Script string `json:"Script,omitempty"`
|
||||||
Cmp string `json:"Cmp"`
|
Cmp string `json:"Cmp,omitempty"`
|
||||||
} `json:"Languages"`
|
} `json:"Languages"`
|
||||||
Tasks []struct {
|
Tasks []struct {
|
||||||
Id int `json:"Id"`
|
Id int `json:"Id"`
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package runner
|
package runner
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/pkg/utils"
|
"git.0x7f.app/WOJ/woj-server/pkg/utils"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@ -10,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (s *service) EnsureDeps(force bool) e.Status {
|
func (s *service) EnsureDeps(force bool) e.Status {
|
||||||
mark := filepath.Join(Prefix, ".mark.docker")
|
mark := filepath.Join(Prefix, ".mark.image")
|
||||||
|
|
||||||
if force {
|
if force {
|
||||||
_ = os.Remove(mark)
|
_ = os.Remove(mark)
|
||||||
@ -18,9 +18,13 @@ func (s *service) EnsureDeps(force bool) e.Status {
|
|||||||
return e.Success
|
return e.Success
|
||||||
}
|
}
|
||||||
|
|
||||||
script := filepath.Join(ScriptsDir, "prepare_container.sh")
|
script := filepath.Join(ScriptsDir, "prepare_images.sh")
|
||||||
cmd := exec.Command(script)
|
cmd := exec.Command(script)
|
||||||
cmd.Dir = ScriptsDir
|
cmd.Dir = ScriptsDir
|
||||||
|
if s.verbose {
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
}
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Warn("prebuild docker images failed", zap.Error(err))
|
s.log.Warn("prebuild docker images failed", zap.Error(err))
|
||||||
|
@ -2,10 +2,10 @@ package runner
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/pkg/down"
|
"git.0x7f.app/WOJ/woj-server/pkg/down"
|
||||||
"github.com/WHUPRJ/woj-server/pkg/unzip"
|
"git.0x7f.app/WOJ/woj-server/pkg/unzip"
|
||||||
"github.com/WHUPRJ/woj-server/pkg/utils"
|
"git.0x7f.app/WOJ/woj-server/pkg/utils"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package runner
|
package runner
|
||||||
|
|
||||||
import "github.com/WHUPRJ/woj-server/internal/e"
|
import "git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
|
|
||||||
func (s *service) RunAndJudge(version uint, user string, lang string, config *Config) (JudgeStatus, int32, e.Status) {
|
func (s *service) RunAndJudge(version uint, user string, lang string, config *Config) (JudgeStatus, int32, e.Status) {
|
||||||
// run user program
|
// run user program
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
package runner
|
package runner
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/internal/global"
|
"git.0x7f.app/WOJ/woj-server/internal/misc/config"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/misc/log"
|
||||||
|
"github.com/samber/do"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -23,14 +25,22 @@ type Service interface {
|
|||||||
ParseConfig(version uint, skipCheck bool) (Config, error)
|
ParseConfig(version uint, skipCheck bool) (Config, error)
|
||||||
// ProblemExists check if problem exists
|
// ProblemExists check if problem exists
|
||||||
ProblemExists(version uint) bool
|
ProblemExists(version uint) bool
|
||||||
|
|
||||||
|
HealthCheck() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(i *do.Injector) (Service, error) {
|
||||||
|
return &service{
|
||||||
|
log: do.MustInvoke[log.Service](i).GetLogger("runner"),
|
||||||
|
verbose: do.MustInvoke[config.Service](i).GetConfig().Development,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type service struct {
|
type service struct {
|
||||||
log *zap.Logger
|
log *zap.Logger
|
||||||
|
verbose bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(g *global.Global) Service {
|
func (s *service) HealthCheck() error {
|
||||||
return &service{
|
return nil
|
||||||
log: g.Log,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/WHUPRJ/woj-server/pkg/utils"
|
"git.0x7f.app/WOJ/woj-server/pkg/utils"
|
||||||
"golang.org/x/text/encoding/charmap"
|
"golang.org/x/text/encoding/charmap"
|
||||||
"io"
|
"io"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package status
|
package status
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/internal/model"
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
"github.com/jackc/pgtype"
|
"github.com/jackc/pgtype"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
@ -14,7 +14,7 @@ type CreateData struct {
|
|||||||
Point int32
|
Point int32
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s service) Create(data *CreateData) (*model.Status, e.Status) {
|
func (s *service) Create(data *CreateData) (*model.Status, e.Status) {
|
||||||
status := &model.Status{
|
status := &model.Status{
|
||||||
SubmissionID: data.SubmissionID,
|
SubmissionID: data.SubmissionID,
|
||||||
ProblemVersionID: data.ProblemVersionID,
|
ProblemVersionID: data.ProblemVersionID,
|
||||||
@ -26,7 +26,7 @@ func (s service) Create(data *CreateData) (*model.Status, e.Status) {
|
|||||||
IsEnabled: true,
|
IsEnabled: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.db.Create(status).Error
|
err := s.db.Get().Create(status).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Warn("DatabaseError", zap.Error(err), zap.Any("status", status))
|
s.log.Warn("DatabaseError", zap.Error(err), zap.Any("status", status))
|
||||||
return nil, e.DatabaseError
|
return nil, e.DatabaseError
|
||||||
|
@ -2,21 +2,21 @@ package status
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/internal/model"
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/clause"
|
"gorm.io/gorm/clause"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s service) Query(sid uint, associations bool) (*model.Status, e.Status) {
|
func (s *service) Query(sid uint, associations bool) (*model.Status, e.Status) {
|
||||||
|
|
||||||
status := &model.Status{
|
status := &model.Status{
|
||||||
SubmissionID: sid,
|
SubmissionID: sid,
|
||||||
IsEnabled: true,
|
IsEnabled: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
query := s.db
|
query := s.db.Get()
|
||||||
if associations {
|
if associations {
|
||||||
query = query.Preload(clause.Associations)
|
query = query.Preload(clause.Associations)
|
||||||
}
|
}
|
||||||
@ -36,14 +36,14 @@ func (s service) Query(sid uint, associations bool) (*model.Status, e.Status) {
|
|||||||
return status, e.Success
|
return status, e.Success
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s service) QueryByVersion(pvid uint, offset int, limit int) ([]*model.Status, e.Status) {
|
func (s *service) QueryByVersion(pvid uint, offset int, limit int) ([]*model.Status, e.Status) {
|
||||||
var statuses []*model.Status
|
var statuses []*model.Status
|
||||||
status := &model.Status{
|
status := &model.Status{
|
||||||
ProblemVersionID: pvid,
|
ProblemVersionID: pvid,
|
||||||
IsEnabled: true,
|
IsEnabled: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.db.Preload(clause.Associations).
|
err := s.db.Get().Preload(clause.Associations).
|
||||||
Where(status).
|
Where(status).
|
||||||
Limit(limit).
|
Limit(limit).
|
||||||
Offset(offset).
|
Offset(offset).
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
package status
|
package status
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/internal/global"
|
"git.0x7f.app/WOJ/woj-server/internal/misc/log"
|
||||||
"github.com/WHUPRJ/woj-server/internal/model"
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/repo/db"
|
||||||
|
"github.com/samber/do"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ Service = (*service)(nil)
|
var _ Service = (*service)(nil)
|
||||||
@ -14,16 +15,22 @@ type Service interface {
|
|||||||
Create(data *CreateData) (*model.Status, e.Status)
|
Create(data *CreateData) (*model.Status, e.Status)
|
||||||
Query(sid uint, associations bool) (*model.Status, e.Status)
|
Query(sid uint, associations bool) (*model.Status, e.Status)
|
||||||
QueryByVersion(pvid uint, offset int, limit int) ([]*model.Status, e.Status)
|
QueryByVersion(pvid uint, offset int, limit int) ([]*model.Status, e.Status)
|
||||||
|
|
||||||
|
HealthCheck() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(i *do.Injector) (Service, error) {
|
||||||
|
return &service{
|
||||||
|
log: do.MustInvoke[log.Service](i).GetLogger("status"),
|
||||||
|
db: do.MustInvoke[db.Service](i),
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type service struct {
|
type service struct {
|
||||||
log *zap.Logger
|
log *zap.Logger
|
||||||
db *gorm.DB
|
db db.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(g *global.Global) Service {
|
func (s *service) HealthCheck() error {
|
||||||
return &service{
|
return nil
|
||||||
log: g.Log,
|
|
||||||
db: g.Db.Get().(*gorm.DB),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ package storage
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/internal/global"
|
"git.0x7f.app/WOJ/woj-server/internal/misc/config"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/misc/log"
|
||||||
"github.com/minio/minio-go/v7"
|
"github.com/minio/minio-go/v7"
|
||||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
|
"github.com/samber/do"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -14,6 +16,33 @@ var _ Service = (*service)(nil)
|
|||||||
type Service interface {
|
type Service interface {
|
||||||
Upload(objectName string, expiry time.Duration) (string, e.Status)
|
Upload(objectName string, expiry time.Duration) (string, e.Status)
|
||||||
Get(objectName string, expiry time.Duration) (string, e.Status)
|
Get(objectName string, expiry time.Duration) (string, e.Status)
|
||||||
|
|
||||||
|
HealthCheck() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(i *do.Injector) (Service, error) {
|
||||||
|
conf := do.MustInvoke[config.Service](i).GetConfig()
|
||||||
|
|
||||||
|
srv := &service{
|
||||||
|
log: do.MustInvoke[log.Service](i).GetLogger("storage"),
|
||||||
|
bucket: conf.Storage.Bucket,
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
srv.client, err = minio.New(
|
||||||
|
conf.Storage.Endpoint,
|
||||||
|
&minio.Options{
|
||||||
|
Creds: credentials.NewStaticV4(conf.Storage.AccessKey, conf.Storage.SecretKey, ""),
|
||||||
|
Secure: conf.Storage.UseSSL,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
srv.log.Error("failed to create minio client", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return srv, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type service struct {
|
type service struct {
|
||||||
@ -22,20 +51,6 @@ type service struct {
|
|||||||
bucket string
|
bucket string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(g *global.Global) Service {
|
func (s *service) HealthCheck() error {
|
||||||
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 nil
|
||||||
}
|
|
||||||
|
|
||||||
return &service{
|
|
||||||
log: g.Log,
|
|
||||||
client: minioClient,
|
|
||||||
bucket: g.Conf.Storage.Bucket,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ package storage
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package submission
|
package submission
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/internal/model"
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -21,7 +21,7 @@ func (s *service) Create(data *CreateData) (*model.Submission, e.Status) {
|
|||||||
Code: data.Code,
|
Code: data.Code,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.db.Create(submission).Error
|
err := s.db.Get().Create(submission).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Warn("DatabaseError", zap.Error(err), zap.Any("submission", submission))
|
s.log.Warn("DatabaseError", zap.Error(err), zap.Any("submission", submission))
|
||||||
return nil, e.DatabaseError
|
return nil, e.DatabaseError
|
||||||
|
@ -2,8 +2,8 @@ package submission
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/internal/model"
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/clause"
|
"gorm.io/gorm/clause"
|
||||||
@ -17,7 +17,7 @@ func (s *service) Query(pid uint, uid uint, offset int, limit int) ([]*model.Sub
|
|||||||
UserID: uid,
|
UserID: uid,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.db.Preload(clause.Associations).
|
err := s.db.Get().Preload(clause.Associations).
|
||||||
Where(submission).
|
Where(submission).
|
||||||
Limit(limit).
|
Limit(limit).
|
||||||
Offset(offset).
|
Offset(offset).
|
||||||
@ -37,7 +37,7 @@ func (s *service) Query(pid uint, uid uint, offset int, limit int) ([]*model.Sub
|
|||||||
func (s *service) QueryBySid(sid uint, associations bool) (*model.Submission, e.Status) {
|
func (s *service) QueryBySid(sid uint, associations bool) (*model.Submission, e.Status) {
|
||||||
submission := new(model.Submission)
|
submission := new(model.Submission)
|
||||||
|
|
||||||
query := s.db
|
query := s.db.Get()
|
||||||
if associations {
|
if associations {
|
||||||
query = query.Preload(clause.Associations)
|
query = query.Preload(clause.Associations)
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
package submission
|
package submission
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/internal/global"
|
"git.0x7f.app/WOJ/woj-server/internal/misc/log"
|
||||||
"github.com/WHUPRJ/woj-server/internal/model"
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/repo/db"
|
||||||
|
"github.com/samber/do"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ Service = (*service)(nil)
|
var _ Service = (*service)(nil)
|
||||||
@ -14,16 +15,22 @@ type Service interface {
|
|||||||
Create(data *CreateData) (*model.Submission, e.Status)
|
Create(data *CreateData) (*model.Submission, e.Status)
|
||||||
Query(pid uint, uid uint, offset int, limit int) ([]*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)
|
QueryBySid(sid uint, associations bool) (*model.Submission, e.Status)
|
||||||
|
|
||||||
|
HealthCheck() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(i *do.Injector) (Service, error) {
|
||||||
|
return &service{
|
||||||
|
log: do.MustInvoke[log.Service](i).GetLogger("submission"),
|
||||||
|
db: do.MustInvoke[db.Service](i),
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type service struct {
|
type service struct {
|
||||||
log *zap.Logger
|
log *zap.Logger
|
||||||
db *gorm.DB
|
db db.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(g *global.Global) Service {
|
func (s *service) HealthCheck() error {
|
||||||
return &service{
|
return nil
|
||||||
log: g.Log,
|
|
||||||
db: g.Db.Get().(*gorm.DB),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package task
|
package task
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/hibiken/asynq"
|
"github.com/hibiken/asynq"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
@ -2,8 +2,8 @@ package task
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/WHUPRJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
"github.com/WHUPRJ/woj-server/internal/model"
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user