feat: refactor to use DI

This commit is contained in:
Paul Pan 2023-07-15 16:19:49 +08:00
parent eca749e2c6
commit a0c36c1606
78 changed files with 887 additions and 712 deletions

View File

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

View File

@ -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

View File

@ -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"]

View File

@ -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"]

View File

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

View File

@ -1 +1 @@
1.0.0
1.1.0

View File

@ -1,10 +1,8 @@
package cmd
import (
"git.0x7f.app/WOJ/woj-server/internal/global"
"github.com/urfave/cli/v2"
"log"
"math/rand"
"time"
)
@ -61,15 +59,3 @@ func getBuildTime() time.Time {
}
return build
}
func CommonSetup(c *cli.Context) *global.Global {
rand.Seed(time.Now().Unix())
g := new(global.Global)
g.SetupConfig(c.String("config"))
g.SetupZap()
g.Log.Info("starting...")
return g
}

View File

@ -1,34 +0,0 @@
package main
import (
"git.0x7f.app/WOJ/woj-server/cmd"
"git.0x7f.app/WOJ/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)
}

View File

@ -1,34 +0,0 @@
package main
import (
"git.0x7f.app/WOJ/woj-server/cmd"
"git.0x7f.app/WOJ/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)
}

104
cmd/woj/woj.go Normal file
View File

