Skip to content

Commit 2939297

Browse files
author
Jeff Yanta
committed
activity: initial activity feed implementation for welcome and weekly bonuses
1 parent 9cac3b2 commit 2939297

File tree

26 files changed

+1005
-27
lines changed

26 files changed

+1005
-27
lines changed

activity/localization.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package activity
2+
3+
import (
4+
"context"
5+
"errors"
6+
7+
"golang.org/x/text/language"
8+
"golang.org/x/text/message"
9+
10+
activitypb "github.com/code-payments/flipchat-protobuf-api/generated/go/activity/v1"
11+
12+
codekin "github.com/code-payments/code-server/pkg/kin"
13+
14+
"github.com/code-payments/flipchat-server/chat"
15+
"github.com/code-payments/flipchat-server/profile"
16+
)
17+
18+
var (
19+
kinAmountPrinter = message.NewPrinter(language.English)
20+
)
21+
22+
func InjectLocalizedText(ctx context.Context, chats chat.Store, profiles profile.Store, notification *activitypb.Notification) error {
23+
var localizedText string
24+
switch typed := notification.AdditionalMetadata.(type) {
25+
case *activitypb.Notification_WelcomeBonus:
26+
localizedText = kinAmountPrinter.Sprintf("You received ⬢\u00A0%d\u00A0Kin welcome bonus", codekin.FromQuarks(typed.WelcomeBonus.QuarksReceived))
27+
case *activitypb.Notification_WeeklyBonus:
28+
localizedText = kinAmountPrinter.Sprintf("You received ⬢\u00A0%d\u00A0Kin weekly bonus", codekin.FromQuarks(typed.WeeklyBonus.QuarksReceived))
29+
case *activitypb.Notification_CreateGroup:
30+
localizedText = kinAmountPrinter.Sprintf("You paid ⬢\u00A0%d\u00A0Kin to create a new Flipchat", codekin.FromQuarks(typed.CreateGroup.QuarksSpent))
31+
case *activitypb.Notification_SendListenerMessage:
32+
localizedText = kinAmountPrinter.Sprintf("You paid ⬢\u00A0%d\u00A0Kin", codekin.FromQuarks(typed.SendListenerMessage.QuarksSpent))
33+
case *activitypb.Notification_SendTip:
34+
localizedText = kinAmountPrinter.Sprintf("You tipped ⬢\u00A0%d\u00A0Kin", codekin.FromQuarks(typed.SendTip.TotalQuarksSent))
35+
case *activitypb.Notification_ReceivedTip:
36+
localizedText = kinAmountPrinter.Sprintf("You received ⬢\u00A0%d\u00A0Kin", codekin.FromQuarks(typed.ReceivedTip.TotalQuarksReceived))
37+
case *activitypb.Notification_PromotedToSpeaker:
38+
profile, err := profiles.GetProfile(ctx, typed.PromotedToSpeaker.PromtedBy)
39+
if err != nil {
40+
return err
41+
}
42+
43+
chatMd, err := chats.GetChatMetadata(ctx, typed.PromotedToSpeaker.ChatId)
44+
if err != nil {
45+
return err
46+
}
47+
48+
if len(profile.DisplayName) == 0 {
49+
return errors.New("user doesn't have a display name")
50+
}
51+
if len(chatMd.DisplayName) == 0 {
52+
return errors.New("chat doesn't have a display name")
53+
}
54+
55+
localizedText = kinAmountPrinter.Sprintf("%s made you a speaker in %s", profile.DisplayName, chatMd.DisplayName)
56+
default:
57+
return errors.New("unsupported notification type")
58+
}
59+
notification.LocalizedText = localizedText
60+
return nil
61+
}

activity/memory/server_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package memory
2+
3+
import (
4+
"testing"
5+
6+
account "github.com/code-payments/flipchat-server/account/memory"
7+
"github.com/code-payments/flipchat-server/activity/tests"
8+
chat "github.com/code-payments/flipchat-server/chat/memory"
9+
profile "github.com/code-payments/flipchat-server/profile/memory"
10+
)
11+
12+
func TestActivity_MemoryServer(t *testing.T) {
13+
testStore := NewInMemory()
14+
accounts := account.NewInMemory()
15+
chats := chat.NewInMemory()
16+
profiles := profile.NewInMemory()
17+
teardown := func() {
18+
testStore.(*InMemoryStore).reset()
19+
}
20+
tests.RunServerTests(t, accounts, testStore, chats, profiles, teardown)
21+
}

