feat: add problem support
This commit is contained in:
parent
8d01144d8b
commit
529b41332c
32
internal/api/problem/handler.go
Normal file
32
internal/api/problem/handler.go
Normal 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)
|
||||||
|
}
|
44
internal/api/problem/search.go
Normal file
44
internal/api/problem/search.go
Normal 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
|
||||||
|
}
|
||||||
|
}
|
81
internal/api/problem/update.go
Normal file
81
internal/api/problem/update.go
Normal 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
|
||||||
|
}
|
||||||
|
}
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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]
|
||||||
|
@ -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",
|
||||||
}
|
}
|
||||||
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
@ -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},
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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),
|
||||||
|
20
internal/service/problem/update.go
Normal file
20
internal/service/problem/update.go
Normal 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
|
||||||
|
}
|
@ -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),
|
||||||
|
Loading…
Reference in New Issue
Block a user