Merge remote-tracking branch 'origin/feat/1.0.2/user' into test

This commit is contained in:
LinkinStar 2022-12-30 17:09:47 +08:00
commit 7268fe2331
14 changed files with 294 additions and 200 deletions

View File

@ -204,7 +204,13 @@ backend:
other: "something else" other: "something else"
desc: desc:
other: "This post requires another reason not listed above." other: "This post requires another reason not listed above."
operation_type:
asked:
other: "asked"
answered:
other: "answered"
modified:
other: "modified"
notification: notification:
action: action:
update_question: update_question:

View File

@ -1,10 +1,9 @@
package controller package controller
import ( import (
"context"
"github.com/answerdev/answer/internal/base/handler" "github.com/answerdev/answer/internal/base/handler"
"github.com/answerdev/answer/internal/base/middleware" "github.com/answerdev/answer/internal/base/middleware"
"github.com/answerdev/answer/internal/base/pager"
"github.com/answerdev/answer/internal/base/reason" "github.com/answerdev/answer/internal/base/reason"
"github.com/answerdev/answer/internal/base/validator" "github.com/answerdev/answer/internal/base/validator"
"github.com/answerdev/answer/internal/entity" "github.com/answerdev/answer/internal/entity"
@ -31,7 +30,7 @@ func NewQuestionController(questionService *service.QuestionService, rankService
// RemoveQuestion delete question // RemoveQuestion delete question
// @Summary delete question // @Summary delete question
// @Description delete question // @Description delete question
// @Tags api-question // @Tags Question
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Security ApiKeyAuth // @Security ApiKeyAuth
@ -62,7 +61,7 @@ func (qc *QuestionController) RemoveQuestion(ctx *gin.Context) {
// CloseQuestion Close question // CloseQuestion Close question
// @Summary Close question // @Summary Close question
// @Description Close question // @Description Close question
// @Tags api-question // @Tags Question
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Security ApiKeyAuth // @Security ApiKeyAuth
@ -92,7 +91,7 @@ func (qc *QuestionController) CloseQuestion(ctx *gin.Context) {
// ReopenQuestion reopen question // ReopenQuestion reopen question
// @Summary reopen question // @Summary reopen question
// @Description reopen question // @Description reopen question
// @Tags api-question // @Tags Question
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Security ApiKeyAuth // @Security ApiKeyAuth
@ -119,10 +118,10 @@ func (qc *QuestionController) ReopenQuestion(ctx *gin.Context) {
handler.HandleResponse(ctx, err, nil) handler.HandleResponse(ctx, err, nil)
} }
// GetQuestion godoc // GetQuestion get question details
// @Summary GetQuestion Question // @Summary get question details
// @Description GetQuestion Question // @Description get question details
// @Tags api-question // @Tags Question
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Accept json // @Accept json
// @Produce json // @Produce json
@ -161,7 +160,7 @@ func (qc *QuestionController) GetQuestion(ctx *gin.Context) {
// SimilarQuestion godoc // SimilarQuestion godoc
// @Summary Search Similar Question // @Summary Search Similar Question
// @Description Search Similar Question // @Description Search Similar Question
// @Tags api-question // @Tags Question
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param question_id query string true "question_id" default() // @Param question_id query string true "question_id" default()
@ -181,65 +180,34 @@ func (qc *QuestionController) SimilarQuestion(ctx *gin.Context) {
}) })
} }
// Index godoc // QuestionPage get questions by page
// @Summary SearchQuestionList // @Summary get questions by page
// @Description SearchQuestionList <br> "order" Enums(newest, active,frequent,score,unanswered) // @Description get questions by page
// @Tags api-question // @Tags Question
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param data body schema.QuestionSearch true "QuestionSearch" // @Param data body schema.QuestionPageReq true "QuestionPageReq"
// @Success 200 {string} string "" // @Success 200 {object} handler.RespBody{data=pager.PageModel{list=[]schema.QuestionPageResp}}
// @Router /answer/api/v1/question/page [get] // @Router /answer/api/v1/question/page [get]
func (qc *QuestionController) Index(ctx *gin.Context) { func (qc *QuestionController) QuestionPage(ctx *gin.Context) {
req := &schema.QuestionSearch{} req := &schema.QuestionPageReq{}
if handler.BindAndCheck(ctx, req) { if handler.BindAndCheck(ctx, req) {
return return
} }
userID := middleware.GetLoginUserIDFromContext(ctx) req.LoginUserID = middleware.GetLoginUserIDFromContext(ctx)
list, count, err := qc.questionService.SearchList(ctx, req, userID)
questions, total, err := qc.questionService.GetQuestionPage(ctx, req)
if err != nil { if err != nil {
handler.HandleResponse(ctx, err, nil) handler.HandleResponse(ctx, err, nil)
return return
} }
handler.HandleResponse(ctx, nil, gin.H{ handler.HandleResponse(ctx, nil, pager.NewPageModel(total, questions))
"list": list,
"count": count,
})
}
// SearchList godoc
// @Summary SearchQuestionList
// @Description SearchQuestionList
// @Tags api-question
// @Accept json
// @Produce json
// @Param data body schema.QuestionSearch true "QuestionSearch"
// @Router /answer/api/v1/question/search [post]
// @Success 200 {string} string ""
func (qc *QuestionController) SearchList(c *gin.Context) {
Request := new(schema.QuestionSearch)
err := c.BindJSON(Request)
if err != nil {
handler.HandleResponse(c, err, nil)
return
}
ctx := context.Background()
userID := middleware.GetLoginUserIDFromContext(c)
list, count, err := qc.questionService.SearchList(ctx, Request, userID)
if err != nil {
handler.HandleResponse(c, err, nil)
return
}
handler.HandleResponse(c, nil, gin.H{
"list": list,
"count": count,
})
} }
// AddQuestion add question // AddQuestion add question
// @Summary add question // @Summary add question
// @Description add question // @Description add question
// @Tags api-question // @Tags Question
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Security ApiKeyAuth // @Security ApiKeyAuth
@ -309,7 +277,7 @@ func (qc *QuestionController) AddQuestion(ctx *gin.Context) {
// UpdateQuestion update question // UpdateQuestion update question
// @Summary update question // @Summary update question
// @Description update question // @Description update question
// @Tags api-question // @Tags Question
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Security ApiKeyAuth // @Security ApiKeyAuth
@ -366,7 +334,7 @@ func (qc *QuestionController) UpdateQuestion(ctx *gin.Context) {
// CloseMsgList close question msg list // CloseMsgList close question msg list
// @Summary close question msg list // @Summary close question msg list
// @Description close question msg list // @Description close question msg list
// @Tags api-question // @Tags Question
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Security ApiKeyAuth // @Security ApiKeyAuth
@ -380,7 +348,7 @@ func (qc *QuestionController) CloseMsgList(ctx *gin.Context) {
// SearchByTitleLike add question title like // SearchByTitleLike add question title like
// @Summary add question title like // @Summary add question title like
// @Description add question title like // @Description add question title like
// @Tags api-question // @Tags Question
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Security ApiKeyAuth // @Security ApiKeyAuth
@ -397,7 +365,7 @@ func (qc *QuestionController) SearchByTitleLike(ctx *gin.Context) {
// UserTop godoc // UserTop godoc
// @Summary UserTop // @Summary UserTop
// @Description UserTop // @Description UserTop
// @Tags api-question // @Tags Question
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Security ApiKeyAuth // @Security ApiKeyAuth
@ -417,7 +385,7 @@ func (qc *QuestionController) UserTop(ctx *gin.Context) {
// UserList godoc // UserList godoc
// @Summary UserList // @Summary UserList
// @Description UserList // @Description UserList
// @Tags api-question // @Tags Question
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Security ApiKeyAuth // @Security ApiKeyAuth

View File

@ -91,8 +91,8 @@ func (tc *TemplateController) SiteInfo(ctx *gin.Context) *schema.TemplateSiteInf
// Index question list // Index question list
func (tc *TemplateController) Index(ctx *gin.Context) { func (tc *TemplateController) Index(ctx *gin.Context) {
req := &schema.QuestionSearch{ req := &schema.QuestionPageReq{
Order: "newest", OrderCond: "newest",
} }
if handler.BindAndCheck(ctx, req) { if handler.BindAndCheck(ctx, req) {
tc.Page404(ctx) tc.Page404(ctx)
@ -124,8 +124,8 @@ func (tc *TemplateController) Index(ctx *gin.Context) {
} }
func (tc *TemplateController) QuestionList(ctx *gin.Context) { func (tc *TemplateController) QuestionList(ctx *gin.Context) {
req := &schema.QuestionSearch{ req := &schema.QuestionPageReq{
Order: "newest", OrderCond: "newest",
} }
if handler.BindAndCheck(ctx, req) { if handler.BindAndCheck(ctx, req) {
tc.Page404(ctx) tc.Page404(ctx)

View File

@ -11,8 +11,8 @@ import (
"github.com/segmentfault/pacman/log" "github.com/segmentfault/pacman/log"
) )
func (t *TemplateRenderController) Index(ctx *gin.Context, req *schema.QuestionSearch) ([]*schema.QuestionInfo, int64, error) { func (t *TemplateRenderController) Index(ctx *gin.Context, req *schema.QuestionPageReq) ([]*schema.QuestionPageResp, int64, error) {
return t.questionService.SearchList(ctx, req, req.UserID) return t.questionService.GetQuestionPage(ctx, req)
} }
func (t *TemplateRenderController) QuestionDetail(ctx *gin.Context, id string) (resp *schema.QuestionInfo, err error) { func (t *TemplateRenderController) QuestionDetail(ctx *gin.Context, id string) (resp *schema.QuestionInfo, err error) {

View File

@ -15,19 +15,20 @@ func (q *TemplateRenderController) TagList(ctx context.Context, req *schema.GetT
return return
} }
func (q *TemplateRenderController) TagInfo(ctx context.Context, req *schema.GetTamplateTagInfoReq) (resp *schema.GetTagResp, questionList []*schema.QuestionInfo, questionCount int64, err error) { func (q *TemplateRenderController) TagInfo(ctx context.Context, req *schema.GetTamplateTagInfoReq) (resp *schema.GetTagResp, questionList []*schema.QuestionPageResp, questionCount int64, err error) {
dto := &schema.GetTagInfoReq{} dto := &schema.GetTagInfoReq{}
_ = copier.Copy(dto, req) _ = copier.Copy(dto, req)
resp, err = q.tagService.GetTagInfo(ctx, dto) resp, err = q.tagService.GetTagInfo(ctx, dto)
if err != nil { if err != nil {
return return
} }
searchQuestion := &schema.QuestionSearch{} searchQuestion := &schema.QuestionPageReq{}
searchQuestion.Page = req.Page searchQuestion.Page = req.Page
searchQuestion.PageSize = req.PageSize searchQuestion.PageSize = req.PageSize
searchQuestion.Order = "newest" searchQuestion.OrderCond = "newest"
searchQuestion.Tag = req.Name searchQuestion.Tag = req.Name
questionList, questionCount, err = q.questionService.SearchList(ctx, searchQuestion, "") searchQuestion.LoginUserID = req.UserID
questionList, questionCount, err = q.questionService.GetQuestionPage(ctx, searchQuestion)
if err != nil { if err != nil {
return return
} }

View File

@ -22,11 +22,6 @@ var AdminQuestionSearchStatusIntToString = map[int]string{
QuestionStatusDeleted: "deleted", QuestionStatusDeleted: "deleted",
} }
type QuestionTag struct {
Question `xorm:"extends"`
TagRel `xorm:"extends"`
}
// Question question // Question question
type Question struct { type Question struct {
ID string `xorm:"not null pk BIGINT(20) id"` ID string `xorm:"not null pk BIGINT(20) id"`

View File

@ -205,70 +205,40 @@ func (qr *questionRepo) GetQuestionIDsPage(ctx context.Context, page, pageSize i
return questionIDList, nil return questionIDList, nil
} }
// GetQuestionPage get question page // GetQuestionPage query question page
func (qr *questionRepo) GetQuestionPage(ctx context.Context, page, pageSize int, question *entity.Question) (questionList []*entity.Question, total int64, err error) { func (qr *questionRepo) GetQuestionPage(ctx context.Context, page, pageSize int, userID, tagID, orderCond string) (
questionList []*entity.Question, total int64, err error) {
questionList = make([]*entity.Question, 0) questionList = make([]*entity.Question, 0)
total, err = pager.Help(page, pageSize, questionList, question, qr.data.DB.NewSession())
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
return
}
// SearchList session := qr.data.DB.Where("question.status = ? OR question.status = ?",
func (qr *questionRepo) SearchList(ctx context.Context, search *schema.QuestionSearch) ([]*entity.QuestionTag, int64, error) { entity.QuestionStatusAvailable, entity.QuestionStatusClosed)
var count int64 if len(tagID) > 0 {
var err error session.Join("LEFT", "tag_rel", "question.id = tag_rel.object_id")
rows := make([]*entity.QuestionTag, 0) session.And("tag_rel.tag_id = ?", tagID)
if search.Page > 0 { session.And("tag_rel.status = ?", entity.TagRelStatusAvailable)
search.Page = search.Page - 1
} else {
search.Page = 0
} }
if search.PageSize == 0 { if len(userID) > 0 {
search.PageSize = constant.DefaultPageSize session.And("question.user_id = ?", userID)
} }
offset := search.Page * search.PageSize switch orderCond {
session := qr.data.DB.Table("question")
if len(search.TagIDs) > 0 {
session = session.Join("LEFT", "tag_rel", "question.id = tag_rel.object_id")
session = session.And("tag_rel.tag_id =?", search.TagIDs[0])
// session = session.In("tag_rel.tag_id ", search.TagIDs)
session = session.And("tag_rel.status =?", entity.TagRelStatusAvailable)
}
if len(search.UserID) > 0 {
session = session.And("question.user_id = ?", search.UserID)
}
session = session.In("question.status", []int{entity.QuestionStatusAvailable, entity.QuestionStatusClosed})
// if search.Status > 0 {
// session = session.And("question.status = ?", search.Status)
// }
// switch
// newest, active,frequent,score,unanswered
switch search.Order {
case "newest": case "newest":
session = session.OrderBy("question.created_at desc") session.OrderBy("question.created_at DESC")
case "active": case "active":
session = session.OrderBy("question.post_update_time desc,question.updated_at desc") session.OrderBy("question.post_update_time DESC, question.updated_at DESC")
case "frequent": case "frequent":
session = session.OrderBy("question.view_count desc") session.OrderBy("question.view_count DESC")
case "score": case "score":
session = session.OrderBy("question.vote_count desc,question.view_count desc") session.OrderBy("question.vote_count DESC, question.view_count DESC")
case "unanswered": case "unanswered":
session = session.And("question.last_answer_id = 0") session.Where("question.last_answer_id = 0")
session = session.OrderBy("question.created_at desc") session.OrderBy("question.created_at DESC")
} }
session = session.Limit(search.PageSize, offset)
session = session.Select("question.id,question.user_id,last_edit_user_id,question.title,question.original_text,question.parsed_text,question.status,question.view_count,question.unique_view_count,question.vote_count,question.answer_count,question.collection_count,question.follow_count,question.accepted_answer_id,question.last_answer_id,question.created_at,question.updated_at,question.post_update_time,question.revision_id") total, err = pager.Help(page, pageSize, &questionList, &entity.Question{}, session)
count, err = session.FindAndCount(&rows)
if err != nil { if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
return rows, count, err
} }
return rows, count, nil return questionList, total, err
} }
func (qr *questionRepo) AdminSearchList(ctx context.Context, search *schema.AdminQuestionSearch) ([]*entity.Question, int64, error) { func (qr *questionRepo) AdminSearchList(ctx context.Context, search *schema.AdminQuestionSearch) ([]*entity.Question, int64, error) {

View File

@ -124,8 +124,7 @@ func (a *AnswerAPIRouter) RegisterUnAuthAnswerAPIRouter(r *gin.RouterGroup) {
//question //question
r.GET("/question/info", a.questionController.GetQuestion) r.GET("/question/info", a.questionController.GetQuestion)
r.POST("/question/search", a.questionController.SearchList) r.GET("/question/page", a.questionController.QuestionPage)
r.GET("/question/page", a.questionController.Index)
r.GET("/question/similar/tag", a.questionController.SimilarQuestion) r.GET("/question/similar/tag", a.questionController.SimilarQuestion)
r.GET("/personal/qa/top", a.questionController.UserTop) r.GET("/personal/qa/top", a.questionController.UserTop)
r.GET("/personal/question/page", a.questionController.UserList) r.GET("/personal/question/page", a.questionController.UserList)
@ -143,7 +142,6 @@ func (a *AnswerAPIRouter) RegisterUnAuthAnswerAPIRouter(r *gin.RouterGroup) {
r.GET("/tags/following", a.tagController.GetFollowingTags) r.GET("/tags/following", a.tagController.GetFollowingTags)
r.GET("/tag", a.tagController.GetTagInfo) r.GET("/tag", a.tagController.GetTagInfo)
r.GET("/tag/synonyms", a.tagController.GetTagSynonyms) r.GET("/tag/synonyms", a.tagController.GetTagSynonyms)
r.GET("/question/index", a.questionController.Index)
//search //search
r.GET("/search", a.searchController.Search) r.GET("/search", a.searchController.Search)

View File

@ -1,6 +1,8 @@
package schema package schema
import ( import (
"time"
"github.com/answerdev/answer/internal/base/validator" "github.com/answerdev/answer/internal/base/validator"
"github.com/answerdev/answer/pkg/converter" "github.com/answerdev/answer/pkg/converter"
) )
@ -222,15 +224,66 @@ type UserQuestionInfo struct {
Status string `json:"status"` Status string `json:"status"`
} }
type QuestionSearch struct { const (
Page int `json:"page" form:"page"` // Query number of pages QuestionOrderCondNewest = "newest"
PageSize int `json:"page_size" form:"page_size"` // Search page size QuestionOrderCondActive = "active"
Order string `json:"order" form:"order"` // Search order by QuestionOrderCondFrequent = "frequent"
// Tags []string `json:"tags" form:"tags"` // Search tag QuestionOrderCondScore = "score"
Tag string `json:"tag" form:"tag"` //Search tag QuestionOrderCondUnanswered = "unanswered"
TagIDs []string `json:"-" form:"-"` // Search tag )
UserName string `json:"username" form:"username"` // Search username
UserID string `json:"-" form:"-"` // QuestionPageReq query questions page
type QuestionPageReq struct {
Page int `validate:"omitempty,min=1" form:"page"`
PageSize int `validate:"omitempty,min=1" form:"page_size"`
OrderCond string `validate:"omitempty,oneof=newest active frequent score unanswered" form:"order"`
Tag string `validate:"omitempty,gt=0,lte=100" form:"tag"`
Username string `validate:"omitempty,gt=0,lte=100" form:"username"`
LoginUserID string `json:"-"`
UserIDBeSearched string `json:"-"`
TagID string `json:"-"`
}
const (
QuestionPageRespOperationTypeAsked = "question.operation_type.asked"
QuestionPageRespOperationTypeAnswered = "question.operation_type.answered"
QuestionPageRespOperationTypeModified = "question.operation_type.modified"
)
type QuestionPageResp struct {
ID string `json:"id" `
Title string `json:"title"`
UrlTitle string `json:"url_title"`
Description string `json:"description"`
Status int `json:"status"`
Tags []*TagResp `json:"tags"`
// question statistical information
ViewCount int `json:"view_count"`
UniqueViewCount int `json:"unique_view_count"`
VoteCount int `json:"vote_count"`
AnswerCount int `json:"answer_count"`
CollectionCount int `json:"collection_count"`
FollowCount int `json:"follow_count"`
// answer information
AcceptedAnswerID string `json:"accepted_answer_id"`
LastAnswerID string `json:"last_answer_id"`
LastAnsweredUserID string `json:"-"`
LastAnsweredAt time.Time `json:"-"`
// operator information
OperatedAt int64 `json:"operated_at"`
Operator *QuestionPageRespOperator `json:"operator"`
OperationType string `json:"operation_type"`
}
type QuestionPageRespOperator struct {
ID string `json:"id"`
Username string `json:"username"`
Rank int `json:"rank"`
DisplayName string `json:"display_name"`
} }
type AdminQuestionSearch struct { type AdminQuestionSearch struct {

View File

@ -73,7 +73,7 @@ func (ns *NotificationCommon) HandleNotification() {
// AddNotification // AddNotification
// need set // need set
// UserID // LoginUserID
// Type 1 inbox 2 achievement // Type 1 inbox 2 achievement
// [inbox] Activity // [inbox] Activity
// [achievement] Rank // [achievement] Rank

View File

@ -6,11 +6,14 @@ import (
"time" "time"
"github.com/answerdev/answer/internal/base/constant" "github.com/answerdev/answer/internal/base/constant"
"github.com/answerdev/answer/internal/base/handler"
"github.com/answerdev/answer/internal/base/reason" "github.com/answerdev/answer/internal/base/reason"
"github.com/answerdev/answer/internal/base/translator"
"github.com/answerdev/answer/internal/service/activity_common" "github.com/answerdev/answer/internal/service/activity_common"
"github.com/answerdev/answer/internal/service/activity_queue" "github.com/answerdev/answer/internal/service/activity_queue"
"github.com/answerdev/answer/internal/service/config" "github.com/answerdev/answer/internal/service/config"
"github.com/answerdev/answer/internal/service/meta" "github.com/answerdev/answer/internal/service/meta"
"github.com/answerdev/answer/pkg/checker"
"github.com/answerdev/answer/pkg/htmltext" "github.com/answerdev/answer/pkg/htmltext"
"github.com/segmentfault/pacman/errors" "github.com/segmentfault/pacman/errors"
@ -30,8 +33,8 @@ type QuestionRepo interface {
UpdateQuestion(ctx context.Context, question *entity.Question, Cols []string) (err error) UpdateQuestion(ctx context.Context, question *entity.Question, Cols []string) (err error)
GetQuestion(ctx context.Context, id string) (question *entity.Question, exist bool, err error) GetQuestion(ctx context.Context, id string) (question *entity.Question, exist bool, err error)
GetQuestionList(ctx context.Context, question *entity.Question) (questions []*entity.Question, err error) GetQuestionList(ctx context.Context, question *entity.Question) (questions []*entity.Question, err error)
GetQuestionPage(ctx context.Context, page, pageSize int, question *entity.Question) (questions []*entity.Question, total int64, err error) GetQuestionPage(ctx context.Context, page, pageSize int, userID, tagID, orderCond string) (
SearchList(ctx context.Context, search *schema.QuestionSearch) ([]*entity.QuestionTag, int64, error) questionList []*entity.Question, total int64, err error)
UpdateQuestionStatus(ctx context.Context, question *entity.Question) (err error) UpdateQuestionStatus(ctx context.Context, question *entity.Question) (err error)
SearchByTitleLike(ctx context.Context, title string) (questionList []*entity.Question, err error) SearchByTitleLike(ctx context.Context, title string) (questionList []*entity.Question, err error)
UpdatePvCount(ctx context.Context, questionID string) (err error) UpdatePvCount(ctx context.Context, questionID string) (err error)
@ -126,21 +129,15 @@ func (qs *QuestionCommon) UpdataPostSetTime(ctx context.Context, questionID stri
func (qs *QuestionCommon) FindInfoByID(ctx context.Context, questionIDs []string, loginUserID string) (map[string]*schema.QuestionInfo, error) { func (qs *QuestionCommon) FindInfoByID(ctx context.Context, questionIDs []string, loginUserID string) (map[string]*schema.QuestionInfo, error) {
list := make(map[string]*schema.QuestionInfo) list := make(map[string]*schema.QuestionInfo)
listAddTag := make([]*entity.QuestionTag, 0)
questionList, err := qs.questionRepo.FindByID(ctx, questionIDs) questionList, err := qs.questionRepo.FindByID(ctx, questionIDs)
if err != nil { if err != nil {
return list, err return list, err
} }
for _, item := range questionList { questions, err := qs.FormatQuestions(ctx, questionList, loginUserID)
itemAddTag := &entity.QuestionTag{}
itemAddTag.Question = *item
listAddTag = append(listAddTag, itemAddTag)
}
QuestionInfo, err := qs.ListFormat(ctx, listAddTag, loginUserID)
if err != nil { if err != nil {
return list, err return list, err
} }
for _, item := range QuestionInfo { for _, item := range questions {
list[item.ID] = item list[item.ID] = item
} }
return list, nil return list, nil
@ -244,13 +241,114 @@ func (qs *QuestionCommon) Info(ctx context.Context, questionID string, loginUser
return showinfo, nil return showinfo, nil
} }
func (qs *QuestionCommon) ListFormat(ctx context.Context, questionList []*entity.QuestionTag, loginUserID string) ([]*schema.QuestionInfo, error) { func (qs *QuestionCommon) FormatQuestionsPage(
ctx context.Context, questionList []*entity.Question, loginUserID string, orderCond string) (
formattedQuestions []*schema.QuestionPageResp, err error) {
language := handler.GetLangByCtx(ctx)
askedOp := translator.GlobalTrans.Tr(language, schema.QuestionPageRespOperationTypeAsked)
answeredOp := translator.GlobalTrans.Tr(language, schema.QuestionPageRespOperationTypeAnswered)
modifiedOp := translator.GlobalTrans.Tr(language, schema.QuestionPageRespOperationTypeModified)
formattedQuestions = make([]*schema.QuestionPageResp, 0)
questionIDs := make([]string, 0)
userIDs := make([]string, 0)
for _, questionInfo := range questionList {
t := &schema.QuestionPageResp{
ID: questionInfo.ID,
Title: questionInfo.Title,
UrlTitle: htmltext.UrlTitle(questionInfo.Title),
Description: htmltext.FetchExcerpt(questionInfo.ParsedText, "...", 240),
Status: questionInfo.Status,
ViewCount: questionInfo.ViewCount,
UniqueViewCount: questionInfo.UniqueViewCount,
VoteCount: questionInfo.VoteCount,
AnswerCount: questionInfo.AnswerCount,
CollectionCount: questionInfo.CollectionCount,
FollowCount: questionInfo.FollowCount,
AcceptedAnswerID: questionInfo.AcceptedAnswerID,
LastAnswerID: questionInfo.LastAnswerID,
}
questionIDs = append(questionIDs, questionInfo.ID)
userIDs = append(userIDs, questionInfo.UserID)
haveEdited, haveAnswered := false, false
if checker.IsNotZeroString(questionInfo.LastEditUserID) {
haveEdited = true
userIDs = append(userIDs, questionInfo.LastEditUserID)
}
if checker.IsNotZeroString(questionInfo.LastAnswerID) {
haveAnswered = true
answerInfo, exist, err := qs.answerRepo.GetAnswer(ctx, questionInfo.LastAnswerID)
if err == nil && exist {
if answerInfo.LastEditUserID != "0" {
t.LastAnsweredUserID = answerInfo.LastEditUserID
} else {
t.LastAnsweredUserID = answerInfo.UserID
}
t.LastAnsweredAt = answerInfo.CreatedAt
userIDs = append(userIDs, t.LastAnsweredUserID)
}
}
// if order condition is newest or nobody edited or nobody answered, only show question author
if orderCond == schema.QuestionOrderCondNewest || (!haveEdited && !haveAnswered) {
t.OperationType = askedOp
t.OperatedAt = questionInfo.CreatedAt.Unix()
t.Operator = &schema.QuestionPageRespOperator{ID: questionInfo.UserID}
}
// if no one
if haveEdited {
t.OperationType = modifiedOp
t.OperatedAt = questionInfo.UpdatedAt.Unix()
t.Operator = &schema.QuestionPageRespOperator{ID: questionInfo.LastEditUserID}
}
if haveAnswered {
if t.LastAnsweredAt.Unix() > t.OperatedAt {
t.OperationType = answeredOp
t.OperatedAt = t.LastAnsweredAt.Unix()
t.Operator = &schema.QuestionPageRespOperator{ID: t.LastAnsweredUserID}
}
}
formattedQuestions = append(formattedQuestions, t)
}
tagsMap, err := qs.tagCommon.BatchGetObjectTag(ctx, questionIDs)
if err != nil {
return formattedQuestions, err
}
userInfoMap, err := qs.userCommon.BatchUserBasicInfoByID(ctx, userIDs)
if err != nil {
return formattedQuestions, err
}
for _, item := range formattedQuestions {
tags, ok := tagsMap[item.ID]
if ok {
item.Tags = tags
} else {
item.Tags = make([]*schema.TagResp, 0)
}
userInfo := userInfoMap[item.Operator.ID]
if userInfo != nil {
item.Operator.DisplayName = userInfo.DisplayName
item.Operator.Username = userInfo.Username
item.Operator.Rank = userInfo.Rank
}
}
return formattedQuestions, nil
}
func (qs *QuestionCommon) FormatQuestions(ctx context.Context, questionList []*entity.Question, loginUserID string) ([]*schema.QuestionInfo, error) {
list := make([]*schema.QuestionInfo, 0) list := make([]*schema.QuestionInfo, 0)
objectIds := make([]string, 0) objectIds := make([]string, 0)
userIds := make([]string, 0) userIds := make([]string, 0)
for _, questionInfo := range questionList { for _, questionInfo := range questionList {
item := qs.ShowListFormat(ctx, questionInfo) item := qs.ShowFormat(ctx, questionInfo)
list = append(list, item) list = append(list, item)
objectIds = append(objectIds, item.ID) objectIds = append(objectIds, item.ID)
userIds = append(userIds, item.UserID) userIds = append(userIds, item.UserID)
@ -387,8 +485,8 @@ func (as *QuestionCommon) RemoveAnswer(ctx context.Context, id string) (err erro
return as.answerRepo.RemoveAnswer(ctx, id) return as.answerRepo.RemoveAnswer(ctx, id)
} }
func (qs *QuestionCommon) ShowListFormat(ctx context.Context, data *entity.QuestionTag) *schema.QuestionInfo { func (qs *QuestionCommon) ShowListFormat(ctx context.Context, data *entity.Question) *schema.QuestionInfo {
return qs.ShowFormat(ctx, &data.Question) return qs.ShowFormat(ctx, data)
} }
func (qs *QuestionCommon) ShowFormat(ctx context.Context, data *entity.Question) *schema.QuestionInfo { func (qs *QuestionCommon) ShowFormat(ctx context.Context, data *entity.Question) *schema.QuestionInfo {

View File

@ -655,12 +655,13 @@ func (qs *QuestionService) SearchUserList(ctx context.Context, userName, order s
if !Exist { if !Exist {
return userlist, 0, nil return userlist, 0, nil
} }
search := &schema.QuestionSearch{} search := &schema.QuestionPageReq{}
search.Order = order search.OrderCond = order
search.Page = page search.Page = page
search.PageSize = pageSize search.PageSize = pageSize
search.UserID = userinfo.ID search.UserIDBeSearched = userinfo.ID
questionlist, count, err := qs.SearchList(ctx, search, loginUserID) search.LoginUserID = loginUserID
questionlist, count, err := qs.GetQuestionPage(ctx, search)
if err != nil { if err != nil {
return userlist, 0, err return userlist, 0, err
} }
@ -778,12 +779,13 @@ func (qs *QuestionService) SearchUserTopList(ctx context.Context, userName strin
if !Exist { if !Exist {
return userQuestionlist, userAnswerlist, nil return userQuestionlist, userAnswerlist, nil
} }
search := &schema.QuestionSearch{} search := &schema.QuestionPageReq{}
search.Order = "score" search.OrderCond = "score"
search.Page = 0 search.Page = 0
search.PageSize = 5 search.PageSize = 5
search.UserID = userinfo.ID search.UserIDBeSearched = userinfo.ID
questionlist, _, err := qs.SearchList(ctx, search, loginUserID) search.LoginUserID = loginUserID
questionlist, _, err := qs.GetQuestionPage(ctx, search)
if err != nil { if err != nil {
return userQuestionlist, userAnswerlist, err return userQuestionlist, userAnswerlist, err
} }
@ -858,57 +860,64 @@ func (qs *QuestionService) SearchByTitleLike(ctx context.Context, title string,
} }
// SimilarQuestion // SimilarQuestion
func (qs *QuestionService) SimilarQuestion(ctx context.Context, questionID string, loginUserID string) ([]*schema.QuestionInfo, int64, error) { func (qs *QuestionService) SimilarQuestion(ctx context.Context, questionID string, loginUserID string) ([]*schema.QuestionPageResp, int64, error) {
list := make([]*schema.QuestionInfo, 0)
question, err := qs.questioncommon.Info(ctx, questionID, loginUserID) question, err := qs.questioncommon.Info(ctx, questionID, loginUserID)
if err != nil { if err != nil {
return list, 0, nil return nil, 0, nil
} }
tagNames := make([]string, 0, len(question.Tags)) tagNames := make([]string, 0, len(question.Tags))
for _, tag := range question.Tags { for _, tag := range question.Tags {
tagNames = append(tagNames, tag.SlugName) tagNames = append(tagNames, tag.SlugName)
} }
search := &schema.QuestionSearch{} search := &schema.QuestionPageReq{}
search.Order = "frequent" search.OrderCond = "frequent"
search.Page = 0 search.Page = 0
search.PageSize = 6 search.PageSize = 6
if len(tagNames) > 0 { if len(tagNames) > 0 {
search.Tag = tagNames[0] search.Tag = tagNames[0]
} }
return qs.SearchList(ctx, search, loginUserID) search.LoginUserID = loginUserID
return qs.GetQuestionPage(ctx, search)
} }
// SearchList // GetQuestionPage query questions page
func (qs *QuestionService) SearchList(ctx context.Context, req *schema.QuestionSearch, loginUserID string) ([]*schema.QuestionInfo, int64, error) { func (qs *QuestionService) GetQuestionPage(ctx context.Context, req *schema.QuestionPageReq) (
questions []*schema.QuestionPageResp, total int64, err error) {
questions = make([]*schema.QuestionPageResp, 0)
// query by tag condition
if len(req.Tag) > 0 { if len(req.Tag) > 0 {
tagInfo, has, err := qs.tagCommon.GetTagBySlugName(ctx, strings.ToLower(req.Tag)) tagInfo, exist, err := qs.tagCommon.GetTagBySlugName(ctx, strings.ToLower(req.Tag))
if err != nil { if err != nil {
log.Error("tagCommon.GetTagListByNames error", err) return nil, 0, err
} }
if has { if exist {
req.TagIDs = append(req.TagIDs, tagInfo.ID) req.TagID = tagInfo.ID
} }
} }
list := make([]*schema.QuestionInfo, 0)
if req.UserName != "" { // query by user condition
userinfo, exist, err := qs.userCommon.GetUserBasicInfoByUserName(ctx, req.UserName) if req.Username != "" {
userinfo, exist, err := qs.userCommon.GetUserBasicInfoByUserName(ctx, req.Username)
if err != nil { if err != nil {
return list, 0, err return nil, 0, err
} }
if !exist { if !exist {
return list, 0, err return questions, 0, nil
} }
req.UserID = userinfo.ID req.UserIDBeSearched = userinfo.ID
} }
questionList, count, err := qs.questionRepo.SearchList(ctx, req)
questionList, total, err := qs.questionRepo.GetQuestionPage(ctx, req.Page, req.PageSize,
req.UserIDBeSearched, req.TagID, req.OrderCond)
if err != nil { if err != nil {
return list, count, err return nil, 0, err
} }
list, err = qs.questioncommon.ListFormat(ctx, questionList, loginUserID) questions, err = qs.questioncommon.FormatQuestionsPage(ctx, questionList, req.LoginUserID, req.OrderCond)
if err != nil { if err != nil {
return list, count, err return nil, 0, err
} }
return list, count, nil return questions, total, nil
} }
func (qs *QuestionService) AdminSetQuestionStatus(ctx context.Context, questionID string, setStatusStr string) error { func (qs *QuestionService) AdminSetQuestionStatus(ctx context.Context, questionID string, setStatusStr string) error {

View File

@ -0,0 +1,6 @@
package checker
// IsNotZeroString check s is not empty string and is not "0"
func IsNotZeroString(s string) bool {
return len(s) > 0 && s != "0"
}

View File

@ -25,31 +25,21 @@
> >
<div class="d-flex"> <div class="d-flex">
<div class="text-secondary me-1"> <div class="text-secondary me-1">
<a href="/users/{{.UserInfo.Username}}" <a href="/users/{{.Operator.Username}}"
><span class="me-1 text-break" ><span class="me-1 text-break"
>{{.UserInfo.DisplayName}}</span >{{.Operator.DisplayName}}</span
></a ></a
><span class="fw-bold" title="Reputation" ><span class="fw-bold" title="Reputation"
>{{.UserInfo.Rank}}</span >{{.Operator.Rank}}</span
> >
</div> </div>
• {{if eq .CreateTime .UpdateTime}}
<time <time
class="text-secondary ms-1" class="text-secondary ms-1"
datetime="{{timeFormatISO $.timezone .CreateTime}}" datetime="{{timeFormatISO $.timezone .OperatedAt}}"
title="{{translatorTimeFormatLongDate $.language $.timezone .CreateTime}}" title="{{translatorTimeFormatLongDate $.language $.timezone .OperatedAt}}"
>{{translator $.language "ui.question.asked"}} >{{translator $.language "ui.question.asked"}}
{{translatorTimeFormat $.language $.timezone .CreateTime}} {{translatorTimeFormat $.language $.timezone .OperatedAt}}
</time> </time>
{{else if gt .UpdateTime 0}}
<time
class="text-secondary ms-1"
datetime="{{timeFormatISO $.timezone .UpdateTime}}"
title="{{translatorTimeFormatLongDate $.language $.timezone .UpdateTime}}"
>{{translator $.language "ui.question.modified"}}
{{translatorTimeFormat $.language $.timezone .UpdateTime}}
</time>
{{end}}
</div> </div>
<div class="ms-0 ms-md-3 mt-2 mt-md-0"> <div class="ms-0 ms-md-3 mt-2 mt-md-0">
<span <span