activity/memory/store.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package memory
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"sort"
7+
"sync"
8+
9+
activitypb "github.com/code-payments/flipchat-protobuf-api/generated/go/activity/v1"
10+
commonpb "github.com/code-payments/flipchat-protobuf-api/generated/go/common/v1"
11+
"google.golang.org/protobuf/proto"
12+
13+
"github.com/code-payments/flipchat-server/activity"
14+
"github.com/code-payments/flipchat-server/protoutil"
15+
)
16+
17+
type NotificationsByTimestamp []*activitypb.Notification
18+
19+
func (a NotificationsByTimestamp) Len() int { return len(a) }
20+
func (a NotificationsByTimestamp) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
21+
func (a NotificationsByTimestamp) Less(i, j int) bool {
22+
return a[i].Ts.AsTime().Before(a[j].Ts.AsTime())
23+
}
24+
25+
type InMemoryStore struct {
26+
mu sync.RWMutex
27+
notifications map[string][]*activitypb.Notification
28+
}
29+
30+
func NewInMemory() activity.Store {
31+
return &InMemoryStore{
32+
notifications: map[string][]*activitypb.Notification{},
33+
}
34+
}
35+
36+
func (m *InMemoryStore) SaveNotification(ctx context.Context, activityFeedType activitypb.ActivityFeedType, userID *commonpb.UserId, notification *activitypb.Notification) (*activitypb.Notification, error) {
37+
if activityFeedType != activitypb.ActivityFeedType_TRANSACTION_HISTORY {
38+
return nil, activity.ErrInvalidActivityFeedType
39+
}
40+
41+
switch notification.AdditionalMetadata.(type) {
42+
case
43+
*activitypb.Notification_WelcomeBonus,
44+
*activitypb.Notification_WeeklyBonus:
45+
//*activitypb.Notification_CreateGroup,
46+
//*activitypb.Notification_SendListenerMessage,
47+
//*activitypb.Notification_SendTip,
48+
//*activitypb.Notification_ReceivedTip,
49+
//*activitypb.Notification_PromotedToSpeaker,
50+
default:
51+
return nil, activity.ErrInvalidNotificationType
52+
}
53+
54+
m.mu.Lock()
55+
defer m.mu.Unlock()
56+
57+
for _, existing := range m.notifications[string(userID.Value)] {
58+
if bytes.Equal(notification.Id.Value, existing.Id.Value) {
59+
return proto.Clone(existing).(*activitypb.Notification), nil
60+
}
61+
}
62+
63+
m.notifications[string(userID.Value)] = append(m.notifications[string(userID.Value)], proto.Clone(notification).(*activitypb.Notification))
64+
65+
return proto.Clone(notification).(*activitypb.Notification), nil
66+
}
67+
68+
func (m *InMemoryStore) GetLatestNotifications(ctx context.Context, activityFeedType activitypb.ActivityFeedType, userID *commonpb.UserId, limit int) ([]*activitypb.Notification, error) {
69+
if activityFeedType != activitypb.ActivityFeedType_TRANSACTION_HISTORY {
70+
return nil, activity.ErrInvalidActivityFeedType
71+
}
72+
73+
m.mu.RLock()
74+
defer m.mu.RUnlock()
75+
76+
res := protoutil.SliceClone(m.notifications[string(userID.Value)])
77+
78+
sorted := NotificationsByTimestamp(res)
79+
sort.Sort(sorted)
80+
81+
if len(sorted) > limit {
82+
sorted = sorted[:limit]
83+
}
84+
85+
return sorted, nil
86+
}
87+
88+
func (m *InMemoryStore) reset() {
89+
m.mu.Lock()
90+
m.notifications = map[string][]*activitypb.Notification{}
91+
m.mu.Unlock()
92+
}

activity/memory/store_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package memory
2+
3+
import (
4+
"testing"
5+
6+
"github.com/code-payments/flipchat-server/activity/tests"
7+
)
8+
9+
func TestActivity_MemoryStore(t *testing.T) {
10+
testStore := NewInMemory()
11+
teardown := func() {
12+
testStore.(*InMemoryStore).reset()
13+
}
14+
tests.RunStoreTests(t, testStore, teardown)
15+
}

activity/model.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package activity
2+
3+
import (
4+
"crypto/sha256"
5+
"encoding/binary"
6+
"encoding/hex"
7+
"errors"
8+
9+
activitypb "github.com/code-payments/flipchat-protobuf-api/generated/go/activity/v1"
10+
commonpb "github.com/code-payments/flipchat-protobuf-api/generated/go/common/v1"
11+
)
12+
13+
type NotificationType uint32
14+
15+
const (
16+
NotificationTypeUnknown = iota
17+
NotificationTypeWelcomeBonus
18+
NotificationTypeWeeklyBonus
19+
NotificationTypeCreateGroup
20+
NotificationTypeSendListenerMessage
21+
NotificationTypeSendTip
22+
NotificationTypeReceivedTip
23+
NotificationTypePromotedToSpeaker
24+
)
25+
26+
func NotificationIDString(id *activitypb.NotificationId) string {
27+
if id == nil {
28+
return "<invalid>"
29+
}
30+
return hex.EncodeToString(id.Value)
31+
}
32+
33+
func GetNotificationID(notificationType NotificationType, userID *commonpb.UserId, additionalSeeds ...[]byte) (*activitypb.NotificationId, error) {
34+
if notificationType == NotificationTypeUnknown {
35+
return nil, errors.New("notification type cannot be unknown")
36+
}
37+
38+
var notificationTypeBytes [4]byte
39+
binary.LittleEndian.PutUint32(notificationTypeBytes[:], uint32(notificationType))
40+
41+
hasher := sha256.New()
42+
_, err := hasher.Write(notificationTypeBytes[:])
43+
if err != nil {
44+
return nil, err
45+
}
46+
_, err = hasher.Write(userID.Value)
47+
if err != nil {
48+
return nil, err
49+
}
50+
for _, seed := range additionalSeeds {
51+
_, err = hasher.Write(seed)
52+
if err != nil {
53+
return nil, err
54+
}
55+
}
56+
hashed := hasher.Sum(nil)
57+
58+
return &activitypb.NotificationId{Value: hashed}, nil
59+
}

0 commit comments

Comments
 (0)