diff --git a/cmd/answer/wire_gen.go b/cmd/answer/wire_gen.go index 7634a817..396329cd 100644 --- a/cmd/answer/wire_gen.go +++ b/cmd/answer/wire_gen.go @@ -130,7 +130,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database, revisionRepo := revision.NewRevisionRepo(dataData, uniqueIDRepo) revisionService := revision_common.NewRevisionService(revisionRepo, userRepo) followRepo := activity_common.NewFollowRepo(dataData, uniqueIDRepo, activityRepo) - tagService := tag2.NewTagService(tagRepo, revisionService, followRepo) + tagService := tag2.NewTagService(tagRepo, revisionService, followRepo, siteInfoCommonService) tagController := controller.NewTagController(tagService, rankService) followFollowRepo := activity.NewFollowRepo(dataData, uniqueIDRepo, activityRepo) followService := follow.NewFollowService(followFollowRepo, followRepo, tagRepo) @@ -138,7 +138,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database, collectionRepo := collection.NewCollectionRepo(dataData, uniqueIDRepo) collectionGroupRepo := collection.NewCollectionGroupRepo(dataData) tagRelRepo := tag.NewTagRelRepo(dataData) - tagCommonService := tagcommon.NewTagCommonService(tagRepo, tagRelRepo, revisionService) + tagCommonService := tagcommon.NewTagCommonService(tagRepo, tagRelRepo, revisionService, siteInfoCommonService) collectionCommon := collectioncommon.NewCollectionCommon(collectionRepo) answerCommon := answercommon.NewAnswerCommon(answerRepo) metaRepo := meta.NewMetaRepo(dataData) diff --git a/docs/swagger.json b/docs/swagger.json index 684666ae..92f25519 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -6422,4 +6422,4 @@ "in": "header" } } -} \ No newline at end of file +} diff --git a/i18n/en_US.yaml b/i18n/en_US.yaml index f3fe2602..c89a01ce 100644 --- a/i18n/en_US.yaml +++ b/i18n/en_US.yaml @@ -74,6 +74,8 @@ backend: tag: not_found: other: "Tag not found." + recommend_tag_not_found: + other: "Recommend Tag is not exist." theme: not_found: other: "Theme not found." diff --git a/internal/base/constant/constant.go b/internal/base/constant/constant.go index 54a95c86..c36a0f7f 100644 --- a/internal/base/constant/constant.go +++ b/internal/base/constant/constant.go @@ -57,3 +57,4 @@ const ( SiteTypeWrite = "write" SiteTypeLegal = "legal" ) + diff --git a/internal/base/reason/reason.go b/internal/base/reason/reason.go index 6594df2a..ce9c1f2c 100644 --- a/internal/base/reason/reason.go +++ b/internal/base/reason/reason.go @@ -44,4 +44,5 @@ const ( InstallConfigFailed = "error.install.create_config_failed" SiteInfoNotFound = "error.site_info.not_found" UploadFileSourceUnsupported = "error.upload.source_unsupported" + RecommendTagNotExist = "error.tag.recommend_tag_not_found" ) diff --git a/internal/controller/tag_controller.go b/internal/controller/tag_controller.go index 601c2c6d..034bf6e4 100644 --- a/internal/controller/tag_controller.go +++ b/internal/controller/tag_controller.go @@ -36,7 +36,8 @@ func (tc *TagController) SearchTagLike(ctx *gin.Context) { if handler.BindAndCheck(ctx, req) { return } - + userinfo := middleware.GetUserInfoFromContext(ctx) + req.IsAdmin = userinfo.IsAdmin resp, err := tc.tagService.SearchTagLike(ctx, req) handler.HandleResponse(ctx, err, resp) } diff --git a/internal/entity/auth_user_entity.go b/internal/entity/auth_user_entity.go index d1f366a4..f71d79df 100644 --- a/internal/entity/auth_user_entity.go +++ b/internal/entity/auth_user_entity.go @@ -5,4 +5,5 @@ type UserCacheInfo struct { UserID string `json:"user_id"` UserStatus int `json:"user_status"` EmailStatus int `json:"email_status"` + IsAdmin bool `json:"is_admin"` } diff --git a/internal/entity/tag_entity.go b/internal/entity/tag_entity.go index 2bef34d0..4d934e2d 100644 --- a/internal/entity/tag_entity.go +++ b/internal/entity/tag_entity.go @@ -21,6 +21,8 @@ type Tag struct { FollowCount int `xorm:"not null default 0 INT(11) follow_count"` QuestionCount int `xorm:"not null default 0 INT(11) question_count"` Status int `xorm:"not null default 1 INT(11) status"` + Recommend bool `xorm:"not null default false BOOL recommend"` + Reserved bool `xorm:"not null default false BOOL reserved"` RevisionID string `xorm:"not null default 0 BIGINT(20) revision_id"` } diff --git a/internal/repo/tag/tag_repo.go b/internal/repo/tag/tag_repo.go index 44154593..e6251f0f 100644 --- a/internal/repo/tag/tag_repo.go +++ b/internal/repo/tag/tag_repo.go @@ -71,12 +71,54 @@ func (tr *tagRepo) GetTagBySlugName(ctx context.Context, slugName string) (tagIn } // GetTagListByName get tag list all like name -func (tr *tagRepo) GetTagListByName(ctx context.Context, name string, limit int) (tagList []*entity.Tag, err error) { +func (tr *tagRepo) GetTagListByName(ctx context.Context, name string, limit int, hasReserved bool) (tagList []*entity.Tag, err error) { tagList = make([]*entity.Tag, 0) - session := tr.data.DB.Where("slug_name LIKE ?", name+"%") + cond := &entity.Tag{} + session := tr.data.DB.Where("") + if name != "" { + session.Where("slug_name LIKE ?", name+"%") + } else { + cond.Recommend = true + } session.Where(builder.Eq{"status": entity.TagStatusAvailable}) session.Limit(limit).Asc("slug_name") - err = session.Find(&tagList) + if !hasReserved { + cond.Recommend = false + session.UseBool("recommend", "reserved") + } else { + session.UseBool("recommend") + } + err = session.Find(&tagList, cond) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +func (tr *tagRepo) GetRecommendTagList(ctx context.Context) (tagList []*entity.Tag, err error) { + tagList = make([]*entity.Tag, 0) + cond := &entity.Tag{} + session := tr.data.DB.Where("") + cond.Recommend = true + session.Where(builder.Eq{"status": entity.TagStatusAvailable}) + session.Asc("slug_name") + session.UseBool("recommend") + err = session.Find(&tagList, cond) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +func (tr *tagRepo) GetReservedTagList(ctx context.Context) (tagList []*entity.Tag, err error) { + tagList = make([]*entity.Tag, 0) + cond := &entity.Tag{} + session := tr.data.DB.Where("") + cond.Reserved = true + session.Where(builder.Eq{"status": entity.TagStatusAvailable}) + session.Asc("slug_name") + session.UseBool("reserved") + err = session.Find(&tagList, cond) if err != nil { err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() } @@ -137,6 +179,24 @@ func (tr *tagRepo) UpdateTagSynonym(ctx context.Context, tagSlugNameList []strin return } +func (tr *tagRepo) UpdateTagsAttribute(ctx context.Context, tags []string, attribute string, value bool) (err error) { + bean := &entity.Tag{} + switch attribute { + case "recommend": + bean.Recommend = value + case "reserved": + bean.Reserved = value + default: + return + } + session := tr.data.DB.In("slug_name", tags).Cols(attribute).UseBool(attribute) + _, err = session.Update(bean) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + // GetTagByID get tag one func (tr *tagRepo) GetTagByID(ctx context.Context, tagID string) ( tag *entity.Tag, exist bool, err error, diff --git a/internal/repo/user/user_backyard_repo.go b/internal/repo/user/user_backyard_repo.go index ad20794d..71ae7fca 100644 --- a/internal/repo/user/user_backyard_repo.go +++ b/internal/repo/user/user_backyard_repo.go @@ -7,6 +7,7 @@ import ( "strings" "time" "unicode" + "xorm.io/builder" "github.com/answerdev/answer/internal/base/data" diff --git a/internal/schema/search_schema.go b/internal/schema/search_schema.go index 5cf8c577..143a93c1 100644 --- a/internal/schema/search_schema.go +++ b/internal/schema/search_schema.go @@ -29,6 +29,7 @@ type TagResp struct { 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"` } type SearchResp struct { diff --git a/internal/schema/tag_schema.go b/internal/schema/tag_schema.go index d6b70c39..f22b89b3 100644 --- a/internal/schema/tag_schema.go +++ b/internal/schema/tag_schema.go @@ -11,7 +11,8 @@ import ( // SearchTagLikeReq get tag list all request type SearchTagLikeReq struct { // tag - Tag string `validate:"required,gt=0,lte=35" form:"tag"` + Tag string `validate:"omitempty" form:"tag"` + IsAdmin bool `json:"-"` } // GetTagInfoReq get tag info request @@ -218,3 +219,9 @@ type GetFollowingTagsResp struct { // if main tag slug name is not empty, this tag is synonymous with the main tag MainTagSlugName string `json:"main_tag_slug_name"` } + +type SearchTagLikeResp struct { + SlugName string `json:"slug_name"` + Recommend bool `json:"recommend"` + Reserved bool `json:"reserved"` +} diff --git a/internal/service/auth/auth.go b/internal/service/auth/auth.go index 95604a14..0afffc28 100644 --- a/internal/service/auth/auth.go +++ b/internal/service/auth/auth.go @@ -43,6 +43,7 @@ func (as *AuthService) GetUserCacheInfo(ctx context.Context, accessToken string) log.Infof("user status updated: %+v", cacheInfo) userCacheInfo.UserStatus = cacheInfo.UserStatus userCacheInfo.EmailStatus = cacheInfo.EmailStatus + userCacheInfo.IsAdmin = cacheInfo.IsAdmin // update current user cache info err := as.authRepo.SetUserCacheInfo(ctx, accessToken, userCacheInfo) if err != nil { diff --git a/internal/service/question_service.go b/internal/service/question_service.go index 72f885b1..85bf7673 100644 --- a/internal/service/question_service.go +++ b/internal/service/question_service.go @@ -105,6 +105,16 @@ func (qs *QuestionService) CloseMsgList(ctx context.Context, lang i18n.Language) // AddQuestion add question func (qs *QuestionService) AddQuestion(ctx context.Context, req *schema.QuestionAdd) (questionInfo *schema.QuestionInfo, err error) { + recommendExist, err := qs.tagCommon.ExistRecommend(ctx, req.Tags) + if err != nil { + return + } + if !recommendExist { + err = fmt.Errorf("recommend is not exist") + err = errors.BadRequest(reason.RecommendTagNotExist).WithError(err).WithStack() + return + } + questionInfo = &schema.QuestionInfo{} question := &entity.Question{} now := time.Now() diff --git a/internal/service/tag/tag_service.go b/internal/service/tag/tag_service.go index 6384eb82..aa012be3 100644 --- a/internal/service/tag/tag_service.go +++ b/internal/service/tag/tag_service.go @@ -3,7 +3,9 @@ package tag import ( "context" "encoding/json" + "github.com/answerdev/answer/internal/service/revision_common" + "github.com/answerdev/answer/internal/service/siteinfo_common" "github.com/answerdev/answer/pkg/htmltext" "github.com/answerdev/answer/internal/base/pager" @@ -24,28 +26,35 @@ type TagService struct { tagRepo tagcommon.TagRepo revisionService *revision_common.RevisionService followCommon activity_common.FollowRepo + siteInfoService *siteinfo_common.SiteInfoCommonService } // NewTagService new tag service func NewTagService( tagRepo tagcommon.TagRepo, revisionService *revision_common.RevisionService, - followCommon activity_common.FollowRepo) *TagService { + followCommon activity_common.FollowRepo, + siteInfoService *siteinfo_common.SiteInfoCommonService) *TagService { return &TagService{ tagRepo: tagRepo, revisionService: revisionService, followCommon: followCommon, + siteInfoService: siteInfoService, } } // SearchTagLike get tag list all -func (ts *TagService) SearchTagLike(ctx context.Context, req *schema.SearchTagLikeReq) (resp []string, err error) { - tags, err := ts.tagRepo.GetTagListByName(ctx, req.Tag, 5) +func (ts *TagService) SearchTagLike(ctx context.Context, req *schema.SearchTagLikeReq) (resp []schema.SearchTagLikeResp, err error) { + tags, err := ts.tagRepo.GetTagListByName(ctx, req.Tag, 5, req.IsAdmin) if err != nil { return } for _, tag := range tags { - resp = append(resp, tag.SlugName) + item := schema.SearchTagLikeResp{} + item.SlugName = tag.SlugName + item.Recommend = tag.Recommend + item.Reserved = tag.Reserved + resp = append(resp, item) } return resp, nil } diff --git a/internal/service/tag_common/tag_common.go b/internal/service/tag_common/tag_common.go index 8e91da62..b50fbcf0 100644 --- a/internal/service/tag_common/tag_common.go +++ b/internal/service/tag_common/tag_common.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/answerdev/answer/internal/service/revision_common" + "github.com/answerdev/answer/internal/service/siteinfo_common" "github.com/answerdev/answer/internal/entity" "github.com/answerdev/answer/internal/schema" @@ -16,7 +17,7 @@ type TagRepo interface { AddTagList(ctx context.Context, tagList []*entity.Tag) (err error) GetTagListByIDs(ctx context.Context, ids []string) (tagList []*entity.Tag, err error) GetTagBySlugName(ctx context.Context, slugName string) (tagInfo *entity.Tag, exist bool, err error) - GetTagListByName(ctx context.Context, name string, limit int) (tagList []*entity.Tag, err error) + GetTagListByName(ctx context.Context, name string, limit int, hasReserved bool) (tagList []*entity.Tag, err error) GetTagListByNames(ctx context.Context, names []string) (tagList []*entity.Tag, err error) RemoveTag(ctx context.Context, tagID string) (err error) UpdateTag(ctx context.Context, tag *entity.Tag) (err error) @@ -25,6 +26,9 @@ type TagRepo interface { GetTagByID(ctx context.Context, tagID string) (tag *entity.Tag, exist bool, err error) GetTagList(ctx context.Context, tag *entity.Tag) (tagList []*entity.Tag, err error) GetTagPage(ctx context.Context, page, pageSize int, tag *entity.Tag, queryCond string) (tagList []*entity.Tag, total int64, err error) + GetRecommendTagList(ctx context.Context) (tagList []*entity.Tag, err error) + GetReservedTagList(ctx context.Context) (tagList []*entity.Tag, err error) + UpdateTagsAttribute(ctx context.Context, tags []string, attribute string, value bool) (err error) } type TagRelRepo interface { @@ -42,19 +46,86 @@ type TagCommonService struct { revisionService *revision_common.RevisionService tagRepo TagRepo tagRelRepo TagRelRepo + siteInfoService *siteinfo_common.SiteInfoCommonService } // NewTagCommonService new tag service func NewTagCommonService(tagRepo TagRepo, tagRelRepo TagRelRepo, revisionService *revision_common.RevisionService, + siteInfoService *siteinfo_common.SiteInfoCommonService, ) *TagCommonService { return &TagCommonService{ tagRepo: tagRepo, tagRelRepo: tagRelRepo, revisionService: revisionService, + siteInfoService: siteInfoService, } } +func (ts *TagCommonService) GetSiteWriteRecommendTag(ctx context.Context) (tags []string, err error) { + tags = make([]string, 0) + list, err := ts.tagRepo.GetRecommendTagList(ctx) + for _, item := range list { + tags = append(tags, item.SlugName) + } + return tags, nil +} + +func (ts *TagCommonService) SetSiteWriteRecommendTag(ctx context.Context, tags []string, required bool, userID string) (err error) { + err = ts.UpdateTag(ctx, tags, userID) + if err != nil { + return err + } + err = ts.SetTagsAttribute(ctx, tags, "recommend", true) + if err != nil { + return err + } + return nil +} + +func (ts *TagCommonService) GetSiteWriteReservedTag(ctx context.Context) (tags []string, err error) { + tags = make([]string, 0) + list, err := ts.tagRepo.GetReservedTagList(ctx) + for _, item := range list { + tags = append(tags, item.SlugName) + } + return tags, nil +} + +func (ts *TagCommonService) SetSiteWriteReservedTag(ctx context.Context, tags []string, userID string) (err error) { + err = ts.UpdateTag(ctx, tags, userID) + if err != nil { + return err + } + err = ts.SetTagsAttribute(ctx, tags, "reserved", true) + if err != nil { + return err + } + return nil +} + +// SetTagsAttribute +func (ts *TagCommonService) SetTagsAttribute(ctx context.Context, tags []string, attribute string, value bool) (err error) { + var tagslist []string + switch attribute { + case "recommend": + tagslist, err = ts.GetSiteWriteRecommendTag(ctx) + case "reserved": + tagslist, err = ts.GetSiteWriteReservedTag(ctx) + default: + return + } + err = ts.tagRepo.UpdateTagsAttribute(ctx, tagslist, attribute, false) + if err != nil { + return err + } + err = ts.tagRepo.UpdateTagsAttribute(ctx, tags, attribute, value) + if err != nil { + return err + } + return nil +} + // GetTagListByName func (ts *TagCommonService) GetTagListByName(ctx context.Context, tagName string) (tagInfo *entity.Tag, exist bool, err error) { tagName = strings.ToLower(tagName) @@ -68,6 +139,23 @@ func (ts *TagCommonService) GetTagListByNames(ctx context.Context, tagNames []st return ts.tagRepo.GetTagListByNames(ctx, tagNames) } +func (ts *TagCommonService) ExistRecommend(ctx context.Context, tags []*schema.TagItem) (bool, error) { + tagNames := make([]string, 0) + for _, item := range tags { + tagNames = append(tagNames, item.SlugName) + } + list, err := ts.GetTagListByNames(ctx, tagNames) + if err != nil { + return false, err + } + for _, item := range list { + if item.Recommend { + return true, nil + } + } + return false, nil +} + // // GetObjectTag get object tag @@ -90,6 +178,7 @@ func (ts *TagCommonService) GetObjectTag(ctx context.Context, objectId string) ( SlugName: tagInfo.SlugName, DisplayName: tagInfo.DisplayName, MainTagSlugName: tagInfo.MainTagSlugName, + Recommend: tagInfo.Recommend, }) } return objTags, nil @@ -130,6 +219,69 @@ func (ts *TagCommonService) BatchGetObjectTag(ctx context.Context, objectIds []s return objectIDTagMap, nil } +func (ts *TagCommonService) UpdateTag(ctx context.Context, tags []string, userID string) (err error) { + if len(tags) == 0 { + return nil + } + + thisTagNameList := make([]string, 0) + thisTagIDList := make([]string, 0) + for _, t := range tags { + t = strings.ToLower(t) + thisTagNameList = append(thisTagNameList, t) + } + + // find tags name + tagListInDb, err := ts.tagRepo.GetTagListByNames(ctx, thisTagNameList) + if err != nil { + return err + } + + tagInDbMapping := make(map[string]*entity.Tag) + for _, tag := range tagListInDb { + tagInDbMapping[tag.SlugName] = tag + thisTagIDList = append(thisTagIDList, tag.ID) + } + + addTagList := make([]*entity.Tag, 0) + for _, tag := range tags { + _, ok := tagInDbMapping[tag] + if ok { + continue + } + item := &entity.Tag{} + item.SlugName = tag + item.DisplayName = tag + item.OriginalText = "" + item.ParsedText = "" + item.Status = entity.TagStatusAvailable + addTagList = append(addTagList, item) + } + + if len(addTagList) > 0 { + err = ts.tagRepo.AddTagList(ctx, addTagList) + if err != nil { + return err + } + for _, tag := range addTagList { + thisTagIDList = append(thisTagIDList, tag.ID) + revisionDTO := &schema.AddRevisionDTO{ + UserID: userID, + ObjectID: tag.ID, + Title: tag.SlugName, + } + tagInfoJson, _ := json.Marshal(tag) + revisionDTO.Content = string(tagInfoJson) + err = ts.revisionService.AddRevision(ctx, revisionDTO, true) + if err != nil { + return err + } + } + } + + return nil +} + // ObjectChangeTag change object tag list func (ts *TagCommonService) ObjectChangeTag(ctx context.Context, objectTagData *schema.TagChange) (err error) { if len(objectTagData.Tags) == 0 { diff --git a/internal/service/user_service.go b/internal/service/user_service.go index 8673c3d2..288161c5 100644 --- a/internal/service/user_service.go +++ b/internal/service/user_service.go @@ -112,6 +112,7 @@ func (us *UserService) EmailLogin(ctx context.Context, req *schema.UserEmailLogi UserID: userInfo.ID, EmailStatus: userInfo.MailStatus, UserStatus: userInfo.Status, + IsAdmin: userInfo.IsAdmin, } resp.AccessToken, err = us.authService.SetUserCacheInfo(ctx, userCacheInfo) if err != nil { @@ -322,6 +323,7 @@ func (us *UserService) UserRegisterByEmail(ctx context.Context, registerUserInfo UserID: userInfo.ID, EmailStatus: userInfo.MailStatus, UserStatus: userInfo.Status, + IsAdmin: userInfo.IsAdmin, } resp.AccessToken, err = us.authService.SetUserCacheInfo(ctx, userCacheInfo) if err != nil { @@ -408,6 +410,7 @@ func (us *UserService) UserVerifyEmail(ctx context.Context, req *schema.UserVeri UserID: userInfo.ID, EmailStatus: userInfo.MailStatus, UserStatus: userInfo.Status, + IsAdmin: userInfo.IsAdmin, } resp.AccessToken, err = us.authService.SetUserCacheInfo(ctx, userCacheInfo) if err != nil {