Skip to content

Commit

Permalink
Scheduled telegram notifications. (#48)
Browse files Browse the repository at this point in the history
Scheduled telegram notifications.

Depends on ice-blockchain/wintr#46
  • Loading branch information
ice-myles authored Jun 19, 2024
1 parent 05da2b7 commit c781e6f
Show file tree
Hide file tree
Showing 39 changed files with 1,125 additions and 501 deletions.
7 changes: 7 additions & 0 deletions application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,13 @@ notifications: &notifications
replicaURLs:
- postgresql://root:pass@localhost:5434/husky
tenantName: BogusName
tokenName: BN
wintr/notifications/telegram:
botToken: bogus
telegramBots:
bogusBot:
botToken: bogus
webAppLink: https://bogus.com/startapp
messageBroker: &notificationsMessageBroker
consumerGroup: husky-local-notifications-testing
createTopics: true
Expand Down
12 changes: 6 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ require (
github.com/goccy/go-json v0.10.3
github.com/google/uuid v1.6.0
github.com/hashicorp/go-multierror v1.1.1
github.com/ice-blockchain/eskimo v1.325.0
github.com/ice-blockchain/freezer v1.445.0
github.com/ice-blockchain/eskimo v1.326.0
github.com/ice-blockchain/freezer v1.447.0
github.com/ice-blockchain/go-tarantool-client v0.0.0-20230327200757-4fc71fa3f7bb
github.com/ice-blockchain/wintr v1.140.0
github.com/ice-blockchain/wintr v1.141.0
github.com/imroc/req/v3 v3.43.7
github.com/pkg/errors v0.9.1
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475
Expand Down Expand Up @@ -42,7 +42,7 @@ require (
github.com/bits-and-blooms/bitset v1.13.0 // indirect
github.com/bsm/redislock v0.9.4 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.3.3 // indirect
github.com/bytedance/sonic v1.11.8 // indirect
github.com/bytedance/sonic v1.11.9 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
Expand Down Expand Up @@ -98,7 +98,7 @@ require (
github.com/google/pprof v0.0.0-20240618054019-d3b898a103f8 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.4 // indirect
github.com/googleapis/gax-go/v2 v2.12.5 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-version v1.7.0 // indirect
Expand Down Expand Up @@ -186,7 +186,7 @@ require (
golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.22.0 // indirect
google.golang.org/api v0.184.0 // indirect
google.golang.org/api v0.185.0 // indirect
google.golang.org/appengine/v2 v2.0.6 // indirect
google.golang.org/genproto v0.0.0-20240617180043-68d350f18fd4 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 // indirect
Expand Down
24 changes: 12 additions & 12 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ github.com/btcsuite/btcd/btcec/v2 v2.3.3 h1:6+iXlDKE8RMtKsvK0gshlXIuPbyWM/h84Ens
github.com/btcsuite/btcd/btcec/v2 v2.3.3/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
github.com/bytedance/sonic v1.11.8 h1:Zw/j1KfiS+OYTi9lyB3bb0CFxPJVkM17k1wyDG32LRA=
github.com/bytedance/sonic v1.11.8/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic v1.11.9 h1:LFHENlIY/SLzDWverzdOvgMztTxcfcF+cqNsz9pK5zg=
github.com/bytedance/sonic v1.11.9/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
Expand Down Expand Up @@ -257,8 +257,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg=
github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI=
github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBYGmXdxA=
github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
Expand All @@ -282,14 +282,14 @@ github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU
github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=
github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
github.com/ice-blockchain/eskimo v1.325.0 h1:OMOkY6s5jrFFsKf33pvZoaItAiX5zfwrKStNhlo0uuU=
github.com/ice-blockchain/eskimo v1.325.0/go.mod h1:ACvCKV6SSS+B/AudcS1zHzQ8BiMpsgk1PyeokF4ybxY=
github.com/ice-blockchain/freezer v1.445.0 h1:iIGpCRH6zsNkj0UAuMBqcy64E2PuTEcTAWQhWXzolvg=
github.com/ice-blockchain/freezer v1.445.0/go.mod h1:PDVJt1xS2lROL4sSULL7rOa2KokfHecFB8khQQl3nfM=
github.com/ice-blockchain/eskimo v1.326.0 h1:MNey0egcmR42Lsmi2A4nnl3h2nYKWkyQIryI59Q1jXc=
github.com/ice-blockchain/eskimo v1.326.0/go.mod h1:ACvCKV6SSS+B/AudcS1zHzQ8BiMpsgk1PyeokF4ybxY=
github.com/ice-blockchain/freezer v1.447.0 h1:rlh2emkcsLARVFJIm1ZW3ycYb3kQuXuzjEl747Aploc=
github.com/ice-blockchain/freezer v1.447.0/go.mod h1:FCAvA9qaljPmo0Ljmqr+R8T5f9/J7cbQKIedFIlqpik=
github.com/ice-blockchain/go-tarantool-client v0.0.0-20230327200757-4fc71fa3f7bb h1:8TnFP3mc7O+tc44kv2e0/TpZKnEVUaKH+UstwfBwRkk=
github.com/ice-blockchain/go-tarantool-client v0.0.0-20230327200757-4fc71fa3f7bb/go.mod h1:ZsQU7i3mxhgBBu43Oev7WPFbIjP4TniN/b1UPNGbrq8=
github.com/ice-blockchain/wintr v1.140.0 h1:OGjjZHCD5WWcJ8BRQ5Dmv1xGSPe8DvqSuPwtpXZxUC4=
github.com/ice-blockchain/wintr v1.140.0/go.mod h1:TFymLSdt+kCAydDS9qmt37+quPUS6Amd5RMvV/4q77g=
github.com/ice-blockchain/wintr v1.141.0 h1:JkQkjXoXLmnLh3hoRzr1SbiVXvdzNaWrkHjRxQqS1yQ=
github.com/ice-blockchain/wintr v1.141.0/go.mod h1:6INwquKAzF28T1DwAh/n3fPTpuyGQQz9cSUbDjxSCAY=
github.com/imroc/req/v3 v3.43.7 h1:dOcNb9n0X83N5/5/AOkiU+cLhzx8QFXjv5MhikazzQA=
github.com/imroc/req/v3 v3.43.7/go.mod h1:SQIz5iYop16MJxbo8ib+4LnostGCok8NQf8ToyQc2xA=
github.com/ip2location/ip2location-go/v9 v9.7.0 h1:ipwl67HOWcrw+6GOChkEXcreRQR37NabqBd2ayYa4Q0=
Expand Down Expand Up @@ -618,8 +618,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
google.golang.org/api v0.184.0 h1:dmEdk6ZkJNXy1JcDhn/ou0ZUq7n9zropG2/tR4z+RDg=
google.golang.org/api v0.184.0/go.mod h1:CeDTtUEiYENAf8PPG5VZW2yNp2VM3VWbCeTioAZBTBA=
google.golang.org/api v0.185.0 h1:ENEKk1k4jW8SmmaT6RE+ZasxmxezCrD5Vw4npvr+pAU=
google.golang.org/api v0.185.0/go.mod h1:HNfvIkJGlgrIlrbYkAm9W9IdkmKZjOTVh33YltygGbg=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine/v2 v2.0.6 h1:LvPZLGuchSBslPBp+LAhihBeGSiRh1myRoYK4NtuBIw=
Expand Down
6 changes: 5 additions & 1 deletion notifications/DDL.sql
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ CREATE TABLE IF NOT EXISTS users (
profile_picture_name TEXT,
referred_by TEXT,
phone_number_hash TEXT,
telegram_user_id TEXT NOT NULL,
telegram_bot_id TEXT NOT NULL,
language TEXT NOT NULL default 'en'
);
ALTER TABLE users ADD COLUMN IF NOT EXISTS telegram_user_id text NOT NULL;
ALTER TABLE users ADD COLUMN IF NOT EXISTS telegram_bot_id text NOT NULL;
--************************************************************************************************************************************
-- sent_notifications
CREATE TABLE IF NOT EXISTS sent_notifications (
Expand Down Expand Up @@ -45,7 +49,7 @@ CREATE TABLE IF NOT EXISTS scheduled_notifications (
notification_channel_value TEXT NOT NULL,
primary key(user_id,uniqueness,notification_type,notification_channel,notification_channel_value));
CREATE UNIQUE INDEX IF NOT EXISTS scheduled_notifications_i_ix ON scheduled_notifications (i);
CREATE INDEX IF NOT EXISTS scheduled_notifications_mod_i_ix ON scheduled_notifications (MOD(i, %[1]v), scheduled_for ASC);
CREATE INDEX IF NOT EXISTS scheduled_notifications_mod_i_ix ON scheduled_notifications (MOD(i, %[1]v), scheduled_for, notification_channel ASC);
CREATE INDEX IF NOT EXISTS scheduled_notifications_user_id_notification_type_ix ON scheduled_notifications (user_id,notification_type);
--************************************************************************************************************************************
-- sent_announcements
Expand Down
95 changes: 69 additions & 26 deletions notifications/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/ice-blockchain/wintr/multimedia/picture"
"github.com/ice-blockchain/wintr/notifications/inapp"
"github.com/ice-blockchain/wintr/notifications/push"
"github.com/ice-blockchain/wintr/notifications/telegram"
"github.com/ice-blockchain/wintr/time"
)

Expand All @@ -28,6 +29,7 @@ const (
SMSNotificationChannel NotificationChannel = "sms"
EmailNotificationChannel NotificationChannel = "email"
PushNotificationChannel NotificationChannel = "push"
TelegramNotificationChannel NotificationChannel = "telegram"
PushOrFallbackToEmailNotificationChannel NotificationChannel = "push||email"
)

Expand Down Expand Up @@ -82,6 +84,7 @@ var (
SMSNotificationChannel,
EmailNotificationChannel,
PushNotificationChannel,
TelegramNotificationChannel,
}
//nolint:gochecknoglobals // It's just for more descriptive validation messages.
AllNotificationTypes = users.Enum[NotificationType]{
Expand Down Expand Up @@ -109,6 +112,24 @@ var (
WeeklyStatsNotificationType,
}
//nolint:gochecknoglobals // It's just for more descriptive validation messages.
AllTelegramNotificationTypes = users.Enum[NotificationType]{
LevelBadgeUnlockedNotificationType,
CoinBadgeUnlockedNotificationType,
SocialBadgeUnlockedNotificationType,
RoleChangedNotificationType,
LevelChangedNotificationType,
MiningExtendNotificationType,
MiningEndingSoonNotificationType,
MiningExpiredNotificationType,
MiningNotActiveNotificationType,
InviteFriendNotificationType,
SocialsFollowIceOnXNotificationType,
SocialsFollowUsOnXNotificationType,
SocialsFollowZeusOnXNotificationType,
SocialsFollowIONOnTelegramNotificationType,
SocialsFollowOurTelegramNotificationType,
}
//nolint:gochecknoglobals // It's just for more descriptive validation messages.
AllNotificationDomains = map[NotificationChannel][]NotificationDomain{
EmailNotificationChannel: {
DisableAllNotificationDomain,
Expand Down Expand Up @@ -171,14 +192,17 @@ type (
CheckHealth(ctx context.Context) error
}
Scheduler struct {
pictureClient picture.Client
pushNotificationsClient push.Client
cfg *config
schedulerAnnouncementsMX *sync.Mutex
schedulerNotificationsMX *sync.Mutex
telemetryNotifications *telemetry
telemetryAnnouncements *telemetry
db *storage.DB
pictureClient picture.Client
pushNotificationsClient push.Client
telegramNotificationsClient telegram.Client
cfg *config
schedulerPushAnnouncementsMX *sync.Mutex
schedulerPushNotificationsMX *sync.Mutex
schedulerTelegramNotificationsMX *sync.Mutex
telemetryPushNotifications *telemetry
telemetryTelegramNotifications *telemetry
telemetryAnnouncements *telemetry
db *storage.DB
}
)

Expand All @@ -189,8 +213,8 @@ const (
requestingUserIDCtxValueKey = "requestingUserIDCtxValueKey"
requestDeadline = 30 * stdlibtime.Second

schedulerWorkersCount int64 = 10
schedulerBatchSize int64 = 250
schedulerWorkersCount int64 = 10
schedulerPushBatchSize int64 = 250
)

var (
Expand All @@ -201,14 +225,18 @@ var (
//nolint:gochecknoglobals // Its loaded once at startup.
allPushNotificationTemplates map[NotificationType]map[languageCode]*pushNotificationTemplate
//nolint:gochecknoglobals // Its loaded once at startup.
allTelegramNotificationTemplates map[NotificationType]map[languageCode]*telegramNotificationTemplate
//nolint:gochecknoglobals // Its loaded once at startup.
internationalizedEmailDisplayNames = map[string]string{
"en": "ice: Decentralized Future",
}
)

type (
languageCode = string
user struct {
languageCode = string
telegramBotID = string
telegramBotToken = string
user struct {
LastPingCooldownEndedAt *time.Time `json:"lastPingCooldownEndedAt,omitempty"`
DisabledPushNotificationDomains *users.Enum[NotificationDomain] `json:"disabledPushNotificationDomains,omitempty"`
DisabledEmailNotificationDomains *users.Enum[NotificationDomain] `json:"disabledEmailNotificationDomains,omitempty"`
Expand All @@ -223,6 +251,8 @@ type (
ReferredBy string `json:"referredBy,omitempty"`
PhoneNumberHash string `json:"phoneNumberHash,omitempty"`
Language string `json:"language,omitempty"`
TelegramUserID string `json:"telegramUserId,omitempty"`
TelegramBotID string `json:"telegramBotId,omitempty"`
AgendaContactUserIDs []string `json:"agendaContactUserIDs,omitempty" db:"agenda_contact_user_ids"`
}
userTableSource struct {
Expand Down Expand Up @@ -259,15 +289,16 @@ type (
*processor
}
repository struct {
cfg *config
shutdown func() error
db *storage.DB
mb messagebroker.Client
pushNotificationsClient push.Client
emailClient email.Client
pictureClient picture.Client
personalInAppFeed inapp.Client
globalInAppFeed inapp.Client
cfg *config
shutdown func() error
db *storage.DB
mb messagebroker.Client
pushNotificationsClient push.Client
telegramNotificationsClient telegram.Client
emailClient email.Client
pictureClient picture.Client
personalInAppFeed inapp.Client
globalInAppFeed inapp.Client
}
processor struct {
*repository
Expand All @@ -277,6 +308,8 @@ type (
MaxNotificationDelaySec uint `yaml:"maxNotificationDelaySec"`
}
scheduledNotificationInfo struct {
TelegramUserID string
TelegramBotID string
PushNotificationTokens *users.Enum[push.DeviceToken]
DisabledPushNotificationDomains *users.Enum[NotificationDomain]
scheduledNotification
Expand Down Expand Up @@ -308,8 +341,14 @@ type (
UserID string
Token push.DeviceToken
}
telegramNotification struct {
tn *telegram.Notification
sn *sentNotification
scheduled scheduledNotification
}
config struct {
TenantName string `yaml:"tenantName"`
TokenName string `yaml:"tokenName"`
Socials []struct {
NotificationType string `yaml:"notificationType"`
Link string `yaml:"link"`
Expand All @@ -320,11 +359,15 @@ type (
Roles []string `yaml:"roles"`
} `yaml:"disabledAchievementsNotifications" `
NotificationDelaysByTopic map[push.SubscriptionTopic]notificationDelayConfig `yaml:"notificationDelaysByTopic" mapstructure:"notificationDelaysByTopic"` //nolint:lll // .
DeeplinkScheme string `yaml:"deeplinkScheme"`
messagebroker.Config `mapstructure:",squash"` //nolint:tagliatelle // Nope.
notificationDelayConfig `mapstructure:",squash"`
PingCooldown stdlibtime.Duration `yaml:"pingCooldown"`
WeeklyStats struct {
TelegramBots map[telegramBotID]struct {
BotToken telegramBotToken `yaml:"botToken"`
} `yaml:"telegramBots" mapstructure:"telegramBots"`
DeeplinkScheme string `yaml:"deeplinkScheme"`
WebAppLink string `yaml:"webAppLink"`
messagebroker.Config `mapstructure:",squash"` //nolint:tagliatelle // Nope.
notificationDelayConfig `mapstructure:",squash"`
PingCooldown stdlibtime.Duration `yaml:"pingCooldown"`
WeeklyStats struct {
Weekday stdlibtime.Weekday `yaml:"weekday"`
Hour int `yaml:"hour"`
Minutes int `yaml:"minutes"`
Expand Down
14 changes: 8 additions & 6 deletions notifications/notification_invite_friend.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,32 +27,34 @@ func (r *repository) addScheduledInviteFriendNotifications(ctx context.Context,
dayDuration = 24 * stdlibtime.Hour
firstNotificationDuration = 1 * stdlibtime.Hour
}
scheduled := []*scheduledNotification{
{
availableChannels := []NotificationChannel{PushNotificationChannel, TelegramNotificationChannel}
scheduled := make([]*scheduledNotification, 0, 2*len(availableChannels)) //nolint:gomnd,mnd // .
for _, channel := range availableChannels {
scheduled = append(scheduled, &scheduledNotification{
ScheduledAt: now,
ScheduledFor: time.New(us.CreatedAt.Add(firstNotificationDuration)),
Language: us.Language,
UserID: us.ID,
NotificationType: string(InviteFriendNotificationType),
Uniqueness: fmt.Sprintf("%v_%v_1h", us.ID, InviteFriendNotificationType),
NotificationChannel: string(PushNotificationChannel),
NotificationChannel: string(channel),
NotificationChannelValue: us.ID,
Data: &users.JSON{
"TenantName": r.cfg.TenantName,
},
}, {
}, &scheduledNotification{
ScheduledAt: now,
ScheduledFor: time.New(us.CreatedAt.Add(7 * dayDuration)), //nolint:gomnd,mnd // .
Language: us.Language,
UserID: us.ID,
NotificationType: string(InviteFriendNotificationType),
Uniqueness: fmt.Sprintf("%v_%v_7d", us.ID, InviteFriendNotificationType),
NotificationChannel: string(PushNotificationChannel),
NotificationChannel: string(channel),
NotificationChannelValue: us.ID,
Data: &users.JSON{
"TenantName": r.cfg.TenantName,
},
},
})
}

return errors.Wrapf(r.insertScheduledNotifications(ctx, scheduled), "can't execute insertScheduledNotifications:%#v", scheduled)
Expand Down
Loading

0 comments on commit c781e6f

Please sign in to comment.