feat: add problem support

This commit is contained in:
Paul Pan 2022-09-26 16:13:31 +08:00
parent 8d01144d8b
commit 529b41332c
13 changed files with 197 additions and 12 deletions

View File

@ -0,0 +1,32 @@
package problem
import (
"github.com/WHUPRJ/woj-server/internal/global"
"github.com/WHUPRJ/woj-server/internal/service/problem"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
var _ Handler = (*handler)(nil)
type Handler interface {
Update(c *gin.Context)
Search(c *gin.Context)
}
type handler struct {
log *zap.Logger
problemService problem.Service
jwtService global.JwtService
}
func RouteRegister(g *global.Global, group *gin.RouterGroup) {
app := &handler{
log: g.Log,
problemService: problem.NewService(g),
jwtService: g.Jwt,
}
group.POST("/search", app.Search)
group.POST("/update", app.jwtService.Handler(), app.Update)
}

View File

@ -0,0 +1,44 @@
package problem
import (
"github.com/WHUPRJ/woj-server/internal/e"
"github.com/gin-gonic/gin"
)
type searchRequest struct {
Pid uint `form:"pid"`
Search string `form:"search"`
}
// Search
// @Summary get detail of a problem
// @Description get detail of a problem
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param pid formData int false "problem id"
// @Param search formData string false "search problem"
// @Response 200 {object} e.Response "problem info"
// @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
}
if req.Pid == 0 && req.Search == "" {
e.Pong(c, e.InvalidParameter, nil)
return
}
if req.Pid != 0 {
problem, status := h.problemService.Query(req.Pid)
e.Pong(c, status, problem)
return
} else {
problem, status := h.problemService.QueryFuzz(req.Search)
e.Pong(c, status, problem)
return
}
}

View File

@ -0,0 +1,81 @@
package problem
import (
"github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/global"
"github.com/WHUPRJ/woj-server/internal/repo/model"
"github.com/gin-gonic/gin"
)
type updateRequest struct {
Pid uint `form:"pid"`
Title string `form:"title" binding:"required"`
Content string `form:"content" binding:"required"`
TimeLimit uint `form:"time_limit" binding:"required"`
MemoryLimit uint `form:"memory_limit" binding:"required"`
IsEnabled bool `form:"is_enabled"`
}
// Update
// @Summary create or update a problem
// @Description create or update a problem
// @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 content formData string true "content"
// @Param time_limit formData int true "time limit in ms"
// @Param memory_limit formData int true "memory limit in kb"
// @Param is_enabled formData bool false "is enabled"
// @Response 200 {object} e.Response "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)
return
}
uid := claim.(*global.Claim).UID
role := claim.(*global.Claim).Role
if role < model.RoleAdmin {
e.Pong(c, e.UserUnauthorized, nil)
return
}
req := new(updateRequest)
if err := c.ShouldBind(req); err != nil {
e.Pong(c, e.InvalidParameter, err.Error())
return
}
problem := &model.Problem{
Title: req.Title,
Content: req.Content,
TimeLimit: req.TimeLimit,
MemoryLimit: req.MemoryLimit,
IsEnabled: req.IsEnabled,
}
if req.Pid == 0 {
problem, status := h.problemService.Create(uid, problem)
e.Pong(c, status, problem)
return
} else {
inDb, status := h.problemService.Query(req.Pid)
if status != e.Success && status != e.ProblemNotAvailable {
e.Pong(c, status, nil)
return
}
if inDb.ProviderID != uid {
e.Pong(c, e.UserUnauthorized, nil)
return
}
problem, status := h.problemService.Update(req.Pid, problem)
e.Pong(c, status, problem)
return
}
}

View File

