mirror of https://gitee.com/answerdev/answer.git
434 lines
11 KiB
Go
434 lines
11 KiB
Go
package activity
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"github.com/answerdev/answer/pkg/converter"
|
|
|
|
"github.com/answerdev/answer/internal/base/pager"
|
|
"github.com/answerdev/answer/internal/service/config"
|
|
"github.com/answerdev/answer/internal/service/notice_queue"
|
|
"github.com/answerdev/answer/internal/service/rank"
|
|
"github.com/answerdev/answer/pkg/obj"
|
|
|
|
"xorm.io/builder"
|
|
|
|
"github.com/answerdev/answer/internal/service/activity_common"
|
|
"github.com/answerdev/answer/internal/service/unique"
|
|
|
|
"github.com/answerdev/answer/internal/base/data"
|
|
"github.com/answerdev/answer/internal/base/reason"
|
|
"github.com/answerdev/answer/internal/entity"
|
|
"github.com/answerdev/answer/internal/schema"
|
|
"github.com/answerdev/answer/internal/service"
|
|
"github.com/segmentfault/pacman/errors"
|
|
"xorm.io/xorm"
|
|
)
|
|
|
|
// VoteRepo activity repository
|
|
type VoteRepo struct {
|
|
data *data.Data
|
|
uniqueIDRepo unique.UniqueIDRepo
|
|
configRepo config.ConfigRepo
|
|
activityRepo activity_common.ActivityRepo
|
|
userRankRepo rank.UserRankRepo
|
|
voteCommon activity_common.VoteRepo
|
|
}
|
|
|
|
// NewVoteRepo new repository
|
|
func NewVoteRepo(
|
|
data *data.Data,
|
|
uniqueIDRepo unique.UniqueIDRepo,
|
|
configRepo config.ConfigRepo,
|
|
activityRepo activity_common.ActivityRepo,
|
|
userRankRepo rank.UserRankRepo,
|
|
voteCommon activity_common.VoteRepo,
|
|
) service.VoteRepo {
|
|
return &VoteRepo{
|
|
data: data,
|
|
uniqueIDRepo: uniqueIDRepo,
|
|
configRepo: configRepo,
|
|
activityRepo: activityRepo,
|
|
userRankRepo: userRankRepo,
|
|
voteCommon: voteCommon,
|
|
}
|
|
}
|
|
|
|
var LimitUpActions = map[string][]string{
|
|
"question": {"vote_up", "voted_up"},
|
|
"answer": {"vote_up", "voted_up"},
|
|
"comment": {"vote_up"},
|
|
}
|
|
|
|
var LimitDownActions = map[string][]string{
|
|
"question": {"vote_down", "voted_down"},
|
|
"answer": {"vote_down", "voted_down"},
|
|
"comment": {"vote_down"},
|
|
}
|
|
|
|
func (vr *VoteRepo) vote(ctx context.Context, objectID string, userID, objectUserID string, actions []string) (resp *schema.VoteResp, err error) {
|
|
resp = &schema.VoteResp{}
|
|
_, err = vr.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
|
|
result = nil
|
|
for _, action := range actions {
|
|
var (
|
|
existsActivity entity.Activity
|
|
insertActivity entity.Activity
|
|
has bool
|
|
triggerUserID,
|
|
activityUserID string
|
|
activityType, deltaRank, hasRank int
|
|
)
|
|
|
|
activityUserID, activityType, deltaRank, hasRank, err = vr.CheckRank(ctx, objectID, objectUserID, userID, action)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
triggerUserID = userID
|
|
if userID == activityUserID {
|
|
triggerUserID = "0"
|
|
}
|
|
|
|
// check is voted up
|
|
has, _ = session.
|
|
Where(builder.Eq{"object_id": objectID}).
|
|
And(builder.Eq{"user_id": activityUserID}).
|
|
And(builder.Eq{"trigger_user_id": triggerUserID}).
|
|
And(builder.Eq{"activity_type": activityType}).
|
|
Get(&existsActivity)
|
|
|
|
// is is voted,return
|
|
if has && existsActivity.Cancelled == 0 {
|
|
return
|
|
}
|
|
|
|
insertActivity = entity.Activity{
|
|
ObjectID: objectID,
|
|
UserID: activityUserID,
|
|
TriggerUserID: converter.StringToInt64(triggerUserID),
|
|
ActivityType: activityType,
|
|
Rank: deltaRank,
|
|
HasRank: hasRank,
|
|
Cancelled: 0,
|
|
}
|
|
|
|
// trigger user rank and send notification
|
|
if hasRank != 0 {
|
|
var isReachStandard bool
|
|
isReachStandard, err = vr.userRankRepo.TriggerUserRank(ctx, session, activityUserID, deltaRank, activityType)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if isReachStandard {
|
|
insertActivity.Rank = 0
|
|
}
|
|
|
|
vr.sendNotification(ctx, activityUserID, objectUserID, objectID)
|
|
}
|
|
|
|
if has {
|
|
if _, err = session.Where("id = ?", existsActivity.ID).Cols("`cancelled`").
|
|
Update(&entity.Activity{
|
|
Cancelled: 0,
|
|
}); err != nil {
|
|
return
|
|
}
|
|
} else {
|
|
_, err = session.Insert(&insertActivity)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// update votes
|
|
if action == "vote_down" || action == "vote_up" {
|
|
votes := 1
|
|
if action == "vote_down" {
|
|
votes = -1
|
|
}
|
|
err = vr.updateVotes(ctx, session, objectID, votes)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
return
|
|
})
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
resp, err = vr.GetVoteResultByObjectId(ctx, objectID)
|
|
resp.VoteStatus = vr.voteCommon.GetVoteStatus(ctx, objectID, userID)
|
|
|
|
return
|
|
}
|
|
|
|
func (vr *VoteRepo) voteCancel(ctx context.Context, objectID string, userID, objectUserID string, actions []string) (resp *schema.VoteResp, err error) {
|
|
resp = &schema.VoteResp{}
|
|
_, err = vr.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
|
|
for _, action := range actions {
|
|
var (
|
|
existsActivity entity.Activity
|
|
has bool
|
|
triggerUserID,
|
|
activityUserID string
|
|
activityType,
|
|
deltaRank, hasRank int
|
|
)
|
|
result = nil
|
|
|
|
activityUserID, activityType, deltaRank, hasRank, err = vr.CheckRank(ctx, objectID, objectUserID, userID, action)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
triggerUserID = userID
|
|
if userID == activityUserID {
|
|
triggerUserID = "0"
|
|
}
|
|
|
|
has, err = session.
|
|
Where(builder.Eq{"user_id": activityUserID}).
|
|
And(builder.Eq{"trigger_user_id": triggerUserID}).
|
|
And(builder.Eq{"activity_type": activityType}).
|
|
And(builder.Eq{"object_id": objectID}).
|
|
Get(&existsActivity)
|
|
|
|
if !has {
|
|
return
|
|
}
|
|
|
|
if existsActivity.Cancelled == 1 {
|
|
return
|
|
}
|
|
|
|
if _, err = session.Where("id = ?", existsActivity.ID).Cols("`cancelled`").
|
|
Update(&entity.Activity{
|
|
Cancelled: 1,
|
|
}); err != nil {
|
|
return
|
|
}
|
|
|
|
// trigger user rank and send notification
|
|
if hasRank != 0 {
|
|
_, err = vr.userRankRepo.TriggerUserRank(ctx, session, activityUserID, -deltaRank, activityType)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
vr.sendNotification(ctx, activityUserID, objectUserID, objectID)
|
|
}
|
|
|
|
// update votes
|
|
if action == "vote_down" || action == "vote_up" {
|
|
votes := -1
|
|
if action == "vote_down" {
|
|
votes = 1
|
|
}
|
|
err = vr.updateVotes(ctx, session, objectID, votes)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
return
|
|
})
|
|
if err != nil {
|
|
return
|
|
}
|
|
resp, err = vr.GetVoteResultByObjectId(ctx, objectID)
|
|
resp.VoteStatus = vr.voteCommon.GetVoteStatus(ctx, objectID, userID)
|
|
return
|
|
}
|
|
|
|
func (vr *VoteRepo) VoteUp(ctx context.Context, objectID string, userID, objectUserID string) (resp *schema.VoteResp, err error) {
|
|
resp = &schema.VoteResp{}
|
|
objectType, err := obj.GetObjectTypeStrByObjectID(objectID)
|
|
if err != nil {
|
|
err = errors.BadRequest(reason.ObjectNotFound)
|
|
return
|
|
}
|
|
|
|
actions, ok := LimitUpActions[objectType]
|
|
if !ok {
|
|
err = errors.BadRequest(reason.DisallowVote)
|
|
return
|
|
}
|
|
|
|
_, _ = vr.VoteDownCancel(ctx, objectID, userID, objectUserID)
|
|
return vr.vote(ctx, objectID, userID, objectUserID, actions)
|
|
}
|
|
|
|
func (vr *VoteRepo) VoteDown(ctx context.Context, objectID string, userID, objectUserID string) (resp *schema.VoteResp, err error) {
|
|
resp = &schema.VoteResp{}
|
|
objectType, err := obj.GetObjectTypeStrByObjectID(objectID)
|
|
if err != nil {
|
|
err = errors.BadRequest(reason.ObjectNotFound)
|
|
return
|
|
}
|
|
actions, ok := LimitDownActions[objectType]
|
|
if !ok {
|
|
err = errors.BadRequest(reason.DisallowVote)
|
|
return
|
|
}
|
|
|
|
_, _ = vr.VoteUpCancel(ctx, objectID, userID, objectUserID)
|
|
return vr.vote(ctx, objectID, userID, objectUserID, actions)
|
|
}
|
|
|
|
func (vr *VoteRepo) VoteUpCancel(ctx context.Context, objectID string, userID, objectUserID string) (resp *schema.VoteResp, err error) {
|
|
var objectType string
|
|
resp = &schema.VoteResp{}
|
|
|
|
objectType, err = obj.GetObjectTypeStrByObjectID(objectID)
|
|
if err != nil {
|
|
err = errors.BadRequest(reason.ObjectNotFound)
|
|
return
|
|
}
|
|
actions, ok := LimitUpActions[objectType]
|
|
if !ok {
|
|
err = errors.BadRequest(reason.DisallowVote)
|
|
return
|
|
}
|
|
|
|
return vr.voteCancel(ctx, objectID, userID, objectUserID, actions)
|
|
}
|
|
|
|
func (vr *VoteRepo) VoteDownCancel(ctx context.Context, objectID string, userID, objectUserID string) (resp *schema.VoteResp, err error) {
|
|
var objectType string
|
|
resp = &schema.VoteResp{}
|
|
|
|
objectType, err = obj.GetObjectTypeStrByObjectID(objectID)
|
|
if err != nil {
|
|
err = errors.BadRequest(reason.ObjectNotFound)
|
|
return
|
|
}
|
|
actions, ok := LimitDownActions[objectType]
|
|
if !ok {
|
|
err = errors.BadRequest(reason.DisallowVote)
|
|
return
|
|
}
|
|
|
|
return vr.voteCancel(ctx, objectID, userID, objectUserID, actions)
|
|
}
|
|
|
|
func (vr *VoteRepo) CheckRank(ctx context.Context, objectID, objectUserID, userID string, action string) (activityUserID string, activityType, rank, hasRank int, err error) {
|
|
activityType, rank, hasRank, err = vr.activityRepo.GetActivityTypeByObjID(ctx, objectID, action)
|
|
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
activityUserID = userID
|
|
if strings.Contains(action, "voted") {
|
|
activityUserID = objectUserID
|
|
}
|
|
|
|
return activityUserID, activityType, rank, hasRank, nil
|
|
}
|
|
|
|
func (vr *VoteRepo) GetVoteResultByObjectId(ctx context.Context, objectID string) (resp *schema.VoteResp, err error) {
|
|
resp = &schema.VoteResp{}
|
|
for _, action := range []string{"vote_up", "vote_down"} {
|
|
var (
|
|
activity entity.Activity
|
|
votes int64
|
|
activityType int
|
|
)
|
|
|
|
activityType, _, _, _ = vr.activityRepo.GetActivityTypeByObjID(ctx, objectID, action)
|
|
|
|
votes, err = vr.data.DB.Where(builder.Eq{"object_id": objectID}).
|
|
And(builder.Eq{"activity_type": activityType}).
|
|
And(builder.Eq{"cancelled": 0}).
|
|
Count(&activity)
|
|
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
if action == "vote_up" {
|
|
resp.UpVotes = int(votes)
|
|
} else {
|
|
resp.DownVotes = int(votes)
|
|
}
|
|
}
|
|
|
|
resp.Votes = resp.UpVotes - resp.DownVotes
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
func (vr *VoteRepo) ListUserVotes(
|
|
ctx context.Context,
|
|
userID string,
|
|
req schema.GetVoteWithPageReq,
|
|
activityTypes []int,
|
|
) (voteList []entity.Activity, total int64, err error) {
|
|
session := vr.data.DB.NewSession()
|
|
cond := builder.
|
|
And(
|
|
builder.Eq{"user_id": userID},
|
|
builder.Eq{"cancelled": 0},
|
|
builder.In("activity_type", activityTypes),
|
|
)
|
|
|
|
session.Where(cond).OrderBy("updated_at desc")
|
|
|
|
total, err = pager.Help(req.Page, req.PageSize, &voteList, &entity.Activity{}, session)
|
|
if err != nil {
|
|
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
|
}
|
|
return
|
|
}
|
|
|
|
// updateVotes
|
|
// if votes < 0 Decr object vote_count,otherwise Incr object vote_count
|
|
func (vr *VoteRepo) updateVotes(ctx context.Context, session *xorm.Session, objectID string, votes int) (err error) {
|
|
var (
|
|
objectType string
|
|
e error
|
|
)
|
|
|
|
objectType, err = obj.GetObjectTypeStrByObjectID(objectID)
|
|
switch objectType {
|
|
case "question":
|
|
_, err = session.Where("id = ?", objectID).Incr("vote_count", votes).Update(&entity.Question{})
|
|
case "answer":
|
|
_, err = session.Where("id = ?", objectID).Incr("vote_count", votes).Update(&entity.Answer{})
|
|
case "comment":
|
|
_, err = session.Where("id = ?", objectID).Incr("vote_count", votes).Update(&entity.Comment{})
|
|
default:
|
|
e = errors.BadRequest(reason.DisallowVote)
|
|
}
|
|
|
|
if e != nil {
|
|
err = e
|
|
} else if err != nil {
|
|
err = errors.BadRequest(reason.DatabaseError).WithError(err).WithStack()
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// sendNotification send rank triggered notification
|
|
func (vr *VoteRepo) sendNotification(ctx context.Context, activityUserID, objectUserID, objectID string) {
|
|
objectType, err := obj.GetObjectTypeStrByObjectID(objectID)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
msg := &schema.NotificationMsg{
|
|
ReceiverUserID: activityUserID,
|
|
TriggerUserID: objectUserID,
|
|
Type: schema.NotificationTypeAchievement,
|
|
ObjectID: objectID,
|
|
ObjectType: objectType,
|
|
}
|
|
notice_queue.AddNotification(msg)
|
|
}
|