chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package activity
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/apache/answer/internal/base/data"
|
||||
"github.com/apache/answer/internal/base/reason"
|
||||
"github.com/apache/answer/internal/entity"
|
||||
"github.com/apache/answer/internal/service/activity"
|
||||
"github.com/apache/answer/internal/service/activity_type"
|
||||
"github.com/apache/answer/internal/service/config"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
)
|
||||
|
||||
// activityRepo activity repository
|
||||
type activityRepo struct {
|
||||
data *data.Data
|
||||
configService *config.ConfigService
|
||||
}
|
||||
|
||||
// NewActivityRepo new repository
|
||||
func NewActivityRepo(
|
||||
data *data.Data,
|
||||
configService *config.ConfigService,
|
||||
) activity.ActivityRepo {
|
||||
return &activityRepo{
|
||||
data: data,
|
||||
configService: configService,
|
||||
}
|
||||
}
|
||||
|
||||
func (ar *activityRepo) GetObjectAllActivity(ctx context.Context, objectID string, showVote bool) (
|
||||
activityList []*entity.Activity, err error) {
|
||||
activityList = make([]*entity.Activity, 0)
|
||||
session := ar.data.DB.Context(ctx).Desc("id")
|
||||
|
||||
if !showVote {
|
||||
activityTypeNotShown := ar.getAllActivityType(ctx)
|
||||
session.NotIn("activity_type", activityTypeNotShown)
|
||||
}
|
||||
err = session.Find(&activityList, &entity.Activity{OriginalObjectID: objectID})
|
||||
if err != nil {
|
||||
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return activityList, nil
|
||||
}
|
||||
|
||||
func (ar *activityRepo) getAllActivityType(ctx context.Context) (activityTypes []int) {
|
||||
var activityTypeNotShown []int
|
||||
for _, key := range activity_type.VoteActivityTypeList {
|
||||
id, err := ar.configService.GetIDByKey(ctx, key)
|
||||
if err != nil {
|
||||
log.Errorf("get config id by key [%s] error: %v", key, err)
|
||||
} else {
|
||||
activityTypeNotShown = append(activityTypeNotShown, id)
|
||||
}
|
||||
}
|
||||
return activityTypeNotShown
|
||||
}
|
||||
@@ -0,0 +1,349 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package activity
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/segmentfault/pacman/log"
|
||||
"xorm.io/builder"
|
||||
|
||||
"github.com/apache/answer/internal/base/constant"
|
||||
"github.com/apache/answer/internal/base/data"
|
||||
"github.com/apache/answer/internal/base/reason"
|
||||
"github.com/apache/answer/internal/entity"
|
||||
"github.com/apache/answer/internal/schema"
|
||||
"github.com/apache/answer/internal/service/activity"
|
||||
"github.com/apache/answer/internal/service/activity_common"
|
||||
"github.com/apache/answer/internal/service/noticequeue"
|
||||
"github.com/apache/answer/internal/service/rank"
|
||||
"github.com/apache/answer/pkg/converter"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// AnswerActivityRepo answer accepted
|
||||
type AnswerActivityRepo struct {
|
||||
data *data.Data
|
||||
activityRepo activity_common.ActivityRepo
|
||||
userRankRepo rank.UserRankRepo
|
||||
notificationQueueService noticequeue.Service
|
||||
}
|
||||
|
||||
// NewAnswerActivityRepo new repository
|
||||
func NewAnswerActivityRepo(
|
||||
data *data.Data,
|
||||
activityRepo activity_common.ActivityRepo,
|
||||
userRankRepo rank.UserRankRepo,
|
||||
notificationQueueService noticequeue.Service,
|
||||
) activity.AnswerActivityRepo {
|
||||
return &AnswerActivityRepo{
|
||||
data: data,
|
||||
activityRepo: activityRepo,
|
||||
userRankRepo: userRankRepo,
|
||||
notificationQueueService: notificationQueueService,
|
||||
}
|
||||
}
|
||||
|
||||
func (ar *AnswerActivityRepo) SaveAcceptAnswerActivity(ctx context.Context, op *schema.AcceptAnswerOperationInfo) (
|
||||
err error) {
|
||||
// save activity
|
||||
_, err = ar.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
|
||||
session = session.Context(ctx)
|
||||
|
||||
userInfoMapping, err := ar.acquireUserInfo(session, op.GetUserIDs())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = ar.saveActivitiesAvailable(session, op)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = ar.changeUserRank(ctx, session, op, userInfoMapping)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
|
||||
// notification
|
||||
ar.sendAcceptAnswerNotification(ctx, op)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ar *AnswerActivityRepo) SaveCancelAcceptAnswerActivity(ctx context.Context, op *schema.AcceptAnswerOperationInfo) (
|
||||
err error) {
|
||||
// pre check
|
||||
activities, err := ar.getExistActivity(ctx, op)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var userIDs []string
|
||||
for _, act := range activities {
|
||||
if act.Cancelled == entity.ActivityCancelled {
|
||||
continue
|
||||
}
|
||||
userIDs = append(userIDs, act.UserID)
|
||||
}
|
||||
if len(userIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// save activity
|
||||
_, err = ar.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
|
||||
session = session.Context(ctx)
|
||||
|
||||
userInfoMapping, err := ar.acquireUserInfo(session, userIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = ar.cancelActivities(session, activities)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = ar.rollbackUserRank(ctx, session, activities, userInfoMapping)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
|
||||
// notification
|
||||
ar.sendCancelAcceptAnswerNotification(ctx, op)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ar *AnswerActivityRepo) acquireUserInfo(session *xorm.Session, userIDs []string) (map[string]*entity.User, error) {
|
||||
us := make([]*entity.User, 0)
|
||||
err := session.In("id", userIDs).ForUpdate().Find(&us)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
users := make(map[string]*entity.User, 0)
|
||||
for _, u := range us {
|
||||
users[u.ID] = u
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// saveActivitiesAvailable save activities
|
||||
// If activity not exist it will be created or else will be updated
|
||||
// If this activity is already exist, set activity rank to 0
|
||||
// So after this function, the activity rank will be correct for update user rank
|
||||
func (ar *AnswerActivityRepo) saveActivitiesAvailable(session *xorm.Session, op *schema.AcceptAnswerOperationInfo) (
|
||||
err error) {
|
||||
for _, act := range op.Activities {
|
||||
existsActivity := &entity.Activity{}
|
||||
exist, err := session.
|
||||
Where(builder.Eq{"object_id": op.AnswerObjectID}).
|
||||
And(builder.Eq{"user_id": act.ActivityUserID}).
|
||||
And(builder.Eq{"trigger_user_id": act.TriggerUserID}).
|
||||
And(builder.Eq{"activity_type": act.ActivityType}).
|
||||
Get(existsActivity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exist && existsActivity.Cancelled == entity.ActivityAvailable {
|
||||
act.Rank = 0
|
||||
continue
|
||||
}
|
||||
if exist {
|
||||
bean := &entity.Activity{
|
||||
Cancelled: entity.ActivityAvailable,
|
||||
Rank: act.Rank,
|
||||
HasRank: act.HasRank(),
|
||||
}
|
||||
session.Where("id = ?", existsActivity.ID)
|
||||
if _, err = session.Cols("`cancelled`", "`rank`", "`has_rank`").Update(bean); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
insertActivity := entity.Activity{
|
||||
ObjectID: op.AnswerObjectID,
|
||||
OriginalObjectID: act.OriginalObjectID,
|
||||
UserID: act.ActivityUserID,
|
||||
TriggerUserID: converter.StringToInt64(act.TriggerUserID),
|
||||
ActivityType: act.ActivityType,
|
||||
Rank: act.Rank,
|
||||
HasRank: act.HasRank(),
|
||||
Cancelled: entity.ActivityAvailable,
|
||||
}
|
||||
_, err = session.Insert(&insertActivity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// cancelActivities cancel activities
|
||||
// If this activity is already cancelled, set activity rank to 0
|
||||
// So after this function, the activity rank will be correct for update user rank
|
||||
func (ar *AnswerActivityRepo) cancelActivities(session *xorm.Session, activities []*entity.Activity) (err error) {
|
||||
for _, act := range activities {
|
||||
t := &entity.Activity{}
|
||||
exist, err := session.ID(act.ID).Get(t)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
if !exist {
|
||||
log.Error(fmt.Errorf("%s activity not exist", act.ID))
|
||||
return fmt.Errorf("%s activity not exist", act.ID)
|
||||
}
|
||||
// If this activity is already cancelled, set activity rank to 0
|
||||
if t.Cancelled == entity.ActivityCancelled {
|
||||
act.Rank = 0
|
||||
}
|
||||
if _, err = session.ID(act.ID).Cols("cancelled", "cancelled_at").
|
||||
Update(&entity.Activity{
|
||||
Cancelled: entity.ActivityCancelled,
|
||||
CancelledAt: time.Now(),
|
||||
}); err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ar *AnswerActivityRepo) changeUserRank(ctx context.Context, session *xorm.Session,
|
||||
op *schema.AcceptAnswerOperationInfo,
|
||||
userInfoMapping map[string]*entity.User) (err error) {
|
||||
for _, act := range op.Activities {
|
||||
if act.Rank == 0 {
|
||||
continue
|
||||
}
|
||||
user := userInfoMapping[act.ActivityUserID]
|
||||
if user == nil {
|
||||
continue
|
||||
}
|
||||
if err = ar.userRankRepo.ChangeUserRank(ctx, session,
|
||||
act.ActivityUserID, user.Rank, act.Rank); err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ar *AnswerActivityRepo) rollbackUserRank(ctx context.Context, session *xorm.Session,
|
||||
activities []*entity.Activity,
|
||||
userInfoMapping map[string]*entity.User) (err error) {
|
||||
for _, act := range activities {
|
||||
if act.Rank == 0 {
|
||||
continue
|
||||
}
|
||||
user := userInfoMapping[act.UserID]
|
||||
if user == nil {
|
||||
continue
|
||||
}
|
||||
if err = ar.userRankRepo.ChangeUserRank(ctx, session,
|
||||
act.UserID, user.Rank, -act.Rank); err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ar *AnswerActivityRepo) getExistActivity(ctx context.Context, op *schema.AcceptAnswerOperationInfo) ([]*entity.Activity, error) {
|
||||
var activities []*entity.Activity
|
||||
for _, action := range op.Activities {
|
||||
var t []*entity.Activity
|
||||
err := ar.data.DB.Context(ctx).
|
||||
Where(builder.Eq{"user_id": action.ActivityUserID}).
|
||||
And(builder.Eq{"activity_type": action.ActivityType}).
|
||||
And(builder.Eq{"object_id": op.AnswerObjectID}).
|
||||
Find(&t)
|
||||
if err != nil {
|
||||
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
if len(t) > 0 {
|
||||
activities = append(activities, t...)
|
||||
}
|
||||
}
|
||||
return activities, nil
|
||||
}
|
||||
|
||||
func (ar *AnswerActivityRepo) sendAcceptAnswerNotification(
|
||||
ctx context.Context, op *schema.AcceptAnswerOperationInfo) {
|
||||
for _, act := range op.Activities {
|
||||
msg := &schema.NotificationMsg{
|
||||
Type: schema.NotificationTypeAchievement,
|
||||
ObjectID: op.AnswerObjectID,
|
||||
ReceiverUserID: act.ActivityUserID,
|
||||
TriggerUserID: act.TriggerUserID,
|
||||
}
|
||||
msg.ObjectType = constant.AnswerObjectType
|
||||
if msg.TriggerUserID != msg.ReceiverUserID {
|
||||
ar.notificationQueueService.Send(ctx, msg)
|
||||
}
|
||||
}
|
||||
|
||||
for _, act := range op.Activities {
|
||||
msg := &schema.NotificationMsg{
|
||||
ReceiverUserID: act.ActivityUserID,
|
||||
Type: schema.NotificationTypeInbox,
|
||||
ObjectID: op.AnswerObjectID,
|
||||
TriggerUserID: op.TriggerUserID,
|
||||
}
|
||||
if act.ActivityUserID != op.QuestionUserID {
|
||||
msg.ObjectType = constant.AnswerObjectType
|
||||
msg.NotificationAction = constant.NotificationAcceptAnswer
|
||||
ar.notificationQueueService.Send(ctx, msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ar *AnswerActivityRepo) sendCancelAcceptAnswerNotification(
|
||||
ctx context.Context, op *schema.AcceptAnswerOperationInfo) {
|
||||
for _, act := range op.Activities {
|
||||
msg := &schema.NotificationMsg{
|
||||
TriggerUserID: act.TriggerUserID,
|
||||
ReceiverUserID: act.ActivityUserID,
|
||||
Type: schema.NotificationTypeAchievement,
|
||||
ObjectID: op.AnswerObjectID,
|
||||
}
|
||||
if act.ActivityUserID == op.QuestionObjectID {
|
||||
msg.ObjectType = constant.QuestionObjectType
|
||||
} else {
|
||||
msg.ObjectType = constant.AnswerObjectType
|
||||
}
|
||||
if msg.TriggerUserID != msg.ReceiverUserID {
|
||||
ar.notificationQueueService.Send(ctx, msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package activity
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/apache/answer/internal/service/activity_common"
|
||||
"github.com/apache/answer/internal/service/follow"
|
||||
"github.com/apache/answer/pkg/obj"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
"xorm.io/builder"
|
||||
|
||||
"github.com/apache/answer/internal/base/data"
|
||||
"github.com/apache/answer/internal/base/reason"
|
||||
"github.com/apache/answer/internal/entity"
|
||||
"github.com/apache/answer/internal/service/unique"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// FollowRepo activity repository
|
||||
type FollowRepo struct {
|
||||
data *data.Data
|
||||
uniqueIDRepo unique.UniqueIDRepo
|
||||
activityRepo activity_common.ActivityRepo
|
||||
}
|
||||
|
||||
// NewFollowRepo new repository
|
||||
func NewFollowRepo(
|
||||
data *data.Data,
|
||||
uniqueIDRepo unique.UniqueIDRepo,
|
||||
activityRepo activity_common.ActivityRepo,
|
||||
) follow.FollowRepo {
|
||||
return &FollowRepo{
|
||||
data: data,
|
||||
uniqueIDRepo: uniqueIDRepo,
|
||||
activityRepo: activityRepo,
|
||||
}
|
||||
}
|
||||
|
||||
func (ar *FollowRepo) Follow(ctx context.Context, objectID, userID string) error {
|
||||
objectTypeStr, err := obj.GetObjectTypeStrByObjectID(objectID)
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
activityType, err := ar.activityRepo.GetActivityTypeByObjectType(ctx, objectTypeStr, "follow")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = ar.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
|
||||
session = session.Context(ctx)
|
||||
var (
|
||||
existsActivity entity.Activity
|
||||
has bool
|
||||
)
|
||||
result = nil
|
||||
|
||||
has, err = session.Where(builder.Eq{"activity_type": activityType}).
|
||||
And(builder.Eq{"user_id": userID}).
|
||||
And(builder.Eq{"object_id": objectID}).
|
||||
Get(&existsActivity)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if has && existsActivity.Cancelled == entity.ActivityAvailable {
|
||||
return
|
||||
}
|
||||
|
||||
if has {
|
||||
_, err = session.Where(builder.Eq{"id": existsActivity.ID}).
|
||||
Cols(`cancelled`).
|
||||
Update(&entity.Activity{
|
||||
Cancelled: entity.ActivityAvailable,
|
||||
})
|
||||
} else {
|
||||
// update existing activity with new user id and u object id
|
||||
_, err = session.Insert(&entity.Activity{
|
||||
UserID: userID,
|
||||
ObjectID: objectID,
|
||||
OriginalObjectID: objectID,
|
||||
ActivityType: activityType,
|
||||
Cancelled: entity.ActivityAvailable,
|
||||
Rank: 0,
|
||||
HasRank: 0,
|
||||
})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// start update followers when everything is fine
|
||||
err = ar.updateFollows(ctx, session, objectID, 1)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
return
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (ar *FollowRepo) FollowCancel(ctx context.Context, objectID, userID string) error {
|
||||
objectTypeStr, err := obj.GetObjectTypeStrByObjectID(objectID)
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
activityType, err := ar.activityRepo.GetActivityTypeByObjectType(ctx, objectTypeStr, "follow")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = ar.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
|
||||
session = session.Context(ctx)
|
||||
var (
|
||||
existsActivity entity.Activity
|
||||
has bool
|
||||
)
|
||||
result = nil
|
||||
|
||||
has, err = session.Where(builder.Eq{"activity_type": activityType}).
|
||||
And(builder.Eq{"user_id": userID}).
|
||||
And(builder.Eq{"object_id": objectID}).
|
||||
Get(&existsActivity)
|
||||
|
||||
if err != nil || !has {
|
||||
return
|
||||
}
|
||||
|
||||
if has && existsActivity.Cancelled == entity.ActivityCancelled {
|
||||
return
|
||||
}
|
||||
if _, err = session.Where("id = ?", existsActivity.ID).
|
||||
Cols("cancelled").
|
||||
Update(&entity.Activity{
|
||||
Cancelled: entity.ActivityCancelled,
|
||||
CancelledAt: time.Now(),
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
err = ar.updateFollows(ctx, session, objectID, -1)
|
||||
return
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (ar *FollowRepo) updateFollows(_ context.Context, session *xorm.Session, objectID string, follows int) error {
|
||||
objectType, err := obj.GetObjectTypeStrByObjectID(objectID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch objectType {
|
||||
case "question":
|
||||
_, err = session.Where("id = ?", objectID).Incr("follow_count", follows).Update(&entity.Question{})
|
||||
case "user":
|
||||
_, err = session.Where("id = ?", objectID).Incr("follow_count", follows).Update(&entity.User{})
|
||||
case "tag":
|
||||
_, err = session.Where("id = ?", objectID).Incr("follow_count", follows).Update(&entity.Tag{})
|
||||
default:
|
||||
err = errors.InternalServer(reason.DisallowFollow).WithMsg("this object can't be followed")
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package activity
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/apache/answer/internal/schema"
|
||||
"github.com/apache/answer/pkg/converter"
|
||||
"xorm.io/builder"
|
||||
|
||||
"github.com/apache/answer/internal/base/data"
|
||||
"github.com/apache/answer/internal/base/reason"
|
||||
"github.com/apache/answer/internal/entity"
|
||||
"github.com/apache/answer/internal/service/activity"
|
||||
"github.com/apache/answer/internal/service/activity_common"
|
||||
"github.com/apache/answer/internal/service/config"
|
||||
"github.com/apache/answer/internal/service/rank"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// ReviewActivityRepo answer accepted
|
||||
type ReviewActivityRepo struct {
|
||||
data *data.Data
|
||||
activityRepo activity_common.ActivityRepo
|
||||
userRankRepo rank.UserRankRepo
|
||||
configService *config.ConfigService
|
||||
}
|
||||
|
||||
const (
|
||||
EditAccepted = "edit.accepted"
|
||||
)
|
||||
|
||||
// NewReviewActivityRepo new repository
|
||||
func NewReviewActivityRepo(
|
||||
data *data.Data,
|
||||
activityRepo activity_common.ActivityRepo,
|
||||
userRankRepo rank.UserRankRepo,
|
||||
configService *config.ConfigService,
|
||||
) activity.ReviewActivityRepo {
|
||||
return &ReviewActivityRepo{
|
||||
data: data,
|
||||
activityRepo: activityRepo,
|
||||
userRankRepo: userRankRepo,
|
||||
configService: configService,
|
||||
}
|
||||
}
|
||||
|
||||
// Review user active
|
||||
func (ar *ReviewActivityRepo) Review(ctx context.Context, act *schema.PassReviewActivity) (err error) {
|
||||
cfg, err := ar.configService.GetConfigByKey(ctx, EditAccepted)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
addActivity := &entity.Activity{
|
||||
UserID: act.UserID,
|
||||
TriggerUserID: converter.StringToInt64(act.TriggerUserID),
|
||||
ObjectID: act.ObjectID,
|
||||
OriginalObjectID: act.OriginalObjectID,
|
||||
ActivityType: cfg.ID,
|
||||
Rank: cfg.GetIntValue(),
|
||||
HasRank: 1,
|
||||
RevisionID: converter.StringToInt64(act.RevisionID),
|
||||
}
|
||||
|
||||
_, err = ar.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
|
||||
session = session.Context(ctx)
|
||||
|
||||
user := &entity.User{}
|
||||
exist, err := session.ID(addActivity.UserID).ForUpdate().Get(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exist {
|
||||
return nil, fmt.Errorf("user not exist")
|
||||
}
|
||||
|
||||
existsActivity := &entity.Activity{}
|
||||
exist, err = session.
|
||||
And(builder.Eq{"user_id": addActivity.UserID}).
|
||||
And(builder.Eq{"activity_type": addActivity.ActivityType}).
|
||||
And(builder.Eq{"revision_id": addActivity.RevisionID}).
|
||||
Get(existsActivity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if exist {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
err = ar.userRankRepo.ChangeUserRank(ctx, session, addActivity.UserID, user.Rank, addActivity.Rank)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = session.Insert(addActivity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package activity
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"xorm.io/builder"
|
||||
|
||||
"github.com/apache/answer/internal/base/data"
|
||||
"github.com/apache/answer/internal/base/reason"
|
||||
"github.com/apache/answer/internal/entity"
|
||||
"github.com/apache/answer/internal/service/activity"
|
||||
"github.com/apache/answer/internal/service/activity_common"
|
||||
"github.com/apache/answer/internal/service/config"
|
||||
"github.com/apache/answer/internal/service/rank"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// UserActiveActivityRepo answer accepted
|
||||
type UserActiveActivityRepo struct {
|
||||
data *data.Data
|
||||
activityRepo activity_common.ActivityRepo
|
||||
userRankRepo rank.UserRankRepo
|
||||
configService *config.ConfigService
|
||||
}
|
||||
|
||||
const (
|
||||
UserActivated = "user.activated"
|
||||
)
|
||||
|
||||
// NewUserActiveActivityRepo new repository
|
||||
func NewUserActiveActivityRepo(
|
||||
data *data.Data,
|
||||
activityRepo activity_common.ActivityRepo,
|
||||
userRankRepo rank.UserRankRepo,
|
||||
configService *config.ConfigService,
|
||||
) activity.UserActiveActivityRepo {
|
||||
return &UserActiveActivityRepo{
|
||||
data: data,
|
||||
activityRepo: activityRepo,
|
||||
userRankRepo: userRankRepo,
|
||||
configService: configService,
|
||||
}
|
||||
}
|
||||
|
||||
// UserActive user active
|
||||
func (ar *UserActiveActivityRepo) UserActive(ctx context.Context, userID string) (err error) {
|
||||
cfg, err := ar.configService.GetConfigByKey(ctx, UserActivated)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
addActivity := &entity.Activity{
|
||||
UserID: userID,
|
||||
ObjectID: "0",
|
||||
OriginalObjectID: "0",
|
||||
ActivityType: cfg.ID,
|
||||
Rank: cfg.GetIntValue(),
|
||||
HasRank: 1,
|
||||
}
|
||||
|
||||
_, err = ar.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
|
||||
session = session.Context(ctx)
|
||||
|
||||
user := &entity.User{}
|
||||
exist, err := session.ID(userID).ForUpdate().Get(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exist {
|
||||
return nil, fmt.Errorf("user not exist")
|
||||
}
|
||||
|
||||
existsActivity := &entity.Activity{}
|
||||
exist, err = session.
|
||||
And(builder.Eq{"user_id": addActivity.UserID}).
|
||||
And(builder.Eq{"activity_type": addActivity.ActivityType}).
|
||||
Get(existsActivity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if exist {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
err = ar.userRankRepo.ChangeUserRank(ctx, session, addActivity.UserID, user.Rank, addActivity.Rank)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = session.Insert(addActivity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,507 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package activity
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/apache/answer/internal/service/content"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
|
||||
"github.com/apache/answer/internal/base/constant"
|
||||
"github.com/apache/answer/internal/service/noticequeue"
|
||||
"github.com/apache/answer/pkg/converter"
|
||||
|
||||
"github.com/apache/answer/internal/base/pager"
|
||||
"github.com/apache/answer/internal/service/rank"
|
||||
"github.com/apache/answer/pkg/obj"
|
||||
|
||||
"xorm.io/builder"
|
||||
|
||||
"github.com/apache/answer/internal/base/data"
|
||||
"github.com/apache/answer/internal/base/reason"
|
||||
"github.com/apache/answer/internal/entity"
|
||||
"github.com/apache/answer/internal/schema"
|
||||
"github.com/apache/answer/internal/service/activity_common"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// VoteRepo activity repository
|
||||
type VoteRepo struct {
|
||||
data *data.Data
|
||||
activityRepo activity_common.ActivityRepo
|
||||
userRankRepo rank.UserRankRepo
|
||||
notificationQueueService noticequeue.Service
|
||||
}
|
||||
|
||||
// NewVoteRepo new repository
|
||||
func NewVoteRepo(
|
||||
data *data.Data,
|
||||
activityRepo activity_common.ActivityRepo,
|
||||
userRankRepo rank.UserRankRepo,
|
||||
notificationQueueService noticequeue.Service,
|
||||
) content.VoteRepo {
|
||||
return &VoteRepo{
|
||||
data: data,
|
||||
activityRepo: activityRepo,
|
||||
userRankRepo: userRankRepo,
|
||||
notificationQueueService: notificationQueueService,
|
||||
}
|
||||
}
|
||||
|
||||
func (vr *VoteRepo) Vote(ctx context.Context, op *schema.VoteOperationInfo) (err error) {
|
||||
noNeedToVote, err := vr.votePreCheck(ctx, op)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if noNeedToVote {
|
||||
return nil
|
||||
}
|
||||
|
||||
sendInboxNotification := false
|
||||
maxDailyRank, err := vr.userRankRepo.GetMaxDailyRank(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var userIDs []string
|
||||
for _, activity := range op.Activities {
|
||||
userIDs = append(userIDs, activity.ActivityUserID)
|
||||
}
|
||||
|
||||
_, err = vr.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
|
||||
session = session.Context(ctx)
|
||||
|
||||
userInfoMapping, err := vr.acquireUserInfo(session, userIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = vr.setActivityRankToZeroIfUserReachLimit(ctx, session, op, userInfoMapping, maxDailyRank)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sendInboxNotification, err = vr.saveActivitiesAvailable(session, op)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = vr.changeUserRank(ctx, session, op, userInfoMapping)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, activity := range op.Activities {
|
||||
if activity.Rank == 0 {
|
||||
continue
|
||||
}
|
||||
vr.sendAchievementNotification(ctx, activity.ActivityUserID, op.ObjectCreatorUserID, op.ObjectID)
|
||||
}
|
||||
if sendInboxNotification {
|
||||
vr.sendVoteInboxNotification(ctx, op.OperatingUserID, op.ObjectCreatorUserID, op.ObjectID, op.VoteUp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vr *VoteRepo) CancelVote(ctx context.Context, op *schema.VoteOperationInfo) (err error) {
|
||||
// Pre-Check
|
||||
// 1. check if the activity exist
|
||||
// 2. check if the activity is not cancelled
|
||||
// 3. if all activities are cancelled, return directly
|
||||
activities, err := vr.getExistActivity(ctx, op)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var userIDs []string
|
||||
for _, activity := range activities {
|
||||
if activity.Cancelled == entity.ActivityCancelled {
|
||||
continue
|
||||
}
|
||||
userIDs = append(userIDs, activity.UserID)
|
||||
}
|
||||
if len(userIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = vr.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
|
||||
session = session.Context(ctx)
|
||||
|
||||
userInfoMapping, err := vr.acquireUserInfo(session, userIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = vr.cancelActivities(session, activities)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = vr.rollbackUserRank(ctx, session, activities, userInfoMapping)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, activity := range activities {
|
||||
if activity.Rank == 0 {
|
||||
continue
|
||||
}
|
||||
vr.sendAchievementNotification(ctx, activity.UserID, op.ObjectCreatorUserID, op.ObjectID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vr *VoteRepo) GetAndSaveVoteResult(ctx context.Context, objectID, objectType string) (
|
||||
up, down int64, err error) {
|
||||
up = vr.countVoteUp(ctx, objectID, objectType)
|
||||
down = vr.countVoteDown(ctx, objectID, objectType)
|
||||
err = vr.updateVotes(ctx, objectID, objectType, int(up-down))
|
||||
return
|
||||
}
|
||||
|
||||
func (vr *VoteRepo) ListUserVotes(ctx context.Context, userID string,
|
||||
page int, pageSize int, activityTypes []int) (voteList []*entity.Activity, total int64, err error) {
|
||||
session := vr.data.DB.Context(ctx)
|
||||
cond := builder.
|
||||
And(
|
||||
builder.Eq{"user_id": userID},
|
||||
builder.Eq{"cancelled": 0},
|
||||
builder.In("activity_type", activityTypes),
|
||||
)
|
||||
|
||||
session.Where(cond).Desc("updated_at")
|
||||
|
||||
total, err = pager.Help(page, pageSize, &voteList, &entity.Activity{}, session)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (vr *VoteRepo) votePreCheck(ctx context.Context, op *schema.VoteOperationInfo) (noNeedToVote bool, err error) {
|
||||
activities, err := vr.getExistActivity(ctx, op)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
done := 0
|
||||
for _, activity := range activities {
|
||||
if activity.Cancelled == entity.ActivityAvailable {
|
||||
done++
|
||||
}
|
||||
}
|
||||
return done == len(op.Activities), nil
|
||||
}
|
||||
|
||||
func (vr *VoteRepo) acquireUserInfo(session *xorm.Session, userIDs []string) (map[string]*entity.User, error) {
|
||||
us := make([]*entity.User, 0)
|
||||
err := session.In("id", userIDs).ForUpdate().Find(&us)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
users := make(map[string]*entity.User, 0)
|
||||
for _, u := range us {
|
||||
users[u.ID] = u
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (vr *VoteRepo) setActivityRankToZeroIfUserReachLimit(ctx context.Context, session *xorm.Session,
|
||||
op *schema.VoteOperationInfo, userInfoMapping map[string]*entity.User, maxDailyRank int) (err error) {
|
||||
// check if user reach daily rank limit
|
||||
for _, activity := range op.Activities {
|
||||
if userInfoMapping[activity.ActivityUserID] == nil {
|
||||
continue
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func (vr *VoteRepo) changeUserRank(ctx context.Context, session *xorm.Session,
|
||||
op *schema.VoteOperationInfo,
|
||||
userInfoMapping map[string]*entity.User) (err error) {
|
||||
for _, activity := range op.Activities {
|
||||
if activity.Rank == 0 {
|
||||
continue
|
||||
}
|
||||
user := userInfoMapping[activity.ActivityUserID]
|
||||
if user == nil {
|
||||
continue
|
||||
}
|
||||
if err = vr.userRankRepo.ChangeUserRank(ctx, session,
|
||||
activity.ActivityUserID, user.Rank, activity.Rank); err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vr *VoteRepo) rollbackUserRank(ctx context.Context, session *xorm.Session,
|
||||
activities []*entity.Activity,
|
||||
userInfoMapping map[string]*entity.User) (err error) {
|
||||
for _, activity := range activities {
|
||||
if activity.Rank == 0 {
|
||||
continue
|
||||
}
|
||||
user := userInfoMapping[activity.UserID]
|
||||
if user == nil {
|
||||
continue
|
||||
}
|
||||
if err = vr.userRankRepo.ChangeUserRank(ctx, session,
|
||||
activity.UserID, user.Rank, -activity.Rank); err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// saveActivitiesAvailable save activities
|
||||
// If activity not exist it will be created or else will be updated
|
||||
// If this activity is already exist, set activity rank to 0
|
||||
// So after this function, the activity rank will be correct for update user rank
|
||||
func (vr *VoteRepo) saveActivitiesAvailable(session *xorm.Session, op *schema.VoteOperationInfo) (newAct bool, err error) {
|
||||
for _, activity := range op.Activities {
|
||||
existsActivity := &entity.Activity{}
|
||||
exist, err := session.
|
||||
Where(builder.Eq{"object_id": op.ObjectID}).
|
||||
And(builder.Eq{"user_id": activity.ActivityUserID}).
|
||||
And(builder.Eq{"trigger_user_id": activity.TriggerUserID}).
|
||||
And(builder.Eq{"activity_type": activity.ActivityType}).
|
||||
Get(existsActivity)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if exist && existsActivity.Cancelled == entity.ActivityAvailable {
|
||||
activity.Rank = 0
|
||||
continue
|
||||
}
|
||||
if exist {
|
||||
bean := &entity.Activity{
|
||||
Cancelled: entity.ActivityAvailable,
|
||||
Rank: activity.Rank,
|
||||
HasRank: activity.HasRank(),
|
||||
}
|
||||
session.Where("id = ?", existsActivity.ID)
|
||||
if _, err = session.Cols("`cancelled`", "`rank`", "`has_rank`").
|
||||
Update(bean); err != nil {
|
||||
return false, err
|
||||
}
|
||||
} else {
|
||||
insertActivity := entity.Activity{
|
||||
ObjectID: op.ObjectID,
|
||||
OriginalObjectID: op.ObjectID,
|
||||
UserID: activity.ActivityUserID,
|
||||
TriggerUserID: converter.StringToInt64(activity.TriggerUserID),
|
||||
ActivityType: activity.ActivityType,
|
||||
Rank: activity.Rank,
|
||||
HasRank: activity.HasRank(),
|
||||
Cancelled: entity.ActivityAvailable,
|
||||
}
|
||||
_, err = session.Insert(&insertActivity)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
newAct = true
|
||||
}
|
||||
}
|
||||
return newAct, nil
|
||||
}
|
||||
|
||||
// cancelActivities cancel activities
|
||||
// If this activity is already cancelled, set activity rank to 0
|
||||
// So after this function, the activity rank will be correct for update user rank
|
||||
func (vr *VoteRepo) cancelActivities(session *xorm.Session, activities []*entity.Activity) (err error) {
|
||||
for _, activity := range activities {
|
||||
t := &entity.Activity{}
|
||||
exist, err := session.ID(activity.ID).Get(t)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
if !exist {
|
||||
log.Error(fmt.Errorf("%s activity not exist", activity.ID))
|
||||
return fmt.Errorf("%s activity not exist", activity.ID)
|
||||
}
|
||||
// If this activity is already cancelled, set activity rank to 0
|
||||
if t.Cancelled == entity.ActivityCancelled {
|
||||
activity.Rank = 0
|
||||
}
|
||||
if _, err = session.ID(activity.ID).Cols("cancelled", "cancelled_at").
|
||||
Update(&entity.Activity{
|
||||
Cancelled: entity.ActivityCancelled,
|
||||
CancelledAt: time.Now(),
|
||||
}); err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vr *VoteRepo) getExistActivity(ctx context.Context, op *schema.VoteOperationInfo) ([]*entity.Activity, error) {
|
||||
var activities []*entity.Activity
|
||||
for _, action := range op.Activities {
|
||||
t := &entity.Activity{}
|
||||
exist, err := vr.data.DB.Context(ctx).
|
||||
Where(builder.Eq{"user_id": action.ActivityUserID}).
|
||||
And(builder.Eq{"trigger_user_id": action.TriggerUserID}).
|
||||
And(builder.Eq{"activity_type": action.ActivityType}).
|
||||
And(builder.Eq{"object_id": op.ObjectID}).
|
||||
Get(t)
|
||||
if err != nil {
|
||||
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
if exist {
|
||||
activities = append(activities, t)
|
||||
}
|
||||
}
|
||||
return activities, nil
|
||||
}
|
||||
|
||||
func (vr *VoteRepo) countVoteUp(ctx context.Context, objectID, objectType string) (count int64) {
|
||||
count, err := vr.countVote(ctx, objectID, objectType, constant.ActVoteUp)
|
||||
if err != nil {
|
||||
log.Errorf("get vote up count error: %v", err)
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func (vr *VoteRepo) countVoteDown(ctx context.Context, objectID, objectType string) (count int64) {
|
||||
count, err := vr.countVote(ctx, objectID, objectType, constant.ActVoteDown)
|
||||
if err != nil {
|
||||
log.Errorf("get vote down count error: %v", err)
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func (vr *VoteRepo) countVote(ctx context.Context, objectID, objectType, action string) (count int64, err error) {
|
||||
activity := &entity.Activity{}
|
||||
activityType, _ := vr.activityRepo.GetActivityTypeByObjectType(ctx, objectType, action)
|
||||
count, err = vr.data.DB.Context(ctx).Where(builder.Eq{"object_id": objectID}).
|
||||
And(builder.Eq{"activity_type": activityType}).
|
||||
And(builder.Eq{"cancelled": 0}).
|
||||
Count(activity)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (vr *VoteRepo) updateVotes(ctx context.Context, objectID, objectType string, voteCount int) (err error) {
|
||||
session := vr.data.DB.Context(ctx)
|
||||
switch objectType {
|
||||
case constant.QuestionObjectType:
|
||||
_, err = session.ID(objectID).Cols("vote_count").Update(&entity.Question{VoteCount: voteCount})
|
||||
case constant.AnswerObjectType:
|
||||
_, err = session.ID(objectID).Cols("vote_count").Update(&entity.Answer{VoteCount: voteCount})
|
||||
case constant.CommentObjectType:
|
||||
_, err = session.ID(objectID).Cols("vote_count").Update(&entity.Comment{VoteCount: voteCount})
|
||||
}
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (vr *VoteRepo) sendAchievementNotification(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,
|
||||
}
|
||||
vr.notificationQueueService.Send(ctx, msg)
|
||||
}
|
||||
|
||||
func (vr *VoteRepo) sendVoteInboxNotification(ctx context.Context, triggerUserID, receiverUserID, objectID string, upvote bool) {
|
||||
if triggerUserID == receiverUserID {
|
||||
return
|
||||
}
|
||||
objectType, _ := obj.GetObjectTypeStrByObjectID(objectID)
|
||||
|
||||
msg := &schema.NotificationMsg{
|
||||
TriggerUserID: triggerUserID,
|
||||
ReceiverUserID: receiverUserID,
|
||||
Type: schema.NotificationTypeInbox,
|
||||
ObjectID: objectID,
|
||||
ObjectType: objectType,
|
||||
}
|
||||
if objectType == constant.QuestionObjectType {
|
||||
if upvote {
|
||||
msg.NotificationAction = constant.NotificationUpVotedTheQuestion
|
||||
} else {
|
||||
msg.NotificationAction = constant.NotificationDownVotedTheQuestion
|
||||
}
|
||||
}
|
||||
if objectType == constant.AnswerObjectType {
|
||||
if upvote {
|
||||
msg.NotificationAction = constant.NotificationUpVotedTheAnswer
|
||||
} else {
|
||||
msg.NotificationAction = constant.NotificationDownVotedTheAnswer
|
||||
}
|
||||
}
|
||||
if objectType == constant.CommentObjectType {
|
||||
if upvote {
|
||||
msg.NotificationAction = constant.NotificationUpVotedTheComment
|
||||
}
|
||||
}
|
||||
if len(msg.NotificationAction) > 0 {
|
||||
vr.notificationQueueService.Send(ctx, msg)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user