@ -25,7 +25,7 @@ type handler struct {
func RouteRegister(g *global.Global, group *gin.RouterGroup) { func RouteRegister(g *global.Global, group *gin.RouterGroup) {
app := &handler{ app := &handler{
log: g.Log, log: g.Log,
userService: user.NewUserService(g), userService: user.NewService(g),
jwtService: g.Jwt, jwtService: g.Jwt,
} }

View File

@ -16,7 +16,7 @@ type profileRequest struct {
// @Description fetch user profile // @Description fetch user profile
// @Accept application/x-www-form-urlencoded // @Accept application/x-www-form-urlencoded
// @Produce json // @Produce json
// @Param uid formData string false "user id" // @Param uid formData int false "user id"
// @Response 200 {object} e.Response "user info" // @Response 200 {object} e.Response "user info"
// @Security Authentication // @Security Authentication
// @Router /v1/user/profile [post] // @Router /v1/user/profile [post]

View File

@ -26,6 +26,7 @@ const (
UserDisabled Status = 305 UserDisabled Status = 305
ProblemNotFound Status = 500 ProblemNotFound Status = 500
ProblemNotAvailable Status = 501
) )
var msgText = map[Status]string{ var msgText = map[Status]string{
@ -54,4 +55,5 @@ var msgText = map[Status]string{
UserDisabled: "User Disabled", UserDisabled: "User Disabled",
ProblemNotFound: "Problem Not Found", ProblemNotFound: "Problem Not Found",
ProblemNotAvailable: "Problem Not Available",
} }

View File

@ -3,12 +3,12 @@ package model
import "gorm.io/gorm" import "gorm.io/gorm"
type Problem struct { type Problem struct {
gorm.Model `json:"-"` gorm.Model `json:"meta"`
Title string `json:"title" gorm:"not null"` Title string `json:"title" gorm:"not null"`
Content string `json:"content" gorm:"not null"` Content string `json:"content" gorm:"not null"`
TimeLimit uint `json:"time_limit" gorm:"not null"` TimeLimit uint `json:"time_limit" gorm:"not null"`
MemoryLimit uint `json:"memory_limit" gorm:"not null"` MemoryLimit uint `json:"memory_limit" gorm:"not null"`
ProviderID uint `json:"provider_id" gorm:"not null;index"` ProviderID uint `json:"provider_id" gorm:"not null;index"`
Provider User `json:"provider" gorm:"foreignKey:ProviderID"` Provider User `json:"-" gorm:"foreignKey:ProviderID"`
IsEnabled bool `json:"is_enabled" gorm:"not null;index"` IsEnabled bool `json:"is_enabled" gorm:"not null;index"`
} }

View File

@ -2,6 +2,7 @@ package router
import ( import (
"github.com/WHUPRJ/woj-server/internal/api/debug" "github.com/WHUPRJ/woj-server/internal/api/debug"
"github.com/WHUPRJ/woj-server/internal/api/problem"
"github.com/WHUPRJ/woj-server/internal/api/user" "github.com/WHUPRJ/woj-server/internal/api/user"
"github.com/WHUPRJ/woj-server/internal/global" "github.com/WHUPRJ/woj-server/internal/global"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -23,4 +24,5 @@ func setupApi(g *global.Global, root *gin.RouterGroup) {
var endpoints = []global.EndpointInfo{ var endpoints = []global.EndpointInfo{
{Version: "", Path: "/debug", Register: debug.RouteRegister}, {Version: "", Path: "/debug", Register: debug.RouteRegister},
{Version: "/v1", Path: "/user", Register: user.RouteRegister}, {Version: "/v1", Path: "/user", Register: user.RouteRegister},
{Version: "/v1", Path: "/problem", Register: problem.RouteRegister},
} }

View File

@ -4,6 +4,7 @@ import (
"errors" "errors"
"github.com/WHUPRJ/woj-server/internal/e" "github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/repo/model" "github.com/WHUPRJ/woj-server/internal/repo/model"
"github.com/WHUPRJ/woj-server/pkg/utils"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/clause" "gorm.io/gorm/clause"
) )
@ -19,5 +20,5 @@ func (s *service) Query(problemId uint) (*model.Problem, e.Status) {
return nil, e.DatabaseError return nil, e.DatabaseError
} }
return problem, e.Success return problem, utils.If(problem.IsEnabled, e.Success, e.ProblemNotAvailable).(e.Status)
} }

View File

@ -5,14 +5,16 @@ import (
"github.com/WHUPRJ/woj-server/internal/e" "github.com/WHUPRJ/woj-server/internal/e"
"github.com/WHUPRJ/woj-server/internal/repo/model" "github.com/WHUPRJ/woj-server/internal/repo/model"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/clause"
) )
func (s *service) QueryFuzz(search string) ([]*model.Problem, e.Status) { func (s *service) QueryFuzz(search string) ([]*model.Problem, e.Status) {
var problems []*model.Problem var problems []*model.Problem
err := s.db. err := s.db.Preload(clause.Associations).
Where("title LIKE ?", "%"+search+"%"). Where("is_enabled = true").
Or("content LIKE ?", "%"+search+"%"). Where(s.db.Where("title LIKE ?", "%"+search+"%").
Or("content LIKE ?", "%"+search+"%")).
Find(&problems).Error Find(&problems).Error
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, e.ProblemNotFound return nil, e.ProblemNotFound

View File

@ -12,6 +12,7 @@ var _ Service = (*service)(nil)
type Service interface { type Service interface {
Create(uint, *model.Problem) (*model.Problem, e.Status) Create(uint, *model.Problem) (*model.Problem, e.Status)
Update(uint, *model.Problem) (*model.Problem, e.Status)
Query(uint) (*model.Problem, e.Status) Query(uint) (*model.Problem, e.Status)
QueryFuzz(string) ([]*model.Problem, e.Status) QueryFuzz(string) ([]*model.Problem, e.Status)
} }
@ -21,7 +22,7 @@ type service struct {
db *gorm.DB db *gorm.DB
} }
func NewProblemService(g *global.Global) Service { func NewService(g *global.Global) Service {
return &service{ return &service{
log: g.Log, log: g.Log,
db: g.Db.Get().(*gorm.DB), db: g.Db.Get().(*gorm.DB),

View File

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

View File

@ -24,7 +24,7 @@ type service struct {
redis *redis.Client redis *redis.Client
} }
func NewUserService(g *global.Global) Service { func NewService(g *global.Global) Service {
return &service{ return &service{
log: g.Log, log: g.Log,
db: g.Db.Get().(*gorm.DB), db: g.Db.Get().(*gorm.DB),