mirror of https://gitee.com/answerdev/answer.git
fix(question): Add the function of reopening the problem
This commit is contained in:
parent
2b0634e2e6
commit
c6060d5aae
|
@ -74,8 +74,47 @@ func (qc *QuestionController) CloseQuestion(ctx *gin.Context) {
|
|||
return
|
||||
}
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
req.IsAdmin = middleware.GetIsAdminFromContext(ctx)
|
||||
err := qc.questionService.CloseQuestion(ctx, req)
|
||||
can, err := qc.rankService.CheckOperationPermission(ctx, req.UserID, permission.QuestionClose, "")
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
return
|
||||
}
|
||||
if !can {
|
||||
handler.HandleResponse(ctx, errors.Forbidden(reason.RankFailToMeetTheCondition), nil)
|
||||
return
|
||||
}
|
||||
|
||||
err = qc.questionService.CloseQuestion(ctx, req)
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
}
|
||||
|
||||
// ReopenQuestion reopen question
|
||||
// @Summary reopen question
|
||||
// @Description reopen question
|
||||
// @Tags api-question
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Param data body schema.ReopenQuestionReq true "question"
|
||||
// @Success 200 {object} handler.RespBody
|
||||
// @Router /answer/api/v1/question/reopen [put]
|
||||
func (qc *QuestionController) ReopenQuestion(ctx *gin.Context) {
|
||||
req := &schema.ReopenQuestionReq{}
|
||||
if handler.BindAndCheck(ctx, req) {
|
||||
return
|
||||
}
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
can, err := qc.rankService.CheckOperationPermission(ctx, req.UserID, permission.QuestionReopen, "")
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
return
|
||||
}
|
||||
if !can {
|
||||
handler.HandleResponse(ctx, errors.Forbidden(reason.RankFailToMeetTheCondition), nil)
|
||||
return
|
||||
}
|
||||
|
||||
err = qc.questionService.ReopenQuestion(ctx, req)
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,20 +1,22 @@
|
|||
package migrations
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
"xorm.io/xorm"
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
func addActivityTimeline(x *xorm.Engine) error {
|
||||
func addActivityTimeline(x *xorm.Engine) (err error) {
|
||||
// only increasing field length to 128
|
||||
type Config struct {
|
||||
Key string `xorm:"unique VARCHAR(128) key"`
|
||||
}
|
||||
if err := x.Sync(new(Config)); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("sync config table failed: %w", err)
|
||||
}
|
||||
defaultConfigTable := []*entity.Config{
|
||||
{ID: 36, Key: "rank.question.add", Value: `1`},
|
||||
|
@ -72,18 +74,18 @@ func addActivityTimeline(x *xorm.Engine) error {
|
|||
for _, c := range defaultConfigTable {
|
||||
exist, err := x.Get(&entity.Config{ID: c.ID, Key: c.Key})
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("get config failed: %w", err)
|
||||
}
|
||||
if exist {
|
||||
if _, err = x.Update(c, &entity.Config{ID: c.ID, Key: c.Key}); err != nil {
|
||||
log.Errorf("update %+v config failed: %s", c, err)
|
||||
return err
|
||||
return fmt.Errorf("update config failed: %w", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if _, err = x.Insert(&entity.Config{ID: c.ID, Key: c.Key, Value: c.Value}); err != nil {
|
||||
log.Errorf("insert %+v config failed: %s", c, err)
|
||||
return err
|
||||
return fmt.Errorf("add config failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,5 +109,46 @@ func addActivityTimeline(x *xorm.Engine) error {
|
|||
UpdatedAt time.Time `xorm:"updated_at TIMESTAMP"`
|
||||
LastEditUserID string `xorm:"not null default 0 BIGINT(20) last_edit_user_id"`
|
||||
}
|
||||
return x.Sync(new(Activity), new(Revision), new(Tag), new(Question), new(Answer))
|
||||
//logger := xormlog.NewSimpleLogger(os.Stdout)
|
||||
//logger.SetLevel(xormlog.LOG_DEBUG)
|
||||
//x.SetLogger(xormlog.NewLoggerAdapter(logger))
|
||||
//
|
||||
switch x.Dialect().URI().DBType {
|
||||
case schemas.MYSQL:
|
||||
_, err = x.Exec("ALTER TABLE `answer` CHANGE `updated_at` `updated_at` TIMESTAMP NULL DEFAULT NULL")
|
||||
case schemas.POSTGRES:
|
||||
_, err = x.Exec(`ALTER TABLE "answer" ALTER COLUMN "updated_at" DROP NOT NULL, ALTER COLUMN "updated_at" SET DEFAULT NULL`)
|
||||
case schemas.SQLITE:
|
||||
_, err = x.Exec(`DROP INDEX "main"."IDX_answer_user_id";
|
||||
|
||||
ALTER TABLE "main"."answer" RENAME TO "_answer_old_20221202";
|
||||
|
||||
CREATE TABLE "main"."answer" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" DATETIME DEFAULT NULL,
|
||||
"question_id" INTEGER NOT NULL DEFAULT 0,
|
||||
"user_id" INTEGER NOT NULL DEFAULT 0,
|
||||
"original_text" TEXT NOT NULL,
|
||||
"parsed_text" TEXT NOT NULL,
|
||||
"status" INTEGER NOT NULL DEFAULT 1,
|
||||
"adopted" INTEGER NOT NULL DEFAULT 1,
|
||||
"comment_count" INTEGER NOT NULL DEFAULT 0,
|
||||
"vote_count" INTEGER NOT NULL DEFAULT 0,
|
||||
"revision_id" INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
INSERT INTO "main"."answer" ("id", "created_at", "updated_at", "question_id", "user_id", "original_text", "parsed_text", "status", "adopted", "comment_count", "vote_count", "revision_id") SELECT "id", "created_at", "updated_at", "question_id", "user_id", "original_text", "parsed_text", "status", "adopted", "comment_count", "vote_count", "revision_id" FROM "main"."_answer_old_20221202";
|
||||
|
||||
CREATE INDEX "main"."IDX_answer_user_id"
|
||||
ON "answer" (
|
||||
"user_id" ASC
|
||||
);`)
|
||||
}
|
||||
|
||||
err = x.Sync(new(Activity), new(Revision), new(Tag), new(Question), new(Answer))
|
||||
if err != nil {
|
||||
return fmt.Errorf("sync table failed %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ func addRoleFeatures(x *xorm.Engine) error {
|
|||
return err
|
||||
}
|
||||
if exist {
|
||||
_, err = x.ID(power.ID).Update(&power)
|
||||
_, err = x.ID(power.ID).Update(power)
|
||||
} else {
|
||||
_, err = x.Insert(power)
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ func NewUserRepo(data *data.Data, configRepo config.ConfigRepo) usercommon.UserR
|
|||
|
||||
// AddUser add user
|
||||
func (ur *userRepo) AddUser(ctx context.Context, user *entity.User) (err error) {
|
||||
_, err = ur.data.DB.UseBool("is_admin").Insert(user)
|
||||
_, err = ur.data.DB.Insert(user)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
|
|
|
@ -183,6 +183,7 @@ func (a *AnswerAPIRouter) RegisterAnswerAPIRouter(r *gin.RouterGroup) {
|
|||
r.PUT("/question", a.questionController.UpdateQuestion)
|
||||
r.DELETE("/question", a.questionController.RemoveQuestion)
|
||||
r.PUT("/question/status", a.questionController.CloseQuestion)
|
||||
r.PUT("/question/reopen", a.questionController.ReopenQuestion)
|
||||
r.GET("/question/similar", a.questionController.SearchByTitleLike)
|
||||
|
||||
// answer
|
||||
|
|
|
@ -3,17 +3,16 @@ package schema
|
|||
// RemoveQuestionReq delete question request
|
||||
type RemoveQuestionReq struct {
|
||||
// question id
|
||||
ID string `validate:"required" comment:"question id" json:"id"`
|
||||
ID string `validate:"required" json:"id"`
|
||||
UserID string `json:"-" ` // user_id
|
||||
IsAdmin bool `json:"-"`
|
||||
}
|
||||
|
||||
type CloseQuestionReq struct {
|
||||
ID string `validate:"required" comment:"question id" json:"id"`
|
||||
UserID string `json:"-" ` // user_id
|
||||
CloseType int `json:"close_type" ` // close_type
|
||||
CloseMsg string `json:"close_msg" ` // close_type
|
||||
IsAdmin bool `json:"-"`
|
||||
ID string `validate:"required" json:"id"`
|
||||
CloseType int `json:"close_type"` // close_type
|
||||
CloseMsg string `json:"close_msg"` // close_type
|
||||
UserID string `json:"-"` // user_id
|
||||
}
|
||||
|
||||
type CloseQuestionMeta struct {
|
||||
|
@ -21,6 +20,12 @@ type CloseQuestionMeta struct {
|
|||
CloseMsg string `json:"close_msg"`
|
||||
}
|
||||
|
||||
// ReopenQuestionReq reopen question request
|
||||
type ReopenQuestionReq struct {
|
||||
QuestionID string `json:"question_id"`
|
||||
UserID string `json:"-"`
|
||||
}
|
||||
|
||||
type QuestionAdd struct {
|
||||
// question title
|
||||
Title string `validate:"required,gte=6,lte=150" json:"title"`
|
||||
|
|
|
@ -75,11 +75,6 @@ func (qs *QuestionService) CloseQuestion(ctx context.Context, req *schema.CloseQ
|
|||
return nil
|
||||
}
|
||||
|
||||
if !req.IsAdmin {
|
||||
if questionInfo.UserID != req.UserID {
|
||||
return errors.BadRequest(reason.QuestionCannotClose)
|
||||
}
|
||||
}
|
||||
questionInfo.Status = entity.QuestionStatusClosed
|
||||
err = qs.questionRepo.UpdateQuestionStatus(ctx, questionInfo)
|
||||
if err != nil {
|
||||
|
@ -104,6 +99,30 @@ func (qs *QuestionService) CloseQuestion(ctx context.Context, req *schema.CloseQ
|
|||
return nil
|
||||
}
|
||||
|
||||
// ReopenQuestion reopen question
|
||||
func (qs *QuestionService) ReopenQuestion(ctx context.Context, req *schema.ReopenQuestionReq) error {
|
||||
questionInfo, has, err := qs.questionRepo.GetQuestion(ctx, req.QuestionID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !has {
|
||||
return nil
|
||||
}
|
||||
|
||||
questionInfo.Status = entity.QuestionStatusAvailable
|
||||
err = qs.questionRepo.UpdateQuestionStatus(ctx, questionInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
activity_queue.AddActivity(&schema.ActivityMsg{
|
||||
UserID: req.UserID,
|
||||
ObjectID: questionInfo.ID,
|
||||
OriginalObjectID: questionInfo.ID,
|
||||
ActivityTypeKey: constant.ActQuestionReopened,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// CloseMsgList list close question condition
|
||||
func (qs *QuestionService) CloseMsgList(ctx context.Context, lang i18n.Language) (
|
||||
resp []*schema.GetCloseTypeResp, err error,
|
||||
|
@ -428,6 +447,12 @@ func (qs *QuestionService) GetQuestion(ctx context.Context, questionID, userID s
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
if question.Status != entity.QuestionStatusClosed {
|
||||
per.CanReopen = false
|
||||
}
|
||||
if question.Status == entity.QuestionStatusClosed {
|
||||
per.CanClose = false
|
||||
}
|
||||
question.MemberActions = permission.GetQuestionPermission(ctx, userID, question.UserID,
|
||||
per.CanEdit, per.CanDelete, per.CanClose, per.CanReopen)
|
||||
return question, nil
|
||||
|
|
|
@ -88,7 +88,8 @@ func (rs *RankService) CheckOperationPermission(ctx context.Context, userID stri
|
|||
}
|
||||
}
|
||||
|
||||
return rs.checkUserRank(ctx, userInfo.ID, userInfo.Rank, PermissionPrefix+action)
|
||||
can = rs.checkUserRank(ctx, userInfo.ID, userInfo.Rank, PermissionPrefix+action)
|
||||
return can, nil
|
||||
}
|
||||
|
||||
// CheckOperationPermissions verify that the user has permission
|
||||
|
@ -128,10 +129,7 @@ func (rs *RankService) CheckOperationPermissions(ctx context.Context, userID str
|
|||
can[idx] = true
|
||||
continue
|
||||
}
|
||||
meetRank, err := rs.checkUserRank(ctx, userInfo.ID, userInfo.Rank, PermissionPrefix+action)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
meetRank := rs.checkUserRank(ctx, userInfo.ID, userInfo.Rank, PermissionPrefix+action)
|
||||
can[idx] = meetRank
|
||||
}
|
||||
return can, nil
|
||||
|
@ -179,10 +177,7 @@ func (rs *RankService) CheckVotePermission(ctx context.Context, userID, objectID
|
|||
action = permission.CommentVoteDown
|
||||
}
|
||||
}
|
||||
meetRank, err := rs.checkUserRank(ctx, userInfo.ID, userInfo.Rank, PermissionPrefix+action)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
meetRank := rs.checkUserRank(ctx, userInfo.ID, userInfo.Rank, PermissionPrefix+action)
|
||||
return meetRank, nil
|
||||
}
|
||||
|
||||
|
@ -208,18 +203,19 @@ func (rs *RankService) getUserPowerMapping(ctx context.Context, userID string) (
|
|||
|
||||
// CheckRankPermission verify that the user meets the prestige criteria
|
||||
func (rs *RankService) checkUserRank(ctx context.Context, userID string, userRank int, action string) (
|
||||
can bool, err error) {
|
||||
can bool) {
|
||||
// get the amount of rank required for the current operation
|
||||
requireRank, err := rs.configRepo.GetInt(action)
|
||||
if err != nil {
|
||||
return false, err
|
||||
log.Error(err)
|
||||
return false
|
||||
}
|
||||
if userRank < requireRank || requireRank < 0 {
|
||||
log.Debugf("user %s want to do action %s, but rank %d < %d",
|
||||
userID, action, userRank, requireRank)
|
||||
return false, nil
|
||||
return false
|
||||
}
|
||||
return true, nil
|
||||
return true
|
||||
}
|
||||
|
||||
// GetRankPersonalWithPage get personal comment list page
|
||||
|
|
Loading…
Reference in New Issue