@ -0,0 +1,104 @@
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/samber/do"
"github.com/urfave/cli/v2"
slog "log"
"os"
)
func main() {
a := cmd.App
a.Usage = "woj-server"
a.Commands = []*cli.Command{
{
Name: "web",
Aliases: []string{"w"},
Usage: "start web api server",
Action: runServer,
},
{
Name: "runner",
Aliases: []string{"r"},
Usage: "start runner",
Action: 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 runServer(c *cli.Context) error {
injector := prepareServices(c)
logger := do.MustInvoke[log.Service](injector)
defer func() { _ = logger.GetRawLogger().Sync() }()
logger.GetRawLogger().Info("starting...")
return appServer.RunServer(injector)
}
func runRunner(c *cli.Context) error {
injector := prepareServices(c)
logger := do.MustInvoke[log.Service](injector)
defer func() { _ = logger.GetRawLogger().Sync() }()
logger.GetRawLogger().Info("starting...")
return appRunner.RunRunner(injector)
}

8
go.mod
View File

@ -1,18 +1,19 @@
module git.0x7f.app/WOJ/woj-server
go 1.19
go 1.20
require (
github.com/gin-contrib/cors v1.4.0
github.com/gin-contrib/pprof v1.4.0
github.com/gin-contrib/zap v0.1.0
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/hibiken/asynq v0.24.1
github.com/jackc/pgtype v1.14.0
github.com/minio/minio-go/v7 v7.0.60
github.com/prometheus/client_golang v1.16.0
github.com/redis/go-redis/v9 v9.0.5
github.com/samber/do v1.6.0
github.com/swaggo/files v1.0.1
github.com/swaggo/gin-swagger v1.6.0
github.com/swaggo/swag v1.16.1
@ -37,7 +38,7 @@ require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonpointer v0.20.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/spec v0.20.9 // indirect
github.com/go-openapi/swag v0.22.4 // indirect
@ -70,7 +71,6 @@ require (
github.com/prometheus/client_model v0.4.0 // indirect
github.com/prometheus/common v0.44.0 // indirect
github.com/prometheus/procfs v0.11.0 // indirect
github.com/redis/go-redis/v9 v9.0.5 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/rs/xid v1.5.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect

12
go.sum
View File

@ -33,7 +33,6 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cu
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g=
@ -55,8 +54,9 @@ github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ=
github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA=
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
@ -78,8 +78,6 @@ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k=
github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
@ -216,9 +214,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0=
github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
@ -251,6 +246,8 @@ github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OK
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/samber/do v1.6.0 h1:Jy/N++BXINDB6lAx5wBlbpHlUdl0FKpLWgGEV9YWqaU=
github.com/samber/do v1.6.0/go.mod h1:DWqBvumy8dyb2vEnYZE7D7zaVEB64J45B0NjTlY/M4k=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
@ -439,7 +436,6 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=

View File

@ -2,11 +2,12 @@ package consumer
import (
"context"
"git.0x7f.app/WOJ/woj-server/internal/global"
"git.0x7f.app/WOJ/woj-server/internal/misc/log"
"git.0x7f.app/WOJ/woj-server/internal/service/problem"
"git.0x7f.app/WOJ/woj-server/internal/service/status"
"git.0x7f.app/WOJ/woj-server/internal/service/task"
"github.com/hibiken/asynq"
"github.com/samber/do"
"go.uber.org/zap"
)
@ -24,12 +25,12 @@ type handler struct {
taskService task.Service
}
func NewConsumer(g *global.Global) Handler {
func NewConsumer(i *do.Injector) Handler {
hnd := &handler{
log: g.Log,
problemService: problem.NewService(g),
statusService: status.NewService(g),
taskService: task.NewService(g),
log: do.MustInvoke[log.Service](i).GetLogger("api.consumer"),
problemService: do.MustInvoke[problem.Service](i),
statusService: do.MustInvoke[status.Service](i),
taskService: do.MustInvoke[task.Service](i),
}
return hnd

View File

@ -1,8 +1,9 @@
package debug
import (
"git.0x7f.app/WOJ/woj-server/internal/global"
"git.0x7f.app/WOJ/woj-server/internal/misc/log"
"github.com/gin-gonic/gin"
"github.com/samber/do"
"go.uber.org/zap"
)
@ -16,7 +17,9 @@ type handler struct {
log *zap.Logger
}
func RouteRegister(g *global.Global, group *gin.RouterGroup) {
app := &handler{g.Log}
group.GET("/random", app.randomString)
func RouteRegister(rg *gin.RouterGroup, i *do.Injector) {
app := &handler{}
app.log = do.MustInvoke[log.Service](i).GetLogger("api.debug")
rg.GET("/random", app.randomString)
}

View File

@ -2,7 +2,6 @@ package problem
import (
"git.0x7f.app/WOJ/woj-server/internal/e"
"git.0x7f.app/WOJ/woj-server/internal/global"
"git.0x7f.app/WOJ/woj-server/internal/model"
"git.0x7f.app/WOJ/woj-server/internal/service/problem"
"github.com/gin-gonic/gin"
@ -30,9 +29,9 @@ func (h *handler) CreateVersion(c *gin.Context) {
return
}
// uid := claim.(*global.Claim).UID
// uid := claim.(*model.Claim).UID
role := claim.(*global.Claim).Role
role := claim.(*model.Claim).Role
req := new(createVersionRequest)
if err := c.ShouldBind(req); err != nil {

View File

@ -2,7 +2,6 @@ package problem
import (
"git.0x7f.app/WOJ/woj-server/internal/e"
"git.0x7f.app/WOJ/woj-server/internal/global"
"git.0x7f.app/WOJ/woj-server/internal/model"
"github.com/gin-gonic/gin"
)
@ -28,7 +27,7 @@ func (h *handler) Details(c *gin.Context) {
}
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)
if status != e.Success {

View File

@ -1,11 +1,13 @@
package problem
import (
"git.0x7f.app/WOJ/woj-server/internal/global"
"git.0x7f.app/WOJ/woj-server/internal/misc/log"
"git.0x7f.app/WOJ/woj-server/internal/service/problem"
"git.0x7f.app/WOJ/woj-server/internal/service/storage"
"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/samber/do"
"go.uber.org/zap"
)
@ -20,24 +22,24 @@ type Handler interface {
type handler struct {
log *zap.Logger
jwtService global.JwtService
jwtService jwt.Service
problemService problem.Service
taskService task.Service
storageService storage.Service
}
func RouteRegister(g *global.Global, group *gin.RouterGroup) {
func RouteRegister(rg *gin.RouterGroup, i *do.Injector) {
app := &handler{
log: g.Log,
jwtService: g.Jwt,
problemService: problem.NewService(g),
taskService: task.NewService(g),
storageService: storage.NewService(g),
log: do.MustInvoke[log.Service](i).GetLogger("api.problem"),
jwtService: do.MustInvoke[jwt.Service](i),
problemService: do.MustInvoke[problem.Service](i),
taskService: do.MustInvoke[task.Service](i),
storageService: do.MustInvoke[storage.Service](i),
}
group.POST("/search", app.Search)
group.POST("/details", app.jwtService.Handler(false), app.Details)
group.POST("/update", app.jwtService.Handler(true), app.Update)
group.POST("/upload", app.jwtService.Handler(true), app.Upload)
group.POST("/create_version", app.jwtService.Handler(true), app.CreateVersion)
rg.POST("/search", app.Search)
rg.POST("/details", app.jwtService.Handler(false), app.Details)
rg.POST("/update", app.jwtService.Handler(true), app.Update)
rg.POST("/upload", app.jwtService.Handler(true), app.Upload)
rg.POST("/create_version", app.jwtService.Handler(true), app.CreateVersion)
}

View File

@ -2,7 +2,6 @@ package problem
import (
"git.0x7f.app/WOJ/woj-server/internal/e"
"git.0x7f.app/WOJ/woj-server/internal/global"
"git.0x7f.app/WOJ/woj-server/internal/model"
"git.0x7f.app/WOJ/woj-server/internal/service/problem"
"github.com/gin-gonic/gin"
@ -34,8 +33,8 @@ func (h *handler) Update(c *gin.Context) {
return
}
uid := claim.(*global.Claim).UID
role := claim.(*global.Claim).Role
uid := claim.(*model.Claim).UID
role := claim.(*model.Claim).Role
if role < model.RoleAdmin {
e.Pong(c, e.UserUnauthorized, nil)
return

View File

@ -2,7 +2,6 @@ package problem
import (
"git.0x7f.app/WOJ/woj-server/internal/e"
"git.0x7f.app/WOJ/woj-server/internal/global"
"git.0x7f.app/WOJ/woj-server/internal/model"
"git.0x7f.app/WOJ/woj-server/pkg/utils"
"github.com/gin-gonic/gin"
@ -23,7 +22,7 @@ func (h *handler) Upload(c *gin.Context) {
return
}
role := claim.(*global.Claim).Role
role := claim.(*model.Claim).Role
if role < model.RoleAdmin {
e.Pong(c, e.UserUnauthorized, nil)
return

View File

@ -4,11 +4,12 @@ import (
"context"
"errors"
"git.0x7f.app/WOJ/woj-server/internal/e"
"git.0x7f.app/WOJ/woj-server/internal/global"
"git.0x7f.app/WOJ/woj-server/internal/misc/log"
"git.0x7f.app/WOJ/woj-server/internal/service/runner"
"git.0x7f.app/WOJ/woj-server/internal/service/storage"
"git.0x7f.app/WOJ/woj-server/internal/service/task"
"github.com/hibiken/asynq"
"github.com/samber/do"
"go.uber.org/zap"
)
@ -26,17 +27,17 @@ type handler struct {
storageService storage.Service
}
func NewRunner(g *global.Global) (Handler, error) {
func NewRunner(i *do.Injector) (Handler, error) {
hnd := &handler{
log: g.Log,
runnerService: runner.NewService(g),
taskService: task.NewService(g),
storageService: storage.NewService(g),
log: do.MustInvoke[log.Service](i).GetLogger("api.runner"),
runnerService: do.MustInvoke[runner.Service](i),
taskService: do.MustInvoke[task.Service](i),
storageService: do.MustInvoke[storage.Service](i),
}
status := hnd.runnerService.EnsureDeps(false)
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")
}

View File

@ -1,9 +1,11 @@
package status
import (
"git.0x7f.app/WOJ/woj-server/internal/global"
"git.0x7f.app/WOJ/woj-server/internal/misc/log"
"git.0x7f.app/WOJ/woj-server/internal/service/status"
"git.0x7f.app/WOJ/woj-server/internal/web/jwt"
"github.com/gin-gonic/gin"
"github.com/samber/do"
"go.uber.org/zap"
)
@ -17,16 +19,16 @@ type Handler interface {
type handler struct {
log *zap.Logger
statusService status.Service
jwtService global.JwtService
jwtService jwt.Service
}
func RouteRegister(g *global.Global, group *gin.RouterGroup) {
func RouteRegister(rg *gin.RouterGroup, i *do.Injector) {
app := &handler{
log: g.Log,
statusService: status.NewService(g),
jwtService: g.Jwt,
log: do.MustInvoke[log.Service](i).GetLogger("api.status"),
statusService: do.MustInvoke[status.Service](i),
jwtService: do.MustInvoke[jwt.Service](i),
}
group.POST("/query", app.Query)
group.POST("/query/problem_version", app.jwtService.Handler(true), app.QueryByProblemVersion)
rg.POST("/query", app.Query)
rg.POST("/query/problem_version", app.jwtService.Handler(true), app.QueryByProblemVersion)
}

View File

@ -2,7 +2,6 @@ package status
import (
"git.0x7f.app/WOJ/woj-server/internal/e"
"git.0x7f.app/WOJ/woj-server/internal/global"
"git.0x7f.app/WOJ/woj-server/internal/model"
"github.com/gin-gonic/gin"
)
@ -31,7 +30,7 @@ func (h *handler) QueryByProblemVersion(c *gin.Context) {
return
}
role := claim.(*global.Claim).Role
role := claim.(*model.Claim).Role
req := new(queryByVersionRequest)

View File

@ -2,7 +2,6 @@ package submission
import (
"git.0x7f.app/WOJ/woj-server/internal/e"
"git.0x7f.app/WOJ/woj-server/internal/global"
"git.0x7f.app/WOJ/woj-server/internal/model"
"git.0x7f.app/WOJ/woj-server/internal/service/submission"
"github.com/gin-gonic/gin"
@ -32,9 +31,9 @@ func (h *handler) Create(c *gin.Context) {
return
}
uid := claim.(*global.Claim).UID
uid := claim.(*model.Claim).UID
role := claim.(*global.Claim).Role
role := claim.(*model.Claim).Role
req := new(createRequest)
if err := c.ShouldBind(req); err != nil {

View File

@ -1,12 +1,14 @@
package submission
import (
"git.0x7f.app/WOJ/woj-server/internal/global"
"git.0x7f.app/WOJ/woj-server/internal/misc/log"
"git.0x7f.app/WOJ/woj-server/internal/service/problem"
"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/service/task"
"git.0x7f.app/WOJ/woj-server/internal/web/jwt"
"github.com/gin-gonic/gin"
"github.com/samber/do"
"go.uber.org/zap"
)
@ -20,24 +22,24 @@ type Handler interface {
type handler struct {
log *zap.Logger
jwtService global.JwtService
jwtService jwt.Service
problemService problem.Service
statusService status.Service
submissionService submission.Service
taskService task.Service
}
func RouteRegister(g *global.Global, group *gin.RouterGroup) {
func RouteRegister(rg *gin.RouterGroup, i *do.Injector) {
app := &handler{
log: g.Log,
jwtService: g.Jwt,
problemService: problem.NewService(g),
statusService: status.NewService(g),
submissionService: submission.NewService(g),
taskService: task.NewService(g),
log: do.MustInvoke[log.Service](i).GetLogger("api.submission"),
jwtService: do.MustInvoke[jwt.Service](i),
problemService: do.MustInvoke[problem.Service](i),
statusService: do.MustInvoke[status.Service](i),
submissionService: do.MustInvoke[submission.Service](i),
taskService: do.MustInvoke[task.Service](i),
}
group.POST("/create", app.jwtService.Handler(true), app.Create)
group.POST("/query", app.Query)
group.POST("/rejudge", app.jwtService.Handler(true), app.Rejudge)
rg.POST("/create", app.jwtService.Handler(true), app.Create)
rg.POST("/query", app.Query)
rg.POST("/rejudge", app.jwtService.Handler(true), app.Rejudge)
}

View File

@ -2,7 +2,6 @@ package submission
import (
"git.0x7f.app/WOJ/woj-server/internal/e"
"git.0x7f.app/WOJ/woj-server/internal/global"
"git.0x7f.app/WOJ/woj-server/internal/model"
"github.com/gin-gonic/gin"
)
@ -27,7 +26,7 @@ func (h *handler) Rejudge(c *gin.Context) {
return
}
role := claim.(*global.Claim).Role
role := claim.(*model.Claim).Role
req := new(rejudgeRequest)
if err := c.ShouldBind(req); err != nil {

View File

@ -2,7 +2,7 @@ package user
import (
"git.0x7f.app/WOJ/woj-server/internal/e"
"git.0x7f.app/WOJ/woj-server/internal/global"
"git.0x7f.app/WOJ/woj-server/internal/model"
"git.0x7f.app/WOJ/woj-server/internal/service/user"
"github.com/gin-gonic/gin"
)
@ -48,7 +48,7 @@ func (h *handler) Create(c *gin.Context) {
return
}
claim := &global.Claim{
claim := &model.Claim{
UID: u.ID,
Role: u.Role,
Version: version,

View File

@ -1,9 +1,11 @@
package user
import (
"git.0x7f.app/WOJ/woj-server/internal/global"
"git.0x7f.app/WOJ/woj-server/internal/misc/log"
"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/samber/do"
"go.uber.org/zap"
)
@ -18,19 +20,19 @@ type Handler interface {
type handler struct {
log *zap.Logger
jwtService global.JwtService
jwtService jwt.Service
userService user.Service
}
func RouteRegister(g *global.Global, group *gin.RouterGroup) {
func RouteRegister(rg *gin.RouterGroup, i *do.Injector) {
app := &handler{
log: g.Log,
jwtService: g.Jwt,
userService: user.NewService(g),
log: do.MustInvoke[log.Service](i).GetLogger("api.user"),
jwtService: do.MustInvoke[jwt.Service](i),
userService: do.MustInvoke[user.Service](i),
}
group.POST("/create", app.Create)
group.POST("/login", app.Login)
group.POST("/logout", app.jwtService.Handler(true), app.Logout)
group.POST("/profile", app.jwtService.Handler(true), app.Profile)
rg.POST("/create", app.Create)
rg.POST("/login", app.Login)
rg.POST("/logout", app.jwtService.Handler(true), app.Logout)
rg.POST("/profile", app.jwtService.Handler(true), app.Profile)
}

View File

@ -2,7 +2,7 @@ package user
import (
"git.0x7f.app/WOJ/woj-server/internal/e"
"git.0x7f.app/WOJ/woj-server/internal/global"
"git.0x7f.app/WOJ/woj-server/internal/model"
"git.0x7f.app/WOJ/woj-server/internal/service/user"
"github.com/gin-gonic/gin"
)
@ -46,7 +46,7 @@ func (h *handler) Login(c *gin.Context) {
e.Pong(c, status, nil)
return
}
claim := &global.Claim{
claim := &model.Claim{
UID: u.ID,
Role: u.Role,
Version: version,

View File

@ -2,7 +2,7 @@ package user
import (
"git.0x7f.app/WOJ/woj-server/internal/e"
"git.0x7f.app/WOJ/woj-server/internal/global"
"git.0x7f.app/WOJ/woj-server/internal/model"
"github.com/gin-gonic/gin"
)
@ -21,6 +21,6 @@ func (h *handler) Logout(c *gin.Context) {
return
}
_, status := h.userService.IncrVersion(claim.(*global.Claim).UID)
_, status := h.userService.IncrVersion(claim.(*model.Claim).UID)
e.Pong(c, status, nil)
}

View File

@ -2,7 +2,6 @@ package user
import (
"git.0x7f.app/WOJ/woj-server/internal/e"
"git.0x7f.app/WOJ/woj-server/internal/global"
"git.0x7f.app/WOJ/woj-server/internal/model"
"github.com/gin-gonic/gin"
)
@ -29,8 +28,8 @@ func (h *handler) Profile(c *gin.Context) {
return
}
uid := claim.(*global.Claim).UID
role := claim.(*global.Claim).Role
uid := claim.(*model.Claim).UID
role := claim.(*model.Claim).Role
req := new(profileRequest)

View File

@ -2,17 +2,22 @@ package runner
import (
"git.0x7f.app/WOJ/woj-server/internal/api/runner"
"git.0x7f.app/WOJ/woj-server/internal/global"
"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"
"git.0x7f.app/WOJ/woj-server/pkg/zapasynq"
"github.com/hibiken/asynq"
"github.com/samber/do"
"go.uber.org/zap"
"runtime"
)
func RunRunner(g *global.Global) error {
hnd, err := runner.NewRunner(g)
func RunRunner(i *do.Injector) error {
conf := do.MustInvoke[config.Service](i).GetConfig()
rlog := do.MustInvoke[log.Service](i).GetLogger("app.runner")
hnd, err := runner.NewRunner(i)
if err != nil {
return err
}
@ -23,19 +28,19 @@ func RunRunner(g *global.Global) error {
srv := asynq.NewServer(
asynq.RedisClientOpt{
Addr: g.Conf.Redis.Address,
Password: g.Conf.Redis.Password,
DB: g.Conf.Redis.QueueDb,
Addr: conf.Redis.Address,
Password: conf.Redis.Password,
DB: conf.Redis.QueueDb,
},
asynq.Config{
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},
},
)
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
}

View File

@ -4,15 +4,15 @@ import (
"context"
"fmt"
"git.0x7f.app/WOJ/woj-server/internal/api/consumer"
"git.0x7f.app/WOJ/woj-server/internal/global"
"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/internal/repo/postgresql"
"git.0x7f.app/WOJ/woj-server/internal/repo/redis"
"git.0x7f.app/WOJ/woj-server/internal/router"
"git.0x7f.app/WOJ/woj-server/internal/service/jwt"
"git.0x7f.app/WOJ/woj-server/internal/repo/db"
"git.0x7f.app/WOJ/woj-server/internal/web/router"
"git.0x7f.app/WOJ/woj-server/pkg/utils"
"git.0x7f.app/WOJ/woj-server/pkg/zapasynq"
"github.com/hibiken/asynq"
"github.com/samber/do"
"go.uber.org/zap"
"net/http"
"os"
@ -22,23 +22,14 @@ import (
"time"
)
func RunServer(g *global.Global) error {
// Setup Database
g.Db = new(postgresql.Repo)
g.Db.Setup(g)
func RunServer(i *do.Injector) error { // Prepare Router
conf := do.MustInvoke[config.Service](i).GetConfig()
slog := do.MustInvoke[log.Service](i).GetLogger("app.server")
// Setup Redis
g.Redis = new(redis.Repo)
g.Redis.Setup(g)
// Setup JWT
g.Jwt = jwt.NewJwtService(g)
// Prepare Router
routers := router.InitRouters(g)
routers := do.MustInvoke[router.Service](i).GetRouter()
// 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{
Addr: addr,
Handler: routers,
@ -47,56 +38,56 @@ func RunServer(g *global.Global) error {
// Run Server
go func() {
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
g.Log.Fatal("ListenAndServe Failed", zap.Error(err))
slog.Fatal("ListenAndServe Failed", zap.Error(err))
}
}()
// Create Queue
queueMux := asynq.NewServeMux()
{
handler := consumer.NewConsumer(g)
handler := consumer.NewConsumer(i)
queueMux.HandleFunc(model.TypeProblemUpdate, handler.ProblemUpdate)
queueMux.HandleFunc(model.TypeSubmitUpdate, handler.SubmitUpdate)
}
queueSrv := asynq.NewServer(
asynq.RedisClientOpt{
Addr: g.Conf.Redis.Address,
Password: g.Conf.Redis.Password,
DB: g.Conf.Redis.QueueDb,
Addr: conf.Redis.Address,
Password: conf.Redis.Password,
DB: conf.Redis.QueueDb,
},
asynq.Config{
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},
},
)
// Run Queue
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.
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
g.Log.Info("Shutting down server ...")
slog.Info("Shutting down server ...")
// Graceful Shutdown Server
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
err := server.Shutdown(ctx)
if err != nil {
g.Log.Warn("Server Shutdown Failed", zap.Error(err))
slog.Warn("Server Shutdown Failed", zap.Error(err))
}
// Graceful Shutdown Queue
queueSrv.Shutdown()
// Graceful Shutdown Database
err = g.Db.Close()
err = do.MustInvoke[db.Service](i).Close()
if err != nil {
g.Log.Warn("Database Close Failed", zap.Error(err))
slog.Warn("Database Close Failed", zap.Error(err))
}
return err

View File

@ -1,15 +0,0 @@
package global
import (
"git.0x7f.app/WOJ/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
}

View File

@ -1,23 +0,0 @@
package global
import (
"git.0x7f.app/WOJ/woj-server/internal/e"
"git.0x7f.app/WOJ/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
}

View File

@ -1,7 +0,0 @@
package global
type Repo interface {
Setup(*Global)
Get() interface{}
Close() error
}

View File

@ -1,40 +0,0 @@
package global
import (
"git.0x7f.app/WOJ/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),
),
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())
}
}

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

67
internal/misc/log/zap.go Normal file
View File

@ -0,0 +1,67 @@
package log
import (
"git.0x7f.app/WOJ/woj-server/internal/misc/config"
"git.0x7f.app/WOJ/woj-server/pkg/utils"
"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
}
return srv, nil
}
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
}

View File

@ -1,4 +1,4 @@
package global
package model
type ConfigWebServer struct {
Address string `yaml:"Address"`

12
internal/model/jwt.go Normal file
View 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
}

View File

@ -1,11 +1,12 @@
package global
package model
import (
"github.com/gin-gonic/gin"
"github.com/samber/do"
)
type EndpointInfo struct {
Version string
Path string
Register func(g *Global, group *gin.RouterGroup)
Register func(rg *gin.RouterGroup, i *do.Injector)
}

59
internal/repo/cache/redis.go vendored Normal file
View File

@ -0,0 +1,59 @@
package cache
import (
"context"
"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(conf.Redis.Address, 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
}
}

139
internal/repo/db/pg.go Normal file
View File

@ -0,0 +1,139 @@
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"
"github.com/samber/do"
"go.uber.org/zap"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/schema"
"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
dsn := fmt.Sprintf(
// TODO: timezone as config
"user=%s password=%s dbname=%s host=%s port=%d sslmode=disable TimeZone=Asia/Shanghai",
conf.Database.User,
conf.Database.Password,
conf.Database.Database,
conf.Database.Host,
conf.Database.Port,
)
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...")
_ = 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{})
}
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
}

View File

@ -1,113 +0,0 @@
package postgresql
import (
"database/sql"
"errors"
"fmt"
"git.0x7f.app/WOJ/woj-server/internal/global"
"git.0x7f.app/WOJ/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
}

View File

@ -1,39 +0,0 @@
package redis
import (
"context"
"git.0x7f.app/WOJ/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()
}

View File

@ -1,25 +0,0 @@
package jwt
import (
"git.0x7f.app/WOJ/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,
}
}

View File

@ -21,7 +21,7 @@ func (s *service) Create(data *CreateData) (*model.Problem, e.Status) {
IsEnabled: data.IsEnabled,
}
err := s.db.Create(problem).Error
err := s.db.Get().Create(problem).Error
if err != nil {
s.log.Warn("DatabaseError", zap.Error(err), zap.Any("problem", problem))
return nil, e.DatabaseError

View File

@ -19,7 +19,7 @@ func (s *service) CreateVersion(data *CreateVersionData) (*model.ProblemVersion,
StorageKey: data.StorageKey,
}
err := s.db.Create(problemVersion).Error
err := s.db.Get().Create(problemVersion).Error
if err != nil {
s.log.Warn("DatabaseError", zap.Error(err), zap.Any("problemVersion", problemVersion))
return nil, e.DatabaseError

View File

@ -12,7 +12,7 @@ import (
func (s *service) Query(pid uint, associations bool, shouldEnable bool) (*model.Problem, e.Status) {
problem := new(model.Problem)
query := s.db
query := s.db.Get()
if associations {
query = query.Preload(clause.Associations)
}

View File

@ -10,7 +10,7 @@ import (
func (s *service) QueryFuzz(search string, associations bool, shouldEnable bool) ([]*model.Problem, e.Status) {
problems := make([]*model.Problem, 0)
query := s.db
query := s.db.Get()
if 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(s.db.Where("title LIKE ?", "%"+search+"%").
Where(s.db.Get().Where("title LIKE ?", "%"+search+"%").
Or("statement LIKE ?", "%"+search+"%"))
err := query.Find(&problems).Error
if err != nil {

View File

@ -14,7 +14,7 @@ func (s *service) QueryLatestVersion(pid uint) (*model.ProblemVersion, e.Status)
IsEnabled: true,
}
err := s.db.
err := s.db.Get().
Where(problemVersion).
Last(&problemVersion).Error
if errors.Is(err, gorm.ErrRecordNotFound) {

View File

@ -11,7 +11,7 @@ import (
func (s *service) QueryVersion(pvid uint, shouldEnable bool) (*model.ProblemVersion, e.Status) {
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) {
return nil, e.ProblemVersionNotFound
}

View File

@ -2,10 +2,11 @@ package problem
import (
"git.0x7f.app/WOJ/woj-server/internal/e"
"git.0x7f.app/WOJ/woj-server/internal/global"
"git.0x7f.app/WOJ/woj-server/internal/misc/log"
"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"
"gorm.io/gorm"
)
var _ Service = (*service)(nil)
@ -20,16 +21,22 @@ type Service interface {
UpdateVersion(pvid uint, values interface{}) e.Status
QueryVersion(pvid uint, shouldEnable bool) (*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 {
log *zap.Logger
db *gorm.DB
db db.Service
}
func NewService(g *global.Global) Service {
return &service{
log: g.Log,
db: g.Db.Get().(*gorm.DB),
}
func (s *service) HealthCheck() error {
return nil
}

View File

@ -7,7 +7,7 @@ import (
)
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 {
s.log.Warn("DatabaseError", zap.Error(err), zap.Any("problem", problem))
return nil, e.DatabaseError

View File

@ -7,7 +7,7 @@ import (
)
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 {
s.log.Warn("DatabaseError", zap.Error(err), zap.Any("pvid", pvid), zap.Any("values", values))
return e.DatabaseError

View File

@ -2,7 +2,9 @@ package runner
import (
"git.0x7f.app/WOJ/woj-server/internal/e"
"git.0x7f.app/WOJ/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"
)
@ -23,6 +25,15 @@ type Service interface {
ParseConfig(version uint, skipCheck bool) (Config, error)
// ProblemExists check if problem exists
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 {
@ -30,9 +41,6 @@ type service struct {
verbose bool
}
func NewService(g *global.Global) Service {
return &service{
log: g.Log,
verbose: g.Conf.Development,
}
func (s *service) HealthCheck() error {
return nil
}

View File

@ -14,7 +14,7 @@ type CreateData struct {
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{
SubmissionID: data.SubmissionID,
ProblemVersionID: data.ProblemVersionID,
@ -26,7 +26,7 @@ func (s service) Create(data *CreateData) (*model.Status, e.Status) {
IsEnabled: true,
}
err := s.db.Create(status).Error
err := s.db.Get().Create(status).Error
if err != nil {
s.log.Warn("DatabaseError", zap.Error(err), zap.Any("status", status))
return nil, e.DatabaseError

View File

@ -9,14 +9,14 @@ import (
"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{
SubmissionID: sid,
IsEnabled: true,
}
query := s.db
query := s.db.Get()
if 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
}
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
status := &model.Status{
ProblemVersionID: pvid,
IsEnabled: true,
}
err := s.db.Preload(clause.Associations).
err := s.db.Get().Preload(clause.Associations).
Where(status).
Limit(limit).
Offset(offset).

View File

@ -2,10 +2,11 @@ package status
import (
"git.0x7f.app/WOJ/woj-server/internal/e"
"git.0x7f.app/WOJ/woj-server/internal/global"
"git.0x7f.app/WOJ/woj-server/internal/misc/log"
"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"
"gorm.io/gorm"
)
var _ Service = (*service)(nil)
@ -14,16 +15,22 @@ type Service interface {
Create(data *CreateData) (*model.Status, e.Status)
Query(sid uint, associations bool) (*model.Status, e.Status)
QueryByVersion(pvid uint, offset int, limit int) ([]*model.Status, e.Status)
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 {
log *zap.Logger
db *gorm.DB
db db.Service
}
func NewService(g *global.Global) Service {
return &service{
log: g.Log,
db: g.Db.Get().(*gorm.DB),
}
func (s *service) HealthCheck() error {
return nil
}

View File

@ -2,9 +2,11 @@ package storage
import (
"git.0x7f.app/WOJ/woj-server/internal/e"
"git.0x7f.app/WOJ/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/pkg/credentials"
"github.com/samber/do"
"go.uber.org/zap"
"time"
)
@ -14,6 +16,33 @@ var _ Service = (*service)(nil)
type Service interface {
Upload(objectName string, expiry time.Duration) (string, e.Status)
Get(objectName string, expiry time.Duration) (string, e.Status)
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 {
@ -22,20 +51,6 @@ type service struct {
bucket string
}
func NewService(g *global.Global) Service {
minioClient, err := minio.New(g.Conf.Storage.Endpoint, &minio.Options{
Creds: credentials.NewStaticV4(g.Conf.Storage.AccessKey, g.Conf.Storage.SecretKey, ""),
Secure: g.Conf.Storage.UseSSL,
})
if err != nil {
g.Log.Fatal("failed to create minio client", zap.Error(err))
return nil
}
return &service{
log: g.Log,
client: minioClient,
bucket: g.Conf.Storage.Bucket,
}
func (s *service) HealthCheck() error {
return nil
}

View File

@ -21,7 +21,7 @@ func (s *service) Create(data *CreateData) (*model.Submission, e.Status) {
Code: data.Code,
}
err := s.db.Create(submission).Error
err := s.db.Get().Create(submission).Error
if err != nil {
s.log.Warn("DatabaseError", zap.Error(err), zap.Any("submission", submission))
return nil, e.DatabaseError

View File

@ -17,7 +17,7 @@ func (s *service) Query(pid uint, uid uint, offset int, limit int) ([]*model.Sub
UserID: uid,
}
err := s.db.Preload(clause.Associations).
err := s.db.Get().Preload(clause.Associations).
Where(submission).
Limit(limit).
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) {
submission := new(model.Submission)
query := s.db
query := s.db.Get()
if associations {
query = query.Preload(clause.Associations)
}

View File

@ -2,10 +2,11 @@ package submission
import (
"git.0x7f.app/WOJ/woj-server/internal/e"
"git.0x7f.app/WOJ/woj-server/internal/global"
"git.0x7f.app/WOJ/woj-server/internal/misc/log"
"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"
"gorm.io/gorm"
)
var _ Service = (*service)(nil)
@ -14,16 +15,22 @@ type Service interface {
Create(data *CreateData) (*model.Submission, e.Status)
Query(pid uint, uid uint, offset int, limit int) ([]*model.Submission, e.Status)
QueryBySid(sid uint, associations bool) (*model.Submission, e.Status)
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 {
log *zap.Logger
db *gorm.DB
db db.Service
}
func NewService(g *global.Global) Service {
return &service{
log: g.Log,
db: g.Db.Get().(*gorm.DB),
}
func (s *service) HealthCheck() error {
return nil
}

View File

@ -1,11 +1,14 @@
package task
import (
"errors"
"git.0x7f.app/WOJ/woj-server/internal/e"
"git.0x7f.app/WOJ/woj-server/internal/global"
"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/internal/service/runner"
"github.com/hibiken/asynq"
"github.com/samber/do"
"go.uber.org/zap"
)
@ -18,6 +21,24 @@ type Service interface {
SubmitUpdate(data *model.SubmitUpdatePayload, ctx runner.JudgeStatus) (string, e.Status)
GetTaskInfo(string, string) (*asynq.TaskInfo, e.Status)
HealthCheck() error
}
func NewService(i *do.Injector) (Service, error) {
conf := do.MustInvoke[config.Service](i).GetConfig()
redisOpt := asynq.RedisClientOpt{
Addr: conf.Redis.Address,
Password: conf.Redis.Password,
DB: conf.Redis.QueueDb,
}
return &service{
log: do.MustInvoke[log.Service](i).GetLogger("task"),
queue: asynq.NewClient(redisOpt),
inspector: asynq.NewInspector(redisOpt),
}, nil
}
type service struct {
@ -26,15 +47,13 @@ type service struct {
inspector *asynq.Inspector
}
func NewService(g *global.Global) Service {
redisOpt := asynq.RedisClientOpt{
Addr: g.Conf.Redis.Address,
Password: g.Conf.Redis.Password,
DB: g.Conf.Redis.QueueDb,
func (s *service) HealthCheck() error {
servers, err := s.inspector.Servers()
if err != nil {
return err
}
return &service{
log: g.Log,
queue: asynq.NewClient(redisOpt),
inspector: asynq.NewInspector(redisOpt),
if len(servers) == 0 {
return errors.New("no asynq server found")
}
return nil
}

View File

@ -29,7 +29,7 @@ func (s *service) Create(data *CreateData) (*model.User, e.Status) {
IsEnabled: true,
}
err = s.db.Create(user).Error
err = s.db.Get().Create(user).Error
if err != nil && strings.Contains(err.Error(), "duplicate key") {
return nil, e.UserDuplicated
}

View File

@ -17,7 +17,7 @@ type LoginData struct {
func (s *service) Login(data *LoginData) (*model.User, e.Status) {
user := &model.User{UserName: data.UserName}
err := s.db.Where(user).First(&user).Error
err := s.db.Get().Where(user).First(&user).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, e.UserNotFound
}

View File

@ -11,7 +11,7 @@ import (
func (s *service) Profile(uid uint) (*model.User, e.Status) {
user := new(model.User)
err := s.db.First(&user, uid).Error
err := s.db.Get().First(&user, uid).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, e.UserNotFound
}

View File

@ -2,11 +2,12 @@ package user
import (
"git.0x7f.app/WOJ/woj-server/internal/e"
"git.0x7f.app/WOJ/woj-server/internal/global"
"git.0x7f.app/WOJ/woj-server/internal/misc/log"
"git.0x7f.app/WOJ/woj-server/internal/model"
"github.com/go-redis/redis/v8"
"git.0x7f.app/WOJ/woj-server/internal/repo/cache"
"git.0x7f.app/WOJ/woj-server/internal/repo/db"
"github.com/samber/do"
"go.uber.org/zap"
"gorm.io/gorm"
)
var _ Service = (*service)(nil)
@ -16,18 +17,24 @@ type Service interface {
Login(data *LoginData) (*model.User, e.Status)
IncrVersion(uid uint) (int64, e.Status)
Profile(uid uint) (*model.User, e.Status)
HealthCheck() error
}
func NewService(i *do.Injector) (Service, error) {
return &service{
log: do.MustInvoke[log.Service](i).GetLogger("user"),
db: do.MustInvoke[db.Service](i),
cache: do.MustInvoke[cache.Service](i),
}, nil
}
type service struct {
log *zap.Logger
db *gorm.DB
redis *redis.Client
db db.Service
cache cache.Service
}
func NewService(g *global.Global) Service {
return &service{
log: g.Log,
db: g.Db.Get().(*gorm.DB),
redis: g.Redis.Get().(*redis.Client),
}
func (s *service) HealthCheck() error {
return nil
}

View File

@ -8,7 +8,7 @@ import (
)
func (s *service) IncrVersion(uid uint) (int64, e.Status) {
version, err := s.redis.Incr(context.Background(), fmt.Sprintf("Version:%d", uid)).Result()
version, err := s.cache.Get().Incr(context.Background(), fmt.Sprintf("Version:%d", uid)).Result()
if err != nil {
s.log.Warn("RedisError", zap.Error(err), zap.Any("uid", uid))
return -1, e.RedisError

View File

@ -2,14 +2,14 @@ package jwt
import (
"git.0x7f.app/WOJ/woj-server/internal/e"
"git.0x7f.app/WOJ/woj-server/internal/global"
"git.0x7f.app/WOJ/woj-server/internal/model"
"github.com/gin-gonic/gin"
"strings"
)
func (s *service) Handler(forced bool) gin.HandlerFunc {
return func(c *gin.Context) {
claim, status := func() (*global.Claim, e.Status) {
claim, status := func() (*model.Claim, e.Status) {
const tokenPrefix = "bearer "
tokenHeader := c.GetHeader("Authorization")
if tokenHeader == "" || !strings.HasPrefix(strings.ToLower(tokenHeader), tokenPrefix) {

View File

@ -0,0 +1,50 @@
package jwt
import (
"git.0x7f.app/WOJ/woj-server/internal/e"
"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/internal/repo/cache"
"github.com/gin-gonic/gin"
"github.com/samber/do"
"go.uber.org/zap"
)
var _ Service = (*service)(nil)
type Service interface {
ParseToken(tokenText string) (*model.Claim, e.Status)
SignClaim(claim *model.Claim) (string, e.Status)
Validate(claim *model.Claim) bool
Handler(forced bool) gin.HandlerFunc
HealthCheck() error
}
func NewService(i *do.Injector) (Service, error) {
srv := &service{}
srv.log = do.MustInvoke[log.Service](i).GetLogger("jwt")
srv.cacheService = do.MustInvoke[cache.Service](i) // .Get().(*redis.Client)
conf := do.MustInvoke[config.Service](i).GetConfig()
srv.SigningKey = []byte(conf.WebServer.JwtSigningKey)
srv.ExpireHour = conf.WebServer.JwtExpireHour
return srv, srv.err
}
type service struct {
cacheService cache.Service
log *zap.Logger
SigningKey []byte
ExpireHour int
err error
}
func (s *service) HealthCheck() error {
return s.err
}

View File

@ -4,21 +4,21 @@ import (
"context"
"fmt"
"git.0x7f.app/WOJ/woj-server/internal/e"
"git.0x7f.app/WOJ/woj-server/internal/global"
"git.0x7f.app/WOJ/woj-server/internal/model"
"git.0x7f.app/WOJ/woj-server/pkg/utils"
"github.com/golang-jwt/jwt/v4"
"go.uber.org/zap"
"time"
)
func (s *service) ParseToken(tokenText string) (*global.Claim, e.Status) {
func (s *service) ParseToken(tokenText string) (*model.Claim, e.Status) {
if tokenText == "" {
return nil, e.TokenEmpty
}
token, err := jwt.ParseWithClaims(
tokenText,
new(global.Claim),
new(model.Claim),
func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
@ -41,20 +41,19 @@ func (s *service) ParseToken(tokenText string) (*global.Claim, e.Status) {
}
if token.Valid {
c := token.Claims.(*global.Claim)
c := token.Claims.(*model.Claim)
return c, e.Success
}
return nil, e.TokenInvalid
}
func (s *service) SignClaim(claim *global.Claim) (string, e.Status) {
func (s *service) SignClaim(claim *model.Claim) (string, e.Status) {
now := time.Now()
claim.IssuedAt = jwt.NewNumericDate(now)
claim.ExpiresAt = jwt.NewNumericDate(now.Add(time.Duration(s.ExpireHour) * time.Hour))
claim.ID = utils.RandomString(16)
// TODO: use per-user claim.Version to tracker invalidation
claim.NotBefore = jwt.NewNumericDate(time.Now())
token := jwt.NewWithClaims(jwt.SigningMethodHS512, claim)
@ -66,10 +65,10 @@ func (s *service) SignClaim(claim *global.Claim) (string, e.Status) {
return ss, e.Success
}
func (s *service) Validate(claim *global.Claim) bool {
curVersion, err := s.redis.Get(context.Background(), fmt.Sprintf("Version:%d", claim.UID)).Int64()
func (s *service) Validate(claim *model.Claim) bool {
curVersion, err := s.cacheService.Get().Get(context.Background(), fmt.Sprintf("Version:%d", claim.UID)).Int64()
if err != nil {
s.log.Debug("redis.Get error", zap.Error(err))
s.log.Debug("cache.Get error", zap.Error(err))
return false
}
return curVersion == claim.Version

View File

@ -9,16 +9,16 @@ import (
"time"
)
func (m *Metrics) SetLogPaths(paths []string) {
m.logPaths = paths
func (s *service) SetLogPaths(paths []string) {
s.logPaths = paths
}
func (m *Metrics) Handler() gin.HandlerFunc {
func (s *service) Handler() gin.HandlerFunc {
return func(c *gin.Context) {
url := c.Request.URL.String()
method := c.Request.Method
if !utils.Contains(m.logPaths, func(path string) bool { return strings.HasPrefix(url, path) }) {
if !utils.Contains(s.logPaths, func(path string) bool { return strings.HasPrefix(url, path) }) {
c.Next()
return
}
@ -38,6 +38,6 @@ func (m *Metrics) Handler() gin.HandlerFunc {
success = false
}
m.Record(method, url, success, status, err, elapsed)
s.Record(method, url, success, status, err, elapsed)
}
}

View File

@ -2,11 +2,32 @@ package metrics
import (
"git.0x7f.app/WOJ/woj-server/internal/e"
"git.0x7f.app/WOJ/woj-server/internal/misc/config"
"git.0x7f.app/WOJ/woj-server/internal/pkg/cast"
"github.com/gin-gonic/gin"
"github.com/prometheus/client_golang/prometheus"
"github.com/samber/do"
)
type Metrics struct {
var _ Service = (*service)(nil)
type Service interface {
Record(method, url string, success bool, httpCode int, errCode e.Status, elapsed float64)
SetLogPaths(paths []string)
Handler() gin.HandlerFunc
HealthCheck() error
}
func NewService(i *do.Injector) (Service, error) {
srv := &service{}
conf := do.MustInvoke[config.Service](i).GetConfig()
srv.setup(conf.Metrics.Namespace, conf.Metrics.Subsystem)
return srv, nil
}
type service struct {
namespace string
subsystem string
@ -16,11 +37,15 @@ type Metrics struct {
logPaths []string
}
func (m *Metrics) Setup(namespace string, subsystem string) {
m.namespace = namespace
m.subsystem = subsystem
func (s *service) HealthCheck() error {
return nil
}
m.counter = prometheus.NewCounterVec(
func (s *service) setup(namespace string, subsystem string) {
s.namespace = namespace
s.subsystem = subsystem
s.counter = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
@ -30,7 +55,7 @@ func (m *Metrics) Setup(namespace string, subsystem string) {
[]string{"method", "url"},
)
m.hist = prometheus.NewHistogramVec(
s.hist = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
@ -41,16 +66,16 @@ func (m *Metrics) Setup(namespace string, subsystem string) {
[]string{"method", "url", "success", "http_code", "err_code"},
)
prometheus.MustRegister(m.counter, m.hist)
prometheus.MustRegister(s.counter, s.hist)
}
func (m *Metrics) Record(method, url string, success bool, httpCode int, errCode e.Status, elapsed float64) {
m.counter.With(prometheus.Labels{
func (s *service) Record(method, url string, success bool, httpCode int, errCode e.Status, elapsed float64) {
s.counter.With(prometheus.Labels{
"method": method,
"url": url,
}).Inc()
m.hist.With(prometheus.Labels{
s.hist.With(prometheus.Labels{
"method": method,
"url": url,
"success": cast.ToString(success),

View File

@ -6,8 +6,9 @@ import (
"git.0x7f.app/WOJ/woj-server/internal/api/status"
"git.0x7f.app/WOJ/woj-server/internal/api/submission"
"git.0x7f.app/WOJ/woj-server/internal/api/user"
"git.0x7f.app/WOJ/woj-server/internal/global"
"git.0x7f.app/WOJ/woj-server/internal/model"
"github.com/gin-gonic/gin"
"github.com/samber/do"
)
// @title OJ Server API Documentation
@ -16,14 +17,14 @@ import (
// @securityDefinitions.apikey Authentication
// @in header
// @name Authorization
func setupApi(g *global.Global, root *gin.RouterGroup) {
func (s *service) setupApi(root *gin.RouterGroup, injector *do.Injector) {
for _, v := range endpoints {
group := root.Group(v.Version).Group(v.Path)
v.Register(g, group)
v.Register(group, injector)
}
}
var endpoints = []global.EndpointInfo{
var endpoints = []model.EndpointInfo{
{Version: "", Path: "/debug", Register: debug.RouteRegister},
{Version: "/v1", Path: "/user", Register: user.RouteRegister},
{Version: "/v1", Path: "/problem", Register: problem.RouteRegister},

View File

@ -1,37 +1,74 @@
package router
import (
"git.0x7f.app/WOJ/woj-server/internal/global"
"git.0x7f.app/WOJ/woj-server/internal/pkg/metrics"
_ "git.0x7f.app/WOJ/woj-server/internal/router/docs"
"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/internal/web/metrics"
_ "git.0x7f.app/WOJ/woj-server/internal/web/router/docs"
"git.0x7f.app/WOJ/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"
"github.com/samber/do"
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))
var _ Service = (*service)(nil)
type Service interface {
GetRouter() *gin.Engine
HealthCheck() error
}
func NewService(i *do.Injector) (Service, error) {
srv := &service{}
srv.metric = do.MustInvoke[metrics.Service](i)
srv.logger = do.MustInvoke[log.Service](i)
conf := do.MustInvoke[config.Service](i).GetConfig()
srv.initRouters(conf, i)
return srv, srv.err
}
type service struct {
metric metrics.Service
logger log.Service
engine *gin.Engine
err error
}
func (s *service) GetRouter() *gin.Engine {
return s.engine
}
func (s *service) HealthCheck() error {
return s.err
}
func (s *service) initRouters(conf *model.Config, injector *do.Injector) *gin.Engine {
gin.SetMode(utils.If(conf.Development, gin.DebugMode, gin.ReleaseMode))
r := gin.New()
r.MaxMultipartMemory = 8 << 20
// Logger middleware and debug
if g.Conf.Development {
if 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))
ginLog := s.logger.GetLogger("gin")
r.Use(ginZap.Ginzap(ginLog, time.RFC3339, false))
r.Use(ginZap.RecoveryWithZap(ginLog, true))
}
// CORS middleware
@ -43,10 +80,8 @@ func InitRouters(g *global.Global) *gin.Engine {
}))
// Prometheus middleware
g.Stat = new(metrics.Metrics)
g.Stat.Setup(g.Conf.Metrics.Namespace, g.Conf.Metrics.Subsystem)
g.Stat.SetLogPaths([]string{"/api"})
r.Use(g.Stat.Handler())
s.metric.SetLogPaths([]string{"/api"})
r.Use(s.metric.Handler())
// metrics
r.GET("/metrics", gin.WrapH(promhttp.Handler()))
@ -68,7 +103,7 @@ func InitRouters(g *global.Global) *gin.Engine {
// api
api := r.Group("/api/")
setupApi(g, api)
s.setupApi(api, injector)
// static files
r.Static("/static", "./resource/frontend/static")

1
internal/web/web.go Normal file
View File

@ -0,0 +1 @@
package web