chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,507 @@
|
||||
/*
|
||||
* 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 tag
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/apache/answer/internal/base/constant"
|
||||
"github.com/apache/answer/internal/service/activityqueue"
|
||||
"github.com/apache/answer/internal/service/revision_common"
|
||||
"github.com/apache/answer/internal/service/siteinfo_common"
|
||||
tagcommonser "github.com/apache/answer/internal/service/tag_common"
|
||||
"github.com/apache/answer/pkg/htmltext"
|
||||
"github.com/jinzhu/copier"
|
||||
|
||||
"github.com/apache/answer/internal/base/pager"
|
||||
"github.com/apache/answer/internal/base/reason"
|
||||
"github.com/apache/answer/internal/entity"
|
||||
"github.com/apache/answer/internal/schema"
|
||||
"github.com/apache/answer/internal/service/activity_common"
|
||||
"github.com/apache/answer/internal/service/permission"
|
||||
"github.com/apache/answer/pkg/converter"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
)
|
||||
|
||||
// TagService user service
|
||||
type TagService struct {
|
||||
tagRepo tagcommonser.TagRepo
|
||||
tagCommonService *tagcommonser.TagCommonService
|
||||
revisionService *revision_common.RevisionService
|
||||
followCommon activity_common.FollowRepo
|
||||
siteInfoService siteinfo_common.SiteInfoCommonService
|
||||
activityQueueService activityqueue.Service
|
||||
}
|
||||
|
||||
// NewTagService new tag service
|
||||
func NewTagService(
|
||||
tagRepo tagcommonser.TagRepo,
|
||||
tagCommonService *tagcommonser.TagCommonService,
|
||||
revisionService *revision_common.RevisionService,
|
||||
followCommon activity_common.FollowRepo,
|
||||
siteInfoService siteinfo_common.SiteInfoCommonService,
|
||||
activityQueueService activityqueue.Service,
|
||||
) *TagService {
|
||||
return &TagService{
|
||||
tagRepo: tagRepo,
|
||||
tagCommonService: tagCommonService,
|
||||
revisionService: revisionService,
|
||||
followCommon: followCommon,
|
||||
siteInfoService: siteInfoService,
|
||||
activityQueueService: activityQueueService,
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveTag delete tag
|
||||
func (ts *TagService) RemoveTag(ctx context.Context, req *schema.RemoveTagReq) (err error) {
|
||||
// If the tag has associated problems, it cannot be deleted
|
||||
tagCount, err := ts.tagCommonService.CountTagRelByTagID(ctx, req.TagID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tagCount > 0 {
|
||||
return errors.BadRequest(reason.TagIsUsedCannotDelete)
|
||||
}
|
||||
|
||||
// If the tag has associated problems, it cannot be deleted
|
||||
tagSynonymCount, err := ts.tagRepo.GetTagSynonymCount(ctx, req.TagID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tagSynonymCount > 0 {
|
||||
return errors.BadRequest(reason.TagIsUsedCannotDelete)
|
||||
}
|
||||
|
||||
// tagRelRepo
|
||||
err = ts.tagRepo.RemoveTag(ctx, req.TagID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ts.activityQueueService.Send(ctx, &schema.ActivityMsg{
|
||||
UserID: req.UserID,
|
||||
ObjectID: req.TagID,
|
||||
OriginalObjectID: req.TagID,
|
||||
ActivityTypeKey: constant.ActTagDeleted,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateTag update tag
|
||||
func (ts *TagService) UpdateTag(ctx context.Context, req *schema.UpdateTagReq) (err error) {
|
||||
return ts.tagCommonService.UpdateTag(ctx, req)
|
||||
}
|
||||
|
||||
// RecoverTag recover tag
|
||||
func (ts *TagService) RecoverTag(ctx context.Context, req *schema.RecoverTagReq) (err error) {
|
||||
tagInfo, exist, err := ts.tagRepo.MustGetTagByNameOrID(ctx, req.TagID, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exist {
|
||||
return errors.BadRequest(reason.TagNotFound)
|
||||
}
|
||||
if tagInfo.Status != entity.TagStatusDeleted {
|
||||
return nil
|
||||
}
|
||||
|
||||
err = ts.tagRepo.RecoverTag(ctx, req.TagID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ts.activityQueueService.Send(ctx, &schema.ActivityMsg{
|
||||
UserID: req.UserID,
|
||||
TriggerUserID: converter.StringToInt64(req.UserID),
|
||||
ObjectID: req.TagID,
|
||||
OriginalObjectID: req.TagID,
|
||||
ActivityTypeKey: constant.ActTagUndeleted,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTagInfo get tag one
|
||||
func (ts *TagService) GetTagInfo(ctx context.Context, req *schema.GetTagInfoReq) (resp *schema.GetTagResp, err error) {
|
||||
var (
|
||||
tagInfo *entity.Tag
|
||||
exist bool
|
||||
)
|
||||
if len(req.ID) > 0 {
|
||||
tagInfo, exist, err = ts.tagCommonService.GetTagByID(ctx, req.ID)
|
||||
} else {
|
||||
tagInfo, exist, err = ts.tagCommonService.GetTagBySlugName(ctx, req.Name)
|
||||
}
|
||||
// If user can recover deleted tag, try to search in all tags including deleted tags
|
||||
if !exist && req.CanRecover {
|
||||
tagInfo, exist, err = ts.tagRepo.MustGetTagByNameOrID(ctx, req.ID, req.Name)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exist {
|
||||
return nil, errors.NotFound(reason.TagNotFound)
|
||||
}
|
||||
|
||||
resp = &schema.GetTagResp{}
|
||||
// if tag is synonyms get original tag info
|
||||
if tagInfo.MainTagID > 0 {
|
||||
tagInfo, exist, err = ts.tagCommonService.GetTagByID(ctx, converter.IntToString(tagInfo.MainTagID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exist {
|
||||
return nil, errors.NotFound(reason.TagNotFound)
|
||||
}
|
||||
resp.MainTagSlugName = tagInfo.SlugName
|
||||
}
|
||||
resp.TagID = tagInfo.ID
|
||||
resp.CreatedAt = tagInfo.CreatedAt.Unix()
|
||||
resp.UpdatedAt = tagInfo.UpdatedAt.Unix()
|
||||
resp.SlugName = tagInfo.SlugName
|
||||
resp.DisplayName = tagInfo.DisplayName
|
||||
resp.OriginalText = tagInfo.OriginalText
|
||||
resp.ParsedText = tagInfo.ParsedText
|
||||
resp.Description = htmltext.FetchExcerpt(tagInfo.ParsedText, "...", 240)
|
||||
resp.FollowCount = tagInfo.FollowCount
|
||||
resp.QuestionCount = tagInfo.QuestionCount
|
||||
resp.Recommend = tagInfo.Recommend
|
||||
resp.Reserved = tagInfo.Reserved
|
||||
resp.IsFollower = ts.checkTagIsFollow(ctx, req.UserID, tagInfo.ID)
|
||||
resp.Status = entity.TagStatusDisplayMapping[tagInfo.Status]
|
||||
resp.MemberActions = permission.GetTagPermission(ctx, tagInfo.Status, req.CanEdit, req.CanDelete, req.CanMerge, req.CanRecover)
|
||||
resp.GetExcerpt()
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetTagsBySlugName get tags by slug name
|
||||
func (ts *TagService) GetTagsBySlugName(ctx context.Context, req *schema.SearchTagsBySlugName) (
|
||||
resp []*schema.GetTagBasicResp, err error) {
|
||||
resp = make([]*schema.GetTagBasicResp, 0)
|
||||
tagSlugNames := strings.Split(req.Tags, ",")
|
||||
if len(tagSlugNames) == 0 {
|
||||
return resp, nil
|
||||
}
|
||||
tagList, err := ts.tagCommonService.GetTagListByNames(ctx, tagSlugNames)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
for _, tag := range tagList {
|
||||
tagItem := &schema.GetTagBasicResp{}
|
||||
_ = copier.Copy(tagItem, tag)
|
||||
resp = append(resp, tagItem)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetFollowingTags get following tags
|
||||
func (ts *TagService) GetFollowingTags(ctx context.Context, userID string) (
|
||||
resp []*schema.GetFollowingTagsResp, err error) {
|
||||
resp = make([]*schema.GetFollowingTagsResp, 0)
|
||||
if len(userID) == 0 {
|
||||
return resp, nil
|
||||
}
|
||||
objIDs, err := ts.followCommon.GetFollowIDs(ctx, userID, entity.Tag{}.TableName())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tagList, err := ts.tagCommonService.GetTagListByIDs(ctx, objIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, t := range tagList {
|
||||
tagInfo := &schema.GetFollowingTagsResp{
|
||||
TagID: t.ID,
|
||||
SlugName: t.SlugName,
|
||||
DisplayName: t.DisplayName,
|
||||
Recommend: t.Recommend,
|
||||
Reserved: t.Reserved,
|
||||
}
|
||||
if t.MainTagID > 0 {
|
||||
mainTag, exist, err := ts.tagCommonService.GetTagByID(ctx, converter.IntToString(t.MainTagID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if exist {
|
||||
tagInfo.MainTagSlugName = mainTag.SlugName
|
||||
}
|
||||
}
|
||||
resp = append(resp, tagInfo)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetTagSynonyms get tag synonyms
|
||||
func (ts *TagService) GetTagSynonyms(ctx context.Context, req *schema.GetTagSynonymsReq) (
|
||||
resp *schema.GetTagSynonymsResp, err error) {
|
||||
resp = &schema.GetTagSynonymsResp{Synonyms: make([]*schema.TagSynonym, 0)}
|
||||
tag, exist, err := ts.tagCommonService.GetTagByID(ctx, req.TagID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !exist {
|
||||
return nil, errors.BadRequest(reason.TagNotFound)
|
||||
}
|
||||
|
||||
var tagList []*entity.Tag
|
||||
var mainTagSlugName string
|
||||
if tag.MainTagID > 0 {
|
||||
tagList, err = ts.tagRepo.GetTagList(ctx, &entity.Tag{MainTagID: tag.MainTagID})
|
||||
} else {
|
||||
tagList, err = ts.tagRepo.GetTagList(ctx, &entity.Tag{MainTagID: converter.StringToInt64(tag.ID)})
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// get main tag slug name
|
||||
if tag.MainTagID > 0 {
|
||||
for _, tagInfo := range tagList {
|
||||
if tag.MainTagID == 0 {
|
||||
mainTagSlugName = tagInfo.SlugName
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mainTagSlugName = tag.SlugName
|
||||
}
|
||||
|
||||
for _, t := range tagList {
|
||||
resp.Synonyms = append(resp.Synonyms, &schema.TagSynonym{
|
||||
TagID: t.ID,
|
||||
SlugName: t.SlugName,
|
||||
DisplayName: t.DisplayName,
|
||||
MainTagSlugName: mainTagSlugName,
|
||||
})
|
||||
}
|
||||
resp.MemberActions = permission.GetTagSynonymPermission(ctx, req.CanEdit)
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateTagSynonym add tag synonym
|
||||
func (ts *TagService) UpdateTagSynonym(ctx context.Context, req *schema.UpdateTagSynonymReq) (err error) {
|
||||
// format tag slug name
|
||||
req.Format()
|
||||
addSynonymTagList := make([]string, 0)
|
||||
removeSynonymTagList := make([]string, 0)
|
||||
mainTagInfo, exist, err := ts.tagCommonService.GetTagByID(ctx, req.TagID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exist {
|
||||
return errors.BadRequest(reason.TagNotFound)
|
||||
}
|
||||
|
||||
// find all exist tag
|
||||
for _, item := range req.SynonymTagList {
|
||||
if item.SlugName == mainTagInfo.SlugName {
|
||||
return errors.BadRequest(reason.TagCannotSetSynonymAsItself)
|
||||
}
|
||||
addSynonymTagList = append(addSynonymTagList, item.SlugName)
|
||||
}
|
||||
tagListInDB, err := ts.tagCommonService.GetTagListByNames(ctx, addSynonymTagList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
existTagMapping := make(map[string]*entity.Tag, 0)
|
||||
for _, tag := range tagListInDB {
|
||||
existTagMapping[tag.SlugName] = tag
|
||||
}
|
||||
|
||||
// add tag list
|
||||
needAddTagList := make([]*entity.Tag, 0)
|
||||
for _, tag := range req.SynonymTagList {
|
||||
if existTagMapping[tag.SlugName] != nil {
|
||||
continue
|
||||
}
|
||||
item := &entity.Tag{}
|
||||
item.SlugName = tag.SlugName
|
||||
item.DisplayName = tag.DisplayName
|
||||
item.OriginalText = tag.OriginalText
|
||||
item.ParsedText = tag.ParsedText
|
||||
item.Status = entity.TagStatusAvailable
|
||||
item.UserID = req.UserID
|
||||
needAddTagList = append(needAddTagList, item)
|
||||
}
|
||||
|
||||
if len(needAddTagList) > 0 {
|
||||
err = ts.tagCommonService.AddTagList(ctx, needAddTagList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// update tag revision
|
||||
for _, tag := range needAddTagList {
|
||||
existTagMapping[tag.SlugName] = tag
|
||||
revisionDTO := &schema.AddRevisionDTO{
|
||||
UserID: req.UserID,
|
||||
ObjectID: tag.ID,
|
||||
Title: tag.SlugName,
|
||||
}
|
||||
tagInfoJson, _ := json.Marshal(tag)
|
||||
revisionDTO.Content = string(tagInfoJson)
|
||||
revisionID, err := ts.revisionService.AddRevision(ctx, revisionDTO, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ts.activityQueueService.Send(ctx, &schema.ActivityMsg{
|
||||
UserID: req.UserID,
|
||||
ObjectID: tag.ID,
|
||||
OriginalObjectID: tag.ID,
|
||||
ActivityTypeKey: constant.ActTagCreated,
|
||||
RevisionID: revisionID,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// get all old synonyms list
|
||||
oldSynonymList, err := ts.tagRepo.GetTagList(ctx, &entity.Tag{MainTagID: converter.StringToInt64(mainTagInfo.ID)})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, oldSynonym := range oldSynonymList {
|
||||
if existTagMapping[oldSynonym.SlugName] == nil {
|
||||
removeSynonymTagList = append(removeSynonymTagList, oldSynonym.SlugName)
|
||||
}
|
||||
}
|
||||
|
||||
// remove old synonyms
|
||||
if len(removeSynonymTagList) > 0 {
|
||||
err = ts.tagRepo.UpdateTagSynonym(ctx, removeSynonymTagList, 0, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// update new synonyms
|
||||
if len(addSynonymTagList) > 0 {
|
||||
err = ts.tagRepo.UpdateTagSynonym(ctx, addSynonymTagList, converter.StringToInt64(req.TagID), mainTagInfo.SlugName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTagWithPage get tag list page
|
||||
func (ts *TagService) GetTagWithPage(ctx context.Context, req *schema.GetTagWithPageReq) (pageModel *pager.PageModel, err error) {
|
||||
tag := &entity.Tag{}
|
||||
_ = copier.Copy(tag, req)
|
||||
tag.UserID = ""
|
||||
|
||||
page := req.Page
|
||||
pageSize := req.PageSize
|
||||
|
||||
tags, total, err := ts.tagCommonService.GetTagPage(ctx, page, pageSize, tag, req.QueryCond)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
resp := make([]*schema.GetTagPageResp, 0)
|
||||
for _, tag := range tags {
|
||||
item := &schema.GetTagPageResp{
|
||||
TagID: tag.ID,
|
||||
SlugName: tag.SlugName,
|
||||
Description: htmltext.FetchExcerpt(tag.ParsedText, "...", 240),
|
||||
DisplayName: tag.DisplayName,
|
||||
OriginalText: tag.OriginalText,
|
||||
ParsedText: tag.ParsedText,
|
||||
FollowCount: tag.FollowCount,
|
||||
QuestionCount: tag.QuestionCount,
|
||||
IsFollower: ts.checkTagIsFollow(ctx, req.UserID, tag.ID),
|
||||
CreatedAt: tag.CreatedAt.Unix(),
|
||||
UpdatedAt: tag.UpdatedAt.Unix(),
|
||||
Recommend: tag.Recommend,
|
||||
Reserved: tag.Reserved,
|
||||
}
|
||||
item.GetExcerpt()
|
||||
resp = append(resp, item)
|
||||
}
|
||||
return pager.NewPageModel(total, resp), nil
|
||||
}
|
||||
|
||||
// MergeTag merge tag
|
||||
func (ts *TagService) MergeTag(ctx context.Context, req *schema.MergeTagReq) (err error) {
|
||||
// 1. get source tag and its synonyms
|
||||
sourceTag, exist, err := ts.tagCommonService.GetTagByID(ctx, req.SourceTagID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exist {
|
||||
return errors.BadRequest(reason.TagNotFound)
|
||||
}
|
||||
|
||||
sourceTagSynonyms, err := ts.tagRepo.GetTagList(ctx, &entity.Tag{MainTagID: converter.StringToInt64(sourceTag.ID)})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
addSynonymTagList := make([]string, 0)
|
||||
addSynonymTagList = append(addSynonymTagList, sourceTag.SlugName)
|
||||
for _, tag := range sourceTagSynonyms {
|
||||
addSynonymTagList = append(addSynonymTagList, tag.SlugName)
|
||||
}
|
||||
|
||||
// 2. get target tag
|
||||
targetTagInfo, exist, err := ts.tagCommonService.GetTagByID(ctx, req.TargetTagID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exist {
|
||||
return errors.BadRequest(reason.TagNotFound)
|
||||
}
|
||||
|
||||
// 3. update source tag and its synonyms as synonyms of target tag
|
||||
if len(addSynonymTagList) > 0 {
|
||||
err = ts.tagRepo.UpdateTagSynonym(ctx, addSynonymTagList, converter.StringToInt64(targetTagInfo.ID), targetTagInfo.SlugName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 4. update tag followers
|
||||
err = ts.followCommon.MigrateFollowers(ctx, sourceTag.ID, targetTagInfo.ID, "follow")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 5. update question tags
|
||||
err = ts.tagCommonService.MigrateTagQuestions(ctx, sourceTag.ID, targetTagInfo.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ts.tagCommonService.RefreshTagQuestionCount(ctx, []string{targetTagInfo.ID, sourceTag.ID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkTagIsFollow get tag list page
|
||||
func (ts *TagService) checkTagIsFollow(ctx context.Context, userID, tagID string) bool {
|
||||
if len(userID) == 0 {
|
||||
return false
|
||||
}
|
||||
followed, err := ts.followCommon.IsFollowed(ctx, userID, tagID)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
return followed
|
||||
}
|
||||
Reference in New Issue
Block a user