Loading CHANGELOG.md +1 −0 Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ The format is based on [keep a changelog](http://keepachangelog.com/) and this p - New storage list feature. - Ban users and create groups from within the Runtime environment. - New In-App Purchase validation feature. - New In-App Notification feature. ### Changed - Run Facebook friends import after registration completes. Loading main.go +3 −2 Original line number Diff line number Diff line Loading @@ -92,15 +92,16 @@ func main() { messageRouter := server.NewMessageRouterService(sessionRegistry) presenceNotifier := server.NewPresenceNotifier(jsonLogger, config.GetName(), trackerService, messageRouter) trackerService.AddDiffListener(presenceNotifier.HandleDiff) notificationService := server.NewNotificationService(jsonLogger, db, trackerService, messageRouter, config.GetSocial().Notification) runtime, err := server.NewRuntime(jsonLogger, multiLogger, db, config.GetRuntime()) runtime, err := server.NewRuntime(jsonLogger, multiLogger, db, config.GetRuntime(), notificationService) if err != nil { multiLogger.Fatal("Failed initializing runtime modules.", zap.Error(err)) } socialClient := social.NewClient(5 * time.Second) purchaseService := server.NewPurchaseService(jsonLogger, multiLogger, db, config.GetPurchase()) pipeline := server.NewPipeline(config, db, trackerService, matchmakerService, messageRouter, sessionRegistry, socialClient, runtime, purchaseService) pipeline := server.NewPipeline(config, db, trackerService, matchmakerService, messageRouter, sessionRegistry, socialClient, runtime, purchaseService, notificationService) authService := server.NewAuthenticationService(jsonLogger, config, db, statsService, sessionRegistry, socialClient, pipeline, runtime) dashboardService := server.NewDashboardService(jsonLogger, multiLogger, semver, config, statsService) Loading migrations/20170620104217_storage_list.sql→migrations/20170620104217_storage_list_purchase_notifications.sql +17 −0 Original line number Diff line number Diff line Loading @@ -51,5 +51,22 @@ CREATE INDEX IF NOT EXISTS purchase_user_id_created_at_provider_receipt_id_idx O -- list purchases by most recent timestamp CREATE INDEX IF NOT EXISTS purchase_created_at_user_id_provider_receipt_id_idx ON purchase (created_at, user_id, provider, receipt_id); CREATE TABLE IF NOT EXISTS notification ( PRIMARY KEY (id), id BYTEA NOT NULL, user_id BYTEA NOT NULL, subject VARCHAR(255) NOT NULL, content BYTEA DEFAULT '{}' CHECK (length(content) < 16000) NOT NULL, code SMALLINT NOT NULL, -- O to 100 is System reserved. sender_id BYTEA, -- NULL for System messages created_at BIGINT CHECK (created_at > 0) NOT NULL, expires_at BIGINT CHECK (expires_at > created_at) NOT NULL, deleted_at BIGINT DEFAULT 0 NOT NULL ); -- list notifications for a user that are not deleted or expired, starting from a given ID (cursor). CREATE INDEX IF NOT EXISTS notification_user_id_deleted_at_expires_at_id_idx ON notification (user_id, deleted_at ASC, expires_at ASC, id); -- +migrate Down DROP TABLE IF EXISTS purchase; DROP TABLE IF EXISTS notification; server/api.proto +62 −7 Original line number Diff line number Diff line Loading @@ -258,6 +258,11 @@ message Envelope { TPurchaseValidation purchase = 65; TPurchaseRecord purchase_record = 66; TNotificationsList notifications_list = 67; TNotificationsRemove notifications_remove = 68; TNotifications notifications = 69; Notifications live_notifications = 70; } } Loading Loading @@ -567,14 +572,14 @@ message TGroupsRemove { /** * TGroupsSelfList fetches a list of groups that the current user is part of. * * @returns TGroup * @returns TGroups */ message TGroupsSelfList {} /** * TGroupsFetch fetches a list of groups that the match the group ID or group name. * * @returns TGroup * @returns TGroups */ message TGroupsFetch { message GroupFetch { Loading Loading @@ -1280,15 +1285,65 @@ message TPurchaseValidation { * TPurchaseRecord is the response of purchase validation */ message TPurchaseRecord { // Whether or not the transaction is valid and all the information matches. /// Whether or not the transaction is valid and all the information matches. bool success = 1; // If this is a new transaction or if Nakama has a log of it. /// If this is a new transaction or if Nakama has a log of it. bool seen_before = 2; // Indicates whether or not Nakama was able to reach the remote purchase service. /// Indicates whether or not Nakama was able to reach the remote purchase service. bool purchase_provider_reachable = 3; // A string indicating why the purchase verification failed, if appropriate. /// A string indicating why the purchase verification failed, if appropriate. string message = 6; // The complete response Nakama received from the remote service. /// The complete response Nakama received from the remote service. string data = 5; } /** * Notification is the core domain type representing an in-app notification. */ message Notification { bytes id = 1; string subject = 2; bytes content = 3; int64 code = 4; bytes sender_id = 5; int64 created_at = 6; int64 expires_at = 7; bool persistent = 8; } /** * Notification is the core domain type representing a list of live in-app notification. */ message Notifications { repeated Notification notifications = 1; } /** * TNotificationsList is used to list unexpired notifications. */ message TNotificationsList { /// Max number of notifications to list. Between 10 and 100. int64 limit = 1; /// Use this cursor to paginate notifications. /// Cache this to catch up to new notifications. /// The value of this comes from TNotifications.resumable_cursor. bytes resumable_cursor = 2; } /** * TNotifications is the response of listing notifications */ message TNotifications { repeated Notification notifications = 1; /// Use this cursor to paginate notifications. /// Cache this to catch up to new notifications. bytes resumable_cursor = 2; } /** * TNotificationsRemove is used to delete notifications. */ message TNotificationsRemove { repeated bytes notification_ids = 1; } server/config.go +10 −1 Original line number Diff line number Diff line Loading @@ -240,6 +240,7 @@ func NewDatabaseConfig() *DatabaseConfig { // SocialConfig is configuration relevant to the Social providers type SocialConfig struct { Notification *NotificationConfig `yaml:"notification" json:"notification" usage:"Notification configuration"` Steam *SocialConfigSteam `yaml:"steam" json:"steam" usage:"Steam configuration"` } Loading @@ -249,6 +250,11 @@ type SocialConfigSteam struct { AppID int `yaml:"app_id" json:"app_id" usage:"Steam App ID."` } // NotificationConfig is configuration relevant to notification center type NotificationConfig struct { ExpiryMs int64 `yaml:"expiry_ms" json:"expiry_ms" usage:"Notification expiry in milliseconds."` } // NewSocialConfig creates a new SocialConfig struct func NewSocialConfig() *SocialConfig { return &SocialConfig{ Loading @@ -256,6 +262,9 @@ func NewSocialConfig() *SocialConfig { PublisherKey: "", AppID: 0, }, Notification: &NotificationConfig{ ExpiryMs: 86400000, // one day expiry }, } } Loading Loading
CHANGELOG.md +1 −0 Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ The format is based on [keep a changelog](http://keepachangelog.com/) and this p - New storage list feature. - Ban users and create groups from within the Runtime environment. - New In-App Purchase validation feature. - New In-App Notification feature. ### Changed - Run Facebook friends import after registration completes. Loading
main.go +3 −2 Original line number Diff line number Diff line Loading @@ -92,15 +92,16 @@ func main() { messageRouter := server.NewMessageRouterService(sessionRegistry) presenceNotifier := server.NewPresenceNotifier(jsonLogger, config.GetName(), trackerService, messageRouter) trackerService.AddDiffListener(presenceNotifier.HandleDiff) notificationService := server.NewNotificationService(jsonLogger, db, trackerService, messageRouter, config.GetSocial().Notification) runtime, err := server.NewRuntime(jsonLogger, multiLogger, db, config.GetRuntime()) runtime, err := server.NewRuntime(jsonLogger, multiLogger, db, config.GetRuntime(), notificationService) if err != nil { multiLogger.Fatal("Failed initializing runtime modules.", zap.Error(err)) } socialClient := social.NewClient(5 * time.Second) purchaseService := server.NewPurchaseService(jsonLogger, multiLogger, db, config.GetPurchase()) pipeline := server.NewPipeline(config, db, trackerService, matchmakerService, messageRouter, sessionRegistry, socialClient, runtime, purchaseService) pipeline := server.NewPipeline(config, db, trackerService, matchmakerService, messageRouter, sessionRegistry, socialClient, runtime, purchaseService, notificationService) authService := server.NewAuthenticationService(jsonLogger, config, db, statsService, sessionRegistry, socialClient, pipeline, runtime) dashboardService := server.NewDashboardService(jsonLogger, multiLogger, semver, config, statsService) Loading
migrations/20170620104217_storage_list.sql→migrations/20170620104217_storage_list_purchase_notifications.sql +17 −0 Original line number Diff line number Diff line Loading @@ -51,5 +51,22 @@ CREATE INDEX IF NOT EXISTS purchase_user_id_created_at_provider_receipt_id_idx O -- list purchases by most recent timestamp CREATE INDEX IF NOT EXISTS purchase_created_at_user_id_provider_receipt_id_idx ON purchase (created_at, user_id, provider, receipt_id); CREATE TABLE IF NOT EXISTS notification ( PRIMARY KEY (id), id BYTEA NOT NULL, user_id BYTEA NOT NULL, subject VARCHAR(255) NOT NULL, content BYTEA DEFAULT '{}' CHECK (length(content) < 16000) NOT NULL, code SMALLINT NOT NULL, -- O to 100 is System reserved. sender_id BYTEA, -- NULL for System messages created_at BIGINT CHECK (created_at > 0) NOT NULL, expires_at BIGINT CHECK (expires_at > created_at) NOT NULL, deleted_at BIGINT DEFAULT 0 NOT NULL ); -- list notifications for a user that are not deleted or expired, starting from a given ID (cursor). CREATE INDEX IF NOT EXISTS notification_user_id_deleted_at_expires_at_id_idx ON notification (user_id, deleted_at ASC, expires_at ASC, id); -- +migrate Down DROP TABLE IF EXISTS purchase; DROP TABLE IF EXISTS notification;
server/api.proto +62 −7 Original line number Diff line number Diff line Loading @@ -258,6 +258,11 @@ message Envelope { TPurchaseValidation purchase = 65; TPurchaseRecord purchase_record = 66; TNotificationsList notifications_list = 67; TNotificationsRemove notifications_remove = 68; TNotifications notifications = 69; Notifications live_notifications = 70; } } Loading Loading @@ -567,14 +572,14 @@ message TGroupsRemove { /** * TGroupsSelfList fetches a list of groups that the current user is part of. * * @returns TGroup * @returns TGroups */ message TGroupsSelfList {} /** * TGroupsFetch fetches a list of groups that the match the group ID or group name. * * @returns TGroup * @returns TGroups */ message TGroupsFetch { message GroupFetch { Loading Loading @@ -1280,15 +1285,65 @@ message TPurchaseValidation { * TPurchaseRecord is the response of purchase validation */ message TPurchaseRecord { // Whether or not the transaction is valid and all the information matches. /// Whether or not the transaction is valid and all the information matches. bool success = 1; // If this is a new transaction or if Nakama has a log of it. /// If this is a new transaction or if Nakama has a log of it. bool seen_before = 2; // Indicates whether or not Nakama was able to reach the remote purchase service. /// Indicates whether or not Nakama was able to reach the remote purchase service. bool purchase_provider_reachable = 3; // A string indicating why the purchase verification failed, if appropriate. /// A string indicating why the purchase verification failed, if appropriate. string message = 6; // The complete response Nakama received from the remote service. /// The complete response Nakama received from the remote service. string data = 5; } /** * Notification is the core domain type representing an in-app notification. */ message Notification { bytes id = 1; string subject = 2; bytes content = 3; int64 code = 4; bytes sender_id = 5; int64 created_at = 6; int64 expires_at = 7; bool persistent = 8; } /** * Notification is the core domain type representing a list of live in-app notification. */ message Notifications { repeated Notification notifications = 1; } /** * TNotificationsList is used to list unexpired notifications. */ message TNotificationsList { /// Max number of notifications to list. Between 10 and 100. int64 limit = 1; /// Use this cursor to paginate notifications. /// Cache this to catch up to new notifications. /// The value of this comes from TNotifications.resumable_cursor. bytes resumable_cursor = 2; } /** * TNotifications is the response of listing notifications */ message TNotifications { repeated Notification notifications = 1; /// Use this cursor to paginate notifications. /// Cache this to catch up to new notifications. bytes resumable_cursor = 2; } /** * TNotificationsRemove is used to delete notifications. */ message TNotificationsRemove { repeated bytes notification_ids = 1; }
server/config.go +10 −1 Original line number Diff line number Diff line Loading @@ -240,6 +240,7 @@ func NewDatabaseConfig() *DatabaseConfig { // SocialConfig is configuration relevant to the Social providers type SocialConfig struct { Notification *NotificationConfig `yaml:"notification" json:"notification" usage:"Notification configuration"` Steam *SocialConfigSteam `yaml:"steam" json:"steam" usage:"Steam configuration"` } Loading @@ -249,6 +250,11 @@ type SocialConfigSteam struct { AppID int `yaml:"app_id" json:"app_id" usage:"Steam App ID."` } // NotificationConfig is configuration relevant to notification center type NotificationConfig struct { ExpiryMs int64 `yaml:"expiry_ms" json:"expiry_ms" usage:"Notification expiry in milliseconds."` } // NewSocialConfig creates a new SocialConfig struct func NewSocialConfig() *SocialConfig { return &SocialConfig{ Loading @@ -256,6 +262,9 @@ func NewSocialConfig() *SocialConfig { PublisherKey: "", AppID: 0, }, Notification: &NotificationConfig{ ExpiryMs: 86400000, // one day expiry }, } } Loading