diff --git a/internal/api/problem/handler.go b/internal/api/problem/handler.go new file mode 100644 index 0000000..12378f8 --- /dev/null +++ b/internal/api/problem/handler.go @@ -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) +} diff --git a/internal/api/problem/search.go b/internal/api/problem/search.go new file mode 100644 index 0000000..5e932f6 --- /dev/null +++ b/internal/api/problem/search.go @@ -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 + } +} diff --git a/internal/api/problem/update.go b/internal/api/problem/update.go new file mode 100644 index 0000000..83192d2 --- /dev/null +++ b/internal/api/problem/update.go @@ -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 + } +} diff --git a/internal/api/user/handler.go b/internal/api/user/handler.go index c61f324..1dcc8b5 100644 --- a/internal/api/user/handler.go +++ b/internal/api/user/handler.go @@ -25,7 +25,7 @@ type handler struct { func RouteRegister(g *global.Global, group *gin.RouterGroup) { app := &handler{ log: g.Log, - userService: user.NewUserService(g), + userService: user.NewService(g), jwtService: g.Jwt, } diff --git a/internal/api/user/profile.go b/internal/api/user/profile.go index 45eef59..b8b6117 100644 --- a/internal/api/user/profile.go +++ b/internal/api/user/profile.go @@ -16,7 +16,7 @@ type profileRequest struct { // @Description fetch user profile // @Accept application/x-www-form-urlencoded // @Produce json -// @Param uid formData string false "user id" +// @Param uid formData int false "user id" // @Response 200 {object} e.Response "user info" // @Security Authentication // @Router /v1/user/profile [post] diff --git a/internal/e/code.go b/internal/e/code.go index 37e90ca..5165c0c 100644 --- a/internal/e/code.go +++ b/internal/e/code.go @@ -25,7 +25,8 @@ const ( UserUnauthorized Status = 304 UserDisabled Status = 305 - ProblemNotFound Status = 500 + ProblemNotFound Status = 500 + ProblemNotAvailable Status = 501 ) var msgText = map[Status]string{ @@ -53,5 +54,6 @@ var msgText = map[Status]string{ UserUnauthorized: "User Unauthorized", UserDisabled: "User Disabled", - ProblemNotFound: "Problem Not Found", + ProblemNotFound: "Problem Not Found", + ProblemNotAvailable: "Problem Not Available", } diff --git a/internal/repo/model/Problem.go b/internal/repo/model/Problem.go index bc5adfe..075ce26 100644 --- a/internal/repo/model/Problem.go +++ b/internal/repo/model/Problem.go @@ -3,12 +3,12 @@ package model import "gorm.io/gorm" type Problem struct { - gorm.Model `json:"-"` + gorm.Model `json:"meta"` Title string `json:"title" gorm:"not null"` Content string `json:"content" gorm:"not null"` TimeLimit uint `json:"time_limit" gorm:"not null"` MemoryLimit uint `json:"memory_limit" gorm:"not null"` 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"` } diff --git a/internal/router/api.go b/internal/router/api.go index 0687aae..80fab98 100644 --- a/internal/router/api.go +++ b/internal/router/api.go @@ -2,6 +2,7 @@ package router import ( "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/global" "github.com/gin-gonic/gin" @@ -23,4 +24,5 @@ func setupApi(g *global.Global, root *gin.RouterGroup) { var endpoints = []global.EndpointInfo{ {Version: "", Path: "/debug", Register: debug.RouteRegister}, {Version: "/v1", Path: "/user", Register: user.RouteRegister}, + {Version: "/v1", Path: "/problem", Register: problem.RouteRegister}, } diff --git a/internal/service/problem/query.go b/internal/service/problem/query.go index 264f87a..9930338 100644 --- a/internal/service/problem/query.go +++ b/internal/service/problem/query.go @@ -4,6 +4,7 @@ import ( "errors" "github.com/WHUPRJ/woj-server/internal/e" "github.com/WHUPRJ/woj-server/internal/repo/model" + "github.com/WHUPRJ/woj-server/pkg/utils" "gorm.io/gorm" "gorm.io/gorm/clause" ) @@ -19,5 +20,5 @@ func (s *service) Query(problemId uint) (*model.Problem, e.Status) { return nil, e.DatabaseError } - return problem, e.Success + return problem, utils.If(problem.IsEnabled, e.Success, e.ProblemNotAvailable).(e.Status) } diff --git a/internal/service/problem/queryFuzz.go b/internal/service/problem/queryFuzz.go index b09da9a..e7ddee6 100644 --- a/internal/service/problem/queryFuzz.go +++ b/internal/service/problem/queryFuzz.go @@ -5,14 +5,16 @@ import ( "github.com/WHUPRJ/woj-server/internal/e" "github.com/WHUPRJ/woj-server/internal/repo/model" "gorm.io/gorm" + "gorm.io/gorm/clause" ) func (s *service) QueryFuzz(search string) ([]*model.Problem, e.Status) { var problems []*model.Problem - err := s.db. - Where("title LIKE ?", "%"+search+"%"). - Or("content LIKE ?", "%"+search+"%"). + err := s.db.Preload(clause.Associations). + Where("is_enabled = true"). + Where(s.db.Where("title LIKE ?", "%"+search+"%"). + Or("content LIKE ?", "%"+search+"%")). Find(&problems).Error if errors.Is(err, gorm.ErrRecordNotFound) { return nil, e.ProblemNotFound diff --git a/internal/service/problem/service.go b/internal/service/problem/service.go index 6911eca..1abd869 100644 --- a/internal/service/problem/service.go +++ b/internal/service/problem/service.go @@ -12,6 +12,7 @@ var _ Service = (*service)(nil) type Service interface { Create(uint, *model.Problem) (*model.Problem, e.Status) + Update(uint, *model.Problem) (*model.Problem, e.Status) Query(uint) (*model.Problem, e.Status) QueryFuzz(string) ([]*model.Problem, e.Status) } @@ -21,7 +22,7 @@ type service struct { db *gorm.DB } -func NewProblemService(g *global.Global) Service { +func NewService(g *global.Global) Service { return &service{ log: g.Log, db: g.Db.Get().(*gorm.DB), diff --git a/internal/service/problem/update.go b/internal/service/problem/update.go new file mode 100644 index 0000000..7617f8d --- /dev/null +++ b/internal/service/problem/update.go @@ -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 +} diff --git a/internal/service/user/service.go b/internal/service/user/service.go index b430350..9242231 100644 --- a/internal/service/user/service.go +++ b/internal/service/user/service.go @@ -24,7 +24,7 @@ type service struct { redis *redis.Client } -func NewUserService(g *global.Global) Service { +func NewService(g *global.Global) Service { return &service{ log: g.Log, db: g.Db.Get().(*gorm.DB),