feat: use generic Response type and rewrite swagger documentation. close #1
This commit is contained in:
parent
9485dbbce4
commit
eb6f5d0aca
2
Makefile
2
Makefile
@ -31,7 +31,7 @@ dep:
|
||||
|
||||
swagger:
|
||||
go install github.com/swaggo/swag/cmd/swag@latest
|
||||
$(GOBIN)/swag init -g internal/web/router/api.go -o internal/web/router/docs
|
||||
$(GOBIN)/swag init -g internal/web/router/api.go -d .,./internal/e,./internal/model --pdl 1 -o internal/web/router/docs
|
||||
|
||||
fmt:
|
||||
go fmt ./...
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
var _ Handler = (*handler)(nil)
|
||||
|
||||
type Handler interface {
|
||||
randomString(c *gin.Context)
|
||||
RandomString(c *gin.Context)
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
@ -21,5 +21,5 @@ 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)
|
||||
rg.GET("/random", app.RandomString)
|
||||
}
|
||||
|
@ -7,14 +7,14 @@ import (
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// randomString
|
||||
// @Summary random string
|
||||
// @Description generate random string with length = 32
|
||||
// RandomString
|
||||
// @Summary generate random string
|
||||
// @Description Generate random string with length = 32.
|
||||
// @Tags debug
|
||||
// @Produce json
|
||||
// @Response 200 {object} e.Response "random string"
|
||||
// @Response 200 {object} e.Response[string] "random string"
|
||||
// @Router /debug/random [get]
|
||||
func (h *handler) randomString(c *gin.Context) {
|
||||
func (h *handler) RandomString(c *gin.Context) {
|
||||
str := utils.RandomString(32)
|
||||
h.log.Info("random string", zap.String("str", str))
|
||||
e.Pong(c, e.Success, str)
|
||||
|
@ -13,55 +13,61 @@ type createVersionRequest struct {
|
||||
}
|
||||
|
||||
// CreateVersion
|
||||
// @Summary create a problem version
|
||||
// @Description create a problem version
|
||||
// @Tags problem
|
||||
// @Summary [admin] create a problem version
|
||||
// @Description Create a problem version associated with `pid`.
|
||||
// @Tags problem,admin
|
||||
// @Accept application/x-www-form-urlencoded
|
||||
// @Produce json
|
||||
// @Param pid formData int true "problem id"
|
||||
// @Param storage_key formData string true "storage key"
|
||||
// @Response 200 {object} e.Response ""
|
||||
// @Param storage_key formData string true "storage key, zip file containing problem data"
|
||||
// @Response 200 {object} e.Response[any] "nothing"
|
||||
// @Security Authentication
|
||||
// @Router /v1/problem/create_version [post]
|
||||
func (h *handler) CreateVersion(c *gin.Context) {
|
||||
claim, exist := c.Get("claim")
|
||||
if !exist {
|
||||
e.Pong(c, e.UserUnauthenticated, nil)
|
||||
e.Pong[any](c, e.UserUnauthenticated, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// uid := claim.(*model.Claim).UID
|
||||
|
||||
role := claim.(*model.Claim).Role
|
||||
req := new(createVersionRequest)
|
||||
|
||||
if err := c.ShouldBind(req); err != nil {
|
||||
e.Pong(c, e.InvalidParameter, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// guest can not submit
|
||||
// only admin can create problem version
|
||||
role := claim.(*model.Claim).Role
|
||||
if role < model.RoleAdmin {
|
||||
e.Pong(c, e.UserUnauthorized, nil)
|
||||
e.Pong[any](c, e.UserUnauthorized, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: check pid exist
|
||||
// make sure problem exists
|
||||
_, status := h.problemService.Query(req.ProblemID, false, false)
|
||||
if status != e.Success {
|
||||
e.Pong[any](c, status, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// create problem version
|
||||
createVersionData := &problem.CreateVersionData{
|
||||
ProblemID: req.ProblemID,
|
||||
StorageKey: req.StorageKey,
|
||||
}
|
||||
pv, status := h.problemService.CreateVersion(createVersionData)
|
||||
if status != e.Success {
|
||||
e.Pong(c, status, nil)
|
||||
e.Pong[any](c, status, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// enqueue task: runner build problem
|
||||
payload := &model.ProblemBuildPayload{
|
||||
ProblemVersionID: pv.ID,
|
||||
StorageKey: pv.StorageKey,
|
||||
}
|
||||
_, status = h.taskService.ProblemBuild(payload)
|
||||
e.Pong(c, status, nil)
|
||||
e.Pong[any](c, status, nil)
|
||||
|
||||
// TODO: if failed, delete problem version
|
||||
}
|
@ -10,18 +10,22 @@ type detailsRequest struct {
|
||||
Pid uint `form:"pid"`
|
||||
}
|
||||
|
||||
type problemDetailsResponse struct {
|
||||
Problem *model.Problem `json:"problem"`
|
||||
Context interface{} `json:"context"`
|
||||
}
|
||||
|
||||
// Details
|
||||
// @Summary get details of a problem
|
||||
// @Description get details of a problem
|
||||
// @Description Get details of a problem.
|
||||
// @Tags problem
|
||||
// @Accept application/x-www-form-urlencoded
|
||||
// @Produce json
|
||||
// @Param pid formData int true "problem id"
|
||||
// @Response 200 {object} e.Response "problem details"
|
||||
// @Response 200 {object} e.Response[problemDetailsResponse] "problem details"
|
||||
// @Router /v1/problem/details [post]
|
||||
func (h *handler) Details(c *gin.Context) {
|
||||
req := new(detailsRequest)
|
||||
|
||||
if err := c.ShouldBind(req); err != nil {
|
||||
e.Pong(c, e.InvalidParameter, err.Error())
|
||||
return
|
||||
@ -32,17 +36,18 @@ func (h *handler) Details(c *gin.Context) {
|
||||
|
||||
p, status := h.problemService.Query(req.Pid, true, shouldEnable)
|
||||
if status != e.Success {
|
||||
e.Pong(c, status, nil)
|
||||
e.Pong[any](c, status, nil)
|
||||
return
|
||||
}
|
||||
|
||||
pv, status := h.problemService.QueryLatestVersion(req.Pid)
|
||||
if status != e.Success {
|
||||
e.Pong(c, status, nil)
|
||||
e.Pong[any](c, status, nil)
|
||||
return
|
||||
}
|
||||
e.Pong(c, e.Success, gin.H{
|
||||
"problem": p,
|
||||
"context": pv.Context.Get(),
|
||||
|
||||
e.Pong(c, e.Success, problemDetailsResponse{
|
||||
Problem: p,
|
||||
Context: pv.Context.Get(),
|
||||
})
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package problem
|
||||
|
||||
import (
|
||||
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||
_ "git.0x7f.app/WOJ/woj-server/internal/model" // swag requires this
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@ -10,17 +11,16 @@ type searchRequest struct {
|
||||
}
|
||||
|
||||
// Search
|
||||
// @Summary get detail of a problem
|
||||
// @Description get detail of a problem
|
||||
// @Summary search for problems
|
||||
// @Description Search for problems based on keywords. If the keyword is empty, return all problems.
|
||||
// @Tags problem
|
||||
// @Accept application/x-www-form-urlencoded
|
||||
// @Produce json
|
||||
// @Param search formData string false "word search"
|
||||
// @Response 200 {object} e.Response "problemset"
|
||||
// @Param search formData string false "keyword"
|
||||
// @Response 200 {object} e.Response[[]model.Problem] "problems found"
|
||||
// @Router /v1/problem/search [post]
|
||||
func (h *handler) Search(c *gin.Context) {
|
||||
req := new(searchRequest)
|
||||
|
||||
if err := c.ShouldBind(req); err != nil {
|
||||
e.Pong(c, e.InvalidParameter, err.Error())
|
||||
return
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||
"git.0x7f.app/WOJ/woj-server/internal/service/problem"
|
||||
"git.0x7f.app/WOJ/woj-server/pkg/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@ -15,29 +16,31 @@ type updateRequest struct {
|
||||
}
|
||||
|
||||
// Update
|
||||
// @Summary create or update a problem
|
||||
// @Description create or update a problem
|
||||
// @Tags problem
|
||||
// @Summary [admin] create or update a problem
|
||||
// @Description Create or update a problem.
|
||||
// @Tags problem,admin
|
||||
// @Accept application/x-www-form-urlencoded
|
||||
// @Produce json
|
||||
// @Param pid formData int false "problem id, 0 for create"
|
||||
// @Param title formData string true "title"
|
||||
// @Param statement formData string true "statement"
|
||||
// @Param is_enabled formData bool false "is enabled"
|
||||
// @Response 200 {object} e.Response "problem info without provider information"
|
||||
// @Response 200 {object} e.Response[model.Problem] "problem info without provider information"
|
||||
// @Security Authentication
|
||||
// @Router /v1/problem/update [post]
|
||||
func (h *handler) Update(c *gin.Context) {
|
||||
claim, exist := c.Get("claim")
|
||||
if !exist {
|
||||
e.Pong(c, e.UserUnauthenticated, nil)
|
||||
e.Pong[any](c, e.UserUnauthenticated, nil)
|
||||
return
|
||||
}
|
||||
|
||||
uid := claim.(*model.Claim).UID
|
||||
role := claim.(*model.Claim).Role
|
||||
|
||||
// only admin can modify problem
|
||||
if role < model.RoleAdmin {
|
||||
e.Pong(c, e.UserUnauthorized, nil)
|
||||
e.Pong[any](c, e.UserUnauthorized, nil)
|
||||
return
|
||||
}
|
||||
|
||||
@ -48,6 +51,7 @@ func (h *handler) Update(c *gin.Context) {
|
||||
}
|
||||
|
||||
if req.Pid == 0 {
|
||||
// create problem
|
||||
createData := &problem.CreateData{
|
||||
Title: req.Title,
|
||||
Statement: req.Statement,
|
||||
@ -58,18 +62,24 @@ func (h *handler) Update(c *gin.Context) {
|
||||
e.Pong(c, status, p)
|
||||
return
|
||||
} else {
|
||||
// update problem
|
||||
|
||||
// check if problem exists
|
||||
p, status := h.problemService.Query(req.Pid, true, false)
|
||||
if status != e.Success {
|
||||
e.Pong(c, status, nil)
|
||||
return
|
||||
}
|
||||
if p.ProviderID != uid {
|
||||
e.Pong(c, e.UserUnauthorized, nil)
|
||||
e.Pong[any](c, status, nil)
|
||||
return
|
||||
}
|
||||
|
||||
p.Title = req.Title
|
||||
p.Statement = req.Statement
|
||||
// check if user is the provider of the problem
|
||||
if p.ProviderID != uid {
|
||||
e.Pong[any](c, e.UserUnauthorized, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// update problem
|
||||
p.Title = utils.If(req.Title != "", req.Title, p.Title)
|
||||
p.Statement = utils.If(req.Statement != "", req.Statement, p.Statement)
|
||||
p.IsEnabled = req.IsEnabled
|
||||
|
||||
p, status = h.problemService.Update(p)
|
||||
|
@ -8,37 +8,40 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type uploadResponse struct {
|
||||
Key string `json:"key"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
// Upload
|
||||
// @Summary get upload url
|
||||
// @Description get upload url
|
||||
// @Tags problem
|
||||
// @Summary [admin] get upload url
|
||||
// @Description Retrieve a pre-signed upload URL from the object storage
|
||||
// @Tags problem,admin
|
||||
// @Produce json
|
||||
// @Response 200 {object} e.Response "upload url and key"
|
||||
// @Response 200 {object} e.Response[uploadResponse] "upload url and key"
|
||||
// @Security Authentication
|
||||
// @Router /v1/problem/upload [post]
|
||||
func (h *handler) Upload(c *gin.Context) {
|
||||
claim, exist := c.Get("claim")
|
||||
if !exist {
|
||||
e.Pong(c, e.UserUnauthenticated, nil)
|
||||
e.Pong[any](c, e.UserUnauthenticated, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// only admin can upload
|
||||
role := claim.(*model.Claim).Role
|
||||
if role < model.RoleAdmin {
|
||||
e.Pong(c, e.UserUnauthorized, nil)
|
||||
e.Pong[any](c, e.UserUnauthorized, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// generate random key
|
||||
key := utils.RandomString(16)
|
||||
url, status := h.storageService.Upload(key, time.Second*60*60)
|
||||
|
||||
if status != e.Success {
|
||||
e.Pong(c, status, nil)
|
||||
e.Pong[any](c, status, nil)
|
||||
return
|
||||
}
|
||||
|
||||
e.Pong(c, e.Success, gin.H{
|
||||
"key": key,
|
||||
"url": url,
|
||||
})
|
||||
e.Pong(c, e.Success, uploadResponse{Key: key, URL: url})
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package status
|
||||
import (
|
||||
"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/service/submission"
|
||||
"git.0x7f.app/WOJ/woj-server/internal/web/jwt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/samber/do"
|
||||
@ -13,22 +14,26 @@ var _ Handler = (*handler)(nil)
|
||||
|
||||
type Handler interface {
|
||||
Query(c *gin.Context)
|
||||
QueryBySubmissionID(c *gin.Context)
|
||||
QueryByProblemVersion(c *gin.Context)
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
log *zap.Logger
|
||||
statusService status.Service
|
||||
jwtService jwt.Service
|
||||
log *zap.Logger
|
||||
statusService status.Service
|
||||
submissionService submission.Service
|
||||
jwtService jwt.Service
|
||||
}
|
||||
|
||||
func RouteRegister(rg *gin.RouterGroup, i *do.Injector) {
|
||||
app := &handler{
|
||||
log: do.MustInvoke[log.Service](i).GetLogger("api.status"),
|
||||
statusService: do.MustInvoke[status.Service](i),
|
||||
jwtService: do.MustInvoke[jwt.Service](i),
|
||||
log: do.MustInvoke[log.Service](i).GetLogger("api.status"),
|
||||
submissionService: do.MustInvoke[submission.Service](i),
|
||||
statusService: do.MustInvoke[status.Service](i),
|
||||
jwtService: do.MustInvoke[jwt.Service](i),
|
||||
}
|
||||
|
||||
rg.POST("/query", app.Query)
|
||||
rg.POST("/query/problem_version", app.jwtService.Handler(true), app.QueryByProblemVersion)
|
||||
rg.POST("/query", app.jwtService.Handler(true), app.Query)
|
||||
rg.POST("/query/submission", app.jwtService.Handler(true), app.QueryBySubmissionID)
|
||||
rg.POST("/query/version", app.jwtService.Handler(true), app.QueryByProblemVersion)
|
||||
}
|
||||
|
@ -2,33 +2,74 @@ package status
|
||||
|
||||
import (
|
||||
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||
"git.0x7f.app/WOJ/woj-server/pkg/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type queryRequest struct {
|
||||
SubmissionID uint `form:"sid" binding:"required"`
|
||||
Pid uint `form:"pid"`
|
||||
Uid uint `form:"uid"`
|
||||
Offset int `form:"offset"`
|
||||
Limit int `form:"limit" binding:"required"`
|
||||
}
|
||||
|
||||
type queryResponse struct {
|
||||
Submission model.Submission `json:"submission"`
|
||||
Point int32 `json:"point"`
|
||||
}
|
||||
|
||||
// Query
|
||||
// @Summary query submissions by via submission id
|
||||
// @Description query submissions by via submission id
|
||||
// @Summary query status via problem id or user id
|
||||
// @Description Batch query judgement status based on either the question or user.
|
||||
// @Tags status
|
||||
// @Accept application/x-www-form-urlencoded
|
||||
// @Produce json
|
||||
// @Param sid formData uint true "submission id"
|
||||
// @Response 200 {object} e.Response "model.status"
|
||||
// @Param pid formData uint false "problem id"
|
||||
// @Param uid formData uint false "user id"
|
||||
// @Param offset formData int false "start position"
|
||||
// @Param limit formData int true "limit number of records"
|
||||
// @Response 200 {object} e.Response[[]queryResponse] "queryResponse"
|
||||
// @Router /v1/status/query [post]
|
||||
func (h *handler) Query(c *gin.Context) {
|
||||
// TODO: add permission check
|
||||
claim, exist := c.Get("claim")
|
||||
if !exist {
|
||||
e.Pong[any](c, e.UserUnauthenticated, nil)
|
||||
return
|
||||
}
|
||||
|
||||
req := new(queryRequest)
|
||||
|
||||
if err := c.ShouldBind(req); err != nil {
|
||||
e.Pong(c, e.InvalidParameter, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
status, eStatus := h.statusService.Query(req.SubmissionID, true)
|
||||
if req.Pid == 0 && req.Uid == 0 {
|
||||
e.Pong[any](c, e.InvalidParameter, nil)
|
||||
return
|
||||
}
|
||||
|
||||
e.Pong(c, eStatus, status)
|
||||
submissions, status := h.submissionService.Query(req.Pid, req.Uid, req.Offset, req.Limit)
|
||||
|
||||
uid := claim.(*model.Claim).UID
|
||||
role := claim.(*model.Claim).Role
|
||||
var response []*queryResponse
|
||||
|
||||
for _, submission := range submissions {
|
||||
cur, _ := h.statusService.Query(submission.ID, false)
|
||||
point := utils.If(cur == nil, -1, cur.Point)
|
||||
resp := &queryResponse{
|
||||
Submission: *submission,
|
||||
Point: point,
|
||||
}
|
||||
|
||||
if role < model.RoleAdmin || uid != submission.UserID {
|
||||
// strip out code
|
||||
resp.Submission.Code = ""
|
||||
}
|
||||
|
||||
response = append(response, resp)
|
||||
}
|
||||
|
||||
e.Pong(c, status, response)
|
||||
}
|
||||
|
50
internal/api/status/query_one.go
Normal file
50
internal/api/status/query_one.go
Normal file
@ -0,0 +1,50 @@
|
||||
package status
|
||||
|
||||
import (
|
||||
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type queryOneRequest struct {
|
||||
SubmissionID uint `form:"sid" binding:"required"`
|
||||
}
|
||||
|
||||
// QueryBySubmissionID
|
||||
// @Summary query status via submission id
|
||||
// @Description Query the detailed results of the judgement based on the submission ID.
|
||||
// @Tags status
|
||||
// @Accept application/x-www-form-urlencoded
|
||||
// @Produce json
|
||||
// @Param sid formData uint true "submission id"
|
||||
// @Response 200 {object} e.Response[model.Status] "submission status"
|
||||
// @Router /v1/status/query/submission [post]
|
||||
func (h *handler) QueryBySubmissionID(c *gin.Context) {
|
||||
claim, exist := c.Get("claim")
|
||||
if !exist {
|
||||
e.Pong[any](c, e.UserUnauthenticated, nil)
|
||||
return
|
||||
}
|
||||
|
||||
req := new(queryOneRequest)
|
||||
if err := c.ShouldBind(req); err != nil {
|
||||
e.Pong(c, e.InvalidParameter, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// query status
|
||||
submitStatus, status := h.statusService.Query(req.SubmissionID, true)
|
||||
|
||||
// check permission
|
||||
role := claim.(*model.Claim).Role
|
||||
if role >= model.RoleAdmin || submitStatus.Submission.UserID == claim.(*model.Claim).UID {
|
||||
// full status
|
||||
e.Pong(c, status, submitStatus)
|
||||
return
|
||||
} else {
|
||||
// strip out code
|
||||
submitStatus.Submission.Code = ""
|
||||
e.Pong(c, status, submitStatus)
|
||||
return
|
||||
}
|
||||
}
|
@ -13,39 +13,37 @@ type queryByVersionRequest struct {
|
||||
}
|
||||
|
||||
// QueryByProblemVersion
|
||||
// @Summary query submissions by problem version (admin only)
|
||||
// @Description query submissions by problem version (admin only)
|
||||
// @Tags status
|
||||
// @Summary [admin] query status by problem version
|
||||
// @Description Retrieve all judgement results corresponding to the problem version.
|
||||
// @Tags status,admin
|
||||
// @Accept application/x-www-form-urlencoded
|
||||
// @Produce json
|
||||
// @Param pvid formData uint true "problem version id"
|
||||
// @Param offset formData int false "start position"
|
||||
// @Param limit formData int true "limit number of records"
|
||||
// @Response 200 {object} e.Response "[]*model.status"
|
||||
// @Param pvid formData uint true "problem version"
|
||||
// @Param offset formData int false "start position"
|
||||
// @Param limit formData int true "max number of results"
|
||||
// @Response 200 {object} e.Response[[]model.Status] "submission status array"
|
||||
// @Security Authentication
|
||||
// @Router /v1/status/query/problem_version [post]
|
||||
// @Router /v1/status/query/version [post]
|
||||
func (h *handler) QueryByProblemVersion(c *gin.Context) {
|
||||
claim, exist := c.Get("claim")
|
||||
if !exist {
|
||||
e.Pong(c, e.UserUnauthenticated, nil)
|
||||
e.Pong[any](c, e.UserUnauthenticated, nil)
|
||||
return
|
||||
}
|
||||
|
||||
role := claim.(*model.Claim).Role
|
||||
|
||||
req := new(queryByVersionRequest)
|
||||
|
||||
if err := c.ShouldBind(req); err != nil {
|
||||
e.Pong(c, e.InvalidParameter, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// check permission
|
||||
role := claim.(*model.Claim).Role
|
||||
if role < model.RoleAdmin {
|
||||
e.Pong(c, e.UserUnauthorized, nil)
|
||||
e.Pong[any](c, e.UserUnauthorized, nil)
|
||||
return
|
||||
}
|
||||
|
||||
statuses, eStatus := h.statusService.QueryByVersion(req.ProblemVersionID, req.Offset, req.Limit)
|
||||
|
||||
e.Pong(c, eStatus, statuses)
|
||||
submitStatus, status := h.statusService.QueryByVersion(req.ProblemVersionID, req.Offset, req.Limit)
|
||||
e.Pong(c, status, submitStatus)
|
||||
}
|
@ -14,21 +14,21 @@ type createRequest struct {
|
||||
}
|
||||
|
||||
// Create
|
||||
// @Summary create a submission
|
||||
// @Description create a submission
|
||||
// @Summary submit for judgement
|
||||
// @Description Submit the code for judgement.
|
||||
// @Tags submission
|
||||
// @Accept application/x-www-form-urlencoded
|
||||
// @Produce json
|
||||
// @Param pid formData int true "problem id"
|
||||
// @Param language formData string true "language"
|
||||
// @Param code formData string true "code"
|
||||
// @Response 200 {object} e.Response ""
|
||||
// @Response 200 {object} e.Response[uint] "submission id"
|
||||
// @Security Authentication
|
||||
// @Router /v1/submission/create [post]
|
||||
func (h *handler) Create(c *gin.Context) {
|
||||
claim, exist := c.Get("claim")
|
||||
if !exist {
|
||||
e.Pong(c, e.UserUnauthenticated, nil)
|
||||
e.Pong[any](c, e.UserUnauthenticated, nil)
|
||||
return
|
||||
}
|
||||
|
||||
@ -37,7 +37,7 @@ func (h *handler) Create(c *gin.Context) {
|
||||
|
||||
// guest can not submit
|
||||
if role < model.RoleGeneral {
|
||||
e.Pong(c, e.UserUnauthorized, nil)
|
||||
e.Pong[any](c, e.UserUnauthorized, nil)
|
||||
return
|
||||
}
|
||||
|
||||
@ -47,30 +47,33 @@ func (h *handler) Create(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// create submission
|
||||
createData := &submission.CreateData{
|
||||
ProblemID: req.Pid,
|
||||
UserID: uid,
|
||||
Language: req.Language,
|
||||
Code: req.Code,
|
||||
}
|
||||
s, status := h.submissionService.Create(createData)
|
||||
res, status := h.submissionService.Create(createData)
|
||||
if status != e.Success {
|
||||
e.Pong(c, status, nil)
|
||||
e.Pong[any](c, status, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// query latest version
|
||||
pv, status := h.problemService.QueryLatestVersion(req.Pid)
|
||||
if status != e.Success {
|
||||
e.Pong(c, status, nil)
|
||||
e.Pong[any](c, status, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// submit judge
|
||||
payload := &model.SubmitJudgePayload{
|
||||
ProblemVersionID: pv.ID,
|
||||
StorageKey: pv.StorageKey,
|
||||
Submission: *s,
|
||||
Submission: *res,
|
||||
}
|
||||
_, status = h.taskService.SubmitJudge(payload)
|
||||
|
||||
e.Pong(c, status, nil)
|
||||
e.Pong[any](c, status, res.ID)
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ var _ Handler = (*handler)(nil)
|
||||
|
||||
type Handler interface {
|
||||
Create(c *gin.Context)
|
||||
Query(c *gin.Context)
|
||||
Rejudge(c *gin.Context)
|
||||
}
|
||||
|
||||
@ -40,6 +39,5 @@ func RouteRegister(rg *gin.RouterGroup, i *do.Injector) {
|
||||
}
|
||||
|
||||
rg.POST("/create", app.jwtService.Handler(true), app.Create)
|
||||
rg.POST("/query", app.Query)
|
||||
rg.POST("/rejudge", app.jwtService.Handler(true), app.Rejudge)
|
||||
}
|
||||
|
@ -1,72 +0,0 @@
|
||||
package submission
|
||||
|
||||
import (
|
||||
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type queryRequest struct {
|
||||
Pid uint `form:"pid"`
|
||||
Uid uint `form:"uid"`
|
||||
Offset int `form:"offset"`
|
||||
Limit int `form:"limit" binding:"required"`
|
||||
}
|
||||
|
||||
type queryResponse struct {
|
||||
Submission model.Submission `json:"submission"`
|
||||
Point int32 `json:"point"`
|
||||
}
|
||||
|
||||
// Query
|
||||
// @Summary Query submissions
|
||||
// @Description Query submissions
|
||||
// @Tags submission
|
||||
// @Accept application/x-www-form-urlencoded
|
||||
// @Produce json
|
||||
// @Param pid formData uint false "problem id"
|
||||
// @Param uid formData uint false "user id"
|
||||
// @Param offset formData int false "start position"
|
||||
// @Param limit formData int true "limit number of records"
|
||||
// @Response 200 {object} e.Response "queryResponse"
|
||||
// @Router /v1/submission/query [post]
|
||||
func (h *handler) Query(c *gin.Context) {
|
||||
req := new(queryRequest)
|
||||
|
||||
if err := c.ShouldBind(req); err != nil {
|
||||
e.Pong(c, e.InvalidParameter, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if req.Pid == 0 && req.Uid == 0 {
|
||||
e.Pong(c, e.InvalidParameter, nil)
|
||||
return
|
||||
}
|
||||
|
||||
submissions, status := h.submissionService.Query(req.Pid, req.Uid, req.Offset, req.Limit)
|
||||
|
||||
var response []*queryResponse
|
||||
|
||||
for _, submission := range submissions {
|
||||
currentStatus, _ := h.statusService.Query(submission.ID, false)
|
||||
var currentPoint int32
|
||||
|
||||
if currentStatus == nil {
|
||||
currentPoint = -1
|
||||
} else {
|
||||
currentPoint = currentStatus.Point
|
||||
}
|
||||
|
||||
newResponse := &queryResponse{
|
||||
Submission: *submission,
|
||||
Point: currentPoint,
|
||||
}
|
||||
|
||||
// TODO: only show code when user is admin or the code is submitted by the user
|
||||
newResponse.Submission.Code = ""
|
||||
|
||||
response = append(response, newResponse)
|
||||
}
|
||||
|
||||
e.Pong(c, status, response)
|
||||
}
|
@ -11,53 +11,55 @@ type rejudgeRequest struct {
|
||||
}
|
||||
|
||||
// Rejudge
|
||||
// @Summary rejudge a submission
|
||||
// @Description rejudge a submission
|
||||
// @Tags submission
|
||||
// @Summary [admin] rejudge a specific submission
|
||||
// @Description rejudge a specific submission
|
||||
// @Tags submission,admin
|
||||
// @Accept application/x-www-form-urlencoded
|
||||
// @Produce json
|
||||
// @Param sid formData int true "submission id"
|
||||
// @Response 200 {object} e.Response ""
|
||||
// @Param sid formData int true "submission id"
|
||||
// @Response 200 {object} e.Response[any] "nothing"
|
||||
// @Security Authentication
|
||||
// @Router /v1/submission/rejudge [post]
|
||||
func (h *handler) Rejudge(c *gin.Context) {
|
||||
claim, exist := c.Get("claim")
|
||||
if !exist {
|
||||
e.Pong(c, e.UserUnauthenticated, nil)
|
||||
e.Pong[any](c, e.UserUnauthenticated, nil)
|
||||
return
|
||||
}
|
||||
|
||||
role := claim.(*model.Claim).Role
|
||||
req := new(rejudgeRequest)
|
||||
|
||||
if err := c.ShouldBind(req); err != nil {
|
||||
e.Pong(c, e.InvalidParameter, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// only admin can rejudge
|
||||
role := claim.(*model.Claim).Role
|
||||
if role < model.RoleAdmin {
|
||||
e.Pong(c, e.UserUnauthorized, nil)
|
||||
e.Pong[any](c, e.UserUnauthorized, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// query submission
|
||||
s, status := h.submissionService.QueryBySid(req.Sid, false)
|
||||
if status != e.Success {
|
||||
e.Pong(c, status, nil)
|
||||
e.Pong[any](c, status, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// query latest problem version
|
||||
pv, status := h.problemService.QueryLatestVersion(s.ProblemID)
|
||||
if status != e.Success {
|
||||
e.Pong(c, status, nil)
|
||||
e.Pong[any](c, status, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// submit judge
|
||||
_, status = h.taskService.SubmitJudge(&model.SubmitJudgePayload{
|
||||
ProblemVersionID: pv.ID,
|
||||
StorageKey: pv.StorageKey,
|
||||
Submission: *s,
|
||||
})
|
||||
|
||||
e.Pong(c, status, nil)
|
||||
e.Pong[any](c, status, nil)
|
||||
}
|
||||
|
@ -22,16 +22,16 @@ type createRequest struct {
|
||||
// @Param username formData string true "username"
|
||||
// @Param nickname formData string true "nickname"
|
||||
// @Param password formData string true "password"
|
||||
// @Response 200 {object} e.Response "jwt token"
|
||||
// @Response 200 {object} e.Response[string] "jwt token"
|
||||
// @Router /v1/user/create [post]
|
||||
func (h *handler) Create(c *gin.Context) {
|
||||
req := new(createRequest)
|
||||
|
||||
if err := c.ShouldBind(req); err != nil {
|
||||
e.Pong(c, e.InvalidParameter, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// create user
|
||||
createData := &user.CreateData{
|
||||
UserName: req.UserName,
|
||||
Password: req.Password,
|
||||
@ -39,16 +39,18 @@ func (h *handler) Create(c *gin.Context) {
|
||||
}
|
||||
u, status := h.userService.Create(createData)
|
||||
if status != e.Success {
|
||||
e.Pong(c, status, nil)
|
||||
e.Pong[any](c, status, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// update version in cache
|
||||
version, status := h.userService.IncrVersion(u.ID)
|
||||
if status != e.Success {
|
||||
e.Pong(c, status, nil)
|
||||
e.Pong[any](c, status, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// sign jwt token
|
||||
claim := &model.Claim{
|
||||
UID: u.ID,
|
||||
Role: u.Role,
|
||||
|
@ -12,6 +12,11 @@ type loginRequest struct {
|
||||
Password string `form:"password" binding:"required"`
|
||||
}
|
||||
|
||||
type loginResponse struct {
|
||||
Token string `json:"token"`
|
||||
NickName string `json:"nickname"`
|
||||
}
|
||||
|
||||
// Login
|
||||
// @Summary login
|
||||
// @Description login and return token
|
||||
@ -20,13 +25,12 @@ type loginRequest struct {
|
||||
// @Produce json
|
||||
// @Param username formData string true "username"
|
||||
// @Param password formData string true "password"
|
||||
// @Response 200 {object} e.Response "jwt token and user nickname"
|
||||
// @Response 200 {object} e.Response[loginResponse] "jwt token and user's nickname"
|
||||
// @Router /v1/user/login [post]
|
||||
func (h *handler) Login(c *gin.Context) {
|
||||
req := new(loginRequest)
|
||||
|
||||
if err := c.ShouldBind(req); err != nil {
|
||||
e.Pong(c, e.InvalidParameter, nil)
|
||||
e.Pong(c, e.InvalidParameter, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@ -37,14 +41,14 @@ func (h *handler) Login(c *gin.Context) {
|
||||
}
|
||||
u, status := h.userService.Login(loginData)
|
||||
if status != e.Success {
|
||||
e.Pong(c, status, nil)
|
||||
e.Pong[any](c, status, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// sign and return token
|
||||
version, status := h.userService.IncrVersion(u.ID)
|
||||
if status != e.Success {
|
||||
e.Pong(c, status, nil)
|
||||
e.Pong[any](c, status, nil)
|
||||
return
|
||||
}
|
||||
claim := &model.Claim{
|
||||
@ -53,8 +57,5 @@ func (h *handler) Login(c *gin.Context) {
|
||||
Version: version,
|
||||
}
|
||||
token, status := h.jwtService.SignClaim(claim)
|
||||
e.Pong(c, status, gin.H{
|
||||
"token": token,
|
||||
"nickname": u.NickName,
|
||||
})
|
||||
e.Pong(c, status, loginResponse{Token: token, NickName: u.NickName})
|
||||
}
|
||||
|
@ -12,16 +12,16 @@ import (
|
||||
// @Tags user
|
||||
// @Accept application/x-www-form-urlencoded
|
||||
// @Produce json
|
||||
// @Response 200 {object} e.Response "nil"
|
||||
// @Response 200 {object} e.Response[any] "nothing"
|
||||
// @Security Authentication
|
||||
// @Router /v1/user/logout [post]
|
||||
func (h *handler) Logout(c *gin.Context) {
|
||||
claim, exist := c.Get("claim")
|
||||
if !exist {
|
||||
e.Pong(c, e.UserUnauthenticated, nil)
|
||||
e.Pong[any](c, e.UserUnauthenticated, nil)
|
||||
return
|
||||
}
|
||||
|
||||
_, status := h.userService.IncrVersion(claim.(*model.Claim).UID)
|
||||
e.Pong(c, status, nil)
|
||||
e.Pong[any](c, status, nil)
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package user
|
||||
import (
|
||||
"git.0x7f.app/WOJ/woj-server/internal/e"
|
||||
"git.0x7f.app/WOJ/woj-server/internal/model"
|
||||
"git.0x7f.app/WOJ/woj-server/pkg/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@ -17,15 +18,13 @@ type profileRequest struct {
|
||||
// @Accept application/x-www-form-urlencoded
|
||||
// @Produce json
|
||||
// @Param uid formData int false "user id"
|
||||
// @Response 200 {object} e.Response "user info"
|
||||
// @Response 200 {object} e.Response[model.User] "user info"
|
||||
// @Security Authentication
|
||||
// @Router /v1/user/profile [post]
|
||||
func (h *handler) Profile(c *gin.Context) {
|
||||
// TODO: create a new struct for profile (user info & solve info)
|
||||
|
||||
claim, exist := c.Get("claim")
|
||||
if !exist {
|
||||
e.Pong(c, e.UserUnauthenticated, nil)
|
||||
e.Pong[any](c, e.UserUnauthenticated, nil)
|
||||
return
|
||||
}
|
||||
|
||||
@ -33,22 +32,21 @@ func (h *handler) Profile(c *gin.Context) {
|
||||
role := claim.(*model.Claim).Role
|
||||
|
||||
req := new(profileRequest)
|
||||
|
||||
if err := c.ShouldBind(req); err != nil {
|
||||
e.Pong(c, e.InvalidParameter, nil)
|
||||
e.Pong(c, e.InvalidParameter, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if req.UID == 0 {
|
||||
req.UID = uid
|
||||
} else if req.UID != uid && role < model.RoleGeneral {
|
||||
e.Pong(c, e.UserUnauthorized, nil)
|
||||
user, status := h.userService.Profile(utils.If(req.UID == 0, uid, req.UID))
|
||||
if status != e.Success {
|
||||
e.Pong[any](c, status, nil)
|
||||
return
|
||||
}
|
||||
|
||||
user, status := h.userService.Profile(req.UID)
|
||||
|
||||
// TODO: >= admin can see is_enable
|
||||
if role < model.RoleAdmin && user.ID != uid {
|
||||
e.Pong[any](c, e.UserUnauthorized, nil)
|
||||
return
|
||||
}
|
||||
|
||||
e.Pong(c, status, user)
|
||||
}
|
||||
|
@ -6,30 +6,21 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Body interface{} `json:"body"`
|
||||
type Response[T any] struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Body T `json:"body"`
|
||||
}
|
||||
|
||||
func Wrap(status Status, body interface{}) interface{} {
|
||||
return Response{
|
||||
func wrap[T any](status Status, body T) Response[interface{}] {
|
||||
return Response[interface{}]{
|
||||
Code: int(status),
|
||||
Msg: status.String(),
|
||||
Body: utils.If(status == Success, body, nil),
|
||||
Body: utils.If[interface{}](status == Success, body, nil),
|
||||
}
|
||||
}
|
||||
|
||||
func Pong(c *gin.Context, status Status, body interface{}) {
|
||||
func Pong[T any](c *gin.Context, status Status, body T) {
|
||||
c.Set("err", status)
|
||||
c.JSON(http.StatusOK, Wrap(status, body))
|
||||
}
|
||||
|
||||
type Endpoint func(*gin.Context) (Status, interface{})
|
||||
|
||||
func PongWrapper(handler Endpoint) func(*gin.Context) {
|
||||
return func(c *gin.Context) {
|
||||
status, body := handler(c)
|
||||
Pong(c, status, body)
|
||||
}
|
||||
c.JSON(http.StatusOK, wrap(status, body))
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ func (s *service) Handler(forced bool) gin.HandlerFunc {
|
||||
c.Set("claim", claim)
|
||||
}
|
||||
if forced && status != e.Success {
|
||||
e.Pong(c, status, nil)
|
||||
e.Pong[any](c, status, nil)
|
||||
c.Abort()
|
||||
} else {
|
||||
c.Next()
|
||||
|
@ -11,8 +11,8 @@ import (
|
||||
"github.com/samber/do"
|
||||
)
|
||||
|
||||
// @title OJ Server API Documentation
|
||||
// @version 1.0
|
||||
// @title WOJ Server API Documentation
|
||||
// @version 1.1.0
|
||||
// @BasePath /api
|
||||
// @securityDefinitions.apikey Authentication
|
||||
// @in header
|
||||
|
Loading…
Reference in New Issue
Block a user