fix(question): Add the function of reopening the problem

This commit is contained in:
LinkinStar 2022-12-02 17:24:46 +08:00
parent 2b0634e2e6
commit c6060d5aae
8 changed files with 143 additions and 34 deletions

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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()
}

View File

@ -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

View File

@ -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"`

View File

@ -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

View File

@ -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