| /* |
| * 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 ( |
| "regexp" |
| "strings" |
| |
| "github.com/apache/answer/internal/base/constant" |
| "github.com/apache/answer/internal/base/validator" |
| "github.com/apache/answer/plugin" |
| ) |
| |
| type SearchDTO struct { |
| Query string `validate:"required,gte=1,lte=60" form:"q"` |
| Page int `validate:"omitempty,min=1" form:"page,default=1"` |
| Size int `validate:"omitempty,min=1,max=50" form:"size,default=30"` |
| Order string `validate:"required,oneof=newest active score relevance" form:"order,default=relevance" enums:"newest,active,score,relevance"` |
| CaptchaID string `form:"captcha_id"` |
| CaptchaCode string `form:"captcha_code"` |
| UserID string `json:"-"` |
| } |
| |
| func (s *SearchDTO) Check() (errField []*validator.FormErrorField, err error) { |
| // Replace special characters. |
| // Special characters will cause the search abnormal, such as search for "#" will get nearly all the content that Markdown format. |
| replacedContent, patterns := ReplaceSearchContent(s.Query) |
| s.Query = strings.Join(append(patterns, replacedContent), " ") |
| |
| return nil, nil |
| } |
| |
| func ReplaceSearchContent(content string) (string, []string) { |
| // Define the regular expressions for key:value pairs and [tag] |
| keyValueRegex := regexp.MustCompile(`\w+:\S+`) |
| tagRegex := regexp.MustCompile(`\[\w+\]`) |
| // Define the pattern for characters to replace |
| replaceCharsPattern := regexp.MustCompile(`[+#.<>\-_()*]`) |
| |
| // Extract key:value pairs |
| keyValues := keyValueRegex.FindAllString(content, -1) |
| // Extract [tag] |
| tags := tagRegex.FindAllString(content, -1) |
| |
| // Replace key:value pairs and [tag] with empty string |
| contentWithoutPatterns := keyValueRegex.ReplaceAllString(content, "") |
| contentWithoutPatterns = tagRegex.ReplaceAllString(contentWithoutPatterns, "") |
| |
| // Replace characters with pattern [+#.<>_()*] with space |
| replacedContent := replaceCharsPattern.ReplaceAllString(contentWithoutPatterns, " ") |
| |
| return strings.TrimSpace(replacedContent), append(keyValues, tags...) |
| } |
| |
| type SearchCondition struct { |
| // search target type: all/question/answer |
| TargetType string |
| // search query user id |
| UserID string |
| // vote amount |
| VoteAmount int |
| // only show not accepted answer's question |
| NotAccepted bool |
| // view amount |
| Views int |
| // answer count |
| AnswerAmount int |
| // only show accepted answer |
| Accepted bool |
| // only show this question's answer |
| QuestionID string |
| // search query tags |
| Tags [][]string |
| // search query keywords |
| Words []string |
| } |
| |
| // SearchAll check if search all |
| func (s *SearchCondition) SearchAll() bool { |
| return len(s.TargetType) == 0 |
| } |
| |
| // SearchQuestion check if search only need question |
| func (s *SearchCondition) SearchQuestion() bool { |
| return s.TargetType == constant.QuestionObjectType |
| } |
| |
| // SearchAnswer check if search only need answer |
| func (s *SearchCondition) SearchAnswer() bool { |
| return s.TargetType == constant.AnswerObjectType |
| } |
| |
| // Convert2PluginSearchCond convert to plugin search condition |
| func (s *SearchCondition) Convert2PluginSearchCond(page, pageSize int, order string) *plugin.SearchBasicCond { |
| basic := &plugin.SearchBasicCond{ |
| Page: page, |
| PageSize: pageSize, |
| Words: s.Words, |
| TagIDs: s.Tags, |
| UserID: s.UserID, |
| Order: plugin.SearchOrderCond(order), |
| QuestionID: s.QuestionID, |
| VoteAmount: s.VoteAmount, |
| ViewAmount: s.Views, |
| AnswerAmount: s.AnswerAmount, |
| } |
| if s.Accepted { |
| basic.AnswerAccepted = plugin.AcceptedCondTrue |
| } else { |
| basic.AnswerAccepted = plugin.AcceptedCondAll |
| } |
| if s.NotAccepted { |
| basic.QuestionAccepted = plugin.AcceptedCondFalse |
| } else { |
| basic.QuestionAccepted = plugin.AcceptedCondAll |
| } |
| return basic |
| } |
| |
| type SearchObject struct { |
| ID string `json:"id"` |
| QuestionID string `json:"question_id"` |
| Title string `json:"title"` |
| UrlTitle string `json:"url_title"` |
| Excerpt string `json:"excerpt"` |
| CreatedAtParsed int64 `json:"created_at"` |
| VoteCount int `json:"vote_count"` |
| Accepted bool `json:"accepted"` |
| AnswerCount int `json:"answer_count"` |
| // user info |
| UserInfo *SearchObjectUser `json:"user_info"` |
| // tags |
| Tags []*TagResp `json:"tags"` |
| // Status |
| StatusStr string `json:"status"` |
| } |
| |
| type SearchObjectUser struct { |
| ID string `json:"id"` |
| Username string `json:"username"` |
| DisplayName string `json:"display_name"` |
| Rank int `json:"rank"` |
| Status string `json:"status"` |
| } |
| |
| type TagResp struct { |
| ID string `json:"-"` |
| SlugName string `json:"slug_name"` |
| DisplayName string `json:"display_name"` |
| // if main tag slug name is not empty, this tag is synonymous with the main tag |
| MainTagSlugName string `json:"main_tag_slug_name"` |
| Recommend bool `json:"recommend"` |
| Reserved bool `json:"reserved"` |
| } |
| |
| type SearchResult struct { |
| // object_type |
| ObjectType string `json:"object_type"` |
| // this object |
| Object *SearchObject `json:"object"` |
| } |
| |
| type SearchResp struct { |
| Total int64 `json:"count"` |
| // search response |
| SearchResults []*SearchResult `json:"list"` |
| } |
| |
| type SearchDescResp struct { |
| Name string `json:"name"` |
| Icon string `json:"icon"` |
| Link string `json:"link"` |
| } |