mirror of https://gitee.com/answerdev/answer.git
Merge branch 'feat/1.1.1/state' into test
This commit is contained in:
commit
57744467a5
|
@ -36,11 +36,10 @@ func (sc *SearchController) Search(ctx *gin.Context) {
|
|||
}
|
||||
dto.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
|
||||
resp, total, extra, err := sc.searchService.Search(ctx, &dto)
|
||||
resp, total, err := sc.searchService.Search(ctx, &dto)
|
||||
|
||||
handler.HandleResponse(ctx, err, schema.SearchListResp{
|
||||
Total: total,
|
||||
SearchResp: resp,
|
||||
Extra: extra,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -15,14 +15,14 @@ const minDBVersion = 0
|
|||
type Migration interface {
|
||||
Version() string
|
||||
Description() string
|
||||
Migrate(*xorm.Engine) error
|
||||
Migrate(ctx context.Context, x *xorm.Engine) error
|
||||
ShouldCleanCache() bool
|
||||
}
|
||||
|
||||
type migration struct {
|
||||
version string
|
||||
description string
|
||||
migrate func(*xorm.Engine) error
|
||||
migrate func(ctx context.Context, x *xorm.Engine) error
|
||||
shouldCleanCache bool
|
||||
}
|
||||
|
||||
|
@ -37,8 +37,8 @@ func (m *migration) Description() string {
|
|||
}
|
||||
|
||||
// Migrate executes the migration
|
||||
func (m *migration) Migrate(x *xorm.Engine) error {
|
||||
return m.migrate(x)
|
||||
func (m *migration) Migrate(ctx context.Context, x *xorm.Engine) error {
|
||||
return m.migrate(ctx, x)
|
||||
}
|
||||
|
||||
// ShouldCleanCache should clean the cache
|
||||
|
@ -47,12 +47,12 @@ func (m *migration) ShouldCleanCache() bool {
|
|||
}
|
||||
|
||||
// NewMigration creates a new migration
|
||||
func NewMigration(version, desc string, fn func(*xorm.Engine) error, shouldCleanCache bool) Migration {
|
||||
func NewMigration(version, desc string, fn func(ctx context.Context, x *xorm.Engine) error, shouldCleanCache bool) Migration {
|
||||
return &migration{version: version, description: desc, migrate: fn, shouldCleanCache: shouldCleanCache}
|
||||
}
|
||||
|
||||
// Use noopMigration when there is a migration that has been no-oped
|
||||
var noopMigration = func(_ *xorm.Engine) error { return nil }
|
||||
var noopMigration = func(_ context.Context, _ *xorm.Engine) error { return nil }
|
||||
|
||||
var migrations = []Migration{
|
||||
// 0->1
|
||||
|
@ -72,6 +72,10 @@ var migrations = []Migration{
|
|||
NewMigration("v1.1.0", "add gravatar base url", updateCount, true),
|
||||
}
|
||||
|
||||
func GetMigrations() []Migration {
|
||||
return migrations
|
||||
}
|
||||
|
||||
// GetCurrentDBVersion returns the current db version
|
||||
func GetCurrentDBVersion(engine *xorm.Engine) (int64, error) {
|
||||
if err := engine.Sync(new(entity.Version)); err != nil {
|
||||
|
@ -130,7 +134,7 @@ func Migrate(dbConf *data.Database, cacheConf *data.CacheConf, upgradeToSpecific
|
|||
currentDBVersion, currentDBVersion+1, expectedVersion)
|
||||
migrationFunc := migrations[currentDBVersion]
|
||||
fmt.Printf("[migrate] try to migrate Answer version %s, description: %s\n", migrationFunc.Version(), migrationFunc.Description())
|
||||
if err := migrationFunc.Migrate(engine); err != nil {
|
||||
if err := migrationFunc.Migrate(context.Background(), engine); err != nil {
|
||||
fmt.Printf("[migrate] migrate to db version %d failed: %s\n", currentDBVersion+1, err.Error())
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func addUserLanguage(x *xorm.Engine) error {
|
||||
func addUserLanguage(ctx context.Context, x *xorm.Engine) error {
|
||||
type User struct {
|
||||
ID string `xorm:"not null pk autoincr BIGINT(20) id"`
|
||||
Username string `xorm:"not null default '' VARCHAR(50) UNIQUE username"`
|
||||
Language string `xorm:"not null default '' VARCHAR(100) language"`
|
||||
}
|
||||
return x.Sync(new(User))
|
||||
return x.Context(ctx).Sync(new(User))
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
|
@ -11,11 +12,11 @@ import (
|
|||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func addLoginLimitations(x *xorm.Engine) error {
|
||||
func addLoginLimitations(ctx context.Context, x *xorm.Engine) error {
|
||||
loginSiteInfo := &entity.SiteInfo{
|
||||
Type: constant.SiteTypeLogin,
|
||||
}
|
||||
exist, err := x.Get(loginSiteInfo)
|
||||
exist, err := x.Context(ctx).Get(loginSiteInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get config failed: %w", err)
|
||||
}
|
||||
|
@ -26,7 +27,7 @@ func addLoginLimitations(x *xorm.Engine) error {
|
|||
content.AllowEmailDomains = make([]string, 0)
|
||||
data, _ := json.Marshal(content)
|
||||
loginSiteInfo.Content = string(data)
|
||||
_, err = x.ID(loginSiteInfo.ID).Cols("content").Update(loginSiteInfo)
|
||||
_, err = x.Context(ctx).ID(loginSiteInfo.ID).Cols("content").Update(loginSiteInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("update site info failed: %w", err)
|
||||
}
|
||||
|
@ -35,7 +36,7 @@ func addLoginLimitations(x *xorm.Engine) error {
|
|||
interfaceSiteInfo := &entity.SiteInfo{
|
||||
Type: constant.SiteTypeInterface,
|
||||
}
|
||||
exist, err = x.Get(interfaceSiteInfo)
|
||||
exist, err = x.Context(ctx).Get(interfaceSiteInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get config failed: %w", err)
|
||||
}
|
||||
|
@ -52,7 +53,7 @@ func addLoginLimitations(x *xorm.Engine) error {
|
|||
}
|
||||
data, _ := json.Marshal(siteUsers)
|
||||
|
||||
exist, err = x.Get(&entity.SiteInfo{Type: constant.SiteTypeUsers})
|
||||
exist, err = x.Context(ctx).Get(&entity.SiteInfo{Type: constant.SiteTypeUsers})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get config failed: %w", err)
|
||||
}
|
||||
|
@ -62,7 +63,7 @@ func addLoginLimitations(x *xorm.Engine) error {
|
|||
Content: string(data),
|
||||
Status: 1,
|
||||
}
|
||||
_, err = x.InsertOne(usersSiteInfo)
|
||||
_, err = x.Context(ctx).Insert(usersSiteInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("insert site info failed: %w", err)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
|
@ -8,8 +9,7 @@ import (
|
|||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func updateRolePinAndHideFeatures(x *xorm.Engine) error {
|
||||
|
||||
func updateRolePinAndHideFeatures(ctx context.Context, x *xorm.Engine) error {
|
||||
defaultConfigTable := []*entity.Config{
|
||||
{ID: 119, Key: "question.pin", Value: `0`},
|
||||
{ID: 120, Key: "question.unpin", Value: `0`},
|
||||
|
@ -21,18 +21,18 @@ func updateRolePinAndHideFeatures(x *xorm.Engine) error {
|
|||
{ID: 126, Key: "rank.question.hide", Value: `-1`},
|
||||
}
|
||||
for _, c := range defaultConfigTable {
|
||||
exist, err := x.Get(&entity.Config{ID: c.ID})
|
||||
exist, err := x.Context(ctx).Get(&entity.Config{ID: c.ID})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get config failed: %w", err)
|
||||
}
|
||||
if exist {
|
||||
if _, err = x.Update(c, &entity.Config{ID: c.ID}); err != nil {
|
||||
if _, err = x.Context(ctx).Update(c, &entity.Config{ID: c.ID}); err != nil {
|
||||
log.Errorf("update %+v config failed: %s", c, err)
|
||||
return fmt.Errorf("update config failed: %w", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if _, err = x.Insert(&entity.Config{ID: c.ID, Key: c.Key, Value: c.Value}); err != nil {
|
||||
if _, err = x.Context(ctx).Insert(&entity.Config{ID: c.ID, Key: c.Key, Value: c.Value}); err != nil {
|
||||
log.Errorf("insert %+v config failed: %s", c, err)
|
||||
return fmt.Errorf("add config failed: %w", err)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
|
@ -37,9 +38,9 @@ func (QuestionPostTime) TableName() string {
|
|||
return "question"
|
||||
}
|
||||
|
||||
func updateQuestionPostTime(x *xorm.Engine) error {
|
||||
func updateQuestionPostTime(ctx context.Context, x *xorm.Engine) error {
|
||||
questionList := make([]QuestionPostTime, 0)
|
||||
err := x.Find(&questionList, &entity.Question{})
|
||||
err := x.Context(ctx).Find(&questionList, &entity.Question{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get questions failed: %w", err)
|
||||
}
|
||||
|
@ -50,7 +51,7 @@ func updateQuestionPostTime(x *xorm.Engine) error {
|
|||
} else if !item.CreatedAt.IsZero() {
|
||||
item.PostUpdateTime = item.CreatedAt
|
||||
}
|
||||
if _, err = x.Update(item, &QuestionPostTime{ID: item.ID}); err != nil {
|
||||
if _, err = x.Context(ctx).Update(item, &QuestionPostTime{ID: item.ID}); err != nil {
|
||||
log.Errorf("update %+v config failed: %s", item, err)
|
||||
return fmt.Errorf("update question failed: %w", err)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
@ -13,8 +14,8 @@ import (
|
|||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func updateCount(x *xorm.Engine) error {
|
||||
fns := []func(*xorm.Engine) error{
|
||||
func updateCount(ctx context.Context, x *xorm.Engine) error {
|
||||
fns := []func(ctx context.Context, x *xorm.Engine) error{
|
||||
inviteAnswer,
|
||||
addPrivilegeForInviteSomeoneToAnswer,
|
||||
addGravatarBaseURL,
|
||||
|
@ -25,18 +26,18 @@ func updateCount(x *xorm.Engine) error {
|
|||
inBoxData,
|
||||
}
|
||||
for _, fn := range fns {
|
||||
if err := fn(x); err != nil {
|
||||
if err := fn(ctx, x); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func addGravatarBaseURL(x *xorm.Engine) error {
|
||||
func addGravatarBaseURL(ctx context.Context, x *xorm.Engine) error {
|
||||
usersSiteInfo := &entity.SiteInfo{
|
||||
Type: constant.SiteTypeUsers,
|
||||
}
|
||||
exist, err := x.Get(usersSiteInfo)
|
||||
exist, err := x.Context(ctx).Get(usersSiteInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get config failed: %w", err)
|
||||
}
|
||||
|
@ -47,7 +48,7 @@ func addGravatarBaseURL(x *xorm.Engine) error {
|
|||
data, _ := json.Marshal(content)
|
||||
usersSiteInfo.Content = string(data)
|
||||
|
||||
_, err = x.ID(usersSiteInfo.ID).Cols("content").Update(usersSiteInfo)
|
||||
_, err = x.Context(ctx).ID(usersSiteInfo.ID).Cols("content").Update(usersSiteInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("update site info failed: %w", err)
|
||||
}
|
||||
|
@ -55,20 +56,20 @@ func addGravatarBaseURL(x *xorm.Engine) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func addPrivilegeForInviteSomeoneToAnswer(x *xorm.Engine) error {
|
||||
func addPrivilegeForInviteSomeoneToAnswer(ctx context.Context, x *xorm.Engine) error {
|
||||
// add rank for invite to answer
|
||||
powers := []*entity.Power{
|
||||
{ID: 38, Name: "invite someone to answer", PowerType: permission.AnswerInviteSomeoneToAnswer, Description: "invite someone to answer"},
|
||||
}
|
||||
for _, power := range powers {
|
||||
exist, err := x.Get(&entity.Power{PowerType: power.PowerType})
|
||||
exist, err := x.Context(ctx).Get(&entity.Power{PowerType: power.PowerType})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exist {
|
||||
_, err = x.ID(power.ID).Update(power)
|
||||
_, err = x.Context(ctx).ID(power.ID).Update(power)
|
||||
} else {
|
||||
_, err = x.Insert(power)
|
||||
_, err = x.Context(ctx).Insert(power)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -79,14 +80,14 @@ func addPrivilegeForInviteSomeoneToAnswer(x *xorm.Engine) error {
|
|||
{RoleID: 3, PowerType: permission.AnswerInviteSomeoneToAnswer},
|
||||
}
|
||||
for _, rel := range rolePowerRels {
|
||||
exist, err := x.Get(&entity.RolePowerRel{RoleID: rel.RoleID, PowerType: rel.PowerType})
|
||||
exist, err := x.Context(ctx).Get(&entity.RolePowerRel{RoleID: rel.RoleID, PowerType: rel.PowerType})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exist {
|
||||
continue
|
||||
}
|
||||
_, err = x.Insert(rel)
|
||||
_, err = x.Context(ctx).Insert(rel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -96,27 +97,27 @@ func addPrivilegeForInviteSomeoneToAnswer(x *xorm.Engine) error {
|
|||
{ID: 127, Key: "rank.answer.invite_someone_to_answer", Value: `1000`},
|
||||
}
|
||||
for _, c := range defaultConfigTable {
|
||||
exist, err := x.Get(&entity.Config{ID: c.ID})
|
||||
exist, err := x.Context(ctx).Get(&entity.Config{ID: c.ID})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get config failed: %w", err)
|
||||
}
|
||||
if exist {
|
||||
if _, err = x.Update(c, &entity.Config{ID: c.ID}); err != nil {
|
||||
if _, err = x.Context(ctx).Update(c, &entity.Config{ID: c.ID}); err != nil {
|
||||
return fmt.Errorf("update config failed: %w", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if _, err = x.Insert(&entity.Config{ID: c.ID, Key: c.Key, Value: c.Value}); err != nil {
|
||||
if _, err = x.Context(ctx).Insert(&entity.Config{ID: c.ID, Key: c.Key, Value: c.Value}); err != nil {
|
||||
return fmt.Errorf("add config failed: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateQuestionCount(x *xorm.Engine) error {
|
||||
func updateQuestionCount(ctx context.Context, x *xorm.Engine) error {
|
||||
//question answer count
|
||||
answers := make([]AnswerV13, 0)
|
||||
err := x.Find(&answers, &AnswerV13{Status: entity.AnswerStatusAvailable})
|
||||
err := x.Context(ctx).Find(&answers, &AnswerV13{Status: entity.AnswerStatusAvailable})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get answers failed: %w", err)
|
||||
}
|
||||
|
@ -130,7 +131,7 @@ func updateQuestionCount(x *xorm.Engine) error {
|
|||
}
|
||||
}
|
||||
questionList := make([]QuestionV13, 0)
|
||||
err = x.Find(&questionList, &QuestionV13{})
|
||||
err = x.Context(ctx).Find(&questionList, &QuestionV13{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get questions failed: %w", err)
|
||||
}
|
||||
|
@ -138,7 +139,7 @@ func updateQuestionCount(x *xorm.Engine) error {
|
|||
_, ok := questionAnswerCount[item.ID]
|
||||
if ok {
|
||||
item.AnswerCount = questionAnswerCount[item.ID]
|
||||
if _, err = x.Cols("answer_count").Update(item, &QuestionV13{ID: item.ID}); err != nil {
|
||||
if _, err = x.Context(ctx).Cols("answer_count").Update(item, &QuestionV13{ID: item.ID}); err != nil {
|
||||
log.Errorf("update %+v config failed: %s", item, err)
|
||||
return fmt.Errorf("update question failed: %w", err)
|
||||
}
|
||||
|
@ -149,9 +150,9 @@ func updateQuestionCount(x *xorm.Engine) error {
|
|||
}
|
||||
|
||||
// updateTagCount update tag count
|
||||
func updateTagCount(x *xorm.Engine) error {
|
||||
func updateTagCount(ctx context.Context, x *xorm.Engine) error {
|
||||
tagRelList := make([]entity.TagRel, 0)
|
||||
err := x.Find(&tagRelList, &entity.TagRel{})
|
||||
err := x.Context(ctx).Find(&tagRelList, &entity.TagRel{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get tag rel failed: %w", err)
|
||||
}
|
||||
|
@ -164,7 +165,7 @@ func updateTagCount(x *xorm.Engine) error {
|
|||
questionsHideMap[item.ObjectID] = false
|
||||
}
|
||||
questionList := make([]QuestionV13, 0)
|
||||
err = x.In("id", questionIDs).In("question.status", []int{entity.QuestionStatusAvailable, entity.QuestionStatusClosed}).Find(&questionList, &QuestionV13{})
|
||||
err = x.Context(ctx).In("id", questionIDs).In("question.status", []int{entity.QuestionStatusAvailable, entity.QuestionStatusClosed}).Find(&questionList, &QuestionV13{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get questions failed: %w", err)
|
||||
}
|
||||
|
@ -180,7 +181,7 @@ func updateTagCount(x *xorm.Engine) error {
|
|||
|
||||
for id, ok := range questionsHideMap {
|
||||
if ok {
|
||||
if _, err = x.Cols("status").Update(&entity.TagRel{Status: entity.TagRelStatusHide}, &entity.TagRel{ObjectID: id}); err != nil {
|
||||
if _, err = x.Context(ctx).Cols("status").Update(&entity.TagRel{Status: entity.TagRelStatusHide}, &entity.TagRel{ObjectID: id}); err != nil {
|
||||
log.Errorf("update %+v config failed: %s", id, err)
|
||||
}
|
||||
}
|
||||
|
@ -188,7 +189,7 @@ func updateTagCount(x *xorm.Engine) error {
|
|||
|
||||
for id, ok := range questionsAvailableMap {
|
||||
if !ok {
|
||||
if _, err = x.Cols("status").Update(&entity.TagRel{Status: entity.TagRelStatusDeleted}, &entity.TagRel{ObjectID: id}); err != nil {
|
||||
if _, err = x.Context(ctx).Cols("status").Update(&entity.TagRel{Status: entity.TagRelStatusDeleted}, &entity.TagRel{ObjectID: id}); err != nil {
|
||||
log.Errorf("update %+v config failed: %s", id, err)
|
||||
}
|
||||
}
|
||||
|
@ -196,7 +197,7 @@ func updateTagCount(x *xorm.Engine) error {
|
|||
|
||||
//select tag count
|
||||
newTagRelList := make([]entity.TagRel, 0)
|
||||
err = x.Find(&newTagRelList, &entity.TagRel{Status: entity.TagRelStatusAvailable})
|
||||
err = x.Context(ctx).Find(&newTagRelList, &entity.TagRel{Status: entity.TagRelStatusAvailable})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get tag rel failed: %w", err)
|
||||
}
|
||||
|
@ -210,7 +211,7 @@ func updateTagCount(x *xorm.Engine) error {
|
|||
}
|
||||
}
|
||||
TagList := make([]entity.Tag, 0)
|
||||
err = x.Find(&TagList, &entity.Tag{})
|
||||
err = x.Context(ctx).Find(&TagList, &entity.Tag{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get tag failed: %w", err)
|
||||
}
|
||||
|
@ -218,13 +219,13 @@ func updateTagCount(x *xorm.Engine) error {
|
|||
_, ok := tagCountMap[tag.ID]
|
||||
if ok {
|
||||
tag.QuestionCount = tagCountMap[tag.ID]
|
||||
if _, err = x.Update(tag, &entity.Tag{ID: tag.ID}); err != nil {
|
||||
if _, err = x.Context(ctx).Update(tag, &entity.Tag{ID: tag.ID}); err != nil {
|
||||
log.Errorf("update %+v tag failed: %s", tag.ID, err)
|
||||
return fmt.Errorf("update tag failed: %w", err)
|
||||
}
|
||||
} else {
|
||||
tag.QuestionCount = 0
|
||||
if _, err = x.Cols("question_count").Update(tag, &entity.Tag{ID: tag.ID}); err != nil {
|
||||
if _, err = x.Context(ctx).Cols("question_count").Update(tag, &entity.Tag{ID: tag.ID}); err != nil {
|
||||
log.Errorf("update %+v tag failed: %s", tag.ID, err)
|
||||
return fmt.Errorf("update tag failed: %w", err)
|
||||
}
|
||||
|
@ -234,9 +235,9 @@ func updateTagCount(x *xorm.Engine) error {
|
|||
}
|
||||
|
||||
// updateUserQuestionCount update user question count
|
||||
func updateUserQuestionCount(x *xorm.Engine) error {
|
||||
func updateUserQuestionCount(ctx context.Context, x *xorm.Engine) error {
|
||||
questionList := make([]QuestionV13, 0)
|
||||
err := x.In("status", []int{entity.QuestionStatusAvailable, entity.QuestionStatusClosed}).Find(&questionList, &QuestionV13{})
|
||||
err := x.Context(ctx).In("status", []int{entity.QuestionStatusAvailable, entity.QuestionStatusClosed}).Find(&questionList, &QuestionV13{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get question failed: %w", err)
|
||||
}
|
||||
|
@ -250,7 +251,7 @@ func updateUserQuestionCount(x *xorm.Engine) error {
|
|||
}
|
||||
}
|
||||
userList := make([]entity.User, 0)
|
||||
err = x.Find(&userList, &entity.User{})
|
||||
err = x.Context(ctx).Find(&userList, &entity.User{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get user failed: %w", err)
|
||||
}
|
||||
|
@ -258,13 +259,13 @@ func updateUserQuestionCount(x *xorm.Engine) error {
|
|||
_, ok := userQuestionCountMap[user.ID]
|
||||
if ok {
|
||||
user.QuestionCount = userQuestionCountMap[user.ID]
|
||||
if _, err = x.Cols("question_count").Update(user, &entity.User{ID: user.ID}); err != nil {
|
||||
if _, err = x.Context(ctx).Cols("question_count").Update(user, &entity.User{ID: user.ID}); err != nil {
|
||||
log.Errorf("update %+v user failed: %s", user.ID, err)
|
||||
return fmt.Errorf("update user failed: %w", err)
|
||||
}
|
||||
} else {
|
||||
user.QuestionCount = 0
|
||||
if _, err = x.Cols("question_count").Update(user, &entity.User{ID: user.ID}); err != nil {
|
||||
if _, err = x.Context(ctx).Cols("question_count").Update(user, &entity.User{ID: user.ID}); err != nil {
|
||||
log.Errorf("update %+v user failed: %s", user.ID, err)
|
||||
return fmt.Errorf("update user failed: %w", err)
|
||||
}
|
||||
|
@ -286,9 +287,9 @@ func (AnswerV13) TableName() string {
|
|||
}
|
||||
|
||||
// updateUserAnswerCount update user answer count
|
||||
func updateUserAnswerCount(x *xorm.Engine) error {
|
||||
func updateUserAnswerCount(ctx context.Context, x *xorm.Engine) error {
|
||||
answers := make([]AnswerV13, 0)
|
||||
err := x.Find(&answers, &AnswerV13{Status: entity.AnswerStatusAvailable})
|
||||
err := x.Context(ctx).Find(&answers, &AnswerV13{Status: entity.AnswerStatusAvailable})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get answers failed: %w", err)
|
||||
}
|
||||
|
@ -302,7 +303,7 @@ func updateUserAnswerCount(x *xorm.Engine) error {
|
|||
}
|
||||
}
|
||||
userList := make([]entity.User, 0)
|
||||
err = x.Find(&userList, &entity.User{})
|
||||
err = x.Context(ctx).Find(&userList, &entity.User{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get user failed: %w", err)
|
||||
}
|
||||
|
@ -310,13 +311,13 @@ func updateUserAnswerCount(x *xorm.Engine) error {
|
|||
_, ok := userAnswerCount[user.ID]
|
||||
if ok {
|
||||
user.AnswerCount = userAnswerCount[user.ID]
|
||||
if _, err = x.Cols("answer_count").Update(user, &entity.User{ID: user.ID}); err != nil {
|
||||
if _, err = x.Context(ctx).Cols("answer_count").Update(user, &entity.User{ID: user.ID}); err != nil {
|
||||
log.Errorf("update %+v user failed: %s", user.ID, err)
|
||||
return fmt.Errorf("update user failed: %w", err)
|
||||
}
|
||||
} else {
|
||||
user.AnswerCount = 0
|
||||
if _, err = x.Cols("answer_count").Update(user, &entity.User{ID: user.ID}); err != nil {
|
||||
if _, err = x.Context(ctx).Cols("answer_count").Update(user, &entity.User{ID: user.ID}); err != nil {
|
||||
log.Errorf("update %+v user failed: %s", user.ID, err)
|
||||
return fmt.Errorf("update user failed: %w", err)
|
||||
}
|
||||
|
@ -354,8 +355,8 @@ func (QuestionV13) TableName() string {
|
|||
return "question"
|
||||
}
|
||||
|
||||
func inviteAnswer(x *xorm.Engine) error {
|
||||
err := x.Sync(new(QuestionV13))
|
||||
func inviteAnswer(ctx context.Context, x *xorm.Engine) error {
|
||||
err := x.Context(ctx).Sync(new(QuestionV13))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -363,7 +364,7 @@ func inviteAnswer(x *xorm.Engine) error {
|
|||
}
|
||||
|
||||
// inBoxData Classify messages
|
||||
func inBoxData(x *xorm.Engine) error {
|
||||
func inBoxData(ctx context.Context, x *xorm.Engine) error {
|
||||
type Notification struct {
|
||||
ID string `xorm:"not null pk autoincr BIGINT(20) id"`
|
||||
CreatedAt time.Time `xorm:"created TIMESTAMP created_at"`
|
||||
|
@ -376,12 +377,12 @@ func inBoxData(x *xorm.Engine) error {
|
|||
IsRead int `xorm:"not null default 1 INT(11) is_read"`
|
||||
Status int `xorm:"not null default 1 INT(11) status"`
|
||||
}
|
||||
err := x.Sync(new(Notification))
|
||||
err := x.Context(ctx).Sync(new(Notification))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msglist := make([]entity.Notification, 0)
|
||||
err = x.Find(&msglist, &entity.Notification{})
|
||||
err = x.Context(ctx).Find(&msglist, &entity.Notification{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get Notification failed: %w", err)
|
||||
}
|
||||
|
@ -394,7 +395,7 @@ func inBoxData(x *xorm.Engine) error {
|
|||
_, ok := constant.NotificationMsgTypeMapping[Content.NotificationAction]
|
||||
if ok {
|
||||
v.MsgType = constant.NotificationMsgTypeMapping[Content.NotificationAction]
|
||||
if _, err = x.Update(v, &entity.Notification{ID: v.ID}); err != nil {
|
||||
if _, err = x.Context(ctx).Update(v, &entity.Notification{ID: v.ID}); err != nil {
|
||||
log.Errorf("update %+v Notification failed: %s", v.ID, err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func addTagRecommendedAndReserved(x *xorm.Engine) error {
|
||||
func addTagRecommendedAndReserved(ctx context.Context, x *xorm.Engine) error {
|
||||
type Tag struct {
|
||||
ID string `xorm:"not null pk comment('tag_id') BIGINT(20) id"`
|
||||
SlugName string `xorm:"not null default '' unique VARCHAR(35) slug_name"`
|
||||
Recommend bool `xorm:"not null default false BOOL recommend"`
|
||||
Reserved bool `xorm:"not null default false BOOL reserved"`
|
||||
}
|
||||
return x.Sync(new(Tag))
|
||||
return x.Context(ctx).Sync(new(Tag))
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
|
@ -10,28 +11,28 @@ import (
|
|||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
func addActivityTimeline(x *xorm.Engine) (err error) {
|
||||
func addActivityTimeline(ctx context.Context, x *xorm.Engine) (err error) {
|
||||
switch x.Dialect().URI().DBType {
|
||||
case schemas.MYSQL:
|
||||
_, err = x.Exec("ALTER TABLE `answer` CHANGE `updated_at` `updated_at` TIMESTAMP NULL DEFAULT NULL")
|
||||
_, err = x.Context(ctx).Exec("ALTER TABLE `answer` CHANGE `updated_at` `updated_at` TIMESTAMP NULL DEFAULT NULL")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = x.Exec("ALTER TABLE `question` CHANGE `updated_at` `updated_at` TIMESTAMP NULL DEFAULT NULL")
|
||||
_, err = x.Context(ctx).Exec("ALTER TABLE `question` CHANGE `updated_at` `updated_at` TIMESTAMP NULL DEFAULT NULL")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case schemas.POSTGRES:
|
||||
_, err = x.Exec(`ALTER TABLE "answer" ALTER COLUMN "updated_at" DROP NOT NULL, ALTER COLUMN "updated_at" SET DEFAULT NULL`)
|
||||
_, err = x.Context(ctx).Exec(`ALTER TABLE "answer" ALTER COLUMN "updated_at" DROP NOT NULL, ALTER COLUMN "updated_at" SET DEFAULT NULL`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = x.Exec(`ALTER TABLE "question" ALTER COLUMN "updated_at" DROP NOT NULL, ALTER COLUMN "updated_at" SET DEFAULT NULL`)
|
||||
_, err = x.Context(ctx).Exec(`ALTER TABLE "question" ALTER COLUMN "updated_at" DROP NOT NULL, ALTER COLUMN "updated_at" SET DEFAULT NULL`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case schemas.SQLITE:
|
||||
_, err = x.Exec(`DROP INDEX "IDX_answer_user_id";
|
||||
_, err = x.Context(ctx).Exec(`DROP INDEX "IDX_answer_user_id";
|
||||
|
||||
ALTER TABLE "answer" RENAME TO "_answer_old_v3";
|
||||
|
||||
|
@ -98,7 +99,7 @@ ON "question" (
|
|||
ID int `xorm:"not null pk autoincr INT(11) id"`
|
||||
Key string `xorm:"unique VARCHAR(128) key"`
|
||||
}
|
||||
if err := x.Sync(new(Config)); err != nil {
|
||||
if err := x.Context(ctx).Sync(new(Config)); err != nil {
|
||||
return fmt.Errorf("sync config table failed: %w", err)
|
||||
}
|
||||
defaultConfigTable := []*entity.Config{
|
||||
|
@ -155,18 +156,18 @@ ON "question" (
|
|||
{ID: 114, Key: "rank.tag.audit", Value: `20000`},
|
||||
}
|
||||
for _, c := range defaultConfigTable {
|
||||
exist, err := x.Get(&entity.Config{ID: c.ID, Key: c.Key})
|
||||
exist, err := x.Context(ctx).Get(&entity.Config{ID: c.ID, Key: c.Key})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get config failed: %w", err)
|
||||
}
|
||||
if exist {
|
||||
if _, err = x.Update(c, &entity.Config{ID: c.ID, Key: c.Key}); err != nil {
|
||||
if _, err = x.Context(ctx).Update(c, &entity.Config{ID: c.ID, Key: c.Key}); err != nil {
|
||||
log.Errorf("update %+v config failed: %s", c, err)
|
||||
return fmt.Errorf("update config failed: %w", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if _, err = x.Insert(&entity.Config{ID: c.ID, Key: c.Key, Value: c.Value}); err != nil {
|
||||
if _, err = x.Context(ctx).Insert(&entity.Config{ID: c.ID, Key: c.Key, Value: c.Value}); err != nil {
|
||||
log.Errorf("insert %+v config failed: %s", c, err)
|
||||
return fmt.Errorf("add config failed: %w", err)
|
||||
}
|
||||
|
@ -205,7 +206,7 @@ ON "question" (
|
|||
LastEditUserID string `xorm:"not null default 0 BIGINT(20) last_edit_user_id"`
|
||||
}
|
||||
|
||||
err = x.Sync(new(Activity), new(Revision), new(Tag), new(Question), new(Answer))
|
||||
err = x.Context(ctx).Sync(new(Activity), new(Revision), new(Tag), new(Question), new(Answer))
|
||||
if err != nil {
|
||||
return fmt.Errorf("sync table failed %w", err)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
|
@ -9,8 +10,8 @@ import (
|
|||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func addRoleFeatures(x *xorm.Engine) error {
|
||||
err := x.Sync(new(entity.Role), new(entity.RolePowerRel), new(entity.Power), new(entity.UserRoleRel))
|
||||
func addRoleFeatures(ctx context.Context, x *xorm.Engine) error {
|
||||
err := x.Context(ctx).Sync(new(entity.Role), new(entity.RolePowerRel), new(entity.Power), new(entity.UserRoleRel))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -23,14 +24,14 @@ func addRoleFeatures(x *xorm.Engine) error {
|
|||
|
||||
// insert default roles
|
||||
for _, role := range roles {
|
||||
exist, err := x.Get(&entity.Role{ID: role.ID, Name: role.Name})
|
||||
exist, err := x.Context(ctx).Get(&entity.Role{ID: role.ID, Name: role.Name})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exist {
|
||||
continue
|
||||
}
|
||||
_, err = x.Insert(role)
|
||||
_, err = x.Context(ctx).Insert(role)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -73,14 +74,14 @@ func addRoleFeatures(x *xorm.Engine) error {
|
|||
}
|
||||
// insert default powers
|
||||
for _, power := range powers {
|
||||
exist, err := x.Get(&entity.Power{ID: power.ID})
|
||||
exist, err := x.Context(ctx).Get(&entity.Power{ID: power.ID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exist {
|
||||
_, err = x.ID(power.ID).Update(power)
|
||||
_, err = x.Context(ctx).ID(power.ID).Update(power)
|
||||
} else {
|
||||
_, err = x.Insert(power)
|
||||
_, err = x.Context(ctx).Insert(power)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -160,14 +161,14 @@ func addRoleFeatures(x *xorm.Engine) error {
|
|||
|
||||
// insert default powers
|
||||
for _, rel := range rolePowerRels {
|
||||
exist, err := x.Get(&entity.RolePowerRel{RoleID: rel.RoleID, PowerType: rel.PowerType})
|
||||
exist, err := x.Context(ctx).Get(&entity.RolePowerRel{RoleID: rel.RoleID, PowerType: rel.PowerType})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exist {
|
||||
continue
|
||||
}
|
||||
_, err = x.Insert(rel)
|
||||
_, err = x.Context(ctx).Insert(rel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -178,12 +179,12 @@ func addRoleFeatures(x *xorm.Engine) error {
|
|||
RoleID: 2,
|
||||
}
|
||||
|
||||
exist, err := x.Get(adminUserRoleRel)
|
||||
exist, err := x.Context(ctx).Get(adminUserRoleRel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exist {
|
||||
_, err = x.Insert(adminUserRoleRel)
|
||||
_, err = x.Context(ctx).Insert(adminUserRoleRel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -195,18 +196,18 @@ func addRoleFeatures(x *xorm.Engine) error {
|
|||
{ID: 117, Key: "rank.tag.use_reserved_tag", Value: `-1`},
|
||||
}
|
||||
for _, c := range defaultConfigTable {
|
||||
exist, err := x.Get(&entity.Config{ID: c.ID, Key: c.Key})
|
||||
exist, err := x.Context(ctx).Get(&entity.Config{ID: c.ID, Key: c.Key})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get config failed: %w", err)
|
||||
}
|
||||
if exist {
|
||||
if _, err = x.Update(c, &entity.Config{ID: c.ID, Key: c.Key}); err != nil {
|
||||
if _, err = x.Context(ctx).Update(c, &entity.Config{ID: c.ID, Key: c.Key}); err != nil {
|
||||
log.Errorf("update %+v config failed: %s", c, err)
|
||||
return fmt.Errorf("update config failed: %w", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if _, err = x.Insert(&entity.Config{ID: c.ID, Key: c.Key, Value: c.Value}); err != nil {
|
||||
if _, err = x.Context(ctx).Insert(&entity.Config{ID: c.ID, Key: c.Key, Value: c.Value}); err != nil {
|
||||
log.Errorf("insert %+v config failed: %s", c, err)
|
||||
return fmt.Errorf("add config failed: %w", err)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
|
@ -8,7 +9,7 @@ import (
|
|||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func addThemeAndPrivateMode(x *xorm.Engine) error {
|
||||
func addThemeAndPrivateMode(ctx context.Context, x *xorm.Engine) error {
|
||||
loginConfig := map[string]bool{
|
||||
"allow_new_registrations": true,
|
||||
"login_required": false,
|
||||
|
@ -19,12 +20,12 @@ func addThemeAndPrivateMode(x *xorm.Engine) error {
|
|||
Content: string(loginConfigDataBytes),
|
||||
Status: 1,
|
||||
}
|
||||
exist, err := x.Get(&entity.SiteInfo{Type: siteInfo.Type})
|
||||
exist, err := x.Context(ctx).Get(&entity.SiteInfo{Type: siteInfo.Type})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get config failed: %w", err)
|
||||
}
|
||||
if !exist {
|
||||
_, err = x.InsertOne(siteInfo)
|
||||
_, err = x.Context(ctx).Insert(siteInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("insert site info failed: %w", err)
|
||||
}
|
||||
|
@ -36,12 +37,12 @@ func addThemeAndPrivateMode(x *xorm.Engine) error {
|
|||
Content: themeConfig,
|
||||
Status: 1,
|
||||
}
|
||||
exist, err = x.Get(&entity.SiteInfo{Type: themeSiteInfo.Type})
|
||||
exist, err = x.Context(ctx).Get(&entity.SiteInfo{Type: themeSiteInfo.Type})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get config failed: %w", err)
|
||||
}
|
||||
if !exist {
|
||||
_, err = x.InsertOne(themeSiteInfo)
|
||||
_, err = x.Context(ctx).Insert(themeSiteInfo)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
|
@ -8,15 +9,15 @@ import (
|
|||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func addNewAnswerNotification(x *xorm.Engine) error {
|
||||
func addNewAnswerNotification(ctx context.Context, x *xorm.Engine) error {
|
||||
cond := &entity.Config{Key: "email.config"}
|
||||
exists, err := x.Get(cond)
|
||||
exists, err := x.Context(ctx).Get(cond)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get email config failed: %w", err)
|
||||
}
|
||||
if !exists {
|
||||
// This should be impossible except that the config was deleted manually by user.
|
||||
_, err = x.InsertOne(&entity.Config{
|
||||
_, err = x.Context(ctx).Insert(&entity.Config{
|
||||
Key: "email.config",
|
||||
Value: `{"from_name":"","from_email":"","smtp_host":"","smtp_port":465,"smtp_password":"","smtp_username":"","smtp_authentication":true,"encryption":"","register_title":"[{{.SiteName}}] Confirm your new account","register_body":"Welcome to {{.SiteName}}<br><br>\n\nClick the following link to confirm and activate your new account:<br>\n<a href='{{.RegisterUrl}}' target='_blank'>{{.RegisterUrl}}</a><br><br>\n\nIf the above link is not clickable, try copying and pasting it into the address bar of your web browser.\n","pass_reset_title":"[{{.SiteName }}] Password reset","pass_reset_body":"Somebody asked to reset your password on [{{.SiteName}}].<br><br>\n\nIf it was not you, you can safely ignore this email.<br><br>\n\nClick the following link to choose a new password:<br>\n<a href='{{.PassResetUrl}}' target='_blank'>{{.PassResetUrl}}</a>\n","change_title":"[{{.SiteName}}] Confirm your new email address","change_body":"Confirm your new email address for {{.SiteName}} by clicking on the following link:<br><br>\n\n<a href='{{.ChangeEmailUrl}}' target='_blank'>{{.ChangeEmailUrl}}</a><br><br>\n\nIf you did not request this change, please ignore this email.\n","test_title":"[{{.SiteName}}] Test Email","test_body":"This is a test email.","new_answer_title":"[{{.SiteName}}] {{.DisplayName}} answered your question","new_answer_body":"<strong><a href='{{.AnswerUrl}}'>{{.QuestionTitle}}</a></strong><br><br>\n\n<small>{{.DisplayName}}:</small><br>\n<blockquote>{{.AnswerSummary}}</blockquote><br>\n<a href='{{.AnswerUrl}}'>View it on {{.SiteName}}</a><br><br>\n\n<small>You are receiving this because you authored the thread. <a href='{{.UnsubscribeUrl}}'>Unsubscribe</a></small>","new_comment_title":"[{{.SiteName}}] {{.DisplayName}} commented on your post","new_comment_body":"<strong><a href='{{.CommentUrl}}'>{{.QuestionTitle}}</a></strong><br><br>\n\n<small>{{.DisplayName}}:</small><br>\n<blockquote>{{.CommentSummary}}</blockquote><br>\n<a href='{{.CommentUrl}}'>View it on {{.SiteName}}</a><br><br>\n\n<small>You are receiving this because you authored the thread. <a href='{{.UnsubscribeUrl}}'>Unsubscribe</a></small>"}`,
|
||||
})
|
||||
|
@ -33,7 +34,7 @@ func addNewAnswerNotification(x *xorm.Engine) error {
|
|||
m["new_comment_body"] = "<strong><a href='{{.CommentUrl}}'>{{.QuestionTitle}}</a></strong><br><br>\n\n<small>{{.DisplayName}}:</small><br>\n<blockquote>{{.CommentSummary}}</blockquote><br>\n<a href='{{.CommentUrl}}'>View it on {{.SiteName}}</a><br><br>\n\n<small>You are receiving this because you authored the thread. <a href='{{.UnsubscribeUrl}}'>Unsubscribe</a></small>"
|
||||
|
||||
val, _ := json.Marshal(m)
|
||||
_, err = x.ID(cond.ID).Update(&entity.Config{Value: string(val)})
|
||||
_, err = x.Context(ctx).ID(cond.ID).Update(&entity.Config{Value: string(val)})
|
||||
if err != nil {
|
||||
return fmt.Errorf("update email config failed: %v", err)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
|
@ -8,23 +9,23 @@ import (
|
|||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func addPlugin(x *xorm.Engine) error {
|
||||
func addPlugin(ctx context.Context, x *xorm.Engine) error {
|
||||
defaultConfigTable := []*entity.Config{
|
||||
{ID: 118, Key: "plugin.status", Value: `{}`},
|
||||
}
|
||||
for _, c := range defaultConfigTable {
|
||||
exist, err := x.Get(&entity.Config{ID: c.ID, Key: c.Key})
|
||||
exist, err := x.Context(ctx).Get(&entity.Config{ID: c.ID, Key: c.Key})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get config failed: %w", err)
|
||||
}
|
||||
if exist {
|
||||
continue
|
||||
}
|
||||
if _, err = x.Insert(&entity.Config{ID: c.ID, Key: c.Key, Value: c.Value}); err != nil {
|
||||
if _, err = x.Context(ctx).Insert(&entity.Config{ID: c.ID, Key: c.Key, Value: c.Value}); err != nil {
|
||||
log.Errorf("insert %+v config failed: %s", c, err)
|
||||
return fmt.Errorf("add config failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return x.Sync(new(entity.PluginConfig), new(entity.UserExternalLogin))
|
||||
return x.Context(ctx).Sync(new(entity.PluginConfig), new(entity.UserExternalLogin))
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
|
@ -10,8 +11,7 @@ import (
|
|||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func addRolePinAndHideFeatures(x *xorm.Engine) error {
|
||||
|
||||
func addRolePinAndHideFeatures(ctx context.Context, x *xorm.Engine) error {
|
||||
powers := []*entity.Power{
|
||||
{ID: 34, Name: "question pin", PowerType: permission.QuestionPin, Description: "top the question"},
|
||||
{ID: 35, Name: "question hide", PowerType: permission.QuestionHide, Description: "hide the question"},
|
||||
|
@ -20,14 +20,14 @@ func addRolePinAndHideFeatures(x *xorm.Engine) error {
|
|||
}
|
||||
// insert default powers
|
||||
for _, power := range powers {
|
||||
exist, err := x.Get(&entity.Power{ID: power.ID})
|
||||
exist, err := x.Context(ctx).Get(&entity.Power{ID: power.ID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exist {
|
||||
_, err = x.ID(power.ID).Update(power)
|
||||
_, err = x.Context(ctx).ID(power.ID).Update(power)
|
||||
} else {
|
||||
_, err = x.Insert(power)
|
||||
_, err = x.Context(ctx).Insert(power)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -49,14 +49,14 @@ func addRolePinAndHideFeatures(x *xorm.Engine) error {
|
|||
|
||||
// insert default powers
|
||||
for _, rel := range rolePowerRels {
|
||||
exist, err := x.Get(&entity.RolePowerRel{RoleID: rel.RoleID, PowerType: rel.PowerType})
|
||||
exist, err := x.Context(ctx).Get(&entity.RolePowerRel{RoleID: rel.RoleID, PowerType: rel.PowerType})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exist {
|
||||
continue
|
||||
}
|
||||
_, err = x.Insert(rel)
|
||||
_, err = x.Context(ctx).Insert(rel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -73,18 +73,18 @@ func addRolePinAndHideFeatures(x *xorm.Engine) error {
|
|||
{ID: 126, Key: "rank.question.hide", Value: `-1`},
|
||||
}
|
||||
for _, c := range defaultConfigTable {
|
||||
exist, err := x.Get(&entity.Config{ID: c.ID})
|
||||
exist, err := x.Context(ctx).Get(&entity.Config{ID: c.ID})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get config failed: %w", err)
|
||||
}
|
||||
if exist {
|
||||
if _, err = x.Update(c, &entity.Config{ID: c.ID}); err != nil {
|
||||
if _, err = x.Context(ctx).Update(c, &entity.Config{ID: c.ID}); err != nil {
|
||||
log.Errorf("update %+v config failed: %s", c, err)
|
||||
return fmt.Errorf("update config failed: %w", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if _, err = x.Insert(&entity.Config{ID: c.ID, Key: c.Key, Value: c.Value}); err != nil {
|
||||
if _, err = x.Context(ctx).Insert(&entity.Config{ID: c.ID, Key: c.Key, Value: c.Value}); err != nil {
|
||||
log.Errorf("insert %+v config failed: %s", c, err)
|
||||
return fmt.Errorf("add config failed: %w", err)
|
||||
}
|
||||
|
@ -113,7 +113,7 @@ func addRolePinAndHideFeatures(x *xorm.Engine) error {
|
|||
PostUpdateTime time.Time `xorm:"post_update_time TIMESTAMP"`
|
||||
RevisionID string `xorm:"not null default 0 BIGINT(20) revision_id"`
|
||||
}
|
||||
err := x.Sync(new(Question))
|
||||
err := x.Context(ctx).Sync(new(Question))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
|
@ -8,9 +9,9 @@ import (
|
|||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func updateAcceptAnswerRank(x *xorm.Engine) error {
|
||||
func updateAcceptAnswerRank(ctx context.Context, x *xorm.Engine) error {
|
||||
c := &entity.Config{ID: 44, Key: "rank.answer.accept", Value: `-1`}
|
||||
if _, err := x.Update(c, &entity.Config{ID: 44, Key: "rank.answer.accept"}); err != nil {
|
||||
if _, err := x.Context(ctx).Update(c, &entity.Config{ID: 44, Key: "rank.answer.accept"}); err != nil {
|
||||
log.Errorf("update %+v config failed: %s", c, err)
|
||||
return fmt.Errorf("update config failed: %w", err)
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@ func (vr *VoteRepo) Vote(ctx context.Context, op *schema.VoteOperationInfo) (err
|
|||
return nil, err
|
||||
}
|
||||
|
||||
err = vr.setActivityRankToZeroIfUserReachLimit(ctx, session, op, maxDailyRank)
|
||||
err = vr.setActivityRankToZeroIfUserReachLimit(ctx, session, op, userInfoMapping, maxDailyRank)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -217,16 +217,26 @@ func (vr *VoteRepo) acquireUserInfo(session *xorm.Session, userIDs []string) (ma
|
|||
}
|
||||
|
||||
func (vr *VoteRepo) setActivityRankToZeroIfUserReachLimit(ctx context.Context, session *xorm.Session,
|
||||
op *schema.VoteOperationInfo, maxDailyRank int) (err error) {
|
||||
op *schema.VoteOperationInfo, userInfoMapping map[string]*entity.User, maxDailyRank int) (err error) {
|
||||
// check if user reach daily rank limit
|
||||
for _, activity := range op.Activities {
|
||||
reach, err := vr.userRankRepo.CheckReachLimit(ctx, session, activity.ActivityUserID, maxDailyRank)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
if reach {
|
||||
activity.Rank = 0
|
||||
if activity.Rank > 0 {
|
||||
// check if reach max daily rank
|
||||
reach, err := vr.userRankRepo.CheckReachLimit(ctx, session, activity.ActivityUserID, maxDailyRank)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
if reach {
|
||||
activity.Rank = 0
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
// If user rank is lower than 1 after this action, then user rank will be set to 1 only.
|
||||
userCurrentScore := userInfoMapping[activity.ActivityUserID].Rank
|
||||
if userCurrentScore+activity.Rank < 1 {
|
||||
activity.Rank = 1 - userCurrentScore
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
|
|
@ -2,6 +2,7 @@ package answer
|
|||
|
||||
import (
|
||||
"context"
|
||||
"github.com/answerdev/answer/plugin"
|
||||
"time"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/constant"
|
||||
|
@ -59,6 +60,7 @@ func (ar *answerRepo) AddAnswer(ctx context.Context, answer *entity.Answer) (err
|
|||
answer.ID = uid.EnShortID(answer.ID)
|
||||
answer.QuestionID = uid.EnShortID(answer.QuestionID)
|
||||
}
|
||||
_ = ar.updateSearch(ctx, answer.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -73,6 +75,7 @@ func (ar *answerRepo) RemoveAnswer(ctx context.Context, id string) (err error) {
|
|||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
_ = ar.updateSearch(ctx, answer.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -84,6 +87,7 @@ func (ar *answerRepo) UpdateAnswer(ctx context.Context, answer *entity.Answer, C
|
|||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
_ = ar.updateSearch(ctx, answer.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -95,6 +99,7 @@ func (ar *answerRepo) UpdateAnswerStatus(ctx context.Context, answer *entity.Ans
|
|||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
_ = ar.updateSearch(ctx, answer.ID)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -184,6 +189,7 @@ func (ar *answerRepo) UpdateAccepted(ctx context.Context, id string, questionID
|
|||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
}
|
||||
_ = ar.updateSearch(ctx, id)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -319,3 +325,72 @@ func (ar *answerRepo) AdminSearchList(ctx context.Context, req *schema.AdminAnsw
|
|||
}
|
||||
return resp, total, nil
|
||||
}
|
||||
|
||||
// updateSearch update search, if search plugin not enable, do nothing
|
||||
func (ar *answerRepo) updateSearch(ctx context.Context, answerID string) (err error) {
|
||||
answerID = uid.DeShortID(answerID)
|
||||
// check search plugin
|
||||
var (
|
||||
s plugin.Search
|
||||
)
|
||||
_ = plugin.CallSearch(func(search plugin.Search) error {
|
||||
s = search
|
||||
return nil
|
||||
})
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
answer, exist, err := ar.GetAnswer(ctx, answerID)
|
||||
if !exist {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// get question
|
||||
var (
|
||||
question *entity.Question
|
||||
)
|
||||
exist, err = ar.data.DB.Context(ctx).Where("id = ?", answer.QuestionID).Get(&question)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
if !exist {
|
||||
return
|
||||
}
|
||||
|
||||
// get tags
|
||||
var (
|
||||
tagListList = make([]*entity.TagRel, 0)
|
||||
tags = make([]string, 0)
|
||||
)
|
||||
st := ar.data.DB.Context(ctx).Where("object_id = ?", uid.DeShortID(question.ID))
|
||||
st.Where("status = ?", entity.TagRelStatusAvailable)
|
||||
err = st.Find(&tagListList)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
for _, tag := range tagListList {
|
||||
tags = append(tags, tag.TagID)
|
||||
}
|
||||
|
||||
content := &plugin.SearchContent{
|
||||
ObjectID: answerID,
|
||||
Title: question.Title,
|
||||
Type: constant.AnswerObjectType,
|
||||
Content: answer.ParsedText,
|
||||
Answers: 0,
|
||||
Status: int64(answer.Status),
|
||||
Tags: tags,
|
||||
QuestionID: answer.QuestionID,
|
||||
UserID: answer.UserID,
|
||||
Views: int64(question.ViewCount),
|
||||
Created: answer.CreatedAt.Unix(),
|
||||
Active: answer.UpdatedAt.Unix(),
|
||||
Score: int64(answer.VoteCount),
|
||||
HasAccepted: answer.Accepted == schema.AnswerAcceptedEnable,
|
||||
}
|
||||
err = s.UpdateContent(ctx, answerID, content)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/answerdev/answer/plugin"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -56,6 +57,7 @@ func (qr *questionRepo) AddQuestion(ctx context.Context, question *entity.Questi
|
|||
if handler.GetEnableShortID(ctx) {
|
||||
question.ID = uid.EnShortID(question.ID)
|
||||
}
|
||||
_ = qr.updateSearch(ctx, question.ID)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -79,6 +81,7 @@ func (qr *questionRepo) UpdateQuestion(ctx context.Context, question *entity.Que
|
|||
if handler.GetEnableShortID(ctx) {
|
||||
question.ID = uid.EnShortID(question.ID)
|
||||
}
|
||||
_ = qr.updateSearch(ctx, question.ID)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -89,6 +92,7 @@ func (qr *questionRepo) UpdatePvCount(ctx context.Context, questionID string) (e
|
|||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
_ = qr.updateSearch(ctx, question.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -100,6 +104,7 @@ func (qr *questionRepo) UpdateAnswerCount(ctx context.Context, questionID string
|
|||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
_ = qr.updateSearch(ctx, question.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -121,6 +126,7 @@ func (qr *questionRepo) UpdateQuestionStatus(ctx context.Context, question *enti
|
|||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
_ = qr.updateSearch(ctx, question.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -130,6 +136,7 @@ func (qr *questionRepo) UpdateQuestionStatusWithOutUpdateTime(ctx context.Contex
|
|||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
_ = qr.updateSearch(ctx, question.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -148,6 +155,7 @@ func (qr *questionRepo) UpdateAccepted(ctx context.Context, question *entity.Que
|
|||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
_ = qr.updateSearch(ctx, question.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -157,6 +165,7 @@ func (qr *questionRepo) UpdateLastAnswer(ctx context.Context, question *entity.Q
|
|||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
_ = qr.updateSearch(ctx, question.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -407,3 +416,57 @@ func (qr *questionRepo) AdminQuestionPage(ctx context.Context, search *schema.Ad
|
|||
}
|
||||
return rows, count, nil
|
||||
}
|
||||
|
||||
// updateSearch update search, if search plugin not enable, do nothing
|
||||
func (qr *questionRepo) updateSearch(ctx context.Context, questionID string) (err error) {
|
||||
// check search plugin
|
||||
var s plugin.Search
|
||||
_ = plugin.CallSearch(func(search plugin.Search) error {
|
||||
s = search
|
||||
return nil
|
||||
})
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
questionID = uid.DeShortID(questionID)
|
||||
question, exist, err := qr.GetQuestion(ctx, questionID)
|
||||
if !exist {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// get tags
|
||||
var (
|
||||
tagListList = make([]*entity.TagRel, 0)
|
||||
tags = make([]string, 0)
|
||||
)
|
||||
session := qr.data.DB.Context(ctx).Where("object_id = ?", questionID)
|
||||
session.Where("status = ?", entity.TagRelStatusAvailable)
|
||||
err = session.Find(&tagListList)
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
for _, tag := range tagListList {
|
||||
tags = append(tags, tag.TagID)
|
||||
}
|
||||
content := &plugin.SearchContent{
|
||||
ObjectID: questionID,
|
||||
Title: question.Title,
|
||||
Type: constant.QuestionObjectType,
|
||||
Content: question.ParsedText,
|
||||
Answers: int64(question.AnswerCount),
|
||||
Status: int64(question.Status),
|
||||
Tags: tags,
|
||||
QuestionID: questionID,
|
||||
UserID: question.UserID,
|
||||
Views: int64(question.ViewCount),
|
||||
Created: question.CreatedAt.Unix(),
|
||||
Active: question.UpdatedAt.Unix(),
|
||||
Score: int64(question.VoteCount),
|
||||
HasAccepted: question.AcceptedAnswerID != "" && question.AcceptedAnswerID != "0",
|
||||
}
|
||||
err = s.UpdateContent(ctx, questionID, content)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package search_common
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/answerdev/answer/plugin"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -426,6 +427,34 @@ func (sr *searchRepo) parseOrder(ctx context.Context, order string) (res string)
|
|||
return
|
||||
}
|
||||
|
||||
// ParseSearchPluginResult parse search plugin result
|
||||
func (sr *searchRepo) ParseSearchPluginResult(ctx context.Context, sres []plugin.SearchResult) (resp []schema.SearchResp, err error) {
|
||||
var (
|
||||
qres []map[string][]byte
|
||||
res = make([]map[string][]byte, 0)
|
||||
b *builder.Builder
|
||||
)
|
||||
for _, r := range sres {
|
||||
switch r.Type {
|
||||
case "question":
|
||||
b = builder.MySQL().Select(qFields...).From("question").Where(builder.Eq{"id": r.ID}).
|
||||
And(builder.Lt{"`status`": entity.QuestionStatusDeleted})
|
||||
case "answer":
|
||||
b = builder.MySQL().Select(aFields...).From("answer").LeftJoin("`question`", "`question`.`id` = `answer`.`question_id`").
|
||||
Where(builder.Eq{"`answer`.`id`": r.ID}).
|
||||
And(builder.Lt{"`question`.`status`": entity.QuestionStatusDeleted}).
|
||||
And(builder.Lt{"`answer`.`status`": entity.AnswerStatusDeleted}).And(builder.Eq{"`question`.`show`": entity.QuestionShow})
|
||||
}
|
||||
qres, err = sr.data.DB.Context(ctx).Query(b)
|
||||
if err != nil || len(qres) == 0 {
|
||||
continue
|
||||
}
|
||||
res = append(res, qres[0])
|
||||
}
|
||||
return sr.parseResult(ctx, res)
|
||||
}
|
||||
|
||||
// parseResult parse search result, return the data structure
|
||||
func (sr *searchRepo) parseResult(ctx context.Context, res []map[string][]byte) (resp []schema.SearchResp, err error) {
|
||||
for _, r := range res {
|
||||
var (
|
||||
|
|
|
@ -2,6 +2,7 @@ package schema
|
|||
|
||||
// AcceptAnswerOperationInfo accept answer operation info
|
||||
type AcceptAnswerOperationInfo struct {
|
||||
TriggerUserID string
|
||||
QuestionObjectID string
|
||||
QuestionUserID string
|
||||
AnswerObjectID string
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package schema
|
||||
|
||||
import "github.com/answerdev/answer/internal/base/constant"
|
||||
|
||||
type SearchDTO struct {
|
||||
UserID string // UserID current login user ID
|
||||
Query string `validate:"required,gte=1,lte=60" json:"q" form:"q"` // Query the query string
|
||||
|
@ -8,6 +10,44 @@ type SearchDTO struct {
|
|||
Order string `validate:"required,oneof=newest active score relevance" form:"order,default=relevance" json:"order" enums:"newest,active,score,relevance"`
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
type SearchObject struct {
|
||||
ID string `json:"id"`
|
||||
QuestionID string `json:"question_id"`
|
||||
|
|
|
@ -33,23 +33,24 @@ func NewAnswerActivityService(
|
|||
|
||||
// AcceptAnswer accept answer change activity
|
||||
func (as *AnswerActivityService) AcceptAnswer(ctx context.Context,
|
||||
answerObjID, questionObjID, questionUserID, answerUserID string, isSelf bool) (err error) {
|
||||
operationInfo := as.createAcceptAnswerOperationInfo(ctx,
|
||||
loginUserID, answerObjID, questionObjID, questionUserID, answerUserID string, isSelf bool) (err error) {
|
||||
operationInfo := as.createAcceptAnswerOperationInfo(ctx, loginUserID,
|
||||
answerObjID, questionObjID, questionUserID, answerUserID, isSelf)
|
||||
return as.answerActivityRepo.SaveAcceptAnswerActivity(ctx, operationInfo)
|
||||
}
|
||||
|
||||
// CancelAcceptAnswer cancel accept answer change activity
|
||||
func (as *AnswerActivityService) CancelAcceptAnswer(ctx context.Context,
|
||||
answerObjID, questionObjID, questionUserID, answerUserID string) (err error) {
|
||||
operationInfo := as.createAcceptAnswerOperationInfo(ctx,
|
||||
loginUserID, answerObjID, questionObjID, questionUserID, answerUserID string) (err error) {
|
||||
operationInfo := as.createAcceptAnswerOperationInfo(ctx, loginUserID,
|
||||
answerObjID, questionObjID, questionUserID, answerUserID, false)
|
||||
return as.answerActivityRepo.SaveCancelAcceptAnswerActivity(ctx, operationInfo)
|
||||
}
|
||||
|
||||
func (as *AnswerActivityService) createAcceptAnswerOperationInfo(ctx context.Context,
|
||||
func (as *AnswerActivityService) createAcceptAnswerOperationInfo(ctx context.Context, loginUserID,
|
||||
answerObjID, questionObjID, questionUserID, answerUserID string, isSelf bool) *schema.AcceptAnswerOperationInfo {
|
||||
operationInfo := &schema.AcceptAnswerOperationInfo{
|
||||
TriggerUserID: loginUserID,
|
||||
QuestionObjectID: questionObjID,
|
||||
QuestionUserID: questionUserID,
|
||||
AnswerObjectID: answerObjID,
|
||||
|
@ -79,11 +80,11 @@ func (as *AnswerActivityService) getActivities(ctx context.Context, op *schema.A
|
|||
|
||||
if action == activity_type.AnswerAccept {
|
||||
t.ActivityUserID = op.QuestionUserID
|
||||
t.TriggerUserID = op.AnswerUserID
|
||||
t.TriggerUserID = op.TriggerUserID
|
||||
t.OriginalObjectID = op.QuestionObjectID // if activity is 'accept' means this question is accept the answer.
|
||||
} else {
|
||||
t.ActivityUserID = op.AnswerUserID
|
||||
t.TriggerUserID = op.AnswerUserID
|
||||
t.TriggerUserID = op.TriggerUserID
|
||||
t.OriginalObjectID = op.AnswerObjectID // if activity is 'accepted' means this answer was accepted.
|
||||
}
|
||||
activities = append(activities, t)
|
||||
|
|
|
@ -390,15 +390,15 @@ func (as *AnswerService) updateAnswerRank(ctx context.Context, userID string,
|
|||
) {
|
||||
// if this question is already been answered, should cancel old answer rank
|
||||
if oldAnswerInfo != nil {
|
||||
err := as.answerActivityService.CancelAcceptAnswer(
|
||||
ctx, questionInfo.AcceptedAnswerID, questionInfo.ID, questionInfo.UserID, oldAnswerInfo.UserID)
|
||||
err := as.answerActivityService.CancelAcceptAnswer(ctx, userID,
|
||||
questionInfo.AcceptedAnswerID, questionInfo.ID, questionInfo.UserID, oldAnswerInfo.UserID)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
if newAnswerInfo.ID != "" {
|
||||
err := as.answerActivityService.AcceptAnswer(
|
||||
ctx, newAnswerInfo.ID, questionInfo.ID, questionInfo.UserID, newAnswerInfo.UserID, newAnswerInfo.UserID == userID)
|
||||
err := as.answerActivityService.AcceptAnswer(ctx, userID, newAnswerInfo.ID,
|
||||
questionInfo.ID, questionInfo.UserID, newAnswerInfo.UserID, newAnswerInfo.UserID == userID)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ func (cs *CommentCommonService) GetComment(ctx context.Context, commentID string
|
|||
return
|
||||
}
|
||||
if !exist {
|
||||
return nil, errors.BadRequest(reason.UnknownError)
|
||||
return nil, errors.BadRequest(reason.CommentNotFound)
|
||||
}
|
||||
|
||||
resp = &schema.GetCommentResp{}
|
||||
|
|
|
@ -3,10 +3,12 @@ package search_common
|
|||
import (
|
||||
"context"
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/answerdev/answer/plugin"
|
||||
)
|
||||
|
||||
type SearchRepo interface {
|
||||
SearchContents(ctx context.Context, words []string, tagIDs []string, userID string, votes, page, size int, order string) (resp []schema.SearchResp, total int64, err error)
|
||||
SearchQuestions(ctx context.Context, words []string, tagIDs []string, notAccepted bool, views, answers int, page, size int, order string) (resp []schema.SearchResp, total int64, err error)
|
||||
SearchAnswers(ctx context.Context, words []string, tagIDs []string, accepted bool, questionID string, page, size int, order string) (resp []schema.SearchResp, total int64, err error)
|
||||
ParseSearchPluginResult(ctx context.Context, sres []plugin.SearchResult) (resp []schema.SearchResp, err error)
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package search_parser
|
|||
|
||||
import (
|
||||
"context"
|
||||
"github.com/answerdev/answer/internal/base/constant"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
|
@ -25,141 +26,66 @@ func NewSearchParser(tagCommonService *tag_common.TagCommonService, userCommon *
|
|||
|
||||
// ParseStructure parse search structure, maybe match one of type all/questions/answers,
|
||||
// but if match two type, it will return false
|
||||
func (sp *SearchParser) ParseStructure(dto *schema.SearchDTO) (
|
||||
searchType string,
|
||||
// search all
|
||||
userID string,
|
||||
votes int,
|
||||
// search questions
|
||||
notAccepted bool,
|
||||
isQuestion bool,
|
||||
views,
|
||||
answers int,
|
||||
// search answers
|
||||
accepted bool,
|
||||
questionID string,
|
||||
isAnswer bool,
|
||||
// common fields
|
||||
tags,
|
||||
words []string,
|
||||
) {
|
||||
func (sp *SearchParser) ParseStructure(ctx context.Context, dto *schema.SearchDTO) (cond *schema.SearchCondition) {
|
||||
cond = &schema.SearchCondition{}
|
||||
var (
|
||||
query = dto.Query
|
||||
currentUserID = dto.UserID
|
||||
all = 0
|
||||
q = 0
|
||||
a = 0
|
||||
withWords []string
|
||||
limitWords = 5
|
||||
query = dto.Query
|
||||
limitWords = 5
|
||||
)
|
||||
|
||||
// match tags
|
||||
tags = sp.parseTags(&query)
|
||||
cond.Tags = sp.parseTags(ctx, &query)
|
||||
|
||||
// match all
|
||||
userID = sp.parseUserID(&query, currentUserID)
|
||||
if userID != "" {
|
||||
searchType = "all"
|
||||
all = 1
|
||||
}
|
||||
votes = sp.parseVotes(&query)
|
||||
if votes != -1 {
|
||||
searchType = "all"
|
||||
all = 1
|
||||
}
|
||||
withWords = sp.parseWithin(&query)
|
||||
if len(withWords) > 0 {
|
||||
searchType = "all"
|
||||
all = 1
|
||||
}
|
||||
cond.UserID = sp.parseUserID(ctx, &query, dto.UserID)
|
||||
cond.VoteAmount = sp.parseVotes(&query)
|
||||
cond.Words = sp.parseWithin(&query)
|
||||
|
||||
// match questions
|
||||
notAccepted = sp.parseNotAccepted(&query)
|
||||
if notAccepted {
|
||||
searchType = "question"
|
||||
q = 1
|
||||
cond.NotAccepted = sp.parseNotAccepted(&query)
|
||||
if cond.NotAccepted {
|
||||
cond.TargetType = constant.QuestionObjectType
|
||||
}
|
||||
isQuestion = sp.parseIsQuestion(&query)
|
||||
if isQuestion {
|
||||
searchType = "question"
|
||||
q = 1
|
||||
cond.Views = sp.parseViews(&query)
|
||||
if cond.Views != -1 {
|
||||
cond.TargetType = constant.QuestionObjectType
|
||||
}
|
||||
views = sp.parseViews(&query)
|
||||
if views != -1 {
|
||||
searchType = "question"
|
||||
q = 1
|
||||
}
|
||||
answers = sp.parseAnswers(&query)
|
||||
if answers != -1 {
|
||||
searchType = "question"
|
||||
q = 1
|
||||
cond.AnswerAmount = sp.parseAnswers(&query)
|
||||
if cond.AnswerAmount != -1 {
|
||||
cond.TargetType = constant.QuestionObjectType
|
||||
}
|
||||
|
||||
// match answers
|
||||
accepted = sp.parseAccepted(&query)
|
||||
if accepted {
|
||||
searchType = "answer"
|
||||
a = 1
|
||||
cond.Accepted = sp.parseAccepted(&query)
|
||||
if cond.Accepted {
|
||||
cond.TargetType = constant.AnswerObjectType
|
||||
}
|
||||
questionID = sp.parseQuestionID(&query)
|
||||
if questionID != "" {
|
||||
searchType = "answer"
|
||||
a = 1
|
||||
cond.QuestionID = sp.parseQuestionID(&query)
|
||||
if cond.QuestionID != "" {
|
||||
cond.TargetType = constant.AnswerObjectType
|
||||
}
|
||||
isAnswer = sp.parseIsAnswer(&query)
|
||||
if isAnswer {
|
||||
searchType = "answer"
|
||||
a = 1
|
||||
|
||||
if sp.parseIsQuestion(&query) {
|
||||
cond.TargetType = constant.QuestionObjectType
|
||||
}
|
||||
if sp.parseIsAnswer(&query) {
|
||||
cond.TargetType = constant.AnswerObjectType
|
||||
}
|
||||
|
||||
if len(strings.TrimSpace(query)) > 0 {
|
||||
words = strings.Split(strings.TrimSpace(query), " ")
|
||||
} else {
|
||||
words = []string{}
|
||||
}
|
||||
|
||||
if len(withWords) > 0 {
|
||||
words = append(withWords, words...)
|
||||
words := strings.Split(strings.TrimSpace(query), " ")
|
||||
cond.Words = append(cond.Words, words...)
|
||||
}
|
||||
|
||||
// check limit words
|
||||
if len(words) > limitWords {
|
||||
words = words[:limitWords]
|
||||
if len(cond.Words) > limitWords {
|
||||
cond.Words = cond.Words[:limitWords]
|
||||
}
|
||||
|
||||
// check tags' search is all or question
|
||||
if len(tags) > 0 {
|
||||
if len(words) > 0 {
|
||||
searchType = "all"
|
||||
all = 1
|
||||
} else if isAnswer {
|
||||
searchType = "answer"
|
||||
a = 1
|
||||
all = 0
|
||||
q = 0
|
||||
} else {
|
||||
searchType = "question"
|
||||
q = 1
|
||||
all = 0
|
||||
a = 0
|
||||
}
|
||||
}
|
||||
|
||||
// check match types greater than 1
|
||||
if all+q+a > 1 {
|
||||
searchType = ""
|
||||
}
|
||||
|
||||
// check not match
|
||||
if all+q+a == 0 && len(words) > 0 {
|
||||
searchType = "all"
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// parseTags parse search tags, return tag ids array
|
||||
func (sp *SearchParser) parseTags(query *string) (tags []string) {
|
||||
func (sp *SearchParser) parseTags(ctx context.Context, query *string) (tags []string) {
|
||||
var (
|
||||
// expire tag pattern
|
||||
exprTag = `(?m)\[([a-zA-Z0-9-\+\.#]+)\]{1}?`
|
||||
|
@ -175,7 +101,7 @@ func (sp *SearchParser) parseTags(query *string) (tags []string) {
|
|||
|
||||
tags = []string{}
|
||||
for _, item := range res {
|
||||
tag, exists, err := sp.tagCommonService.GetTagBySlugName(context.TODO(), item[1])
|
||||
tag, exists, err := sp.tagCommonService.GetTagBySlugName(ctx, item[1])
|
||||
if err != nil || !exists {
|
||||
continue
|
||||
}
|
||||
|
@ -193,7 +119,7 @@ func (sp *SearchParser) parseTags(query *string) (tags []string) {
|
|||
}
|
||||
|
||||
// parseUserID return user id or current login user id
|
||||
func (sp *SearchParser) parseUserID(query *string, currentUserID string) (userID string) {
|
||||
func (sp *SearchParser) parseUserID(ctx context.Context, query *string, currentUserID string) (userID string) {
|
||||
var (
|
||||
exprUserID = `(?m)^user:([a-z0-9._-]+)`
|
||||
exprMe = "user:me"
|
||||
|
@ -207,7 +133,7 @@ func (sp *SearchParser) parseUserID(query *string, currentUserID string) (userID
|
|||
q = strings.ReplaceAll(q, exprMe, "")
|
||||
} else if len(res) == 2 {
|
||||
name := res[1]
|
||||
user, has, err := sp.userCommon.GetUserBasicInfoByUserName(context.TODO(), name)
|
||||
user, has, err := sp.userCommon.GetUserBasicInfoByUserName(ctx, name)
|
||||
if err == nil && has {
|
||||
userID = user.ID
|
||||
q = re.ReplaceAllString(q, "")
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/answerdev/answer/internal/service/search_common"
|
||||
"github.com/answerdev/answer/internal/service/search_parser"
|
||||
"github.com/answerdev/answer/plugin"
|
||||
)
|
||||
|
||||
type SearchService struct {
|
||||
|
@ -23,40 +24,48 @@ func NewSearchService(
|
|||
}
|
||||
|
||||
// Search search contents
|
||||
func (ss *SearchService) Search(ctx context.Context, dto *schema.SearchDTO) (resp []schema.SearchResp, total int64, extra interface{}, err error) {
|
||||
extra = nil
|
||||
func (ss *SearchService) Search(ctx context.Context, dto *schema.SearchDTO) (resp []schema.SearchResp, total int64, err error) {
|
||||
if dto.Page < 1 {
|
||||
dto.Page = 1
|
||||
}
|
||||
|
||||
// search type
|
||||
searchType,
|
||||
// search all
|
||||
userID,
|
||||
votes,
|
||||
// search questions
|
||||
notAccepted,
|
||||
_,
|
||||
views,
|
||||
answers,
|
||||
// search answers
|
||||
accepted,
|
||||
questionID,
|
||||
_,
|
||||
// common fields
|
||||
tags,
|
||||
words := ss.searchParser.ParseStructure(dto)
|
||||
cond := ss.searchParser.ParseStructure(ctx, dto)
|
||||
|
||||
switch searchType {
|
||||
case "all":
|
||||
resp, total, err = ss.searchRepo.SearchContents(ctx, words, tags, userID, votes, dto.Page, dto.Size, dto.Order)
|
||||
if err != nil {
|
||||
return nil, 0, nil, err
|
||||
// check search plugin
|
||||
var s plugin.Search
|
||||
_ = plugin.CallSearch(func(search plugin.Search) error {
|
||||
s = search
|
||||
return nil
|
||||
})
|
||||
|
||||
// search plugin is not found, call system search
|
||||
if s == nil {
|
||||
if cond.SearchAll() {
|
||||
resp, total, err = ss.searchRepo.SearchContents(ctx, cond.Words, cond.Tags, cond.UserID, cond.VoteAmount, dto.Page, dto.Size, dto.Order)
|
||||
} else if cond.SearchQuestion() {
|
||||
resp, total, err = ss.searchRepo.SearchQuestions(ctx, cond.Words, cond.Tags, cond.NotAccepted, cond.Views, cond.AnswerAmount, dto.Page, dto.Size, dto.Order)
|
||||
} else if cond.SearchAnswer() {
|
||||
resp, total, err = ss.searchRepo.SearchAnswers(ctx, cond.Words, cond.Tags, cond.Accepted, cond.QuestionID, dto.Page, dto.Size, dto.Order)
|
||||
}
|
||||
case "question":
|
||||
resp, total, err = ss.searchRepo.SearchQuestions(ctx, words, tags, notAccepted, views, answers, dto.Page, dto.Size, dto.Order)
|
||||
case "answer":
|
||||
resp, total, err = ss.searchRepo.SearchAnswers(ctx, words, tags, accepted, questionID, dto.Page, dto.Size, dto.Order)
|
||||
return
|
||||
}
|
||||
return ss.searchByPlugin(ctx, s, cond, dto)
|
||||
}
|
||||
|
||||
func (ss *SearchService) searchByPlugin(ctx context.Context, finder plugin.Search, cond *schema.SearchCondition, dto *schema.SearchDTO) (resp []schema.SearchResp, total int64, err error) {
|
||||
var res []plugin.SearchResult
|
||||
if cond.SearchAll() {
|
||||
res, total, err = finder.SearchContents(ctx, cond.Words, cond.Tags, cond.UserID, cond.VoteAmount, dto.Page, dto.Size, dto.Order)
|
||||
} else if cond.SearchQuestion() {
|
||||
res, total, err = finder.SearchQuestions(ctx, cond.Words, cond.Tags, cond.NotAccepted, cond.Views, cond.AnswerAmount, dto.Page, dto.Size, dto.Order)
|
||||
} else if cond.SearchAnswer() {
|
||||
res, total, err = finder.SearchAnswers(ctx, cond.Words, cond.Tags, cond.Accepted, cond.QuestionID, dto.Page, dto.Size, dto.Order)
|
||||
}
|
||||
|
||||
resp, err = ss.searchRepo.ParseSearchPluginResult(ctx, res)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -56,6 +56,10 @@ func Register(p Base) {
|
|||
if _, ok := p.(Agent); ok {
|
||||
registerAgent(p.(Agent))
|
||||
}
|
||||
|
||||
if _, ok := p.(Search); ok {
|
||||
registerSearch(p.(Search))
|
||||
}
|
||||
}
|
||||
|
||||
type Stack[T Base] struct {
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
package plugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type SearchResult struct {
|
||||
// ID content ID
|
||||
ID string
|
||||
// Type content type, example: "answer", "question"
|
||||
Type string
|
||||
}
|
||||
|
||||
type SearchContent struct {
|
||||
ObjectID string `json:"objectID"`
|
||||
Title string `json:"title"`
|
||||
Type string `json:"type"`
|
||||
Content string `json:"content"`
|
||||
Answers int64 `json:"answers"`
|
||||
Status int64 `json:"status"`
|
||||
Tags []string `json:"tags"`
|
||||
QuestionID string `json:"questionID"`
|
||||
UserID string `json:"userID"`
|
||||
Views int64 `json:"views"`
|
||||
Created int64 `json:"created"`
|
||||
Active int64 `json:"active"`
|
||||
Score int64 `json:"score"`
|
||||
HasAccepted bool `json:"hasAccepted"`
|
||||
}
|
||||
|
||||
type Search interface {
|
||||
Base
|
||||
SearchContents(ctx context.Context, words []string, tagIDs []string, userID string, votes int, page, size int, order string) (res []SearchResult, total int64, err error)
|
||||
SearchQuestions(ctx context.Context, words []string, tagIDs []string, notAccepted bool, views, answers int, page, size int, order string) (res []SearchResult, total int64, err error)
|
||||
SearchAnswers(ctx context.Context, words []string, tagIDs []string, accepted bool, questionID string, page, size int, order string) (res []SearchResult, total int64, err error)
|
||||
UpdateContent(ctx context.Context, contentID string, content *SearchContent) error
|
||||
DeleteContent(ctx context.Context, contentID string) error
|
||||
}
|
||||
|
||||
var (
|
||||
// CallUserCenter is a function that calls all registered parsers
|
||||
CallSearch,
|
||||
registerSearch = MakePlugin[Search](false)
|
||||
)
|
Loading…
Reference in New Issue