aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--server/controllers/api/users/my-notifications.ts3
-rw-r--r--server/initializers/migrations/0360-notification-instance-follower.ts40
-rw-r--r--server/lib/activitypub/actor.ts2
-rw-r--r--server/lib/activitypub/process/process-follow.ts19
-rw-r--r--server/lib/emailer.ts18
-rw-r--r--server/lib/job-queue/handlers/activitypub-follow.ts2
-rw-r--r--server/lib/notifier.ts38
-rw-r--r--server/lib/user.ts3
-rw-r--r--server/middlewares/validators/user-notifications.ts14
-rw-r--r--server/models/account/user-notification-setting.ts12
-rw-r--r--server/models/account/user-notification.ts1
-rw-r--r--server/tests/api/check-params/user-notifications.ts3
-rw-r--r--server/tests/api/index-1.ts1
-rw-r--r--server/tests/api/notifications/index.ts1
-rw-r--r--server/tests/api/notifications/user-notifications.ts (renamed from server/tests/api/users/user-notifications.ts)35
-rw-r--r--server/tests/api/users/index.ts1
-rw-r--r--shared/models/users/user-notification-setting.model.ts1
-rw-r--r--shared/models/users/user-notification.model.ts7
-rw-r--r--shared/utils/users/user-notifications.ts32
19 files changed, 212 insertions, 21 deletions
diff --git a/server/controllers/api/users/my-notifications.ts b/server/controllers/api/users/my-notifications.ts
index 4edad2a74..f146284e4 100644
--- a/server/controllers/api/users/my-notifications.ts
+++ b/server/controllers/api/users/my-notifications.ts
@@ -75,7 +75,8 @@ async function updateNotificationSettings (req: express.Request, res: express.Re
75 myVideoImportFinished: body.myVideoImportFinished, 75 myVideoImportFinished: body.myVideoImportFinished,
76 newFollow: body.newFollow, 76 newFollow: body.newFollow,
77 newUserRegistration: body.newUserRegistration, 77 newUserRegistration: body.newUserRegistration,
78 commentMention: body.commentMention 78 commentMention: body.commentMention,
79 newInstanceFollower: body.newInstanceFollower
79 } 80 }
80 81
81 await UserNotificationSettingModel.update(values, query) 82 await UserNotificationSettingModel.update(values, query)
diff --git a/server/initializers/migrations/0360-notification-instance-follower.ts b/server/initializers/migrations/0360-notification-instance-follower.ts
new file mode 100644
index 000000000..05caf8e1d
--- /dev/null
+++ b/server/initializers/migrations/0360-notification-instance-follower.ts
@@ -0,0 +1,40 @@
1import * as Sequelize from 'sequelize'
2
3async function up (utils: {
4 transaction: Sequelize.Transaction,
5 queryInterface: Sequelize.QueryInterface,
6 sequelize: Sequelize.Sequelize,
7 db: any
8}): Promise<void> {
9 {
10 const data = {
11 type: Sequelize.INTEGER,
12 defaultValue: null,
13 allowNull: true
14 }
15 await utils.queryInterface.addColumn('userNotificationSetting', 'newInstanceFollower', data)
16 }
17
18 {
19 const query = 'UPDATE "userNotificationSetting" SET "newInstanceFollower" = 1'
20 await utils.sequelize.query(query)
21 }
22
23 {
24 const data = {
25 type: Sequelize.INTEGER,
26 defaultValue: null,
27 allowNull: false
28 }
29 await utils.queryInterface.changeColumn('userNotificationSetting', 'newInstanceFollower', data)
30 }
31}
32
33function down (options) {
34 throw new Error('Not implemented.')
35}
36
37export {
38 up,
39 down
40}
diff --git a/server/lib/activitypub/actor.ts b/server/lib/activitypub/actor.ts
index 63e810642..c0ad07a52 100644
--- a/server/lib/activitypub/actor.ts
+++ b/server/lib/activitypub/actor.ts
@@ -342,6 +342,8 @@ function saveActorAndServerAndModelIfNotExist (
342 actorCreated.VideoChannel.Account = ownerActor.Account 342 actorCreated.VideoChannel.Account = ownerActor.Account
343 } 343 }
344 344
345 actorCreated.Server = server
346
345 return actorCreated 347 return actorCreated
346 } 348 }
347} 349}
diff --git a/server/lib/activitypub/process/process-follow.ts b/server/lib/activitypub/process/process-follow.ts
index 140bbe9f1..276a57e60 100644
--- a/server/lib/activitypub/process/process-follow.ts
+++ b/server/lib/activitypub/process/process-follow.ts
@@ -24,14 +24,16 @@ export {
24// --------------------------------------------------------------------------- 24// ---------------------------------------------------------------------------
25 25
26async function processFollow (actor: ActorModel, targetActorURL: string) { 26async function processFollow (actor: ActorModel, targetActorURL: string) {
27 const { actorFollow, created } = await sequelizeTypescript.transaction(async t => { 27 const { actorFollow, created, isFollowingInstance } = await sequelizeTypescript.transaction(async t => {
28 const targetActor = await ActorModel.loadByUrlAndPopulateAccountAndChannel(targetActorURL, t) 28 const targetActor = await ActorModel.loadByUrlAndPopulateAccountAndChannel(targetActorURL, t)
29 29
30 if (!targetActor) throw new Error('Unknown actor') 30 if (!targetActor) throw new Error('Unknown actor')
31 if (targetActor.isOwned() === false) throw new Error('This is not a local actor.') 31 if (targetActor.isOwned() === false) throw new Error('This is not a local actor.')
32 32
33 const serverActor = await getServerActor() 33 const serverActor = await getServerActor()
34 if (targetActor.id === serverActor.id && CONFIG.FOLLOWERS.INSTANCE.ENABLED === false) { 34 const isFollowingInstance = targetActor.id === serverActor.id
35
36 if (isFollowingInstance && CONFIG.FOLLOWERS.INSTANCE.ENABLED === false) {
35 logger.info('Rejecting %s because instance followers are disabled.', targetActor.url) 37 logger.info('Rejecting %s because instance followers are disabled.', targetActor.url)
36 38
37 return sendReject(actor, targetActor) 39 return sendReject(actor, targetActor)
@@ -50,9 +52,6 @@ async function processFollow (actor: ActorModel, targetActorURL: string) {
50 transaction: t 52 transaction: t
51 }) 53 })
52 54
53 actorFollow.ActorFollower = actor
54 actorFollow.ActorFollowing = targetActor
55
56 if (actorFollow.state !== 'accepted' && CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL === false) { 55 if (actorFollow.state !== 'accepted' && CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL === false) {
57 actorFollow.state = 'accepted' 56 actorFollow.state = 'accepted'
58 await actorFollow.save({ transaction: t }) 57 await actorFollow.save({ transaction: t })
@@ -64,10 +63,16 @@ async function processFollow (actor: ActorModel, targetActorURL: string) {
64 // Target sends to actor he accepted the follow request 63 // Target sends to actor he accepted the follow request
65 if (actorFollow.state === 'accepted') await sendAccept(actorFollow) 64 if (actorFollow.state === 'accepted') await sendAccept(actorFollow)
66 65
67 return { actorFollow, created } 66 return { actorFollow, created, isFollowingInstance }
68 }) 67 })
69 68
70 if (created) Notifier.Instance.notifyOfNewFollow(actorFollow) 69 // Rejected
70 if (!actorFollow) return
71
72 if (created) {
73 if (isFollowingInstance) Notifier.Instance.notifyOfNewInstanceFollow(actorFollow)
74 else Notifier.Instance.notifyOfNewUserFollow(actorFollow)
75 }
71 76
72 logger.info('Actor %s is followed by actor %s.', targetActorURL, actor.url) 77 logger.info('Actor %s is followed by actor %s.', targetActorURL, actor.url)
73} 78}
diff --git a/server/lib/emailer.ts b/server/lib/emailer.ts
index eec97c27e..aa9083362 100644
--- a/server/lib/emailer.ts
+++ b/server/lib/emailer.ts
@@ -129,6 +129,24 @@ class Emailer {
129 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) 129 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
130 } 130 }
131 131
132 addNewInstanceFollowerNotification (to: string[], actorFollow: ActorFollowModel) {
133 const awaitingApproval = actorFollow.state === 'pending' ? ' awaiting manual approval.' : ''
134
135 const text = `Hi dear admin,\n\n` +
136 `Your instance has a new follower: ${actorFollow.ActorFollower.url}${awaitingApproval}` +
137 `\n\n` +
138 `Cheers,\n` +
139 `PeerTube.`
140
141 const emailPayload: EmailPayload = {
142 to,
143 subject: 'New instance follower',
144 text
145 }
146
147 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
148 }
149
132 myVideoPublishedNotification (to: string[], video: VideoModel) { 150 myVideoPublishedNotification (to: string[], video: VideoModel) {
133 const videoUrl = CONFIG.WEBSERVER.URL + video.getWatchStaticPath() 151 const videoUrl = CONFIG.WEBSERVER.URL + video.getWatchStaticPath()
134 152
diff --git a/server/lib/job-queue/handlers/activitypub-follow.ts b/server/lib/job-queue/handlers/activitypub-follow.ts
index b4d381062..e7e5ff950 100644
--- a/server/lib/job-queue/handlers/activitypub-follow.ts
+++ b/server/lib/job-queue/handlers/activitypub-follow.ts
@@ -73,5 +73,5 @@ async function follow (fromActor: ActorModel, targetActor: ActorModel) {
73 return actorFollow 73 return actorFollow
74 }) 74 })
75 75
76 if (actorFollow.state === 'accepted') Notifier.Instance.notifyOfNewFollow(actorFollow) 76 if (actorFollow.state === 'accepted') Notifier.Instance.notifyOfNewUserFollow(actorFollow)
77} 77}
diff --git a/server/lib/notifier.ts b/server/lib/notifier.ts
index 9fe93ec0d..91b71cc64 100644
--- a/server/lib/notifier.ts
+++ b/server/lib/notifier.ts
@@ -92,18 +92,25 @@ class Notifier {
92 .catch(err => logger.error('Cannot notify moderators of new user registration (%s).', user.username, { err })) 92 .catch(err => logger.error('Cannot notify moderators of new user registration (%s).', user.username, { err }))
93 } 93 }
94 94
95 notifyOfNewFollow (actorFollow: ActorFollowModel): void { 95 notifyOfNewUserFollow (actorFollow: ActorFollowModel): void {
96 this.notifyUserOfNewActorFollow(actorFollow) 96 this.notifyUserOfNewActorFollow(actorFollow)
97 .catch(err => { 97 .catch(err => {
98 logger.error( 98 logger.error(
99 'Cannot notify owner of channel %s of a new follow by %s.', 99 'Cannot notify owner of channel %s of a new follow by %s.',
100 actorFollow.ActorFollowing.VideoChannel.getDisplayName(), 100 actorFollow.ActorFollowing.VideoChannel.getDisplayName(),
101 actorFollow.ActorFollower.Account.getDisplayName(), 101 actorFollow.ActorFollower.Account.getDisplayName(),
102 err 102 { err }
103 ) 103 )
104 }) 104 })
105 } 105 }
106 106
107 notifyOfNewInstanceFollow (actorFollow: ActorFollowModel): void {
108 this.notifyAdminsOfNewInstanceFollow(actorFollow)
109 .catch(err => {
110 logger.error('Cannot notify administrators of new follower %s.', actorFollow.ActorFollower.url, { err })
111 })
112 }
113
107 private async notifySubscribersOfNewVideo (video: VideoModel) { 114 private async notifySubscribersOfNewVideo (video: VideoModel) {
108 // List all followers that are users 115 // List all followers that are users
109 const users = await UserModel.listUserSubscribersOf(video.VideoChannel.actorId) 116 const users = await UserModel.listUserSubscribersOf(video.VideoChannel.actorId)
@@ -261,6 +268,33 @@ class Notifier {
261 return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) 268 return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
262 } 269 }
263 270
271 private async notifyAdminsOfNewInstanceFollow (actorFollow: ActorFollowModel) {
272 const admins = await UserModel.listWithRight(UserRight.MANAGE_SERVER_FOLLOW)
273
274 logger.info('Notifying %d administrators of new instance follower: %s.', admins.length, actorFollow.ActorFollower.url)
275
276 function settingGetter (user: UserModel) {
277 return user.NotificationSetting.newInstanceFollower
278 }
279
280 async function notificationCreator (user: UserModel) {
281 const notification = await UserNotificationModel.create({
282 type: UserNotificationType.NEW_INSTANCE_FOLLOWER,
283 userId: user.id,
284 actorFollowId: actorFollow.id
285 })
286 notification.ActorFollow = actorFollow
287
288 return notification
289 }
290
291 function emailSender (emails: string[]) {
292 return Emailer.Instance.addNewInstanceFollowerNotification(emails, actorFollow)
293 }
294
295 return this.notify({ users: admins, settingGetter, notificationCreator, emailSender })
296 }
297
264 private async notifyModeratorsOfNewVideoAbuse (videoAbuse: VideoAbuseModel) { 298 private async notifyModeratorsOfNewVideoAbuse (videoAbuse: VideoAbuseModel) {
265 const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_ABUSES) 299 const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_ABUSES)
266 if (moderators.length === 0) return 300 if (moderators.length === 0) return
diff --git a/server/lib/user.ts b/server/lib/user.ts
index 5588b0f76..6fbe3ed03 100644
--- a/server/lib/user.ts
+++ b/server/lib/user.ts
@@ -110,7 +110,8 @@ function createDefaultUserNotificationSettings (user: UserModel, t: Sequelize.Tr
110 blacklistOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, 110 blacklistOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
111 newUserRegistration: UserNotificationSettingValue.WEB, 111 newUserRegistration: UserNotificationSettingValue.WEB,
112 commentMention: UserNotificationSettingValue.WEB, 112 commentMention: UserNotificationSettingValue.WEB,
113 newFollow: UserNotificationSettingValue.WEB 113 newFollow: UserNotificationSettingValue.WEB,
114 newInstanceFollower: UserNotificationSettingValue.WEB
114 } 115 }
115 116
116 return UserNotificationSettingModel.create(values, { transaction: t }) 117 return UserNotificationSettingModel.create(values, { transaction: t })
diff --git a/server/middlewares/validators/user-notifications.ts b/server/middlewares/validators/user-notifications.ts
index 46486e081..3ded8d8cf 100644
--- a/server/middlewares/validators/user-notifications.ts
+++ b/server/middlewares/validators/user-notifications.ts
@@ -28,8 +28,22 @@ const updateNotificationSettingsValidator = [
28 .custom(isUserNotificationSettingValid).withMessage('Should have a valid new comment on my video notification setting'), 28 .custom(isUserNotificationSettingValid).withMessage('Should have a valid new comment on my video notification setting'),
29 body('videoAbuseAsModerator') 29 body('videoAbuseAsModerator')
30 .custom(isUserNotificationSettingValid).withMessage('Should have a valid new video abuse as moderator notification setting'), 30 .custom(isUserNotificationSettingValid).withMessage('Should have a valid new video abuse as moderator notification setting'),
31 body('videoAutoBlacklistAsModerator')
32 .custom(isUserNotificationSettingValid).withMessage('Should have a valid video auto blacklist notification setting'),
31 body('blacklistOnMyVideo') 33 body('blacklistOnMyVideo')
32 .custom(isUserNotificationSettingValid).withMessage('Should have a valid new blacklist on my video notification setting'), 34 .custom(isUserNotificationSettingValid).withMessage('Should have a valid new blacklist on my video notification setting'),
35 body('myVideoImportFinished')
36 .custom(isUserNotificationSettingValid).withMessage('Should have a valid video import finished video notification setting'),
37 body('myVideoPublished')
38 .custom(isUserNotificationSettingValid).withMessage('Should have a valid video published notification setting'),
39 body('commentMention')
40 .custom(isUserNotificationSettingValid).withMessage('Should have a valid comment mention notification setting'),
41 body('newFollow')
42 .custom(isUserNotificationSettingValid).withMessage('Should have a valid new follow notification setting'),
43 body('newUserRegistration')
44 .custom(isUserNotificationSettingValid).withMessage('Should have a valid new user registration notification setting'),
45 body('newInstanceFollower')
46 .custom(isUserNotificationSettingValid).withMessage('Should have a valid new instance follower notification setting'),
33 47
34 (req: express.Request, res: express.Response, next: express.NextFunction) => { 48 (req: express.Request, res: express.Response, next: express.NextFunction) => {
35 logger.debug('Checking updateNotificationSettingsValidator parameters', { parameters: req.body }) 49 logger.debug('Checking updateNotificationSettingsValidator parameters', { parameters: req.body })
diff --git a/server/models/account/user-notification-setting.ts b/server/models/account/user-notification-setting.ts
index ba7f739b9..c2fbc6d23 100644
--- a/server/models/account/user-notification-setting.ts
+++ b/server/models/account/user-notification-setting.ts
@@ -104,6 +104,15 @@ export class UserNotificationSettingModel extends Model<UserNotificationSettingM
104 @AllowNull(false) 104 @AllowNull(false)
105 @Default(null) 105 @Default(null)
106 @Is( 106 @Is(
107 'UserNotificationSettingNewInstanceFollower',
108 value => throwIfNotValid(value, isUserNotificationSettingValid, 'newInstanceFollower')
109 )
110 @Column
111 newInstanceFollower: UserNotificationSettingValue
112
113 @AllowNull(false)
114 @Default(null)
115 @Is(
107 'UserNotificationSettingNewFollow', 116 'UserNotificationSettingNewFollow',
108 value => throwIfNotValid(value, isUserNotificationSettingValid, 'newFollow') 117 value => throwIfNotValid(value, isUserNotificationSettingValid, 'newFollow')
109 ) 118 )
@@ -154,7 +163,8 @@ export class UserNotificationSettingModel extends Model<UserNotificationSettingM
154 myVideoImportFinished: this.myVideoImportFinished, 163 myVideoImportFinished: this.myVideoImportFinished,
155 newUserRegistration: this.newUserRegistration, 164 newUserRegistration: this.newUserRegistration,
156 commentMention: this.commentMention, 165 commentMention: this.commentMention,
157 newFollow: this.newFollow 166 newFollow: this.newFollow,
167 newInstanceFollower: this.newInstanceFollower
158 } 168 }
159 } 169 }
160} 170}
diff --git a/server/models/account/user-notification.ts b/server/models/account/user-notification.ts
index 6cdbb827b..ccf8277ab 100644
--- a/server/models/account/user-notification.ts
+++ b/server/models/account/user-notification.ts
@@ -418,6 +418,7 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
418 418
419 const actorFollow = this.ActorFollow ? { 419 const actorFollow = this.ActorFollow ? {
420 id: this.ActorFollow.id, 420 id: this.ActorFollow.id,
421 state: this.ActorFollow.state,
421 follower: { 422 follower: {
422 id: this.ActorFollow.ActorFollower.Account.id, 423 id: this.ActorFollow.ActorFollower.Account.id,
423 displayName: this.ActorFollow.ActorFollower.Account.getDisplayName(), 424 displayName: this.ActorFollow.ActorFollower.Account.getDisplayName(),
diff --git a/server/tests/api/check-params/user-notifications.ts b/server/tests/api/check-params/user-notifications.ts
index 36eaceac7..4b75f6920 100644
--- a/server/tests/api/check-params/user-notifications.ts
+++ b/server/tests/api/check-params/user-notifications.ts
@@ -174,7 +174,8 @@ describe('Test user notifications API validators', function () {
174 myVideoPublished: UserNotificationSettingValue.WEB, 174 myVideoPublished: UserNotificationSettingValue.WEB,
175 commentMention: UserNotificationSettingValue.WEB, 175 commentMention: UserNotificationSettingValue.WEB,
176 newFollow: UserNotificationSettingValue.WEB, 176 newFollow: UserNotificationSettingValue.WEB,
177 newUserRegistration: UserNotificationSettingValue.WEB 177 newUserRegistration: UserNotificationSettingValue.WEB,
178 newInstanceFollower: UserNotificationSettingValue.WEB
178 } 179 }
179 180
180 it('Should fail with missing fields', async function () { 181 it('Should fail with missing fields', async function () {
diff --git a/server/tests/api/index-1.ts b/server/tests/api/index-1.ts
index 80d752f42..75cdd9025 100644
--- a/server/tests/api/index-1.ts
+++ b/server/tests/api/index-1.ts
@@ -1,2 +1,3 @@
1import './check-params' 1import './check-params'
2import './notifications'
2import './search' 3import './search'
diff --git a/server/tests/api/notifications/index.ts b/server/tests/api/notifications/index.ts
new file mode 100644
index 000000000..95ac8fc51
--- /dev/null
+++ b/server/tests/api/notifications/index.ts
@@ -0,0 +1 @@
export * from './user-notifications'
diff --git a/server/tests/api/users/user-notifications.ts b/server/tests/api/notifications/user-notifications.ts
index ac47978e2..7bff52796 100644
--- a/server/tests/api/users/user-notifications.ts
+++ b/server/tests/api/notifications/user-notifications.ts
@@ -19,7 +19,7 @@ import {
19 userLogin, 19 userLogin,
20 wait, 20 wait,
21 getCustomConfig, 21 getCustomConfig,
22 updateCustomConfig, getVideoThreadComments, getVideoCommentThreads 22 updateCustomConfig, getVideoThreadComments, getVideoCommentThreads, follow
23} from '../../../../shared/utils' 23} from '../../../../shared/utils'
24import { killallServers, ServerInfo, uploadVideo } from '../../../../shared/utils/index' 24import { killallServers, ServerInfo, uploadVideo } from '../../../../shared/utils/index'
25import { setAccessTokensToServers } from '../../../../shared/utils/users/login' 25import { setAccessTokensToServers } from '../../../../shared/utils/users/login'
@@ -41,7 +41,7 @@ import {
41 getUserNotifications, 41 getUserNotifications,
42 markAsReadNotifications, 42 markAsReadNotifications,
43 updateMyNotificationSettings, 43 updateMyNotificationSettings,
44 markAsReadAllNotifications 44 markAsReadAllNotifications, checkNewInstanceFollower
45} from '../../../../shared/utils/users/user-notifications' 45} from '../../../../shared/utils/users/user-notifications'
46import { 46import {
47 User, 47 User,
@@ -103,7 +103,8 @@ describe('Test users notifications', function () {
103 myVideoPublished: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, 103 myVideoPublished: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
104 commentMention: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, 104 commentMention: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
105 newFollow: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, 105 newFollow: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
106 newUserRegistration: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL 106 newUserRegistration: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
107 newInstanceFollower: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL
107 } 108 }
108 109
109 before(async function () { 110 before(async function () {
@@ -118,7 +119,7 @@ describe('Test users notifications', function () {
118 hostname: 'localhost' 119 hostname: 'localhost'
119 } 120 }
120 } 121 }
121 servers = await flushAndRunMultipleServers(2, overrideConfig) 122 servers = await flushAndRunMultipleServers(3, overrideConfig)
122 123
123 // Get the access tokens 124 // Get the access tokens
124 await setAccessTokensToServers(servers) 125 await setAccessTokensToServers(servers)
@@ -861,6 +862,32 @@ describe('Test users notifications', function () {
861 }) 862 })
862 }) 863 })
863 864
865 describe('New instance follower', function () {
866 let baseParams: CheckerBaseParams
867
868 before(async () => {
869 baseParams = {
870 server: servers[0],
871 emails,
872 socketNotifications: adminNotifications,
873 token: servers[0].accessToken
874 }
875 })
876
877 it('Should send a notification only to admin when there is a new instance follower', async function () {
878 this.timeout(10000)
879
880 await follow(servers[2].url, [ servers[0].url ], servers[2].accessToken)
881
882 await waitJobs(servers)
883
884 await checkNewInstanceFollower(baseParams, 'localhost:9003', 'presence')
885
886 const userOverride = { socketNotifications: userNotifications, token: userAccessToken, check: { web: true, mail: false } }
887 await checkNewInstanceFollower(immutableAssign(baseParams, userOverride), 'localhost:9003', 'absence')
888 })
889 })
890
864 describe('New actor follow', function () { 891 describe('New actor follow', function () {
865 let baseParams: CheckerBaseParams 892 let baseParams: CheckerBaseParams
866 let myChannelName = 'super channel name' 893 let myChannelName = 'super channel name'
diff --git a/server/tests/api/users/index.ts b/server/tests/api/users/index.ts
index 52ba6984e..fcd022429 100644
--- a/server/tests/api/users/index.ts
+++ b/server/tests/api/users/index.ts
@@ -1,5 +1,4 @@
1import './users-verification' 1import './users-verification'
2import './user-notifications'
3import './blocklist' 2import './blocklist'
4import './user-subscriptions' 3import './user-subscriptions'
5import './users' 4import './users'
diff --git a/shared/models/users/user-notification-setting.model.ts b/shared/models/users/user-notification-setting.model.ts
index 57b33e4b8..e2a882b69 100644
--- a/shared/models/users/user-notification-setting.model.ts
+++ b/shared/models/users/user-notification-setting.model.ts
@@ -15,4 +15,5 @@ export interface UserNotificationSetting {
15 newUserRegistration: UserNotificationSettingValue 15 newUserRegistration: UserNotificationSettingValue
16 newFollow: UserNotificationSettingValue 16 newFollow: UserNotificationSettingValue
17 commentMention: UserNotificationSettingValue 17 commentMention: UserNotificationSettingValue
18 newInstanceFollower: UserNotificationSettingValue
18} 19}
diff --git a/shared/models/users/user-notification.model.ts b/shared/models/users/user-notification.model.ts
index 19892b61a..fafc2b7d7 100644
--- a/shared/models/users/user-notification.model.ts
+++ b/shared/models/users/user-notification.model.ts
@@ -1,3 +1,5 @@
1import { FollowState } from '../actors'
2
1export enum UserNotificationType { 3export enum UserNotificationType {
2 NEW_VIDEO_FROM_SUBSCRIPTION = 1, 4 NEW_VIDEO_FROM_SUBSCRIPTION = 1,
3 NEW_COMMENT_ON_MY_VIDEO = 2, 5 NEW_COMMENT_ON_MY_VIDEO = 2,
@@ -15,7 +17,9 @@ export enum UserNotificationType {
15 NEW_FOLLOW = 10, 17 NEW_FOLLOW = 10,
16 COMMENT_MENTION = 11, 18 COMMENT_MENTION = 11,
17 19
18 VIDEO_AUTO_BLACKLIST_FOR_MODERATORS = 12 20 VIDEO_AUTO_BLACKLIST_FOR_MODERATORS = 12,
21
22 NEW_INSTANCE_FOLLOWER = 13
19} 23}
20 24
21export interface VideoInfo { 25export interface VideoInfo {
@@ -73,6 +77,7 @@ export interface UserNotification {
73 actorFollow?: { 77 actorFollow?: {
74 id: number 78 id: number
75 follower: ActorInfo 79 follower: ActorInfo
80 state: FollowState
76 following: { 81 following: {
77 type: 'account' | 'channel' 82 type: 'account' | 'channel'
78 name: string 83 name: string
diff --git a/shared/utils/users/user-notifications.ts b/shared/utils/users/user-notifications.ts
index e3a79f523..495ff80d9 100644
--- a/shared/utils/users/user-notifications.ts
+++ b/shared/utils/users/user-notifications.ts
@@ -298,6 +298,35 @@ async function checkNewActorFollow (
298 await checkNotification(base, notificationChecker, emailFinder, type) 298 await checkNotification(base, notificationChecker, emailFinder, type)
299} 299}
300 300
301async function checkNewInstanceFollower (base: CheckerBaseParams, followerHost: string, type: CheckerType) {
302 const notificationType = UserNotificationType.NEW_INSTANCE_FOLLOWER
303
304 function notificationChecker (notification: UserNotification, type: CheckerType) {
305 if (type === 'presence') {
306 expect(notification).to.not.be.undefined
307 expect(notification.type).to.equal(notificationType)
308
309 checkActor(notification.actorFollow.follower)
310 expect(notification.actorFollow.follower.name).to.equal('peertube')
311 expect(notification.actorFollow.follower.host).to.equal(followerHost)
312
313 expect(notification.actorFollow.following.name).to.equal('peertube')
314 } else {
315 expect(notification).to.satisfy(n => {
316 return n.type !== notificationType || n.actorFollow.follower.host !== followerHost
317 })
318 }
319 }
320
321 function emailFinder (email: object) {
322 const text: string = email[ 'text' ]
323
324 return text.includes('instance has a new follower') && text.includes(followerHost)
325 }
326
327 await checkNotification(base, notificationChecker, emailFinder, type)
328}
329
301async function checkCommentMention ( 330async function checkCommentMention (
302 base: CheckerBaseParams, 331 base: CheckerBaseParams,
303 uuid: string, 332 uuid: string,
@@ -462,5 +491,6 @@ export {
462 checkVideoAutoBlacklistForModerators, 491 checkVideoAutoBlacklistForModerators,
463 getUserNotifications, 492 getUserNotifications,
464 markAsReadNotifications, 493 markAsReadNotifications,
465 getLastNotification 494 getLastNotification,
495 checkNewInstanceFollower
466} 496}