Merge branch 'develop' version 1.2.1
This commit is contained in:
commit
71724fd1bf
@ -2,17 +2,18 @@
|
|||||||
FROM docker.io/library/golang:alpine AS builder
|
FROM docker.io/library/golang:alpine AS builder
|
||||||
|
|
||||||
ENV GOPROXY=https://goproxy.cn
|
ENV GOPROXY=https://goproxy.cn
|
||||||
|
ENV CGO_ENABLED=0
|
||||||
WORKDIR /builder
|
WORKDIR /builder
|
||||||
|
|
||||||
RUN apk add --no-cache git make
|
RUN apk add --no-cache git make
|
||||||
RUN go install github.com/swaggo/swag/cmd/swag@latest
|
RUN --mount=type=cache,id=golang,target=/go/pkg go install github.com/swaggo/swag/cmd/swag@latest
|
||||||
|
|
||||||
COPY go.mod /builder/go.mod
|
COPY go.mod /builder/go.mod
|
||||||
COPY go.sum /builder/go.sum
|
COPY go.sum /builder/go.sum
|
||||||
RUN go mod download
|
RUN --mount=type=cache,id=golang,target=/go/pkg go mod download
|
||||||
|
|
||||||
COPY . /builder
|
COPY . /builder
|
||||||
RUN make build
|
RUN --mount=type=cache,id=golang,target=/go/pkg make build
|
||||||
|
|
||||||
|
|
||||||
# main image
|
# main image
|
||||||
|
@ -1,19 +1,22 @@
|
|||||||
# builder
|
# Go builder
|
||||||
FROM docker.io/library/golang:alpine AS builder
|
FROM docker.io/library/golang:alpine AS go-builder
|
||||||
|
|
||||||
ENV GOPROXY=https://goproxy.cn
|
ENV GOPROXY=https://goproxy.cn
|
||||||
|
ENV CGO_ENABLED=0
|
||||||
WORKDIR /builder
|
WORKDIR /builder
|
||||||
|
|
||||||
RUN apk add --no-cache git make
|
RUN apk add --no-cache git make
|
||||||
RUN go install github.com/swaggo/swag/cmd/swag@latest
|
RUN --mount=type=cache,id=golang,target=/go/pkg go install github.com/swaggo/swag/cmd/swag@latest
|
||||||
|
|
||||||
COPY go.mod /builder/go.mod
|
COPY go.mod /builder/go.mod
|
||||||
COPY go.sum /builder/go.sum
|
COPY go.sum /builder/go.sum
|
||||||
RUN go mod download
|
RUN --mount=type=cache,id=golang,target=/go/pkg go mod download
|
||||||
|
|
||||||
COPY . /builder
|
COPY . /builder
|
||||||
RUN make build
|
RUN --mount=type=cache,id=golang,target=/go/pkg make build
|
||||||
|
|
||||||
|
# UI Builder
|
||||||
|
FROM git.0x7f.app/woj/woj-ui:1.0.0 AS ui-builder
|
||||||
|
|
||||||
# main image
|
# main image
|
||||||
FROM docker.io/library/alpine
|
FROM docker.io/library/alpine
|
||||||
@ -21,9 +24,11 @@ FROM docker.io/library/alpine
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
RUN apk --no-cache add tzdata ca-certificates libc6-compat bash
|
RUN apk --no-cache add tzdata ca-certificates libc6-compat bash
|
||||||
|
|
||||||
COPY --from=builder /builder/config.docker.yaml /app
|
COPY --from=go-builder /builder/config.docker.yaml /app
|
||||||
COPY --from=builder /builder/docker-entrypoint.sh /app
|
COPY --from=go-builder /builder/docker-entrypoint.sh /app
|
||||||
COPY --from=builder /builder/resource/frontend /app/resource/frontend
|
COPY --from=go-builder /builder/resource/frontend /app/resource/frontend
|
||||||
COPY --from=builder /builder/woj /app
|
COPY --from=go-builder /builder/woj /app
|
||||||
|
|
||||||
|
COPY --from=ui-builder /app /app/resource/frontend
|
||||||
|
|
||||||
ENTRYPOINT ["/app/docker-entrypoint.sh"]
|
ENTRYPOINT ["/app/docker-entrypoint.sh"]
|
||||||
|
@ -13,7 +13,7 @@ function build_base() {
|
|||||||
(log_error "Build Full Image failed" && exit 1)
|
(log_error "Build Full Image failed" && exit 1)
|
||||||
$DOCKER build -t git.0x7f.app/woj/ubuntu-run:latest -f scripts/ubuntu-run.Dockerfile . ||
|
$DOCKER build -t git.0x7f.app/woj/ubuntu-run:latest -f scripts/ubuntu-run.Dockerfile . ||
|
||||||
(log_error "Build Tiny Image failed" && exit 1)
|
(log_error "Build Tiny Image failed" && exit 1)
|
||||||
popd
|
popd || exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
function push_base() {
|
function push_base() {
|
||||||
@ -53,9 +53,27 @@ function push_runner() {
|
|||||||
$DOCKER push "git.0x7f.app/woj/woj-runner:$VERSION"
|
$DOCKER push "git.0x7f.app/woj/woj-runner:$VERSION"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if [ "$1" == "base" ]; then
|
||||||
|
build_base
|
||||||
|
push_base
|
||||||
|
exit 0
|
||||||
|
elif [ "$1" == "server" ]; then
|
||||||
|
build_server
|
||||||
|
push_server
|
||||||
|
exit 0
|
||||||
|
elif [ "$1" == "runner" ]; then
|
||||||
|
build_runner
|
||||||
|
push_runner
|
||||||
|
exit 0
|
||||||
|
elif [ "$1" == "all" ]; then
|
||||||
build_base
|
build_base
|
||||||
push_base
|
push_base
|
||||||
build_server
|
build_server
|
||||||
push_server
|
push_server
|
||||||
build_runner
|
build_runner
|
||||||
push_runner
|
push_runner
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
log_error "Usage: $0 [base|server|runner|all]"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
@ -77,7 +77,7 @@ func setupSentry() {
|
|||||||
err := sentry.Init(sentry.ClientOptions{
|
err := sentry.Init(sentry.ClientOptions{
|
||||||
Dsn: SentryDSN,
|
Dsn: SentryDSN,
|
||||||
EnableTracing: true,
|
EnableTracing: true,
|
||||||
TracesSampleRate: 1.0,
|
TracesSampleRate: 0.5,
|
||||||
SendDefaultPII: true,
|
SendDefaultPII: true,
|
||||||
Release: GitCommit,
|
Release: GitCommit,
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
server:
|
server:
|
||||||
image: git.0x7f.app/woj/woj-server:1.1.0
|
image: git.0x7f.app/woj/woj-server:1.2.1
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: [ "CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:8000/health" ]
|
test: [ "CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:8000/health" ]
|
||||||
@ -33,7 +33,7 @@ services:
|
|||||||
- "8000:8000"
|
- "8000:8000"
|
||||||
|
|
||||||
runner:
|
runner:
|
||||||
image: git.0x7f.app/woj/woj-runner:1.1.0
|
image: git.0x7f.app/woj/woj-runner:1.2.1
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: runner
|
command: runner
|
||||||
security_opt:
|
security_opt:
|
||||||
@ -84,7 +84,7 @@ services:
|
|||||||
- cache:/data
|
- cache:/data
|
||||||
|
|
||||||
db:
|
db:
|
||||||
image: docker.io/library/postgres:alpine
|
image: docker.io/library/postgres:16-alpine
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: [ "CMD", "pg_isready", "-U", "dev" ]
|
test: [ "CMD", "pg_isready", "-U", "dev" ]
|
||||||
|
1
go.mod
1
go.mod
@ -8,6 +8,7 @@ require (
|
|||||||
github.com/gin-contrib/cors v1.5.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.2.0
|
github.com/gin-contrib/zap v0.2.0
|
||||||
|
github.com/gin-gonic/contrib v0.0.0-20221130124618-7e01895a63f2
|
||||||
github.com/gin-gonic/gin v1.9.1
|
github.com/gin-gonic/gin v1.9.1
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||||
github.com/hibiken/asynq v0.24.1
|
github.com/hibiken/asynq v0.24.1
|
||||||
|
10
go.sum
10
go.sum
@ -52,14 +52,14 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE
|
|||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
github.com/gin-contrib/zap v0.2.0 h1:HLvt3rZXyC8XC+s2lHzMFow3UDqiEbfrBWJyHHS6L8A=
|
github.com/gin-contrib/zap v0.2.0 h1:HLvt3rZXyC8XC+s2lHzMFow3UDqiEbfrBWJyHHS6L8A=
|
||||||
github.com/gin-contrib/zap v0.2.0/go.mod h1:eqfbe9ZmI+GgTZF6nRiC2ZwDeM4DK1Viwc8OxTCphh0=
|
github.com/gin-contrib/zap v0.2.0/go.mod h1:eqfbe9ZmI+GgTZF6nRiC2ZwDeM4DK1Viwc8OxTCphh0=
|
||||||
|
github.com/gin-gonic/contrib v0.0.0-20221130124618-7e01895a63f2 h1:dyuNlYlG1faymw39NdJddnzJICy6587tiGSVioWhYoE=
|
||||||
|
github.com/gin-gonic/contrib v0.0.0-20221130124618-7e01895a63f2/go.mod h1:iqneQ2Df3omzIVTkIfn7c1acsVnMGiSLn4XF5Blh3Yg=
|
||||||
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
|
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
|
||||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||||
github.com/go-openapi/jsonpointer v0.20.1 h1:MkK4VEIEZMj4wT9PmjaUmGflVBr9nvud4Q4UVFbDoBE=
|
|
||||||
github.com/go-openapi/jsonpointer v0.20.1/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs=
|
|
||||||
github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q=
|
github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q=
|
||||||
github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs=
|
github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs=
|
||||||
github.com/go-openapi/jsonreference v0.20.3 h1:EjGcjTW8pD1mRis6+w/gmoBdqv5+RbE9B85D1NgDOVQ=
|
github.com/go-openapi/jsonreference v0.20.3 h1:EjGcjTW8pD1mRis6+w/gmoBdqv5+RbE9B85D1NgDOVQ=
|
||||||
@ -152,7 +152,6 @@ github.com/jackc/pgx/v5 v5.5.1/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSlj
|
|||||||
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0=
|
|
||||||
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||||
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
@ -202,7 +201,6 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
|
|||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
|
||||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
|
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
|
||||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
|
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
|
||||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||||
@ -234,8 +232,6 @@ github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGy
|
|||||||
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||||
github.com/redis/go-redis/v9 v9.0.3/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
|
github.com/redis/go-redis/v9 v9.0.3/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
|
||||||
github.com/redis/go-redis/v9 v9.3.0 h1:RiVDjmig62jIWp7Kk4XVLs0hzV6pI3PyTnnL0cnn0u0=
|
|
||||||
github.com/redis/go-redis/v9 v9.3.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
|
||||||
github.com/redis/go-redis/v9 v9.3.1 h1:KqdY8U+3X6z+iACvumCNxnoluToB+9Me+TvyFa21Mds=
|
github.com/redis/go-redis/v9 v9.3.1 h1:KqdY8U+3X6z+iACvumCNxnoluToB+9Me+TvyFa21Mds=
|
||||||
github.com/redis/go-redis/v9 v9.3.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
github.com/redis/go-redis/v9 v9.3.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
@ -335,8 +331,6 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y
|
|||||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||||
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
|
|
||||||
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
|
||||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
@ -8,8 +8,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type createVersionRequest struct {
|
type createVersionRequest struct {
|
||||||
ProblemID uint `form:"pid" binding:"required"`
|
ProblemID uint `form:"pid" json:"pid" binding:"required"`
|
||||||
StorageKey string `form:"storage_key" binding:"required"`
|
StorageKey string `form:"storage_key" json:"storage_key" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateVersion
|
// CreateVersion
|
||||||
@ -44,7 +44,7 @@ func (h *handler) CreateVersion(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// make sure problem exists
|
// make sure problem exists
|
||||||
_, status := h.problemService.Query(req.ProblemID, false, false)
|
_, status := h.problemService.Query(&problem.QueryData{ID: req.ProblemID, Associations: false, ShouldEnable: false})
|
||||||
if status != e.Success {
|
if status != e.Success {
|
||||||
e.Pong[any](c, status, nil)
|
e.Pong[any](c, status, nil)
|
||||||
return
|
return
|
||||||
|
@ -3,11 +3,12 @@ package problem
|
|||||||
import (
|
import (
|
||||||
"git.0x7f.app/WOJ/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/internal/model"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/service/problem"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type detailsRequest struct {
|
type detailsRequest struct {
|
||||||
Pid uint `form:"pid"`
|
Pid uint `form:"pid" json:"pid"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type problemDetailsResponse struct {
|
type problemDetailsResponse struct {
|
||||||
@ -34,7 +35,7 @@ func (h *handler) Details(c *gin.Context) {
|
|||||||
claim, exist := c.Get("claim")
|
claim, exist := c.Get("claim")
|
||||||
shouldEnable := !exist || claim.(*model.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(&problem.QueryData{ID: req.Pid, Associations: true, ShouldEnable: shouldEnable})
|
||||||
if status != e.Success {
|
if status != e.Success {
|
||||||
e.Pong[any](c, status, nil)
|
e.Pong[any](c, status, nil)
|
||||||
return
|
return
|
||||||
|
@ -2,12 +2,16 @@ package problem
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"git.0x7f.app/WOJ/woj-server/internal/e"
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
_ "git.0x7f.app/WOJ/woj-server/internal/model" // swag requires this
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/service/problem"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type searchRequest struct {
|
type searchRequest struct {
|
||||||
Search string `form:"search"`
|
Keyword string `form:"keyword" json:"keyword"`
|
||||||
|
Tag string `form:"tag" json:"tag"`
|
||||||
|
Offset int `form:"offset" json:"offset"`
|
||||||
|
Limit int `form:"limit" json:"limit" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search
|
// Search
|
||||||
@ -16,8 +20,11 @@ type searchRequest struct {
|
|||||||
// @Tags problem
|
// @Tags problem
|
||||||
// @Accept application/x-www-form-urlencoded
|
// @Accept application/x-www-form-urlencoded
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param search formData string false "keyword"
|
// @Param keyword formData string false "keyword"
|
||||||
// @Response 200 {object} e.Response[[]model.Problem] "problems found"
|
// @Param tag formData string false "tag"
|
||||||
|
// @Param offset formData int false "start position"
|
||||||
|
// @Param limit formData int true "limit number of records"
|
||||||
|
// @Response 200 {object} e.Response[e.WithCount[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)
|
||||||
@ -26,15 +33,15 @@ func (h *handler) Search(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: pagination
|
var count int64
|
||||||
if req.Search == "" {
|
param := problem.QueryData{
|
||||||
// TODO: query without LIKE
|
Keyword: req.Keyword,
|
||||||
problems, status := h.problemService.QueryFuzz(req.Search, true, true)
|
Tag: req.Tag,
|
||||||
e.Pong(c, status, problems)
|
Offset: req.Offset,
|
||||||
return
|
Limit: req.Limit,
|
||||||
} else {
|
Count: &count,
|
||||||
problems, status := h.problemService.QueryFuzz(req.Search, true, true)
|
|
||||||
e.Pong(c, status, problems)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
problems, status := h.problemService.QueryFuzz(¶m)
|
||||||
|
e.Pong(c, status, e.WithCount[*model.Problem]{Count: count, Data: problems})
|
||||||
}
|
}
|
||||||
|
@ -6,13 +6,15 @@ import (
|
|||||||
"git.0x7f.app/WOJ/woj-server/internal/service/problem"
|
"git.0x7f.app/WOJ/woj-server/internal/service/problem"
|
||||||
"git.0x7f.app/WOJ/woj-server/pkg/utils"
|
"git.0x7f.app/WOJ/woj-server/pkg/utils"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/jackc/pgtype"
|
||||||
)
|
)
|
||||||
|
|
||||||
type updateRequest struct {
|
type updateRequest struct {
|
||||||
Pid uint `form:"pid"`
|
Pid uint `form:"pid" json:"pid"`
|
||||||
Title string `form:"title" binding:"required"`
|
Title string `form:"title" json:"title"`
|
||||||
Statement string `form:"statement" binding:"required"`
|
Statement string `form:"statement" json:"statement"`
|
||||||
IsEnabled bool `form:"is_enabled"`
|
Tags []string `form:"tags" json:"tags"`
|
||||||
|
IsEnabled bool `form:"is_enabled" json:"is_enabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update
|
// Update
|
||||||
@ -22,8 +24,9 @@ type updateRequest struct {
|
|||||||
// @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 false "title"
|
||||||
// @Param statement formData string true "statement"
|
// @Param statement formData string false "statement"
|
||||||
|
// @Param tags formData []string false "tags"
|
||||||
// @Param is_enabled formData bool false "is enabled"
|
// @Param is_enabled formData bool false "is enabled"
|
||||||
// @Response 200 {object} e.Response[model.Problem] "problem info without provider information"
|
// @Response 200 {object} e.Response[model.Problem] "problem info without provider information"
|
||||||
// @Security Authentication
|
// @Security Authentication
|
||||||
@ -50,22 +53,26 @@ func (h *handler) Update(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.Pid == 0 {
|
if req.Pid == 0 { // create problem
|
||||||
// create problem
|
// Title and Statement are required
|
||||||
|
if req.Title == "" || req.Statement == "" {
|
||||||
|
e.Pong[any](c, e.InvalidParameter, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
createData := &problem.CreateData{
|
createData := &problem.CreateData{
|
||||||
Title: req.Title,
|
Title: req.Title,
|
||||||
Statement: req.Statement,
|
Statement: req.Statement,
|
||||||
|
Tags: req.Tags,
|
||||||
ProviderID: uid,
|
ProviderID: uid,
|
||||||
IsEnabled: false,
|
IsEnabled: false,
|
||||||
}
|
}
|
||||||
p, status := h.problemService.Create(createData)
|
p, status := h.problemService.Create(createData)
|
||||||
e.Pong(c, status, p)
|
e.Pong(c, status, p)
|
||||||
return
|
return
|
||||||
} else {
|
} else { // update problem
|
||||||
// update problem
|
|
||||||
|
|
||||||
// check if problem exists
|
// check if problem exists
|
||||||
p, status := h.problemService.Query(req.Pid, true, false)
|
p, status := h.problemService.Query(&problem.QueryData{ID: req.Pid, Associations: true, ShouldEnable: false})
|
||||||
if status != e.Success {
|
if status != e.Success {
|
||||||
e.Pong[any](c, status, nil)
|
e.Pong[any](c, status, nil)
|
||||||
return
|
return
|
||||||
@ -77,10 +84,24 @@ func (h *handler) Update(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if IsEnabled is set to true, check if the problem has a latest version
|
||||||
|
if req.IsEnabled {
|
||||||
|
_, status := h.problemService.QueryLatestVersion(p.ID)
|
||||||
|
if status != e.Success {
|
||||||
|
e.Pong[any](c, status, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.IsEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
// update problem
|
// update problem
|
||||||
p.Title = utils.If(req.Title != "", req.Title, p.Title)
|
p.Title = utils.If(req.Title != "", req.Title, p.Title)
|
||||||
p.Statement = utils.If(req.Statement != "", req.Statement, p.Statement)
|
p.Statement = utils.If(req.Statement != "", req.Statement, p.Statement)
|
||||||
p.IsEnabled = req.IsEnabled
|
if len(req.Tags) != 0 {
|
||||||
|
tags := pgtype.TextArray{}
|
||||||
|
_ = tags.Set(req.Tags)
|
||||||
|
p.Tags = tags
|
||||||
|
}
|
||||||
|
|
||||||
p, status = h.problemService.Update(p)
|
p, status = h.problemService.Update(p)
|
||||||
e.Pong(c, status, p)
|
e.Pong(c, status, p)
|
||||||
|
@ -8,13 +8,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type queryRequest struct {
|
type queryRequest struct {
|
||||||
Pid uint `form:"pid"`
|
Pid uint `form:"pid" json:"pid"`
|
||||||
Uid uint `form:"uid"`
|
Uid uint `form:"uid" json:"uid"`
|
||||||
Offset int `form:"offset"`
|
Offset int `form:"offset" json:"offset"`
|
||||||
Limit int `form:"limit" binding:"required"`
|
Limit int `form:"limit" json:"limit" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type queryResponse struct {
|
type submissionWithScore struct {
|
||||||
Submission model.Submission `json:"submission"`
|
Submission model.Submission `json:"submission"`
|
||||||
Point int32 `json:"point"`
|
Point int32 `json:"point"`
|
||||||
}
|
}
|
||||||
@ -29,7 +29,8 @@ type queryResponse struct {
|
|||||||
// @Param uid formData uint false "user id"
|
// @Param uid formData uint false "user id"
|
||||||
// @Param offset formData int false "start position"
|
// @Param offset formData int false "start position"
|
||||||
// @Param limit formData int true "limit number of records"
|
// @Param limit formData int true "limit number of records"
|
||||||
// @Response 200 {object} e.Response[[]queryResponse] "queryResponse"
|
// @Response 200 {object} e.Response[e.WithCount[submissionWithScore]] "status"
|
||||||
|
// @Security Authentication
|
||||||
// @Router /v1/status/query [post]
|
// @Router /v1/status/query [post]
|
||||||
func (h *handler) Query(c *gin.Context) {
|
func (h *handler) Query(c *gin.Context) {
|
||||||
claim, exist := c.Get("claim")
|
claim, exist := c.Get("claim")
|
||||||
@ -44,21 +45,21 @@ func (h *handler) Query(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.Pid == 0 && req.Uid == 0 {
|
|
||||||
e.Pong[any](c, e.InvalidParameter, nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
submissions, status := h.submissionService.Query(req.Pid, req.Uid, req.Offset, req.Limit)
|
|
||||||
|
|
||||||
uid := claim.(*model.Claim).UID
|
uid := claim.(*model.Claim).UID
|
||||||
role := claim.(*model.Claim).Role
|
role := claim.(*model.Claim).Role
|
||||||
var response []*queryResponse
|
|
||||||
|
|
||||||
|
if req.Pid == 0 && req.Uid == 0 {
|
||||||
|
req.Uid = uid
|
||||||
|
}
|
||||||
|
|
||||||
|
var count int64
|
||||||
|
submissions, status := h.submissionService.Query(req.Pid, req.Uid, req.Offset, req.Limit, &count)
|
||||||
|
|
||||||
|
var response []*submissionWithScore
|
||||||
for _, submission := range submissions {
|
for _, submission := range submissions {
|
||||||
cur, _ := h.statusService.Query(submission.ID, false)
|
cur, _ := h.statusService.Query(submission.ID, false)
|
||||||
point := utils.If(cur == nil, -1, cur.Point)
|
point := utils.If(cur == nil, -1, cur.Point)
|
||||||
resp := &queryResponse{
|
resp := &submissionWithScore{
|
||||||
Submission: *submission,
|
Submission: *submission,
|
||||||
Point: point,
|
Point: point,
|
||||||
}
|
}
|
||||||
@ -71,5 +72,5 @@ func (h *handler) Query(c *gin.Context) {
|
|||||||
response = append(response, resp)
|
response = append(response, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
e.Pong(c, status, response)
|
e.Pong(c, status, e.WithCount[*submissionWithScore]{Count: count, Data: response})
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type queryOneRequest struct {
|
type queryOneRequest struct {
|
||||||
SubmissionID uint `form:"sid" binding:"required"`
|
SubmissionID uint `form:"sid" json:"sid" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryBySubmissionID
|
// QueryBySubmissionID
|
||||||
@ -18,6 +18,7 @@ type queryOneRequest struct {
|
|||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param sid formData uint true "submission id"
|
// @Param sid formData uint true "submission id"
|
||||||
// @Response 200 {object} e.Response[model.Status] "submission status"
|
// @Response 200 {object} e.Response[model.Status] "submission status"
|
||||||
|
// @Security Authentication
|
||||||
// @Router /v1/status/query/submission [post]
|
// @Router /v1/status/query/submission [post]
|
||||||
func (h *handler) QueryBySubmissionID(c *gin.Context) {
|
func (h *handler) QueryBySubmissionID(c *gin.Context) {
|
||||||
claim, exist := c.Get("claim")
|
claim, exist := c.Get("claim")
|
||||||
|
@ -7,9 +7,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type queryByVersionRequest struct {
|
type queryByVersionRequest struct {
|
||||||
ProblemVersionID uint `form:"pvid" binding:"required"`
|
ProblemVersionID uint `form:"pvid" json:"pvid" binding:"required"`
|
||||||
Offset int `form:"offset"`
|
Offset int `form:"offset" json:"offset"`
|
||||||
Limit int `form:"limit" binding:"required"`
|
Limit int `form:"limit" json:"limit" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryByProblemVersion
|
// QueryByProblemVersion
|
||||||
@ -21,7 +21,7 @@ type queryByVersionRequest struct {
|
|||||||
// @Param pvid formData uint true "problem version"
|
// @Param pvid formData uint true "problem version"
|
||||||
// @Param offset formData int false "start position"
|
// @Param offset formData int false "start position"
|
||||||
// @Param limit formData int true "max number of results"
|
// @Param limit formData int true "max number of results"
|
||||||
// @Response 200 {object} e.Response[[]model.Status] "submission status array"
|
// @Response 200 {object} e.Response[e.WithCount[model.Status]] "submission status array"
|
||||||
// @Security Authentication
|
// @Security Authentication
|
||||||
// @Router /v1/status/query/version [post]
|
// @Router /v1/status/query/version [post]
|
||||||
func (h *handler) QueryByProblemVersion(c *gin.Context) {
|
func (h *handler) QueryByProblemVersion(c *gin.Context) {
|
||||||
@ -44,6 +44,7 @@ func (h *handler) QueryByProblemVersion(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
submitStatus, status := h.statusService.QueryByVersion(req.ProblemVersionID, req.Offset, req.Limit)
|
var count int64
|
||||||
e.Pong(c, status, submitStatus)
|
submitStatus, status := h.statusService.QueryByVersion(req.ProblemVersionID, req.Offset, req.Limit, &count)
|
||||||
|
e.Pong(c, status, e.WithCount[*model.Status]{Count: count, Data: submitStatus})
|
||||||
}
|
}
|
||||||
|
@ -8,9 +8,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type createRequest struct {
|
type createRequest struct {
|
||||||
Pid uint `form:"pid" binding:"required"`
|
Pid uint `form:"pid" json:"pid" binding:"required"`
|
||||||
Language string `form:"language" binding:"required"`
|
Language string `form:"language" json:"language" binding:"required"`
|
||||||
Code string `form:"code" binding:"required"`
|
Code string `form:"code" json:"code" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create
|
// Create
|
||||||
@ -47,6 +47,13 @@ func (h *handler) Create(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// query latest version
|
||||||
|
pv, status := h.problemService.QueryLatestVersion(req.Pid)
|
||||||
|
if status != e.Success {
|
||||||
|
e.Pong[any](c, status, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// create submission
|
// create submission
|
||||||
createData := &submission.CreateData{
|
createData := &submission.CreateData{
|
||||||
ProblemID: req.Pid,
|
ProblemID: req.Pid,
|
||||||
@ -60,13 +67,6 @@ func (h *handler) Create(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// query latest version
|
|
||||||
pv, status := h.problemService.QueryLatestVersion(req.Pid)
|
|
||||||
if status != e.Success {
|
|
||||||
e.Pong[any](c, status, nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// submit judge
|
// submit judge
|
||||||
payload := &model.SubmitJudgePayload{
|
payload := &model.SubmitJudgePayload{
|
||||||
ProblemVersionID: pv.ID,
|
ProblemVersionID: pv.ID,
|
||||||
|
@ -7,7 +7,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type rejudgeRequest struct {
|
type rejudgeRequest struct {
|
||||||
Sid uint `form:"sid" binding:"required"`
|
Sid uint `form:"sid" json:"sid" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rejudge
|
// Rejudge
|
||||||
|
@ -8,9 +8,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type createRequest struct {
|
type createRequest struct {
|
||||||
UserName string `form:"username" binding:"required"`
|
UserName string `form:"username" json:"username" binding:"required"`
|
||||||
Password string `form:"password" binding:"required"`
|
Password string `form:"password" json:"password" binding:"required"`
|
||||||
NickName string `form:"nickname" binding:"required"`
|
NickName string `form:"nickname" json:"nickname" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create
|
// Create
|
||||||
|
@ -8,8 +8,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type loginRequest struct {
|
type loginRequest struct {
|
||||||
UserName string `form:"username" binding:"required"`
|
UserName string `form:"username" json:"username" binding:"required"`
|
||||||
Password string `form:"password" binding:"required"`
|
Password string `form:"password" json:"password" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type loginResponse struct {
|
type loginResponse struct {
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type profileRequest struct {
|
type profileRequest struct {
|
||||||
UID uint `form:"uid"`
|
UID uint `form:"uid" json:"uid"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Profile
|
// Profile
|
||||||
|
@ -12,6 +12,11 @@ type Response[T any] struct {
|
|||||||
Body T `json:"body"`
|
Body T `json:"body"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WithCount[T any] struct {
|
||||||
|
Count int64 `json:"count"`
|
||||||
|
Data []T `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
func wrap[T any](status Status, body T) Response[interface{}] {
|
func wrap[T any](status Status, body T) Response[interface{}] {
|
||||||
return Response[interface{}]{
|
return Response[interface{}]{
|
||||||
Code: int(status),
|
Code: int(status),
|
||||||
|
@ -7,8 +7,9 @@ import (
|
|||||||
|
|
||||||
type Problem struct {
|
type Problem struct {
|
||||||
gorm.Model `json:"meta"`
|
gorm.Model `json:"meta"`
|
||||||
Title string `json:"title" gorm:"not null"`
|
Title string `json:"title" gorm:"not null;index"`
|
||||||
Statement string `json:"statement" gorm:"not null"`
|
Statement string `json:"statement" gorm:"not null"`
|
||||||
|
Tags pgtype.TextArray `json:"tags" gorm:"type:text[];index"`
|
||||||
ProviderID uint `json:"-" gorm:"not null;index"`
|
ProviderID uint `json:"-" gorm:"not null;index"`
|
||||||
Provider User `json:"provider" gorm:"foreignKey:ProviderID"`
|
Provider User `json:"provider" gorm:"foreignKey:ProviderID"`
|
||||||
IsEnabled bool `json:"is_enabled" gorm:"not null;index"`
|
IsEnabled bool `json:"is_enabled" gorm:"not null;index"`
|
||||||
|
@ -4,7 +4,8 @@ import "gorm.io/gorm"
|
|||||||
|
|
||||||
type Submission struct {
|
type Submission struct {
|
||||||
gorm.Model `json:"meta"`
|
gorm.Model `json:"meta"`
|
||||||
ProblemID uint `json:"problem_id" gorm:"not null;index"`
|
ProblemID uint `json:"-" gorm:"not null;index"`
|
||||||
|
Problem Problem `json:"problem" gorm:"foreignKey:ProblemID"`
|
||||||
UserID uint `json:"-" gorm:"not null;index"`
|
UserID uint `json:"-" gorm:"not null;index"`
|
||||||
User User `json:"user" gorm:"foreignKey:UserID"`
|
User User `json:"user" gorm:"foreignKey:UserID"`
|
||||||
Language string `json:"language" gorm:"not null"`
|
Language string `json:"language" gorm:"not null"`
|
||||||
|
@ -3,20 +3,25 @@ package problem
|
|||||||
import (
|
import (
|
||||||
"git.0x7f.app/WOJ/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/internal/model"
|
||||||
|
"github.com/jackc/pgtype"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CreateData struct {
|
type CreateData struct {
|
||||||
Title string
|
Title string
|
||||||
Statement string
|
Statement string
|
||||||
|
Tags []string
|
||||||
ProviderID uint
|
ProviderID uint
|
||||||
IsEnabled bool
|
IsEnabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) Create(data *CreateData) (*model.Problem, e.Status) {
|
func (s *service) Create(data *CreateData) (*model.Problem, e.Status) {
|
||||||
|
tags := pgtype.TextArray{}
|
||||||
|
_ = tags.Set(data.Tags)
|
||||||
problem := &model.Problem{
|
problem := &model.Problem{
|
||||||
Title: data.Title,
|
Title: data.Title,
|
||||||
Statement: data.Statement,
|
Statement: data.Statement,
|
||||||
|
Tags: tags,
|
||||||
ProviderID: data.ProviderID,
|
ProviderID: data.ProviderID,
|
||||||
IsEnabled: data.IsEnabled,
|
IsEnabled: data.IsEnabled,
|
||||||
}
|
}
|
||||||
|
@ -9,23 +9,24 @@ import (
|
|||||||
"gorm.io/gorm/clause"
|
"gorm.io/gorm/clause"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *service) Query(pid uint, associations bool, shouldEnable bool) (*model.Problem, e.Status) {
|
func (s *service) Query(data *QueryData) (*model.Problem, e.Status) {
|
||||||
problem := new(model.Problem)
|
problem := new(model.Problem)
|
||||||
|
|
||||||
query := s.db.Get()
|
query := s.db.Get()
|
||||||
if associations {
|
|
||||||
|
if data.Associations {
|
||||||
query = query.Preload(clause.Associations)
|
query = query.Preload(clause.Associations)
|
||||||
}
|
}
|
||||||
err := query.First(&problem, pid).Error
|
err := query.First(&problem, data.ID).Error
|
||||||
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return nil, e.ProblemNotFound
|
return nil, e.ProblemNotFound
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Warn("DatabaseError", zap.Error(err), zap.Any("pid", pid))
|
s.log.Warn("DatabaseError", zap.Error(err), zap.Any("pid", data.ID))
|
||||||
return nil, e.DatabaseError
|
return nil, e.DatabaseError
|
||||||
}
|
}
|
||||||
|
|
||||||
if shouldEnable && !problem.IsEnabled {
|
if data.ShouldEnable && !problem.IsEnabled {
|
||||||
return nil, e.ProblemNotAvailable
|
return nil, e.ProblemNotAvailable
|
||||||
}
|
}
|
||||||
return problem, e.Success
|
return problem, e.Success
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
package problem
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.0x7f.app/WOJ/woj-server/internal/e"
|
|
||||||
"git.0x7f.app/WOJ/woj-server/internal/model"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"gorm.io/gorm/clause"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *service) QueryFuzz(search string, associations bool, shouldEnable bool) ([]*model.Problem, e.Status) {
|
|
||||||
problems := make([]*model.Problem, 0)
|
|
||||||
|
|
||||||
query := s.db.Get()
|
|
||||||
if associations {
|
|
||||||
query = query.Preload(clause.Associations)
|
|
||||||
}
|
|
||||||
if shouldEnable {
|
|
||||||
query = query.Where("is_enabled = true")
|
|
||||||
}
|
|
||||||
query = query.
|
|
||||||
Where(s.db.Get().Where("title LIKE ?", "%"+search+"%").
|
|
||||||
Or("statement LIKE ?", "%"+search+"%"))
|
|
||||||
err := query.Find(&problems).Error
|
|
||||||
if err != nil {
|
|
||||||
s.log.Warn("DatabaseError", zap.Error(err), zap.Any("search", search))
|
|
||||||
return nil, e.DatabaseError
|
|
||||||
}
|
|
||||||
|
|
||||||
return problems, e.Success
|
|
||||||
}
|
|
60
internal/service/problem/query_fuzz.go
Normal file
60
internal/service/problem/query_fuzz.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package problem
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||||
|
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
|
)
|
||||||
|
|
||||||
|
type QueryData struct {
|
||||||
|
// precise
|
||||||
|
ID uint
|
||||||
|
|
||||||
|
// fuzz
|
||||||
|
Keyword string
|
||||||
|
Tag string
|
||||||
|
|
||||||
|
// common
|
||||||
|
Associations bool
|
||||||
|
ShouldEnable bool
|
||||||
|
|
||||||
|
// paging
|
||||||
|
Offset int
|
||||||
|
Limit int
|
||||||
|
Count *int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) QueryFuzz(data *QueryData) ([]*model.Problem, e.Status) {
|
||||||
|
problems := make([]*model.Problem, 0)
|
||||||
|
query := s.db.Get()
|
||||||
|
|
||||||
|
if data.Associations {
|
||||||
|
query = query.Preload(clause.Associations)
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.ShouldEnable {
|
||||||
|
query = query.Where("is_enabled = true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.Keyword != "" {
|
||||||
|
query = query.
|
||||||
|
Where(s.db.Get().Where("title LIKE ?", "%"+data.Keyword+"%").
|
||||||
|
Or("statement LIKE ?", "%"+data.Keyword+"%"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.Tag != "" {
|
||||||
|
query = query.Where("EXISTS(SELECT 1 FROM unnest(tags) AS elem WHERE elem LIKE ?)", "%"+data.Tag+"%")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := query.Order("created_at ASC").
|
||||||
|
Offset(data.Offset).Limit(data.Limit).Find(&problems).
|
||||||
|
Offset(-1).Limit(-1).Count(data.Count).
|
||||||
|
Error
|
||||||
|
if err != nil {
|
||||||
|
s.log.Warn("DatabaseError", zap.Error(err), zap.Any("QueryData", data))
|
||||||
|
return nil, e.DatabaseError
|
||||||
|
}
|
||||||
|
|
||||||
|
return problems, e.Success
|
||||||
|
}
|
@ -14,8 +14,8 @@ var _ Service = (*service)(nil)
|
|||||||
type Service interface {
|
type Service interface {
|
||||||
Create(data *CreateData) (*model.Problem, e.Status)
|
Create(data *CreateData) (*model.Problem, e.Status)
|
||||||
Update(problem *model.Problem) (*model.Problem, e.Status)
|
Update(problem *model.Problem) (*model.Problem, e.Status)
|
||||||
Query(pid uint, associations bool, shouldEnable bool) (*model.Problem, e.Status)
|
Query(data *QueryData) (*model.Problem, e.Status)
|
||||||
QueryFuzz(search string, associations bool, shouldEnable bool) ([]*model.Problem, e.Status)
|
QueryFuzz(data *QueryData) ([]*model.Problem, e.Status)
|
||||||
|
|
||||||
CreateVersion(data *CreateVersionData) (*model.ProblemVersion, e.Status)
|
CreateVersion(data *CreateVersionData) (*model.ProblemVersion, e.Status)
|
||||||
UpdateVersion(pvid uint, values interface{}) e.Status
|
UpdateVersion(pvid uint, values interface{}) e.Status
|
||||||
|
@ -18,7 +18,10 @@ func (s *service) Query(sid uint, associations bool) (*model.Status, e.Status) {
|
|||||||
|
|
||||||
query := s.db.Get()
|
query := s.db.Get()
|
||||||
if associations {
|
if associations {
|
||||||
query = query.Preload(clause.Associations)
|
query = query.
|
||||||
|
Preload("Submission.Problem").
|
||||||
|
Preload("Submission.User").
|
||||||
|
Preload(clause.Associations)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := query.
|
err := query.
|
||||||
@ -36,21 +39,23 @@ 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, count *int64) ([]*model.Status, e.Status) {
|
||||||
var statuses []*model.Status
|
var ret []*model.Status
|
||||||
status := &model.Status{
|
status := &model.Status{
|
||||||
ProblemVersionID: pvid,
|
ProblemVersionID: pvid,
|
||||||
IsEnabled: true,
|
IsEnabled: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.db.Get().Preload(clause.Associations).
|
err := s.db.Get().
|
||||||
|
Preload("Submission.Problem").Preload("Submission.User").Preload(clause.Associations).
|
||||||
Where(status).
|
Where(status).
|
||||||
Limit(limit).
|
Order("created_at DESC").
|
||||||
Offset(offset).
|
Offset(offset).Limit(limit).Find(&ret).
|
||||||
Find(&statuses).Error
|
Offset(-1).Limit(-1).Count(count).
|
||||||
|
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
|
||||||
}
|
}
|
||||||
return statuses, e.Success
|
return ret, e.Success
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ var _ Service = (*service)(nil)
|
|||||||
type Service interface {
|
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, count *int64) ([]*model.Status, e.Status)
|
||||||
|
|
||||||
HealthCheck() error
|
HealthCheck() error
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
"gorm.io/gorm/clause"
|
"gorm.io/gorm/clause"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *service) Query(pid uint, uid uint, offset int, limit int) ([]*model.Submission, e.Status) {
|
func (s *service) Query(pid uint, uid uint, offset int, limit int, count *int64) ([]*model.Submission, e.Status) {
|
||||||
submissions := make([]*model.Submission, 0)
|
submissions := make([]*model.Submission, 0)
|
||||||
|
|
||||||
submission := &model.Submission{
|
submission := &model.Submission{
|
||||||
@ -19,9 +19,10 @@ func (s *service) Query(pid uint, uid uint, offset int, limit int) ([]*model.Sub
|
|||||||
|
|
||||||
err := s.db.Get().Preload(clause.Associations).
|
err := s.db.Get().Preload(clause.Associations).
|
||||||
Where(submission).
|
Where(submission).
|
||||||
Limit(limit).
|
Order("created_at DESC").
|
||||||
Offset(offset).
|
Offset(offset).Limit(limit).Find(&submissions).
|
||||||
Find(&submissions).Error
|
Offset(-1).Limit(-1).Count(count).
|
||||||
|
Error
|
||||||
|
|
||||||
//if errors.Is(err, gorm.ErrRecordNotFound) {
|
//if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
// return nil, e.ProblemNotFound
|
// return nil, e.ProblemNotFound
|
||||||
|
@ -13,7 +13,7 @@ var _ Service = (*service)(nil)
|
|||||||
|
|
||||||
type Service interface {
|
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, count *int64) ([]*model.Submission, e.Status)
|
||||||
QueryBySid(sid uint, associations bool) (*model.Submission, e.Status)
|
QueryBySid(sid uint, associations bool) (*model.Submission, e.Status)
|
||||||
|
|
||||||
HealthCheck() error
|
HealthCheck() error
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/gin-contrib/cors"
|
"github.com/gin-contrib/cors"
|
||||||
"github.com/gin-contrib/pprof"
|
"github.com/gin-contrib/pprof"
|
||||||
ginZap "github.com/gin-contrib/zap"
|
ginZap "github.com/gin-contrib/zap"
|
||||||
|
"github.com/gin-gonic/contrib/static"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
"github.com/samber/do"
|
"github.com/samber/do"
|
||||||
@ -57,7 +58,14 @@ func (s *service) initRouters(conf *model.Config, injector *do.Injector) *gin.En
|
|||||||
gin.SetMode(utils.If[string](conf.Development, gin.DebugMode, gin.ReleaseMode))
|
gin.SetMode(utils.If[string](conf.Development, gin.DebugMode, gin.ReleaseMode))
|
||||||
|
|
||||||
r := gin.New()
|
r := gin.New()
|
||||||
r.MaxMultipartMemory = 8 << 20
|
r.MaxMultipartMemory = 8 << 20 // 8MB
|
||||||
|
|
||||||
|
// +-----------+
|
||||||
|
// |Middlewares|
|
||||||
|
// +-----------+
|
||||||
|
|
||||||
|
// static files - must before sentry
|
||||||
|
r.Use(static.Serve("/", static.LocalFile("./resource/frontend", true)))
|
||||||
|
|
||||||
// Sentry middleware
|
// Sentry middleware
|
||||||
r.Use(sentrygin.New(sentrygin.Options{Repanic: true}))
|
r.Use(sentrygin.New(sentrygin.Options{Repanic: true}))
|
||||||
@ -87,6 +95,10 @@ func (s *service) initRouters(conf *model.Config, injector *do.Injector) *gin.En
|
|||||||
s.metric.SetLogPaths([]string{"/api"})
|
s.metric.SetLogPaths([]string{"/api"})
|
||||||
r.Use(s.metric.Handler())
|
r.Use(s.metric.Handler())
|
||||||
|
|
||||||
|
// +------+
|
||||||
|
// |Routes|
|
||||||
|
// +------+
|
||||||
|
|
||||||
// metrics
|
// metrics
|
||||||
r.GET("/metrics", gin.WrapH(promhttp.Handler()))
|
r.GET("/metrics", gin.WrapH(promhttp.Handler()))
|
||||||
|
|
||||||
@ -117,9 +129,7 @@ func (s *service) initRouters(conf *model.Config, injector *do.Injector) *gin.En
|
|||||||
api := r.Group("/api/")
|
api := r.Group("/api/")
|
||||||
s.setupApi(api, injector)
|
s.setupApi(api, injector)
|
||||||
|
|
||||||
// static files
|
// fallback to frontend
|
||||||
r.Static("/static", "./resource/frontend/static")
|
|
||||||
r.StaticFile("/", "./resource/frontend/index.html")
|
|
||||||
r.NoRoute(func(c *gin.Context) {
|
r.NoRoute(func(c *gin.Context) {
|
||||||
c.File("./resource/frontend/index.html")
|
c.File("./resource/frontend/index.html")
|
||||||
})
|
})
|
||||||
|
@ -35,7 +35,7 @@ spec:
|
|||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: runner
|
- name: runner
|
||||||
image: git.0x7f.app/woj/woj-runner:1.1.0
|
image: git.0x7f.app/woj/woj-runner:1.2.1
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
args:
|
args:
|
||||||
- runner
|
- runner
|
||||||
|
@ -18,33 +18,9 @@ spec:
|
|||||||
labels:
|
labels:
|
||||||
app: server
|
app: server
|
||||||
spec:
|
spec:
|
||||||
initContainers:
|
|
||||||
- name: init-server
|
|
||||||
image: git.0x7f.app/woj/woj-server:1.1.0
|
|
||||||
imagePullPolicy: IfNotPresent
|
|
||||||
args:
|
|
||||||
- init
|
|
||||||
env:
|
|
||||||
- name: DATABASE_HOST
|
|
||||||
value: "db-service.woj.svc.cluster.local"
|
|
||||||
- name: DATABASE_USER
|
|
||||||
valueFrom:
|
|
||||||
configMapKeyRef:
|
|
||||||
name: shared-config
|
|
||||||
key: POSTGRES_USER
|
|
||||||
- name: DATABASE_PASSWORD
|
|
||||||
valueFrom:
|
|
||||||
configMapKeyRef:
|
|
||||||
name: shared-config
|
|
||||||
key: POSTGRES_PASSWORD
|
|
||||||
- name: DATABASE_NAME
|
|
||||||
valueFrom:
|
|
||||||
configMapKeyRef:
|
|
||||||
name: shared-config
|
|
||||||
key: POSTGRES_DB
|
|
||||||
containers:
|
containers:
|
||||||
- name: server
|
- name: server
|
||||||
image: git.0x7f.app/woj/woj-server:1.1.0
|
image: git.0x7f.app/woj/woj-server:1.2.1
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
args:
|
args:
|
||||||
- server
|
- server
|
||||||
|
1
resource/frontend/.gitignore
vendored
1
resource/frontend/.gitignore
vendored
@ -0,0 +1 @@
|
|||||||
|
*
|
@ -1,8 +0,0 @@
|
|||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<title>Test</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Building</h1>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -5,7 +5,7 @@
|
|||||||
```
|
```
|
||||||
.
|
.
|
||||||
├── config.json # 题目配置信息
|
├── config.json # 题目配置信息
|
||||||
├── description.md # (not used) 题目描述必须通过 API 提交给系统
|
├── description.md # (optional) 题目描述,必须通过 API 提交,使用 import 脚本时将读取该文件
|
||||||
├── data # 数据目录
|
├── data # 数据目录
|
||||||
│ ├── input # 输入数据
|
│ ├── input # 输入数据
|
||||||
│ │ ├── (x).input # 第 x 组输入数据
|
│ │ ├── (x).input # 第 x 组输入数据
|
||||||
@ -32,11 +32,12 @@
|
|||||||
"NProcLimit": 1 // 进(线)程 限制
|
"NProcLimit": 1 // 进(线)程 限制
|
||||||
},
|
},
|
||||||
"Languages": [
|
"Languages": [
|
||||||
|
// 支持的语言
|
||||||
// c 语言,使用自定义评测脚本,脚本为 ./judge/XYZ.Makefile
|
// c 语言,使用自定义评测脚本,脚本为 ./judge/XYZ.Makefile
|
||||||
{"Lang": "c", "Type": "custom", "Script": "XYZ.Makefile", "Cmp": ""},
|
{"Lang": "c", "Type": "custom", "Script": "XYZ.Makefile", "Cmp": ""},
|
||||||
// c++ 语言,使用默认评测脚本,答案比对方式为 NCMP(testlib)
|
// c++ 语言,使用默认评测脚本,答案比对方式为 NCMP(testlib)
|
||||||
{"Lang": "cpp", "Type": "default", "Script": "", "Cmp": "NCMP"}
|
{"Lang": "cpp", "Type": "default", "Script": "", "Cmp": "NCMP"}
|
||||||
], // 支持的语言
|
],
|
||||||
"Tasks": [
|
"Tasks": [
|
||||||
// 评测点信息
|
// 评测点信息
|
||||||
{"Id": 1, "Points": 10}, // 第一个评测点,分值 25 分,使用 ./data/{input,output}/1.{input,output} 为测试数据
|
{"Id": 1, "Points": 10}, // 第一个评测点,分值 25 分,使用 ./data/{input,output}/1.{input,output} 为测试数据
|
||||||
@ -51,7 +52,7 @@
|
|||||||
|
|
||||||
1. 默认评测脚本目前只支持 `c` 语言和 `cpp`,参见 `../../framework/template/default/{c,cpp}.Makefile`
|
1. 默认评测脚本目前只支持 `c` 语言和 `cpp`,参见 `../../framework/template/default/{c,cpp}.Makefile`
|
||||||
2. 自定义评测脚本参见 `./judge/XYZ.Makefile`
|
2. 自定义评测脚本参见 `./judge/XYZ.Makefile`
|
||||||
3. `prebuild.Makefile`: 题目初始化脚本,用于编译辅助程序、生成数据等。如果存在改文件,系统会在题目分发到判机后自动执行,否则跳过改步骤
|
3. `prebuild.Makefile`: 题目初始化脚本,用于编译辅助程序、生成数据等。如果存在该文件,系统会在题目分发到判机后自动执行,否则跳过该步骤
|
||||||
|
|
||||||
## 注意事项
|
## 注意事项
|
||||||
|
|
||||||
|
@ -3,15 +3,36 @@ include ${TEMPLATE}/c.mk ${TEMPLATE}/Judger.mk
|
|||||||
|
|
||||||
# 评测分四个阶段
|
# 评测分四个阶段
|
||||||
# 1. prebuild: 用于提前生成测试数据、评测器、spj等工具,runner 只执行一次
|
# 1. prebuild: 用于提前生成测试数据、评测器、spj等工具,runner 只执行一次
|
||||||
# 只有 ./data, ./judge 目录可见
|
# 详细信息见 XYZ.Makefile
|
||||||
# 2. compile: 用于编译用户提交的程序
|
# 2. compile: 用于编译用户提交的程序
|
||||||
# 只有 ./user/$(USER_PROG).$(LANG) 和 ./judge 目录可见
|
# 目录映射情况:
|
||||||
|
# /woj/problem
|
||||||
|
# ├── judge 映射到题目目录的 ./judge <-- Readonly
|
||||||
|
# └── user 映射到题目目录的 ./user <-- 用户提交的程序在这里,命名:$(USER_PROG).$(LANG)
|
||||||
|
# 环境变量:
|
||||||
|
# USER_PROG=... <-- 一段随机字符串
|
||||||
|
# LANG=... <-- 用户提交的程序的语言,如 c, cpp
|
||||||
|
# 其余通用环境变量,详见 ubuntu-full.Dockerfile
|
||||||
|
# 执行限制:
|
||||||
|
# 目前版本硬编码限制:时间 60s,内存 256mb
|
||||||
# 3. run: 运行用户程序
|
# 3. run: 运行用户程序
|
||||||
# 只有 ./data/input/*.input 和 ./user/$(USER_PROG).out 可见
|
# 只有 ./data/input/*.input 和 ./user/$(USER_PROG).out 可见
|
||||||
# 用户输出存放于 ./user/?.out.usr
|
# 用户输出存放于 ./user/?.out.usr
|
||||||
# 使用 woj-sandbox 运行,等效于 $(PREFIX)/user/$(USER_PROG).out < $(PREFIX)/data/input/$(TEST_NUM).input > $(PREFIX)/user/$(TEST_NUM).out.usr
|
# 使用 woj-sandbox 运行,等效于 $(PREFIX)/user/$(USER_PROG).out < $(PREFIX)/data/input/$(TEST_NUM).input > $(PREFIX)/user/$(TEST_NUM).out.usr
|
||||||
# 4. judge: 用于判定输出结果 环境变量 TEST_NUM 表示当前测试点编号
|
# 4. judge: 用于判定输出结果 环境变量 TEST_NUM 表示当前测试点编号,每个测试点都会执行一次
|
||||||
# 所有目录 ./data ./judge ./user 可见
|
# 目录映射情况:
|
||||||
|
# /woj/problem
|
||||||
|
# ├── data 映射到题目目录的 ./data <-- Readonly
|
||||||
|
# ├── judge 映射到题目目录的 ./judge <-- Readonly
|
||||||
|
# └── user
|
||||||
|
# ├── $(TEST_NUM).out.usr <-- 用户程序在 $(TEST_NUM) 上的输出
|
||||||
|
# └── $(TEST_NUM).judge <-- 评测结果写在这里,格式要求为 testlib 的 XML 格式
|
||||||
|
# 环境变量:
|
||||||
|
# TEST_NUM=... <-- 当前测试点编号
|
||||||
|
# CMP=... <-- 在 config.json 中配置的比较器,如 NCMP
|
||||||
|
# 其余通用环境变量,详见 ubuntu-full.Dockerfile
|
||||||
|
# 执行限制:
|
||||||
|
# 目前版本硬编码限制:时间 60s,内存 256mb
|
||||||
|
|
||||||
compile:
|
compile:
|
||||||
$(CC) $(CFLAGS) -o $(PREFIX)/user/$(USER_PROG).out $(PREFIX)/user/$(USER_PROG).$(LANG) $(PREFIX)/judge/gadget.c
|
$(CC) $(CFLAGS) -o $(PREFIX)/user/$(USER_PROG).out $(PREFIX)/user/$(USER_PROG).$(LANG) $(PREFIX)/judge/gadget.c
|
||||||
|
@ -2,6 +2,17 @@ include ${TEMPLATE}/c.mk ${TEMPLATE}/Judger.mk
|
|||||||
|
|
||||||
# 当题目被下载到 runner 后,会自动执行 prebuild 阶段,判题时不会再次执行
|
# 当题目被下载到 runner 后,会自动执行 prebuild 阶段,判题时不会再次执行
|
||||||
|
|
||||||
|
# prebuild 阶段环境信息
|
||||||
|
# 目录映射情况:
|
||||||
|
# /woj/problem
|
||||||
|
# ├── data 映射到题目目录的 ./data
|
||||||
|
# └── judge 映射到题目目录的 ./judge
|
||||||
|
# 环境变量:
|
||||||
|
# PREFIX=/woj/problem
|
||||||
|
# 其余通用环境变量,详见 ubuntu-full.Dockerfile
|
||||||
|
# 执行限制:
|
||||||
|
# 目前版本硬编码限制:时间 300s,内存 1g
|
||||||
|
|
||||||
prebuild:
|
prebuild:
|
||||||
# 生成测试数据生成工具
|
# 生成测试数据生成工具
|
||||||
clang++ -I$(TESTLIB) -Ofast -o $(PREFIX)/judge/gen.out $(PREFIX)/judge/gen.cpp
|
clang++ -I$(TESTLIB) -Ofast -o $(PREFIX)/judge/gen.out $(PREFIX)/judge/gen.cpp
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
. common.sh
|
SCRIPT_PATH=$(cd "$(dirname "$0")" && pwd)
|
||||||
|
. "$SCRIPT_PATH/common.sh"
|
||||||
|
|
||||||
function docker_run() {
|
function docker_run() {
|
||||||
local timeout=${TIMEOUT:-10}
|
local timeout=${TIMEOUT:-10}
|
||||||
|
local network=${NETWORK:-"none"}
|
||||||
|
local memory=${MEMORY:-"256m"}
|
||||||
local log_file=${LOG_FILE:-"/dev/stderr"}
|
local log_file=${LOG_FILE:-"/dev/stderr"}
|
||||||
local log_limit=${LOG_LIMIT:-4K}
|
local log_limit=${LOG_LIMIT:-4K}
|
||||||
log_info "$DOCKER run with timeout $timeout"
|
log_info "$DOCKER run with timeout $timeout"
|
||||||
@ -12,7 +15,7 @@ function docker_run() {
|
|||||||
sleep "$timeout"
|
sleep "$timeout"
|
||||||
$DOCKER kill "$CONTAINER_NAME"
|
$DOCKER kill "$CONTAINER_NAME"
|
||||||
) &
|
) &
|
||||||
$DOCKER run --rm --name "$CONTAINER_NAME" "$@" 2>&1 | head -c "$log_limit" >"$log_file"
|
$DOCKER run --rm --name "$CONTAINER_NAME" --network "$network" --memory "$memory" "$@" 2>&1 | head -c "$log_limit" >"$log_file"
|
||||||
pkill -P $$
|
pkill -P $$
|
||||||
$DOCKER kill "$CONTAINER_NAME" >/dev/null 2>&1
|
$DOCKER kill "$CONTAINER_NAME" >/dev/null 2>&1
|
||||||
return 0
|
return 0
|
120
resource/runner/scripts/import.sh
Executable file
120
resource/runner/scripts/import.sh
Executable file
@ -0,0 +1,120 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
WORKSPACE=$(cd "$(dirname "$0")"/.. && pwd)
|
||||||
|
. "$WORKSPACE"/scripts/common.sh
|
||||||
|
|
||||||
|
read -p "Enter HTTP API Endpoint: " -r endpoint
|
||||||
|
if [ -z "$endpoint" ]; then
|
||||||
|
log_error "[-] HTTP API Endpoint cannot be empty"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
read -p "Enter token: " -r token
|
||||||
|
if [ -z "$token" ]; then
|
||||||
|
log_error "[-] Token cannot be empty"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
for problem in "$WORKSPACE/problem/"*; do
|
||||||
|
if [ -d "$problem" ]; then
|
||||||
|
dir_name=$(basename "$problem")
|
||||||
|
log_info "[+] Importing problem $dir_name"
|
||||||
|
|
||||||
|
if [ ! -f "$problem/config.json" ]; then
|
||||||
|
log_warn "[-] Skipping: $dir_name/config.json not found"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "$problem/description.md" ]; then
|
||||||
|
log_warn "[-] Skipping: $dir_name/description.md not found"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
read -p "Are you sure you want to import $dir_name? [y/N] " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
log_warn "[-] Skipping: user cancelled"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
title=$(head -n 1 "$problem/description.md" | sed -e 's/^# //' | xargs)
|
||||||
|
description=$(cat "$problem/description.md")
|
||||||
|
# TODO: extract tags
|
||||||
|
log_info "[*] Title: $title"
|
||||||
|
|
||||||
|
zip_file=$(mktemp -u --suffix .zip)
|
||||||
|
log_info "[*] Compressing $problem into $zip_file" >/dev/null
|
||||||
|
cd "$problem" && zip -9rq "$zip_file" . -x ".mark.prebuild" && cd ..
|
||||||
|
|
||||||
|
payload=$(jq -nc \
|
||||||
|
--arg title "$title" \
|
||||||
|
--arg description "$description" \
|
||||||
|
'{ pid: 0, title: $title, statement: $description, is_enable: false }')
|
||||||
|
|
||||||
|
log_info "[*] Creating problem statement"
|
||||||
|
response=$(curl -s \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: $token" \
|
||||||
|
-X POST \
|
||||||
|
-d "$payload" \
|
||||||
|
"$endpoint/api/v1/problem/update")
|
||||||
|
|
||||||
|
code=$(echo "$response" | jq -r '.code')
|
||||||
|
if [ "$code" != "0" ]; then
|
||||||
|
log_error "[-] Failed to create problem statement"
|
||||||
|
log_error "[-] Response: $response"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
id=$(echo "$response" | jq -r '.body.meta.ID')
|
||||||
|
log_info "[*] Problem statement created with id: $id"
|
||||||
|
|
||||||
|
log_info "[*] Uploading problem package"
|
||||||
|
response=$(curl -s \
|
||||||
|
-H "Authorization: $token" \
|
||||||
|
-X POST \
|
||||||
|
"$endpoint/api/v1/problem/upload")
|
||||||
|
|
||||||
|
code=$(echo "$response" | jq -r '.code')
|
||||||
|
if [ "$code" != "0" ]; then
|
||||||
|
log_error "[-] Failed to get upload url"
|
||||||
|
log_error "[-] Response: $response"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
upload_url=$(echo "$response" | jq -r '.body.url')
|
||||||
|
storage_key=$(echo "$response" | jq -r '.body.key')
|
||||||
|
curl -s -X PUT -T "$zip_file" "$upload_url"
|
||||||
|
# shellcheck disable=SC2181
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
log_error "[-] Failed to upload problem package"
|
||||||
|
echo curl -s -X PUT -T "$zip_file" "$upload_url"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
payload=$(jq -nc \
|
||||||
|
--argjson pid "$id" \
|
||||||
|
--arg storage_key "$storage_key" \
|
||||||
|
'{ pid: $pid, storage_key: $storage_key }')
|
||||||
|
|
||||||
|
log_info "[*] Creating problem version"
|
||||||
|
response=$(curl -s \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: $token" \
|
||||||
|
-X POST \
|
||||||
|
-d "$payload" \
|
||||||
|
"$endpoint/api/v1/problem/create_version")
|
||||||
|
|
||||||
|
code=$(echo "$response" | jq -r '.code')
|
||||||
|
if [ "$code" != "0" ]; then
|
||||||
|
log_error "[-] Failed to create problem version"
|
||||||
|
log_error "[-] Payload: $payload"
|
||||||
|
log_error "[-] Response: $response"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "[*] Problem version created"
|
||||||
|
log_info "[+] Problem $dir_name imported successfully"
|
||||||
|
fi
|
||||||
|
|
||||||
|
done
|
@ -1,6 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
. common.sh
|
SCRIPT_PATH=$(cd "$(dirname "$0")" && pwd)
|
||||||
|
. "$SCRIPT_PATH/common.sh"
|
||||||
|
|
||||||
cd "$(dirname "$0")"/../ || exit 1
|
cd "$(dirname "$0")"/../ || exit 1
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
. common.sh
|
SCRIPT_PATH=$(cd "$(dirname "$0")" && pwd)
|
||||||
|
. "$SCRIPT_PATH/common.sh"
|
||||||
|
|
||||||
# get_problem_info
|
# get_problem_info
|
||||||
# extract language info and limits
|
# extract language info and limits
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
WORKSPACE=$(cd "$(dirname "$0")"/.. && pwd)
|
WORKSPACE=$(cd "$(dirname "$0")"/.. && pwd)
|
||||||
. "$WORKSPACE"/scripts/run_timeout.sh
|
|
||||||
. "$WORKSPACE"/scripts/common.sh
|
. "$WORKSPACE"/scripts/common.sh
|
||||||
|
. "$WORKSPACE"/scripts/docker_run.sh
|
||||||
. "$WORKSPACE"/scripts/problem.sh
|
. "$WORKSPACE"/scripts/problem.sh
|
||||||
|
|
||||||
if [ "$1" == "" ] || [ ! -d "$WORKSPACE/problem/$1" ] || [ "$2" == "" ] || [ ! -d "$WORKSPACE/user/$2" ] || [ -z "$3" ]; then
|
if [ "$1" == "" ] || [ ! -d "$WORKSPACE/problem/$1" ] || [ "$2" == "" ] || [ ! -d "$WORKSPACE/user/$2" ] || [ -z "$3" ]; then
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
WORKSPACE=$(cd "$(dirname "$0")"/.. && pwd)
|
WORKSPACE=$(cd "$(dirname "$0")"/.. && pwd)
|
||||||
. "$WORKSPACE"/scripts/run_timeout.sh
|
|
||||||
. "$WORKSPACE"/scripts/common.sh
|
. "$WORKSPACE"/scripts/common.sh
|
||||||
|
. "$WORKSPACE"/scripts/docker_run.sh
|
||||||
. "$WORKSPACE"/scripts/problem.sh
|
. "$WORKSPACE"/scripts/problem.sh
|
||||||
|
|
||||||
if [ "$1" == "" ] || [ ! -d "$WORKSPACE/problem/$1" ] || [ "$2" == "" ] || [ ! -d "$WORKSPACE/user/$2" ] || [ -z "$3" ]; then
|
if [ "$1" == "" ] || [ ! -d "$WORKSPACE/problem/$1" ] || [ "$2" == "" ] || [ ! -d "$WORKSPACE/user/$2" ] || [ -z "$3" ]; then
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
WORKSPACE=$(cd "$(dirname "$0")"/.. && pwd)
|
WORKSPACE=$(cd "$(dirname "$0")"/.. && pwd)
|
||||||
. "$WORKSPACE"/scripts/run_timeout.sh
|
|
||||||
. "$WORKSPACE"/scripts/common.sh
|
. "$WORKSPACE"/scripts/common.sh
|
||||||
|
. "$WORKSPACE"/scripts/docker_run.sh
|
||||||
|
|
||||||
if [ "$1" == "" ] || [ ! -d "$WORKSPACE/problem/$1" ]; then
|
if [ "$1" == "" ] || [ ! -d "$WORKSPACE/problem/$1" ]; then
|
||||||
log_warn "Usage: $0 <problem> <timeout>"
|
log_warn "Usage: $0 <problem> <timeout>"
|
||||||
@ -22,6 +22,7 @@ if [ ! -f "$WORKSPACE/problem/$1/judge/prebuild.Makefile" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
export TIMEOUT=${2:-300}
|
export TIMEOUT=${2:-300}
|
||||||
|
export MEMORY="1g"
|
||||||
docker_run \
|
docker_run \
|
||||||
-v "$WORKSPACE/problem/$1/data":/woj/problem/data \
|
-v "$WORKSPACE/problem/$1/data":/woj/problem/data \
|
||||||
-v "$WORKSPACE/problem/$1/judge":/woj/problem/judge \
|
-v "$WORKSPACE/problem/$1/judge":/woj/problem/judge \
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
WORKSPACE=$(cd "$(dirname "$0")"/.. && pwd)
|
WORKSPACE=$(cd "$(dirname "$0")"/.. && pwd)
|
||||||
. "$WORKSPACE"/scripts/run_timeout.sh
|
|
||||||
. "$WORKSPACE"/scripts/common.sh
|
. "$WORKSPACE"/scripts/common.sh
|
||||||
|
. "$WORKSPACE"/scripts/docker_run.sh
|
||||||
. "$WORKSPACE"/scripts/problem.sh
|
. "$WORKSPACE"/scripts/problem.sh
|
||||||
|
|
||||||
if [ "$1" == "" ] || [ ! -d "$WORKSPACE/problem/$1" ] || [ "$2" == "" ] || [ ! -d "$WORKSPACE/user/$2" ] || [ -z "$3" ]; then
|
if [ "$1" == "" ] || [ ! -d "$WORKSPACE/problem/$1" ] || [ "$2" == "" ] || [ ! -d "$WORKSPACE/user/$2" ] || [ -z "$3" ]; then
|
||||||
|
Loading…
Reference in New Issue
Block a user