internal/schema/question_schema.go (429 lines of code) (raw):

/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package schema import ( "strings" "time" "github.com/apache/answer/internal/base/reason" "github.com/segmentfault/pacman/errors" "github.com/apache/answer/internal/base/validator" "github.com/apache/answer/internal/entity" "github.com/apache/answer/pkg/converter" "github.com/apache/answer/pkg/uid" ) const ( QuestionOperationPin = "pin" QuestionOperationUnPin = "unpin" QuestionOperationHide = "hide" QuestionOperationShow = "show" ) // RemoveQuestionReq delete question request type RemoveQuestionReq struct { // question id ID string `validate:"required" json:"id"` UserID string `json:"-" ` // user_id IsAdmin bool `json:"-"` CaptchaID string `json:"captcha_id"` // captcha_id CaptchaCode string `json:"captcha_code"` } type CloseQuestionReq struct { 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 OperationQuestionReq struct { ID string `validate:"required" json:"id"` Operation string `json:"operation"` // operation [pin unpin hide show] UserID string `json:"-"` // user_id CanPin bool `json:"-"` CanList bool `json:"-"` } type CloseQuestionMeta struct { CloseType int `json:"close_type"` 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,notblank,gte=6,lte=150" json:"title"` // content Content string `validate:"required,notblank,gte=6,lte=65535" json:"content"` // html HTML string `json:"-"` // tags Tags []*TagItem `validate:"required,dive" json:"tags"` // user id UserID string `json:"-"` QuestionPermission CaptchaID string `json:"captcha_id"` // captcha_id CaptchaCode string `json:"captcha_code"` IP string `json:"-"` UserAgent string `json:"-"` } func (req *QuestionAdd) Check() (errFields []*validator.FormErrorField, err error) { req.HTML = converter.Markdown2HTML(req.Content) for _, tag := range req.Tags { if len(tag.OriginalText) > 0 { tag.ParsedText = converter.Markdown2HTML(tag.OriginalText) } } if req.HTML == "" { return append(errFields, &validator.FormErrorField{ ErrorField: "content", ErrorMsg: reason.QuestionContentCannotEmpty, }), errors.BadRequest(reason.QuestionContentCannotEmpty) } return nil, nil } type QuestionAddByAnswer struct { // question title Title string `validate:"required,notblank,gte=6,lte=150" json:"title"` // content Content string `validate:"required,notblank,gte=6,lte=65535" json:"content"` // html HTML string `json:"-"` AnswerContent string `validate:"required,notblank,gte=6,lte=65535" json:"answer_content"` AnswerHTML string `json:"-"` // tags Tags []*TagItem `validate:"required,dive" json:"tags"` // user id UserID string `json:"-"` MentionUsernameList []string `validate:"omitempty" json:"mention_username_list"` QuestionPermission CaptchaID string `json:"captcha_id"` // captcha_id CaptchaCode string `json:"captcha_code"` IP string `json:"-"` UserAgent string `json:"-"` } func (req *QuestionAddByAnswer) Check() (errFields []*validator.FormErrorField, err error) { req.HTML = converter.Markdown2HTML(req.Content) req.AnswerHTML = converter.Markdown2HTML(req.AnswerContent) for _, tag := range req.Tags { if len(tag.OriginalText) > 0 { tag.ParsedText = converter.Markdown2HTML(tag.OriginalText) } } if req.HTML == "" { errFields = append(errFields, &validator.FormErrorField{ ErrorField: "content", ErrorMsg: reason.QuestionContentCannotEmpty, }) } if req.AnswerHTML == "" { errFields = append(errFields, &validator.FormErrorField{ ErrorField: "answer_content", ErrorMsg: reason.AnswerContentCannotEmpty, }) } if req.HTML == "" || req.AnswerHTML == "" { return errFields, errors.BadRequest(reason.QuestionContentCannotEmpty) } return nil, nil } type QuestionPermission struct { // whether user can add it CanAdd bool `json:"-"` // whether user can edit it CanEdit bool `json:"-"` // whether user can delete it CanDelete bool `json:"-"` // whether user can close it CanClose bool `json:"-"` // whether user can reopen it CanReopen bool `json:"-"` // whether user can pin it CanPin bool `json:"-"` CanUnPin bool `json:"-"` // whether user can hide it CanHide bool `json:"-"` CanShow bool `json:"-"` // whether user can use reserved it CanUseReservedTag bool `json:"-"` // whether user can invite other user to answer this question CanInviteOtherToAnswer bool `json:"-"` CanAddTag bool `json:"-"` CanRecover bool `json:"-"` } type CheckCanQuestionUpdate struct { // question id ID string `validate:"required" form:"id"` // user id UserID string `json:"-"` IsAdmin bool `json:"-"` } type QuestionUpdate struct { // question id ID string `validate:"required" json:"id"` // question title Title string `validate:"required,notblank,gte=6,lte=150" json:"title"` // content Content string `validate:"required,notblank,gte=6,lte=65535" json:"content"` // html HTML string `json:"-"` InviteUser []string `validate:"omitempty" json:"invite_user"` // tags Tags []*TagItem `validate:"required,dive" json:"tags"` // edit summary EditSummary string `validate:"omitempty" json:"edit_summary"` // user id UserID string `json:"-"` NoNeedReview bool `json:"-"` QuestionPermission CaptchaID string `json:"captcha_id"` // captcha_id CaptchaCode string `json:"captcha_code"` } type QuestionRecoverReq struct { QuestionID string `validate:"required" json:"question_id"` UserID string `json:"-"` } type QuestionUpdateInviteUser struct { ID string `validate:"required" json:"id"` InviteUser []string `validate:"omitempty" json:"invite_user"` UserID string `json:"-"` QuestionPermission CaptchaID string `json:"captcha_id"` // captcha_id CaptchaCode string `json:"captcha_code"` } func (req *QuestionUpdate) Check() (errFields []*validator.FormErrorField, err error) { req.HTML = converter.Markdown2HTML(req.Content) if req.HTML == "" { return append(errFields, &validator.FormErrorField{ ErrorField: "content", ErrorMsg: reason.QuestionContentCannotEmpty, }), errors.BadRequest(reason.QuestionContentCannotEmpty) } return nil, nil } type QuestionBaseInfo struct { ID string `json:"id" ` Title string `json:"title"` UrlTitle string `json:"url_title"` ViewCount int `json:"view_count"` AnswerCount int `json:"answer_count"` CollectionCount int `json:"collection_count"` FollowCount int `json:"follow_count"` Status string `json:"status"` AcceptedAnswer bool `json:"accepted_answer"` } type QuestionInfoResp struct { ID string `json:"id" ` Title string `json:"title"` UrlTitle string `json:"url_title"` Content string `json:"content"` HTML string `json:"html"` Description string `json:"description"` Tags []*TagResp `json:"tags"` 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"` AcceptedAnswerID string `json:"accepted_answer_id"` LastAnswerID string `json:"last_answer_id"` CreateTime int64 `json:"create_time"` UpdateTime int64 `json:"-"` PostUpdateTime int64 `json:"update_time"` QuestionUpdateTime int64 `json:"edit_time"` Pin int `json:"pin"` Show int `json:"show"` Status int `json:"status"` Operation *Operation `json:"operation,omitempty"` UserID string `json:"-"` LastEditUserID string `json:"-"` LastAnsweredUserID string `json:"-"` UserInfo *UserBasicInfo `json:"user_info"` UpdateUserInfo *UserBasicInfo `json:"update_user_info,omitempty"` LastAnsweredUserInfo *UserBasicInfo `json:"last_answered_user_info,omitempty"` Answered bool `json:"answered"` FirstAnswerId string `json:"first_answer_id"` Collected bool `json:"collected"` VoteStatus string `json:"vote_status"` IsFollowed bool `json:"is_followed"` // MemberActions MemberActions []*PermissionMemberAction `json:"member_actions"` ExtendsActions []*PermissionMemberAction `json:"extends_actions"` } // UpdateQuestionResp update question resp type UpdateQuestionResp struct { UrlTitle string `json:"url_title"` WaitForReview bool `json:"wait_for_review"` } type AdminQuestionInfo struct { ID string `json:"id"` Title string `json:"title"` VoteCount int `json:"vote_count"` Show int `json:"show"` Pin int `json:"pin"` AnswerCount int `json:"answer_count"` AcceptedAnswerID string `json:"accepted_answer_id"` CreateTime int64 `json:"create_time"` UpdateTime int64 `json:"update_time"` EditTime int64 `json:"edit_time"` UserID string `json:"-" ` UserInfo *UserBasicInfo `json:"user_info"` } type OperationLevel string const ( OperationLevelInfo OperationLevel = "info" OperationLevelDanger OperationLevel = "danger" OperationLevelWarning OperationLevel = "warning" OperationLevelSecondary OperationLevel = "secondary" ) type Operation struct { Type string `json:"type"` Description string `json:"description"` Msg string `json:"msg"` Time int64 `json:"time"` Level OperationLevel `json:"level"` } type GetCloseTypeResp struct { // report name Name string `json:"name"` // report description Description string `json:"description"` // report source Source string `json:"source"` // report type Type int `json:"type"` // is have content HaveContent bool `json:"have_content"` // content type ContentType string `json:"content_type"` } type UserAnswerInfo struct { AnswerID string `json:"answer_id"` QuestionID string `json:"question_id"` Accepted int `json:"accepted"` VoteCount int `json:"vote_count"` CreateTime int `json:"create_time"` UpdateTime int `json:"update_time"` QuestionInfo struct { Title string `json:"title"` UrlTitle string `json:"url_title"` Tags []interface{} `json:"tags"` } `json:"question_info"` } type UserQuestionInfo struct { ID string `json:"question_id"` Title string `json:"title"` UrlTitle string `json:"url_title"` VoteCount int `json:"vote_count"` Tags []interface{} `json:"tags"` ViewCount int `json:"view_count"` AnswerCount int `json:"answer_count"` CollectionCount int `json:"collection_count"` CreatedAt int64 `json:"created_at"` AcceptedAnswerID string `json:"accepted_answer_id"` Status string `json:"status"` } const ( QuestionOrderCondNewest = "newest" QuestionOrderCondActive = "active" QuestionOrderCondHot = "hot" QuestionOrderCondScore = "score" QuestionOrderCondUnanswered = "unanswered" QuestionOrderCondRecommend = "recommend" QuestionOrderCondFrequent = "frequent" // HotInDays limit max days of the hottest question HotInDays = 90 ) // 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 hot score unanswered recommend frequent" form:"order"` Tag string `validate:"omitempty,gt=0,lte=100" form:"tag"` Username string `validate:"omitempty,gt=0,lte=100" form:"username"` InDays int `validate:"omitempty,min=1" form:"in_days"` LoginUserID string `json:"-"` UserIDBeSearched string `json:"-"` TagID string `json:"-"` ShowPending bool `json:"-"` } const ( QuestionPageRespOperationTypeAsked = "asked" QuestionPageRespOperationTypeAnswered = "answered" QuestionPageRespOperationTypeModified = "modified" ) type QuestionPageResp struct { ID string `json:"id" ` CreatedAt int64 `json:"created_at"` Title string `json:"title"` UrlTitle string `json:"url_title"` Description string `json:"description"` Pin int `json:"pin"` // 1: unpin, 2: pin Show int `json:"show"` // 0: show, 1: hide 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"` Status string `json:"status"` Avatar string `json:"avatar"` } type AdminQuestionPageReq struct { Page int `validate:"omitempty,min=1" form:"page"` PageSize int `validate:"omitempty,min=1" form:"page_size"` StatusCond string `validate:"omitempty,oneof=normal closed deleted pending" form:"status"` Query string `validate:"omitempty,gt=0,lte=100" json:"query" form:"query" ` Status int `json:"-"` LoginUserID string `json:"-"` } func (req *AdminQuestionPageReq) Check() (errField []*validator.FormErrorField, err error) { status, ok := entity.AdminQuestionSearchStatus[req.StatusCond] if ok { req.Status = status } if req.Status == 0 { req.Status = 1 } return nil, nil } // AdminAnswerPageReq admin answer page req type AdminAnswerPageReq struct { Page int `validate:"omitempty,min=1" form:"page"` PageSize int `validate:"omitempty,min=1" form:"page_size"` StatusCond string `validate:"omitempty,oneof=normal deleted pending" form:"status"` Query string `validate:"omitempty,gt=0,lte=100" form:"query"` QuestionID string `validate:"omitempty,gt=0,lte=24" form:"question_id"` QuestionTitle string `json:"-"` AnswerID string `json:"-"` Status int `json:"-"` LoginUserID string `json:"-"` } func (req *AdminAnswerPageReq) Check() (errField []*validator.FormErrorField, err error) { req.QuestionID = uid.DeShortID(req.QuestionID) if req.QuestionID == "0" { req.QuestionID = "" } if status, ok := entity.AdminAnswerSearchStatus[req.StatusCond]; ok { req.Status = status } if req.Status == 0 { req.Status = 1 } // parse query condition if len(req.Query) > 0 { prefix := "answer:" if strings.Contains(req.Query, prefix) { req.AnswerID = uid.DeShortID(strings.TrimSpace(strings.TrimPrefix(req.Query, prefix))) } else { req.QuestionTitle = strings.TrimSpace(req.Query) } } return nil, nil } type AdminUpdateQuestionStatusReq struct { QuestionID string `validate:"required" json:"question_id"` Status string `validate:"required,oneof=available closed deleted" json:"status"` UserID string `json:"-"` } type PersonalQuestionPageReq 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 hot score unanswered" form:"order"` Username string `validate:"omitempty,gt=0,lte=100" form:"username"` LoginUserID string `json:"-"` IsAdmin bool `json:"-"` } type PersonalAnswerPageReq 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 hot score unanswered" form:"order"` Username string `validate:"omitempty,gt=0,lte=100" form:"username"` LoginUserID string `json:"-"` IsAdmin bool `json:"-"` } type PersonalCollectionPageReq struct { Page int `validate:"omitempty,min=1" form:"page"` PageSize int `validate:"omitempty,min=1" form:"page_size"` UserID string `json:"-"` } type GetQuestionLinkReq struct { Page int `validate:"omitempty,min=1" form:"page"` PageSize int `validate:"omitempty,min=1,max=100" form:"page_size"` QuestionID string `validate:"required" form:"question_id"` OrderCond string `validate:"omitempty,oneof=newest active hot score unanswered recommend frequent" form:"order"` InDays int `validate:"omitempty,min=1" form:"in_days"` LoginUserID string `json:"-"` } type GetQuestionLinkResp struct { QuestionPageResp }