aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--server/controllers/api/users/my-notifications.ts12
-rw-r--r--server/initializers/migrations/0315-user-notifications.ts6
-rw-r--r--server/lib/emailer.ts61
-rw-r--r--server/lib/job-queue/handlers/video-file.ts24
-rw-r--r--server/lib/job-queue/handlers/video-import.ts3
-rw-r--r--server/lib/notifier.ts76
-rw-r--r--server/lib/schedulers/update-videos-scheduler.ts14
-rw-r--r--server/lib/user.ts2
-rw-r--r--server/middlewares/validators/user-notifications.ts18
-rw-r--r--server/models/account/account-blocklist.ts15
-rw-r--r--server/models/account/user-notification-setting.ts22
-rw-r--r--server/models/account/user-notification.ts150
-rw-r--r--server/models/account/user.ts24
-rw-r--r--server/models/video/video-file.ts2
-rw-r--r--server/models/video/video-import.ts4
-rw-r--r--server/models/video/video.ts10
-rw-r--r--server/tests/api/check-params/user-notifications.ts16
-rw-r--r--server/tests/api/users/user-notifications.ts401
-rw-r--r--server/tests/fixtures/video_short_240p.mp4bin0 -> 14082 bytes
-rw-r--r--shared/models/users/user-notification-setting.model.ts2
-rw-r--r--shared/models/users/user-notification.model.ts17
-rw-r--r--shared/utils/users/user-notifications.ts180
-rw-r--r--shared/utils/videos/video-imports.ts5
23 files changed, 814 insertions, 250 deletions
diff --git a/server/controllers/api/users/my-notifications.ts b/server/controllers/api/users/my-notifications.ts
index cef1d237c..4b81777a4 100644
--- a/server/controllers/api/users/my-notifications.ts
+++ b/server/controllers/api/users/my-notifications.ts
@@ -14,10 +14,11 @@ import { getFormattedObjects } from '../../../helpers/utils'
14import { UserNotificationModel } from '../../../models/account/user-notification' 14import { UserNotificationModel } from '../../../models/account/user-notification'
15import { meRouter } from './me' 15import { meRouter } from './me'
16import { 16import {
17 listUserNotificationsValidator,
17 markAsReadUserNotificationsValidator, 18 markAsReadUserNotificationsValidator,
18 updateNotificationSettingsValidator 19 updateNotificationSettingsValidator
19} from '../../../middlewares/validators/user-notifications' 20} from '../../../middlewares/validators/user-notifications'
20import { UserNotificationSetting } from '../../../../shared/models/users' 21import { UserNotificationSetting, UserNotificationSettingValue } from '../../../../shared/models/users'
21import { UserNotificationSettingModel } from '../../../models/account/user-notification-setting' 22import { UserNotificationSettingModel } from '../../../models/account/user-notification-setting'
22 23
23const myNotificationsRouter = express.Router() 24const myNotificationsRouter = express.Router()
@@ -34,6 +35,7 @@ myNotificationsRouter.get('/me/notifications',
34 userNotificationsSortValidator, 35 userNotificationsSortValidator,
35 setDefaultSort, 36 setDefaultSort,
36 setDefaultPagination, 37 setDefaultPagination,
38 listUserNotificationsValidator,
37 asyncMiddleware(listUserNotifications) 39 asyncMiddleware(listUserNotifications)
38) 40)
39 41
@@ -61,7 +63,11 @@ async function updateNotificationSettings (req: express.Request, res: express.Re
61 63
62 await UserNotificationSettingModel.update({ 64 await UserNotificationSettingModel.update({
63 newVideoFromSubscription: body.newVideoFromSubscription, 65 newVideoFromSubscription: body.newVideoFromSubscription,
64 newCommentOnMyVideo: body.newCommentOnMyVideo 66 newCommentOnMyVideo: body.newCommentOnMyVideo,
67 videoAbuseAsModerator: body.videoAbuseAsModerator,
68 blacklistOnMyVideo: body.blacklistOnMyVideo,
69 myVideoPublished: body.myVideoPublished,
70 myVideoImportFinished: body.myVideoImportFinished
65 }, query) 71 }, query)
66 72
67 return res.status(204).end() 73 return res.status(204).end()
@@ -70,7 +76,7 @@ async function updateNotificationSettings (req: express.Request, res: express.Re
70async function listUserNotifications (req: express.Request, res: express.Response) { 76async function listUserNotifications (req: express.Request, res: express.Response) {
71 const user: UserModel = res.locals.oauth.token.User 77 const user: UserModel = res.locals.oauth.token.User
72 78
73 const resultList = await UserNotificationModel.listForApi(user.id, req.query.start, req.query.count, req.query.sort) 79 const resultList = await UserNotificationModel.listForApi(user.id, req.query.start, req.query.count, req.query.sort, req.query.unread)
74 80
75 return res.json(getFormattedObjects(resultList.data, resultList.total)) 81 return res.json(getFormattedObjects(resultList.data, resultList.total))
76} 82}
diff --git a/server/initializers/migrations/0315-user-notifications.ts b/server/initializers/migrations/0315-user-notifications.ts
index 2bd9c657d..8c54c5d6c 100644
--- a/server/initializers/migrations/0315-user-notifications.ts
+++ b/server/initializers/migrations/0315-user-notifications.ts
@@ -13,6 +13,8 @@ CREATE TABLE IF NOT EXISTS "userNotificationSetting" ("id" SERIAL,
13"newCommentOnMyVideo" INTEGER NOT NULL DEFAULT NULL, 13"newCommentOnMyVideo" INTEGER NOT NULL DEFAULT NULL,
14"videoAbuseAsModerator" INTEGER NOT NULL DEFAULT NULL, 14"videoAbuseAsModerator" INTEGER NOT NULL DEFAULT NULL,
15"blacklistOnMyVideo" INTEGER NOT NULL DEFAULT NULL, 15"blacklistOnMyVideo" INTEGER NOT NULL DEFAULT NULL,
16"myVideoPublished" INTEGER NOT NULL DEFAULT NULL,
17"myVideoImportFinished" INTEGER NOT NULL DEFAULT NULL,
16"userId" INTEGER REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE, 18"userId" INTEGER REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
17"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, 19"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
18"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, 20"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
@@ -24,8 +26,8 @@ PRIMARY KEY ("id"))
24 { 26 {
25 const query = 'INSERT INTO "userNotificationSetting" ' + 27 const query = 'INSERT INTO "userNotificationSetting" ' +
26 '("newVideoFromSubscription", "newCommentOnMyVideo", "videoAbuseAsModerator", "blacklistOnMyVideo", ' + 28 '("newVideoFromSubscription", "newCommentOnMyVideo", "videoAbuseAsModerator", "blacklistOnMyVideo", ' +
27 '"userId", "createdAt", "updatedAt") ' + 29 '"myVideoPublished", "myVideoImportFinished", "userId", "createdAt", "updatedAt") ' +
28 '(SELECT 2, 2, 4, 4, id, NOW(), NOW() FROM "user")' 30 '(SELECT 2, 2, 4, 4, 2, 2, id, NOW(), NOW() FROM "user")'
29 31
30 await utils.sequelize.query(query) 32 await utils.sequelize.query(query)
31 } 33 }
diff --git a/server/lib/emailer.ts b/server/lib/emailer.ts
index d766e655b..6dc8f2adf 100644
--- a/server/lib/emailer.ts
+++ b/server/lib/emailer.ts
@@ -10,6 +10,7 @@ import { readFileSync } from 'fs-extra'
10import { VideoCommentModel } from '../models/video/video-comment' 10import { VideoCommentModel } from '../models/video/video-comment'
11import { VideoAbuseModel } from '../models/video/video-abuse' 11import { VideoAbuseModel } from '../models/video/video-abuse'
12import { VideoBlacklistModel } from '../models/video/video-blacklist' 12import { VideoBlacklistModel } from '../models/video/video-blacklist'
13import { VideoImportModel } from '../models/video/video-import'
13 14
14class Emailer { 15class Emailer {
15 16
@@ -102,6 +103,66 @@ class Emailer {
102 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) 103 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
103 } 104 }
104 105
106 myVideoPublishedNotification (to: string[], video: VideoModel) {
107 const videoUrl = CONFIG.WEBSERVER.URL + video.getWatchStaticPath()
108
109 const text = `Hi dear user,\n\n` +
110 `Your video ${video.name} has been published.` +
111 `\n\n` +
112 `You can view it on ${videoUrl} ` +
113 `\n\n` +
114 `Cheers,\n` +
115 `PeerTube.`
116
117 const emailPayload: EmailPayload = {
118 to,
119 subject: `Your video ${video.name} is published`,
120 text
121 }
122
123 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
124 }
125
126 myVideoImportSuccessNotification (to: string[], videoImport: VideoImportModel) {
127 const videoUrl = CONFIG.WEBSERVER.URL + videoImport.Video.getWatchStaticPath()
128
129 const text = `Hi dear user,\n\n` +
130 `Your video import ${videoImport.getTargetIdentifier()} is finished.` +
131 `\n\n` +
132 `You can view the imported video on ${videoUrl} ` +
133 `\n\n` +
134 `Cheers,\n` +
135 `PeerTube.`
136
137 const emailPayload: EmailPayload = {
138 to,
139 subject: `Your video import ${videoImport.getTargetIdentifier()} is finished`,
140 text
141 }
142
143 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
144 }
145
146 myVideoImportErrorNotification (to: string[], videoImport: VideoImportModel) {
147 const importUrl = CONFIG.WEBSERVER.URL + '/my-account/video-imports'
148
149 const text = `Hi dear user,\n\n` +
150 `Your video import ${videoImport.getTargetIdentifier()} encountered an error.` +
151 `\n\n` +
152 `See your videos import dashboard for more information: ${importUrl}` +
153 `\n\n` +
154 `Cheers,\n` +
155 `PeerTube.`
156
157 const emailPayload: EmailPayload = {
158 to,
159 subject: `Your video import ${videoImport.getTargetIdentifier()} encountered an error`,
160 text
161 }
162
163 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
164 }
165
105 addNewCommentOnMyVideoNotification (to: string[], comment: VideoCommentModel) { 166 addNewCommentOnMyVideoNotification (to: string[], comment: VideoCommentModel) {
106 const accountName = comment.Account.getDisplayName() 167 const accountName = comment.Account.getDisplayName()
107 const video = comment.Video 168 const video = comment.Video
diff --git a/server/lib/job-queue/handlers/video-file.ts b/server/lib/job-queue/handlers/video-file.ts
index 480d324dc..593e43cc5 100644
--- a/server/lib/job-queue/handlers/video-file.ts
+++ b/server/lib/job-queue/handlers/video-file.ts
@@ -68,17 +68,17 @@ async function processVideoFile (job: Bull.Job) {
68async function onVideoFileTranscoderOrImportSuccess (video: VideoModel) { 68async function onVideoFileTranscoderOrImportSuccess (video: VideoModel) {
69 if (video === undefined) return undefined 69 if (video === undefined) return undefined
70 70
71 const { videoDatabase, isNewVideo } = await sequelizeTypescript.transaction(async t => { 71 const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => {
72 // Maybe the video changed in database, refresh it 72 // Maybe the video changed in database, refresh it
73 let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t) 73 let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t)
74 // Video does not exist anymore 74 // Video does not exist anymore
75 if (!videoDatabase) return undefined 75 if (!videoDatabase) return undefined
76 76
77 let isNewVideo = false 77 let videoPublished = false
78 78
79 // We transcoded the video file in another format, now we can publish it 79 // We transcoded the video file in another format, now we can publish it
80 if (videoDatabase.state !== VideoState.PUBLISHED) { 80 if (videoDatabase.state !== VideoState.PUBLISHED) {
81 isNewVideo = true 81 videoPublished = true
82 82
83 videoDatabase.state = VideoState.PUBLISHED 83 videoDatabase.state = VideoState.PUBLISHED
84 videoDatabase.publishedAt = new Date() 84 videoDatabase.publishedAt = new Date()
@@ -86,12 +86,15 @@ async function onVideoFileTranscoderOrImportSuccess (video: VideoModel) {
86 } 86 }
87 87
88 // If the video was not published, we consider it is a new one for other instances 88 // If the video was not published, we consider it is a new one for other instances
89 await federateVideoIfNeeded(videoDatabase, isNewVideo, t) 89 await federateVideoIfNeeded(videoDatabase, videoPublished, t)
90 90
91 return { videoDatabase, isNewVideo } 91 return { videoDatabase, videoPublished }
92 }) 92 })
93 93
94 if (isNewVideo) Notifier.Instance.notifyOnNewVideo(videoDatabase) 94 if (videoPublished) {
95 Notifier.Instance.notifyOnNewVideo(videoDatabase)
96 Notifier.Instance.notifyOnPendingVideoPublished(videoDatabase)
97 }
95} 98}
96 99
97async function onVideoFileOptimizerSuccess (videoArg: VideoModel, isNewVideo: boolean) { 100async function onVideoFileOptimizerSuccess (videoArg: VideoModel, isNewVideo: boolean) {
@@ -100,7 +103,7 @@ async function onVideoFileOptimizerSuccess (videoArg: VideoModel, isNewVideo: bo
100 // Outside the transaction (IO on disk) 103 // Outside the transaction (IO on disk)
101 const { videoFileResolution } = await videoArg.getOriginalFileResolution() 104 const { videoFileResolution } = await videoArg.getOriginalFileResolution()
102 105
103 const videoDatabase = await sequelizeTypescript.transaction(async t => { 106 const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => {
104 // Maybe the video changed in database, refresh it 107 // Maybe the video changed in database, refresh it
105 let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoArg.uuid, t) 108 let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoArg.uuid, t)
106 // Video does not exist anymore 109 // Video does not exist anymore
@@ -113,6 +116,8 @@ async function onVideoFileOptimizerSuccess (videoArg: VideoModel, isNewVideo: bo
113 { resolutions: resolutionsEnabled } 116 { resolutions: resolutionsEnabled }
114 ) 117 )
115 118
119 let videoPublished = false
120
116 if (resolutionsEnabled.length !== 0) { 121 if (resolutionsEnabled.length !== 0) {
117 const tasks: Bluebird<Bull.Job<any>>[] = [] 122 const tasks: Bluebird<Bull.Job<any>>[] = []
118 123
@@ -130,6 +135,8 @@ async function onVideoFileOptimizerSuccess (videoArg: VideoModel, isNewVideo: bo
130 135
131 logger.info('Transcoding jobs created for uuid %s.', videoDatabase.uuid, { resolutionsEnabled }) 136 logger.info('Transcoding jobs created for uuid %s.', videoDatabase.uuid, { resolutionsEnabled })
132 } else { 137 } else {
138 videoPublished = true
139
133 // No transcoding to do, it's now published 140 // No transcoding to do, it's now published
134 videoDatabase.state = VideoState.PUBLISHED 141 videoDatabase.state = VideoState.PUBLISHED
135 videoDatabase = await videoDatabase.save({ transaction: t }) 142 videoDatabase = await videoDatabase.save({ transaction: t })
@@ -139,10 +146,11 @@ async function onVideoFileOptimizerSuccess (videoArg: VideoModel, isNewVideo: bo
139 146
140 await federateVideoIfNeeded(videoDatabase, isNewVideo, t) 147 await federateVideoIfNeeded(videoDatabase, isNewVideo, t)
141 148
142 return videoDatabase 149 return { videoDatabase, videoPublished }
143 }) 150 })
144 151
145 if (isNewVideo) Notifier.Instance.notifyOnNewVideo(videoDatabase) 152 if (isNewVideo) Notifier.Instance.notifyOnNewVideo(videoDatabase)
153 if (videoPublished) Notifier.Instance.notifyOnPendingVideoPublished(videoDatabase)
146} 154}
147 155
148// --------------------------------------------------------------------------- 156// ---------------------------------------------------------------------------
diff --git a/server/lib/job-queue/handlers/video-import.ts b/server/lib/job-queue/handlers/video-import.ts
index 29cd1198c..12004dcd7 100644
--- a/server/lib/job-queue/handlers/video-import.ts
+++ b/server/lib/job-queue/handlers/video-import.ts
@@ -197,6 +197,7 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide
197 }) 197 })
198 198
199 Notifier.Instance.notifyOnNewVideo(videoImportUpdated.Video) 199 Notifier.Instance.notifyOnNewVideo(videoImportUpdated.Video)
200 Notifier.Instance.notifyOnFinishedVideoImport(videoImportUpdated, true)
200 201
201 // Create transcoding jobs? 202 // Create transcoding jobs?
202 if (videoImportUpdated.Video.state === VideoState.TO_TRANSCODE) { 203 if (videoImportUpdated.Video.state === VideoState.TO_TRANSCODE) {
@@ -220,6 +221,8 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide
220 videoImport.state = VideoImportState.FAILED 221 videoImport.state = VideoImportState.FAILED
221 await videoImport.save() 222 await videoImport.save()
222 223
224 Notifier.Instance.notifyOnFinishedVideoImport(videoImport, false)
225
223 throw err 226 throw err
224 } 227 }
225} 228}
diff --git a/server/lib/notifier.ts b/server/lib/notifier.ts
index a21b50b2d..11b0937e9 100644
--- a/server/lib/notifier.ts
+++ b/server/lib/notifier.ts
@@ -11,6 +11,8 @@ import { VideoPrivacy, VideoState } from '../../shared/models/videos'
11import { VideoAbuseModel } from '../models/video/video-abuse' 11import { VideoAbuseModel } from '../models/video/video-abuse'
12import { VideoBlacklistModel } from '../models/video/video-blacklist' 12import { VideoBlacklistModel } from '../models/video/video-blacklist'
13import * as Bluebird from 'bluebird' 13import * as Bluebird from 'bluebird'
14import { VideoImportModel } from '../models/video/video-import'
15import { AccountBlocklistModel } from '../models/account/account-blocklist'
14 16
15class Notifier { 17class Notifier {
16 18
@@ -26,6 +28,14 @@ class Notifier {
26 .catch(err => logger.error('Cannot notify subscribers of new video %s.', video.url, { err })) 28 .catch(err => logger.error('Cannot notify subscribers of new video %s.', video.url, { err }))
27 } 29 }
28 30
31 notifyOnPendingVideoPublished (video: VideoModel): void {
32 // Only notify on public videos that has been published while the user waited transcoding/scheduled update
33 if (video.waitTranscoding === false && !video.ScheduleVideoUpdate) return
34
35 this.notifyOwnedVideoHasBeenPublished(video)
36 .catch(err => logger.error('Cannot notify owner that its video %s has been published.', video.url, { err }))
37 }
38
29 notifyOnNewComment (comment: VideoCommentModel): void { 39 notifyOnNewComment (comment: VideoCommentModel): void {
30 this.notifyVideoOwnerOfNewComment(comment) 40 this.notifyVideoOwnerOfNewComment(comment)
31 .catch(err => logger.error('Cannot notify of new comment %s.', comment.url, { err })) 41 .catch(err => logger.error('Cannot notify of new comment %s.', comment.url, { err }))
@@ -46,6 +56,11 @@ class Notifier {
46 .catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', video.url, { err })) 56 .catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', video.url, { err }))
47 } 57 }
48 58
59 notifyOnFinishedVideoImport (videoImport: VideoImportModel, success: boolean): void {
60 this.notifyOwnerVideoImportIsFinished(videoImport, success)
61 .catch(err => logger.error('Cannot notify owner that its video import %s is finished.', videoImport.getTargetIdentifier(), { err }))
62 }
63
49 private async notifySubscribersOfNewVideo (video: VideoModel) { 64 private async notifySubscribersOfNewVideo (video: VideoModel) {
50 // List all followers that are users 65 // List all followers that are users
51 const users = await UserModel.listUserSubscribersOf(video.VideoChannel.actorId) 66 const users = await UserModel.listUserSubscribersOf(video.VideoChannel.actorId)
@@ -80,6 +95,9 @@ class Notifier {
80 // Not our user or user comments its own video 95 // Not our user or user comments its own video
81 if (!user || comment.Account.userId === user.id) return 96 if (!user || comment.Account.userId === user.id) return
82 97
98 const accountMuted = await AccountBlocklistModel.isAccountMutedBy(user.Account.id, comment.accountId)
99 if (accountMuted) return
100
83 logger.info('Notifying user %s of new comment %s.', user.username, comment.url) 101 logger.info('Notifying user %s of new comment %s.', user.username, comment.url)
84 102
85 function settingGetter (user: UserModel) { 103 function settingGetter (user: UserModel) {
@@ -188,6 +206,64 @@ class Notifier {
188 return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) 206 return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
189 } 207 }
190 208
209 private async notifyOwnedVideoHasBeenPublished (video: VideoModel) {
210 const user = await UserModel.loadByVideoId(video.id)
211 if (!user) return
212
213 logger.info('Notifying user %s of the publication of its video %s.', user.username, video.url)
214
215 function settingGetter (user: UserModel) {
216 return user.NotificationSetting.myVideoPublished
217 }
218
219 async function notificationCreator (user: UserModel) {
220 const notification = await UserNotificationModel.create({
221 type: UserNotificationType.MY_VIDEO_PUBLISHED,
222 userId: user.id,
223 videoId: video.id
224 })
225 notification.Video = video
226
227 return notification
228 }
229
230 function emailSender (emails: string[]) {
231 return Emailer.Instance.myVideoPublishedNotification(emails, video)
232 }
233
234 return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
235 }
236
237 private async notifyOwnerVideoImportIsFinished (videoImport: VideoImportModel, success: boolean) {
238 const user = await UserModel.loadByVideoImportId(videoImport.id)
239 if (!user) return
240
241 logger.info('Notifying user %s its video import %s is finished.', user.username, videoImport.getTargetIdentifier())
242
243 function settingGetter (user: UserModel) {
244 return user.NotificationSetting.myVideoImportFinished
245 }
246
247 async function notificationCreator (user: UserModel) {
248 const notification = await UserNotificationModel.create({
249 type: success ? UserNotificationType.MY_VIDEO_IMPORT_SUCCESS : UserNotificationType.MY_VIDEO_IMPORT_ERROR,
250 userId: user.id,
251 videoImportId: videoImport.id
252 })
253 notification.VideoImport = videoImport
254
255 return notification
256 }
257
258 function emailSender (emails: string[]) {
259 return success
260 ? Emailer.Instance.myVideoImportSuccessNotification(emails, videoImport)
261 : Emailer.Instance.myVideoImportErrorNotification(emails, videoImport)
262 }
263
264 return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
265 }
266
191 private async notify (options: { 267 private async notify (options: {
192 users: UserModel[], 268 users: UserModel[],
193 notificationCreator: (user: UserModel) => Promise<UserNotificationModel>, 269 notificationCreator: (user: UserModel) => Promise<UserNotificationModel>,
diff --git a/server/lib/schedulers/update-videos-scheduler.ts b/server/lib/schedulers/update-videos-scheduler.ts
index b7fb029f1..2618a5857 100644
--- a/server/lib/schedulers/update-videos-scheduler.ts
+++ b/server/lib/schedulers/update-videos-scheduler.ts
@@ -6,6 +6,7 @@ import { federateVideoIfNeeded } from '../activitypub'
6import { SCHEDULER_INTERVALS_MS, sequelizeTypescript } from '../../initializers' 6import { SCHEDULER_INTERVALS_MS, sequelizeTypescript } from '../../initializers'
7import { VideoPrivacy } from '../../../shared/models/videos' 7import { VideoPrivacy } from '../../../shared/models/videos'
8import { Notifier } from '../notifier' 8import { Notifier } from '../notifier'
9import { VideoModel } from '../../models/video/video'
9 10
10export class UpdateVideosScheduler extends AbstractScheduler { 11export class UpdateVideosScheduler extends AbstractScheduler {
11 12
@@ -24,8 +25,9 @@ export class UpdateVideosScheduler extends AbstractScheduler {
24 private async updateVideos () { 25 private async updateVideos () {
25 if (!await ScheduleVideoUpdateModel.areVideosToUpdate()) return undefined 26 if (!await ScheduleVideoUpdateModel.areVideosToUpdate()) return undefined
26 27
27 return sequelizeTypescript.transaction(async t => { 28 const publishedVideos = await sequelizeTypescript.transaction(async t => {
28 const schedules = await ScheduleVideoUpdateModel.listVideosToUpdate(t) 29 const schedules = await ScheduleVideoUpdateModel.listVideosToUpdate(t)
30 const publishedVideos: VideoModel[] = []
29 31
30 for (const schedule of schedules) { 32 for (const schedule of schedules) {
31 const video = schedule.Video 33 const video = schedule.Video
@@ -42,13 +44,21 @@ export class UpdateVideosScheduler extends AbstractScheduler {
42 await federateVideoIfNeeded(video, isNewVideo, t) 44 await federateVideoIfNeeded(video, isNewVideo, t)
43 45
44 if (oldPrivacy === VideoPrivacy.UNLISTED || oldPrivacy === VideoPrivacy.PRIVATE) { 46 if (oldPrivacy === VideoPrivacy.UNLISTED || oldPrivacy === VideoPrivacy.PRIVATE) {
45 Notifier.Instance.notifyOnNewVideo(video) 47 video.ScheduleVideoUpdate = schedule
48 publishedVideos.push(video)
46 } 49 }
47 } 50 }
48 51
49 await schedule.destroy({ transaction: t }) 52 await schedule.destroy({ transaction: t })
50 } 53 }
54
55 return publishedVideos
51 }) 56 })
57
58 for (const v of publishedVideos) {
59 Notifier.Instance.notifyOnNewVideo(v)
60 Notifier.Instance.notifyOnPendingVideoPublished(v)
61 }
52 } 62 }
53 63
54 static get Instance () { 64 static get Instance () {
diff --git a/server/lib/user.ts b/server/lib/user.ts
index 72127819c..481571828 100644
--- a/server/lib/user.ts
+++ b/server/lib/user.ts
@@ -100,6 +100,8 @@ function createDefaultUserNotificationSettings (user: UserModel, t: Sequelize.Tr
100 userId: user.id, 100 userId: user.id,
101 newVideoFromSubscription: UserNotificationSettingValue.WEB_NOTIFICATION, 101 newVideoFromSubscription: UserNotificationSettingValue.WEB_NOTIFICATION,
102 newCommentOnMyVideo: UserNotificationSettingValue.WEB_NOTIFICATION, 102 newCommentOnMyVideo: UserNotificationSettingValue.WEB_NOTIFICATION,
103 myVideoImportFinished: UserNotificationSettingValue.WEB_NOTIFICATION,
104 myVideoPublished: UserNotificationSettingValue.WEB_NOTIFICATION,
103 videoAbuseAsModerator: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL, 105 videoAbuseAsModerator: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL,
104 blacklistOnMyVideo: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL 106 blacklistOnMyVideo: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL
105 }, { transaction: t }) 107 }, { transaction: t })
diff --git a/server/middlewares/validators/user-notifications.ts b/server/middlewares/validators/user-notifications.ts
index 8202f307e..1c31f0a73 100644
--- a/server/middlewares/validators/user-notifications.ts
+++ b/server/middlewares/validators/user-notifications.ts
@@ -1,11 +1,26 @@
1import * as express from 'express' 1import * as express from 'express'
2import 'express-validator' 2import 'express-validator'
3import { body } from 'express-validator/check' 3import { body, query } from 'express-validator/check'
4import { logger } from '../../helpers/logger' 4import { logger } from '../../helpers/logger'
5import { areValidationErrors } from './utils' 5import { areValidationErrors } from './utils'
6import { isUserNotificationSettingValid } from '../../helpers/custom-validators/user-notifications' 6import { isUserNotificationSettingValid } from '../../helpers/custom-validators/user-notifications'
7import { isIntArray } from '../../helpers/custom-validators/misc' 7import { isIntArray } from '../../helpers/custom-validators/misc'
8 8
9const listUserNotificationsValidator = [
10 query('unread')
11 .optional()
12 .toBoolean()
13 .isBoolean().withMessage('Should have a valid unread boolean'),
14
15 (req: express.Request, res: express.Response, next: express.NextFunction) => {
16 logger.debug('Checking listUserNotificationsValidator parameters', { parameters: req.query })
17
18 if (areValidationErrors(req, res)) return
19
20 return next()
21 }
22]
23
9const updateNotificationSettingsValidator = [ 24const updateNotificationSettingsValidator = [
10 body('newVideoFromSubscription') 25 body('newVideoFromSubscription')
11 .custom(isUserNotificationSettingValid).withMessage('Should have a valid new video from subscription notification setting'), 26 .custom(isUserNotificationSettingValid).withMessage('Should have a valid new video from subscription notification setting'),
@@ -41,6 +56,7 @@ const markAsReadUserNotificationsValidator = [
41// --------------------------------------------------------------------------- 56// ---------------------------------------------------------------------------
42 57
43export { 58export {
59 listUserNotificationsValidator,
44 updateNotificationSettingsValidator, 60 updateNotificationSettingsValidator,
45 markAsReadUserNotificationsValidator 61 markAsReadUserNotificationsValidator
46} 62}
diff --git a/server/models/account/account-blocklist.ts b/server/models/account/account-blocklist.ts
index fa2819235..54ac290c4 100644
--- a/server/models/account/account-blocklist.ts
+++ b/server/models/account/account-blocklist.ts
@@ -72,6 +72,21 @@ export class AccountBlocklistModel extends Model<AccountBlocklistModel> {
72 }) 72 })
73 BlockedAccount: AccountModel 73 BlockedAccount: AccountModel
74 74
75 static isAccountMutedBy (accountId: number, targetAccountId: number) {
76 const query = {
77 attributes: [ 'id' ],
78 where: {
79 accountId,
80 targetAccountId
81 },
82 raw: true
83 }
84
85 return AccountBlocklistModel.unscoped()
86 .findOne(query)
87 .then(a => !!a)
88 }
89
75 static loadByAccountAndTarget (accountId: number, targetAccountId: number) { 90 static loadByAccountAndTarget (accountId: number, targetAccountId: number) {
76 const query = { 91 const query = {
77 where: { 92 where: {
diff --git a/server/models/account/user-notification-setting.ts b/server/models/account/user-notification-setting.ts
index bc24b1e33..6470defa7 100644
--- a/server/models/account/user-notification-setting.ts
+++ b/server/models/account/user-notification-setting.ts
@@ -65,6 +65,24 @@ export class UserNotificationSettingModel extends Model<UserNotificationSettingM
65 @Column 65 @Column
66 blacklistOnMyVideo: UserNotificationSettingValue 66 blacklistOnMyVideo: UserNotificationSettingValue
67 67
68 @AllowNull(false)
69 @Default(null)
70 @Is(
71 'UserNotificationSettingMyVideoPublished',
72 value => throwIfNotValid(value, isUserNotificationSettingValid, 'myVideoPublished')
73 )
74 @Column
75 myVideoPublished: UserNotificationSettingValue
76
77 @AllowNull(false)
78 @Default(null)
79 @Is(
80 'UserNotificationSettingMyVideoImportFinished',
81 value => throwIfNotValid(value, isUserNotificationSettingValid, 'myVideoImportFinished')
82 )
83 @Column
84 myVideoImportFinished: UserNotificationSettingValue
85
68 @ForeignKey(() => UserModel) 86 @ForeignKey(() => UserModel)
69 @Column 87 @Column
70 userId: number 88 userId: number
@@ -94,7 +112,9 @@ export class UserNotificationSettingModel extends Model<UserNotificationSettingM
94 newCommentOnMyVideo: this.newCommentOnMyVideo, 112 newCommentOnMyVideo: this.newCommentOnMyVideo,
95 newVideoFromSubscription: this.newVideoFromSubscription, 113 newVideoFromSubscription: this.newVideoFromSubscription,
96 videoAbuseAsModerator: this.videoAbuseAsModerator, 114 videoAbuseAsModerator: this.videoAbuseAsModerator,
97 blacklistOnMyVideo: this.blacklistOnMyVideo 115 blacklistOnMyVideo: this.blacklistOnMyVideo,
116 myVideoPublished: this.myVideoPublished,
117 myVideoImportFinished: this.myVideoImportFinished
98 } 118 }
99 } 119 }
100} 120}
diff --git a/server/models/account/user-notification.ts b/server/models/account/user-notification.ts
index e22f0d57f..251244374 100644
--- a/server/models/account/user-notification.ts
+++ b/server/models/account/user-notification.ts
@@ -1,4 +1,17 @@
1import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' 1import {
2 AllowNull,
3 BelongsTo,
4 Column,
5 CreatedAt,
6 Default,
7 ForeignKey,
8 IFindOptions,
9 Is,
10 Model,
11 Scopes,
12 Table,
13 UpdatedAt
14} from 'sequelize-typescript'
2import { UserNotification, UserNotificationType } from '../../../shared' 15import { UserNotification, UserNotificationType } from '../../../shared'
3import { getSort, throwIfNotValid } from '../utils' 16import { getSort, throwIfNotValid } from '../utils'
4import { isBooleanValid } from '../../helpers/custom-validators/misc' 17import { isBooleanValid } from '../../helpers/custom-validators/misc'
@@ -11,66 +24,68 @@ import { VideoChannelModel } from '../video/video-channel'
11import { AccountModel } from './account' 24import { AccountModel } from './account'
12import { VideoAbuseModel } from '../video/video-abuse' 25import { VideoAbuseModel } from '../video/video-abuse'
13import { VideoBlacklistModel } from '../video/video-blacklist' 26import { VideoBlacklistModel } from '../video/video-blacklist'
27import { VideoImportModel } from '../video/video-import'
14 28
15enum ScopeNames { 29enum ScopeNames {
16 WITH_ALL = 'WITH_ALL' 30 WITH_ALL = 'WITH_ALL'
17} 31}
18 32
33function buildVideoInclude (required: boolean) {
34 return {
35 attributes: [ 'id', 'uuid', 'name' ],
36 model: () => VideoModel.unscoped(),
37 required
38 }
39}
40
41function buildChannelInclude () {
42 return {
43 required: true,
44 attributes: [ 'id', 'name' ],
45 model: () => VideoChannelModel.unscoped()
46 }
47}
48
49function buildAccountInclude () {
50 return {
51 required: true,
52 attributes: [ 'id', 'name' ],
53 model: () => AccountModel.unscoped()
54 }
55}
56
19@Scopes({ 57@Scopes({
20 [ScopeNames.WITH_ALL]: { 58 [ScopeNames.WITH_ALL]: {
21 include: [ 59 include: [
60 Object.assign(buildVideoInclude(false), {
61 include: [ buildChannelInclude() ]
62 }),
22 { 63 {
23 attributes: [ 'id', 'uuid', 'name' ], 64 attributes: [ 'id', 'originCommentId' ],
24 model: () => VideoModel.unscoped(),
25 required: false,
26 include: [
27 {
28 required: true,
29 attributes: [ 'id', 'name' ],
30 model: () => VideoChannelModel.unscoped()
31 }
32 ]
33 },
34 {
35 attributes: [ 'id' ],
36 model: () => VideoCommentModel.unscoped(), 65 model: () => VideoCommentModel.unscoped(),
37 required: false, 66 required: false,
38 include: [ 67 include: [
39 { 68 buildAccountInclude(),
40 required: true, 69 buildVideoInclude(true)
41 attributes: [ 'id', 'name' ],
42 model: () => AccountModel.unscoped()
43 },
44 {
45 required: true,
46 attributes: [ 'id', 'uuid', 'name' ],
47 model: () => VideoModel.unscoped()
48 }
49 ] 70 ]
50 }, 71 },
51 { 72 {
52 attributes: [ 'id' ], 73 attributes: [ 'id' ],
53 model: () => VideoAbuseModel.unscoped(), 74 model: () => VideoAbuseModel.unscoped(),
54 required: false, 75 required: false,
55 include: [ 76 include: [ buildVideoInclude(true) ]
56 {
57 required: true,
58 attributes: [ 'id', 'uuid', 'name' ],
59 model: () => VideoModel.unscoped()
60 }
61 ]
62 }, 77 },
63 { 78 {
64 attributes: [ 'id' ], 79 attributes: [ 'id' ],
65 model: () => VideoBlacklistModel.unscoped(), 80 model: () => VideoBlacklistModel.unscoped(),
66 required: false, 81 required: false,
67 include: [ 82 include: [ buildVideoInclude(true) ]
68 { 83 },
69 required: true, 84 {
70 attributes: [ 'id', 'uuid', 'name' ], 85 attributes: [ 'id', 'magnetUri', 'targetUrl', 'torrentName' ],
71 model: () => VideoModel.unscoped() 86 model: () => VideoImportModel.unscoped(),
72 } 87 required: false,
73 ] 88 include: [ buildVideoInclude(false) ]
74 } 89 }
75 ] 90 ]
76 } 91 }
@@ -166,8 +181,20 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
166 }) 181 })
167 VideoBlacklist: VideoBlacklistModel 182 VideoBlacklist: VideoBlacklistModel
168 183
169 static listForApi (userId: number, start: number, count: number, sort: string) { 184 @ForeignKey(() => VideoImportModel)
170 const query = { 185 @Column
186 videoImportId: number
187
188 @BelongsTo(() => VideoImportModel, {
189 foreignKey: {
190 allowNull: true
191 },
192 onDelete: 'cascade'
193 })
194 VideoImport: VideoImportModel
195
196 static listForApi (userId: number, start: number, count: number, sort: string, unread?: boolean) {
197 const query: IFindOptions<UserNotificationModel> = {
171 offset: start, 198 offset: start,
172 limit: count, 199 limit: count,
173 order: getSort(sort), 200 order: getSort(sort),
@@ -176,6 +203,8 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
176 } 203 }
177 } 204 }
178 205
206 if (unread !== undefined) query.where['read'] = !unread
207
179 return UserNotificationModel.scope(ScopeNames.WITH_ALL) 208 return UserNotificationModel.scope(ScopeNames.WITH_ALL)
180 .findAndCountAll(query) 209 .findAndCountAll(query)
181 .then(({ rows, count }) => { 210 .then(({ rows, count }) => {
@@ -200,45 +229,39 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
200 } 229 }
201 230
202 toFormattedJSON (): UserNotification { 231 toFormattedJSON (): UserNotification {
203 const video = this.Video ? { 232 const video = this.Video ? Object.assign(this.formatVideo(this.Video), {
204 id: this.Video.id,
205 uuid: this.Video.uuid,
206 name: this.Video.name,
207 channel: { 233 channel: {
208 id: this.Video.VideoChannel.id, 234 id: this.Video.VideoChannel.id,
209 displayName: this.Video.VideoChannel.getDisplayName() 235 displayName: this.Video.VideoChannel.getDisplayName()
210 } 236 }
237 }) : undefined
238
239 const videoImport = this.VideoImport ? {
240 id: this.VideoImport.id,
241 video: this.VideoImport.Video ? this.formatVideo(this.VideoImport.Video) : undefined,
242 torrentName: this.VideoImport.torrentName,
243 magnetUri: this.VideoImport.magnetUri,
244 targetUrl: this.VideoImport.targetUrl
211 } : undefined 245 } : undefined
212 246
213 const comment = this.Comment ? { 247 const comment = this.Comment ? {
214 id: this.Comment.id, 248 id: this.Comment.id,
249 threadId: this.Comment.getThreadId(),
215 account: { 250 account: {
216 id: this.Comment.Account.id, 251 id: this.Comment.Account.id,
217 displayName: this.Comment.Account.getDisplayName() 252 displayName: this.Comment.Account.getDisplayName()
218 }, 253 },
219 video: { 254 video: this.formatVideo(this.Comment.Video)
220 id: this.Comment.Video.id,
221 uuid: this.Comment.Video.uuid,
222 name: this.Comment.Video.name
223 }
224 } : undefined 255 } : undefined
225 256
226 const videoAbuse = this.VideoAbuse ? { 257 const videoAbuse = this.VideoAbuse ? {
227 id: this.VideoAbuse.id, 258 id: this.VideoAbuse.id,
228 video: { 259 video: this.formatVideo(this.VideoAbuse.Video)
229 id: this.VideoAbuse.Video.id,
230 uuid: this.VideoAbuse.Video.uuid,
231 name: this.VideoAbuse.Video.name
232 }
233 } : undefined 260 } : undefined
234 261
235 const videoBlacklist = this.VideoBlacklist ? { 262 const videoBlacklist = this.VideoBlacklist ? {
236 id: this.VideoBlacklist.id, 263 id: this.VideoBlacklist.id,
237 video: { 264 video: this.formatVideo(this.VideoBlacklist.Video)
238 id: this.VideoBlacklist.Video.id,
239 uuid: this.VideoBlacklist.Video.uuid,
240 name: this.VideoBlacklist.Video.name
241 }
242 } : undefined 265 } : undefined
243 266
244 return { 267 return {
@@ -246,6 +269,7 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
246 type: this.type, 269 type: this.type,
247 read: this.read, 270 read: this.read,
248 video, 271 video,
272 videoImport,
249 comment, 273 comment,
250 videoAbuse, 274 videoAbuse,
251 videoBlacklist, 275 videoBlacklist,
@@ -253,4 +277,12 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
253 updatedAt: this.updatedAt.toISOString() 277 updatedAt: this.updatedAt.toISOString()
254 } 278 }
255 } 279 }
280
281 private formatVideo (video: VideoModel) {
282 return {
283 id: video.id,
284 uuid: video.uuid,
285 name: video.name
286 }
287 }
256} 288}
diff --git a/server/models/account/user.ts b/server/models/account/user.ts
index 55ec14d05..33f56f641 100644
--- a/server/models/account/user.ts
+++ b/server/models/account/user.ts
@@ -48,6 +48,7 @@ import { UserNotificationSettingModel } from './user-notification-setting'
48import { VideoModel } from '../video/video' 48import { VideoModel } from '../video/video'
49import { ActorModel } from '../activitypub/actor' 49import { ActorModel } from '../activitypub/actor'
50import { ActorFollowModel } from '../activitypub/actor-follow' 50import { ActorFollowModel } from '../activitypub/actor-follow'
51import { VideoImportModel } from '../video/video-import'
51 52
52enum ScopeNames { 53enum ScopeNames {
53 WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL' 54 WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL'
@@ -186,6 +187,12 @@ export class UserModel extends Model<UserModel> {
186 }) 187 })
187 NotificationSetting: UserNotificationSettingModel 188 NotificationSetting: UserNotificationSettingModel
188 189
190 @HasMany(() => VideoImportModel, {
191 foreignKey: 'userId',
192 onDelete: 'cascade'
193 })
194 VideoImports: VideoImportModel[]
195
189 @HasMany(() => OAuthTokenModel, { 196 @HasMany(() => OAuthTokenModel, {
190 foreignKey: 'userId', 197 foreignKey: 'userId',
191 onDelete: 'cascade' 198 onDelete: 'cascade'
@@ -400,6 +407,23 @@ export class UserModel extends Model<UserModel> {
400 return UserModel.findOne(query) 407 return UserModel.findOne(query)
401 } 408 }
402 409
410 static loadByVideoImportId (videoImportId: number) {
411 const query = {
412 include: [
413 {
414 required: true,
415 attributes: [ 'id' ],
416 model: VideoImportModel.unscoped(),
417 where: {
418 id: videoImportId
419 }
420 }
421 ]
422 }
423
424 return UserModel.findOne(query)
425 }
426
403 static getOriginalVideoFileTotalFromUser (user: UserModel) { 427 static getOriginalVideoFileTotalFromUser (user: UserModel) {
404 // Don't use sequelize because we need to use a sub query 428 // Don't use sequelize because we need to use a sub query
405 const query = UserModel.generateUserQuotaBaseSQL() 429 const query = UserModel.generateUserQuotaBaseSQL()
diff --git a/server/models/video/video-file.ts b/server/models/video/video-file.ts
index 3fd2d5a99..0fd868cd6 100644
--- a/server/models/video/video-file.ts
+++ b/server/models/video/video-file.ts
@@ -1,4 +1,3 @@
1import { values } from 'lodash'
2import { 1import {
3 AllowNull, 2 AllowNull,
4 BelongsTo, 3 BelongsTo,
@@ -20,7 +19,6 @@ import {
20 isVideoFileSizeValid, 19 isVideoFileSizeValid,
21 isVideoFPSResolutionValid 20 isVideoFPSResolutionValid
22} from '../../helpers/custom-validators/videos' 21} from '../../helpers/custom-validators/videos'
23import { CONSTRAINTS_FIELDS } from '../../initializers'
24import { throwIfNotValid } from '../utils' 22import { throwIfNotValid } from '../utils'
25import { VideoModel } from './video' 23import { VideoModel } from './video'
26import * as Sequelize from 'sequelize' 24import * as Sequelize from 'sequelize'
diff --git a/server/models/video/video-import.ts b/server/models/video/video-import.ts
index 8d442b3f8..c723e57c0 100644
--- a/server/models/video/video-import.ts
+++ b/server/models/video/video-import.ts
@@ -144,6 +144,10 @@ export class VideoImportModel extends Model<VideoImportModel> {
144 }) 144 })
145 } 145 }
146 146
147 getTargetIdentifier () {
148 return this.targetUrl || this.magnetUri || this.torrentName
149 }
150
147 toFormattedJSON (): VideoImport { 151 toFormattedJSON (): VideoImport {
148 const videoFormatOptions = { 152 const videoFormatOptions = {
149 completeDescription: true, 153 completeDescription: true,
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index fc200e5d1..80a6c7832 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -94,6 +94,7 @@ import {
94import * as validator from 'validator' 94import * as validator from 'validator'
95import { UserVideoHistoryModel } from '../account/user-video-history' 95import { UserVideoHistoryModel } from '../account/user-video-history'
96import { UserModel } from '../account/user' 96import { UserModel } from '../account/user'
97import { VideoImportModel } from './video-import'
97 98
98// FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation 99// FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation
99const indexes: Sequelize.DefineIndexesOptions[] = [ 100const indexes: Sequelize.DefineIndexesOptions[] = [
@@ -785,6 +786,15 @@ export class VideoModel extends Model<VideoModel> {
785 }) 786 })
786 VideoBlacklist: VideoBlacklistModel 787 VideoBlacklist: VideoBlacklistModel
787 788
789 @HasOne(() => VideoImportModel, {
790 foreignKey: {
791 name: 'videoId',
792 allowNull: true
793 },
794 onDelete: 'set null'
795 })
796 VideoImport: VideoImportModel
797
788 @HasMany(() => VideoCaptionModel, { 798 @HasMany(() => VideoCaptionModel, {
789 foreignKey: { 799 foreignKey: {
790 name: 'videoId', 800 name: 'videoId',
diff --git a/server/tests/api/check-params/user-notifications.ts b/server/tests/api/check-params/user-notifications.ts
index 3ae36ddb3..4f21f7b95 100644
--- a/server/tests/api/check-params/user-notifications.ts
+++ b/server/tests/api/check-params/user-notifications.ts
@@ -52,6 +52,18 @@ describe('Test user notifications API validators', function () {
52 await checkBadSortPagination(server.url, path, server.accessToken) 52 await checkBadSortPagination(server.url, path, server.accessToken)
53 }) 53 })
54 54
55 it('Should fail with an incorrect unread parameter', async function () {
56 await makeGetRequest({
57 url: server.url,
58 path,
59 query: {
60 unread: 'toto'
61 },
62 token: server.accessToken,
63 statusCodeExpected: 200
64 })
65 })
66
55 it('Should fail with a non authenticated user', async function () { 67 it('Should fail with a non authenticated user', async function () {
56 await makeGetRequest({ 68 await makeGetRequest({
57 url: server.url, 69 url: server.url,
@@ -125,7 +137,9 @@ describe('Test user notifications API validators', function () {
125 newVideoFromSubscription: UserNotificationSettingValue.WEB_NOTIFICATION, 137 newVideoFromSubscription: UserNotificationSettingValue.WEB_NOTIFICATION,
126 newCommentOnMyVideo: UserNotificationSettingValue.WEB_NOTIFICATION, 138 newCommentOnMyVideo: UserNotificationSettingValue.WEB_NOTIFICATION,
127 videoAbuseAsModerator: UserNotificationSettingValue.WEB_NOTIFICATION, 139 videoAbuseAsModerator: UserNotificationSettingValue.WEB_NOTIFICATION,
128 blacklistOnMyVideo: UserNotificationSettingValue.WEB_NOTIFICATION 140 blacklistOnMyVideo: UserNotificationSettingValue.WEB_NOTIFICATION,
141 myVideoImportFinished: UserNotificationSettingValue.WEB_NOTIFICATION,
142 myVideoPublished: UserNotificationSettingValue.WEB_NOTIFICATION
129 } 143 }
130 144
131 it('Should fail with missing fields', async function () { 145 it('Should fail with missing fields', async function () {
diff --git a/server/tests/api/users/user-notifications.ts b/server/tests/api/users/user-notifications.ts
index 09c0479fd..e4966dbf5 100644
--- a/server/tests/api/users/user-notifications.ts
+++ b/server/tests/api/users/user-notifications.ts
@@ -29,33 +29,46 @@ import {
29 getLastNotification, 29 getLastNotification,
30 getUserNotifications, 30 getUserNotifications,
31 markAsReadNotifications, 31 markAsReadNotifications,
32 updateMyNotificationSettings 32 updateMyNotificationSettings,
33 checkVideoIsPublished, checkMyVideoImportIsFinished
33} from '../../../../shared/utils/users/user-notifications' 34} from '../../../../shared/utils/users/user-notifications'
34import { User, UserNotification, UserNotificationSettingValue } from '../../../../shared/models/users' 35import {
36 User,
37 UserNotification,
38 UserNotificationSetting,
39 UserNotificationSettingValue,
40 UserNotificationType
41} from '../../../../shared/models/users'
35import { MockSmtpServer } from '../../../../shared/utils/miscs/email' 42import { MockSmtpServer } from '../../../../shared/utils/miscs/email'
36import { addUserSubscription } from '../../../../shared/utils/users/user-subscriptions' 43import { addUserSubscription } from '../../../../shared/utils/users/user-subscriptions'
37import { VideoPrivacy } from '../../../../shared/models/videos' 44import { VideoPrivacy } from '../../../../shared/models/videos'
38import { getYoutubeVideoUrl, importVideo } from '../../../../shared/utils/videos/video-imports' 45import { getYoutubeVideoUrl, importVideo, getBadVideoUrl } from '../../../../shared/utils/videos/video-imports'
39import { addVideoCommentReply, addVideoCommentThread } from '../../../../shared/utils/videos/video-comments' 46import { addVideoCommentReply, addVideoCommentThread } from '../../../../shared/utils/videos/video-comments'
47import * as uuidv4 from 'uuid/v4'
48import { addAccountToAccountBlocklist, removeAccountFromAccountBlocklist } from '../../../../shared/utils/users/blocklist'
40 49
41const expect = chai.expect 50const expect = chai.expect
42 51
43async function uploadVideoByRemoteAccount (servers: ServerInfo[], videoNameId: number, additionalParams: any = {}) { 52async function uploadVideoByRemoteAccount (servers: ServerInfo[], additionalParams: any = {}) {
44 const data = Object.assign({ name: 'remote video ' + videoNameId }, additionalParams) 53 const name = 'remote video ' + uuidv4()
54
55 const data = Object.assign({ name }, additionalParams)
45 const res = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, data) 56 const res = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, data)
46 57
47 await waitJobs(servers) 58 await waitJobs(servers)
48 59
49 return res.body.video.uuid 60 return { uuid: res.body.video.uuid, name }
50} 61}
51 62
52async function uploadVideoByLocalAccount (servers: ServerInfo[], videoNameId: number, additionalParams: any = {}) { 63async function uploadVideoByLocalAccount (servers: ServerInfo[], additionalParams: any = {}) {
53 const data = Object.assign({ name: 'local video ' + videoNameId }, additionalParams) 64 const name = 'local video ' + uuidv4()
65
66 const data = Object.assign({ name }, additionalParams)
54 const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, data) 67 const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, data)
55 68
56 await waitJobs(servers) 69 await waitJobs(servers)
57 70
58 return res.body.video.uuid 71 return { uuid: res.body.video.uuid, name }
59} 72}
60 73
61describe('Test users notifications', function () { 74describe('Test users notifications', function () {
@@ -63,7 +76,18 @@ describe('Test users notifications', function () {
63 let userAccessToken: string 76 let userAccessToken: string
64 let userNotifications: UserNotification[] = [] 77 let userNotifications: UserNotification[] = []
65 let adminNotifications: UserNotification[] = [] 78 let adminNotifications: UserNotification[] = []
79 let adminNotificationsServer2: UserNotification[] = []
66 const emails: object[] = [] 80 const emails: object[] = []
81 let channelId: number
82
83 const allNotificationSettings: UserNotificationSetting = {
84 myVideoPublished: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL,
85 myVideoImportFinished: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL,
86 newCommentOnMyVideo: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL,
87 newVideoFromSubscription: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL,
88 videoAbuseAsModerator: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL,
89 blacklistOnMyVideo: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL
90 }
67 91
68 before(async function () { 92 before(async function () {
69 this.timeout(120000) 93 this.timeout(120000)
@@ -94,12 +118,9 @@ describe('Test users notifications', function () {
94 await createUser(servers[0].url, servers[0].accessToken, user.username, user.password, 10 * 1000 * 1000) 118 await createUser(servers[0].url, servers[0].accessToken, user.username, user.password, 10 * 1000 * 1000)
95 userAccessToken = await userLogin(servers[0], user) 119 userAccessToken = await userLogin(servers[0], user)
96 120
97 await updateMyNotificationSettings(servers[0].url, userAccessToken, { 121 await updateMyNotificationSettings(servers[0].url, userAccessToken, allNotificationSettings)
98 newCommentOnMyVideo: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL, 122 await updateMyNotificationSettings(servers[0].url, servers[0].accessToken, allNotificationSettings)
99 newVideoFromSubscription: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL, 123 await updateMyNotificationSettings(servers[1].url, servers[1].accessToken, allNotificationSettings)
100 blacklistOnMyVideo: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL,
101 videoAbuseAsModerator: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL
102 })
103 124
104 { 125 {
105 const socket = getUserNotificationSocket(servers[ 0 ].url, userAccessToken) 126 const socket = getUserNotificationSocket(servers[ 0 ].url, userAccessToken)
@@ -109,6 +130,15 @@ describe('Test users notifications', function () {
109 const socket = getUserNotificationSocket(servers[ 0 ].url, servers[0].accessToken) 130 const socket = getUserNotificationSocket(servers[ 0 ].url, servers[0].accessToken)
110 socket.on('new-notification', n => adminNotifications.push(n)) 131 socket.on('new-notification', n => adminNotifications.push(n))
111 } 132 }
133 {
134 const socket = getUserNotificationSocket(servers[ 1 ].url, servers[1].accessToken)
135 socket.on('new-notification', n => adminNotificationsServer2.push(n))
136 }
137
138 {
139 const resChannel = await getMyUserInformation(servers[0].url, servers[0].accessToken)
140 channelId = resChannel.body.videoChannels[0].id
141 }
112 }) 142 })
113 143
114 describe('New video from my subscription notification', function () { 144 describe('New video from my subscription notification', function () {
@@ -124,7 +154,7 @@ describe('Test users notifications', function () {
124 }) 154 })
125 155
126 it('Should not send notifications if the user does not follow the video publisher', async function () { 156 it('Should not send notifications if the user does not follow the video publisher', async function () {
127 await uploadVideoByLocalAccount(servers, 1) 157 await uploadVideoByLocalAccount(servers)
128 158
129 const notification = await getLastNotification(servers[ 0 ].url, userAccessToken) 159 const notification = await getLastNotification(servers[ 0 ].url, userAccessToken)
130 expect(notification).to.be.undefined 160 expect(notification).to.be.undefined
@@ -136,11 +166,8 @@ describe('Test users notifications', function () {
136 it('Should send a new video notification if the user follows the local video publisher', async function () { 166 it('Should send a new video notification if the user follows the local video publisher', async function () {
137 await addUserSubscription(servers[0].url, userAccessToken, 'root_channel@localhost:9001') 167 await addUserSubscription(servers[0].url, userAccessToken, 'root_channel@localhost:9001')
138 168
139 const videoNameId = 10 169 const { name, uuid } = await uploadVideoByLocalAccount(servers)
140 const videoName = 'local video ' + videoNameId 170 await checkNewVideoFromSubscription(baseParams, name, uuid, 'presence')
141
142 const uuid = await uploadVideoByLocalAccount(servers, videoNameId)
143 await checkNewVideoFromSubscription(baseParams, videoName, uuid, 'presence')
144 }) 171 })
145 172
146 it('Should send a new video notification from a remote account', async function () { 173 it('Should send a new video notification from a remote account', async function () {
@@ -148,21 +175,13 @@ describe('Test users notifications', function () {
148 175
149 await addUserSubscription(servers[0].url, userAccessToken, 'root_channel@localhost:9002') 176 await addUserSubscription(servers[0].url, userAccessToken, 'root_channel@localhost:9002')
150 177
151 const videoNameId = 20 178 const { name, uuid } = await uploadVideoByRemoteAccount(servers)
152 const videoName = 'remote video ' + videoNameId 179 await checkNewVideoFromSubscription(baseParams, name, uuid, 'presence')
153
154 const uuid = await uploadVideoByRemoteAccount(servers, videoNameId)
155 await waitJobs(servers)
156
157 await checkNewVideoFromSubscription(baseParams, videoName, uuid, 'presence')
158 }) 180 })
159 181
160 it('Should send a new video notification on a scheduled publication', async function () { 182 it('Should send a new video notification on a scheduled publication', async function () {
161 this.timeout(20000) 183 this.timeout(20000)
162 184
163 const videoNameId = 30
164 const videoName = 'local video ' + videoNameId
165
166 // In 2 seconds 185 // In 2 seconds
167 let updateAt = new Date(new Date().getTime() + 2000) 186 let updateAt = new Date(new Date().getTime() + 2000)
168 187
@@ -173,18 +192,15 @@ describe('Test users notifications', function () {
173 privacy: VideoPrivacy.PUBLIC 192 privacy: VideoPrivacy.PUBLIC
174 } 193 }
175 } 194 }
176 const uuid = await uploadVideoByLocalAccount(servers, videoNameId, data) 195 const { name, uuid } = await uploadVideoByLocalAccount(servers, data)
177 196
178 await wait(6000) 197 await wait(6000)
179 await checkNewVideoFromSubscription(baseParams, videoName, uuid, 'presence') 198 await checkNewVideoFromSubscription(baseParams, name, uuid, 'presence')
180 }) 199 })
181 200
182 it('Should send a new video notification on a remote scheduled publication', async function () { 201 it('Should send a new video notification on a remote scheduled publication', async function () {
183 this.timeout(20000) 202 this.timeout(20000)
184 203
185 const videoNameId = 40
186 const videoName = 'remote video ' + videoNameId
187
188 // In 2 seconds 204 // In 2 seconds
189 let updateAt = new Date(new Date().getTime() + 2000) 205 let updateAt = new Date(new Date().getTime() + 2000)
190 206
@@ -195,19 +211,16 @@ describe('Test users notifications', function () {
195 privacy: VideoPrivacy.PUBLIC 211 privacy: VideoPrivacy.PUBLIC
196 } 212 }
197 } 213 }
198 const uuid = await uploadVideoByRemoteAccount(servers, videoNameId, data) 214 const { name, uuid } = await uploadVideoByRemoteAccount(servers, data)
199 await waitJobs(servers) 215 await waitJobs(servers)
200 216
201 await wait(6000) 217 await wait(6000)
202 await checkNewVideoFromSubscription(baseParams, videoName, uuid, 'presence') 218 await checkNewVideoFromSubscription(baseParams, name, uuid, 'presence')
203 }) 219 })
204 220
205 it('Should not send a notification before the video is published', async function () { 221 it('Should not send a notification before the video is published', async function () {
206 this.timeout(20000) 222 this.timeout(20000)
207 223
208 const videoNameId = 50
209 const videoName = 'local video ' + videoNameId
210
211 let updateAt = new Date(new Date().getTime() + 100000) 224 let updateAt = new Date(new Date().getTime() + 100000)
212 225
213 const data = { 226 const data = {
@@ -217,86 +230,70 @@ describe('Test users notifications', function () {
217 privacy: VideoPrivacy.PUBLIC 230 privacy: VideoPrivacy.PUBLIC
218 } 231 }
219 } 232 }
220 const uuid = await uploadVideoByLocalAccount(servers, videoNameId, data) 233 const { name, uuid } = await uploadVideoByLocalAccount(servers, data)
221 234
222 await wait(6000) 235 await wait(6000)
223 await checkNewVideoFromSubscription(baseParams, videoName, uuid, 'absence') 236 await checkNewVideoFromSubscription(baseParams, name, uuid, 'absence')
224 }) 237 })
225 238
226 it('Should send a new video notification when a video becomes public', async function () { 239 it('Should send a new video notification when a video becomes public', async function () {
227 this.timeout(10000) 240 this.timeout(10000)
228 241
229 const videoNameId = 60
230 const videoName = 'local video ' + videoNameId
231
232 const data = { privacy: VideoPrivacy.PRIVATE } 242 const data = { privacy: VideoPrivacy.PRIVATE }
233 const uuid = await uploadVideoByLocalAccount(servers, videoNameId, data) 243 const { name, uuid } = await uploadVideoByLocalAccount(servers, data)
234 244
235 await checkNewVideoFromSubscription(baseParams, videoName, uuid, 'absence') 245 await checkNewVideoFromSubscription(baseParams, name, uuid, 'absence')
236 246
237 await updateVideo(servers[0].url, servers[0].accessToken, uuid, { privacy: VideoPrivacy.PUBLIC }) 247 await updateVideo(servers[0].url, servers[0].accessToken, uuid, { privacy: VideoPrivacy.PUBLIC })
238 248
239 await wait(500) 249 await wait(500)
240 await checkNewVideoFromSubscription(baseParams, videoName, uuid, 'presence') 250 await checkNewVideoFromSubscription(baseParams, name, uuid, 'presence')
241 }) 251 })
242 252
243 it('Should send a new video notification when a remote video becomes public', async function () { 253 it('Should send a new video notification when a remote video becomes public', async function () {
244 this.timeout(20000) 254 this.timeout(20000)
245 255
246 const videoNameId = 70
247 const videoName = 'remote video ' + videoNameId
248
249 const data = { privacy: VideoPrivacy.PRIVATE } 256 const data = { privacy: VideoPrivacy.PRIVATE }
250 const uuid = await uploadVideoByRemoteAccount(servers, videoNameId, data) 257 const { name, uuid } = await uploadVideoByRemoteAccount(servers, data)
251 await waitJobs(servers)
252 258
253 await checkNewVideoFromSubscription(baseParams, videoName, uuid, 'absence') 259 await checkNewVideoFromSubscription(baseParams, name, uuid, 'absence')
254 260
255 await updateVideo(servers[1].url, servers[1].accessToken, uuid, { privacy: VideoPrivacy.PUBLIC }) 261 await updateVideo(servers[1].url, servers[1].accessToken, uuid, { privacy: VideoPrivacy.PUBLIC })
256 262
257 await waitJobs(servers) 263 await waitJobs(servers)
258 await checkNewVideoFromSubscription(baseParams, videoName, uuid, 'presence') 264 await checkNewVideoFromSubscription(baseParams, name, uuid, 'presence')
259 }) 265 })
260 266
261 it('Should not send a new video notification when a video becomes unlisted', async function () { 267 it('Should not send a new video notification when a video becomes unlisted', async function () {
262 this.timeout(20000) 268 this.timeout(20000)
263 269
264 const videoNameId = 80
265 const videoName = 'local video ' + videoNameId
266
267 const data = { privacy: VideoPrivacy.PRIVATE } 270 const data = { privacy: VideoPrivacy.PRIVATE }
268 const uuid = await uploadVideoByLocalAccount(servers, videoNameId, data) 271 const { name, uuid } = await uploadVideoByLocalAccount(servers, data)
269 272
270 await updateVideo(servers[0].url, servers[0].accessToken, uuid, { privacy: VideoPrivacy.UNLISTED }) 273 await updateVideo(servers[0].url, servers[0].accessToken, uuid, { privacy: VideoPrivacy.UNLISTED })
271 274
272 await checkNewVideoFromSubscription(baseParams, videoName, uuid, 'absence') 275 await checkNewVideoFromSubscription(baseParams, name, uuid, 'absence')
273 }) 276 })
274 277
275 it('Should not send a new video notification when a remote video becomes unlisted', async function () { 278 it('Should not send a new video notification when a remote video becomes unlisted', async function () {
276 this.timeout(20000) 279 this.timeout(20000)
277 280
278 const videoNameId = 90
279 const videoName = 'remote video ' + videoNameId
280
281 const data = { privacy: VideoPrivacy.PRIVATE } 281 const data = { privacy: VideoPrivacy.PRIVATE }
282 const uuid = await uploadVideoByRemoteAccount(servers, videoNameId, data) 282 const { name, uuid } = await uploadVideoByRemoteAccount(servers, data)
283 await waitJobs(servers)
284 283
285 await updateVideo(servers[1].url, servers[1].accessToken, uuid, { privacy: VideoPrivacy.UNLISTED }) 284 await updateVideo(servers[1].url, servers[1].accessToken, uuid, { privacy: VideoPrivacy.UNLISTED })
286 285
287 await waitJobs(servers) 286 await waitJobs(servers)
288 await checkNewVideoFromSubscription(baseParams, videoName, uuid, 'absence') 287 await checkNewVideoFromSubscription(baseParams, name, uuid, 'absence')
289 }) 288 })
290 289
291 it('Should send a new video notification after a video import', async function () { 290 it('Should send a new video notification after a video import', async function () {
292 this.timeout(30000) 291 this.timeout(30000)
293 292
294 const resChannel = await getMyUserInformation(servers[0].url, servers[0].accessToken) 293 const name = 'video import ' + uuidv4()
295 const channelId = resChannel.body.videoChannels[0].id
296 const videoName = 'local video 100'
297 294
298 const attributes = { 295 const attributes = {
299 name: videoName, 296 name,
300 channelId, 297 channelId,
301 privacy: VideoPrivacy.PUBLIC, 298 privacy: VideoPrivacy.PUBLIC,
302 targetUrl: getYoutubeVideoUrl() 299 targetUrl: getYoutubeVideoUrl()
@@ -306,7 +303,7 @@ describe('Test users notifications', function () {
306 303
307 await waitJobs(servers) 304 await waitJobs(servers)
308 305
309 await checkNewVideoFromSubscription(baseParams, videoName, uuid, 'presence') 306 await checkNewVideoFromSubscription(baseParams, name, uuid, 'presence')
310 }) 307 })
311 }) 308 })
312 309
@@ -348,6 +345,23 @@ describe('Test users notifications', function () {
348 await checkNewCommentOnMyVideo(baseParams, uuid, commentId, commentId, 'absence') 345 await checkNewCommentOnMyVideo(baseParams, uuid, commentId, commentId, 'absence')
349 }) 346 })
350 347
348 it('Should not send a new comment notification if the account is muted', async function () {
349 this.timeout(10000)
350
351 await addAccountToAccountBlocklist(servers[ 0 ].url, userAccessToken, 'root')
352
353 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: 'super video' })
354 const uuid = resVideo.body.video.uuid
355
356 const resComment = await addVideoCommentThread(servers[0].url, servers[0].accessToken, uuid, 'comment')
357 const commentId = resComment.body.comment.id
358
359 await wait(500)
360 await checkNewCommentOnMyVideo(baseParams, uuid, commentId, commentId, 'absence')
361
362 await removeAccountFromAccountBlocklist(servers[ 0 ].url, userAccessToken, 'root')
363 })
364
351 it('Should send a new comment notification after a local comment on my video', async function () { 365 it('Should send a new comment notification after a local comment on my video', async function () {
352 this.timeout(10000) 366 this.timeout(10000)
353 367
@@ -425,23 +439,21 @@ describe('Test users notifications', function () {
425 it('Should send a notification to moderators on local video abuse', async function () { 439 it('Should send a notification to moderators on local video abuse', async function () {
426 this.timeout(10000) 440 this.timeout(10000)
427 441
428 const videoName = 'local video 110' 442 const name = 'video for abuse ' + uuidv4()
429 443 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name })
430 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: videoName })
431 const uuid = resVideo.body.video.uuid 444 const uuid = resVideo.body.video.uuid
432 445
433 await reportVideoAbuse(servers[0].url, servers[0].accessToken, uuid, 'super reason') 446 await reportVideoAbuse(servers[0].url, servers[0].accessToken, uuid, 'super reason')
434 447
435 await waitJobs(servers) 448 await waitJobs(servers)
436 await checkNewVideoAbuseForModerators(baseParams, uuid, videoName, 'presence') 449 await checkNewVideoAbuseForModerators(baseParams, uuid, name, 'presence')
437 }) 450 })
438 451
439 it('Should send a notification to moderators on remote video abuse', async function () { 452 it('Should send a notification to moderators on remote video abuse', async function () {
440 this.timeout(10000) 453 this.timeout(10000)
441 454
442 const videoName = 'remote video 120' 455 const name = 'video for abuse ' + uuidv4()
443 456 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name })
444 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: videoName })
445 const uuid = resVideo.body.video.uuid 457 const uuid = resVideo.body.video.uuid
446 458
447 await waitJobs(servers) 459 await waitJobs(servers)
@@ -449,7 +461,7 @@ describe('Test users notifications', function () {
449 await reportVideoAbuse(servers[1].url, servers[1].accessToken, uuid, 'super reason') 461 await reportVideoAbuse(servers[1].url, servers[1].accessToken, uuid, 'super reason')
450 462
451 await waitJobs(servers) 463 await waitJobs(servers)
452 await checkNewVideoAbuseForModerators(baseParams, uuid, videoName, 'presence') 464 await checkNewVideoAbuseForModerators(baseParams, uuid, name, 'presence')
453 }) 465 })
454 }) 466 })
455 467
@@ -468,23 +480,21 @@ describe('Test users notifications', function () {
468 it('Should send a notification to video owner on blacklist', async function () { 480 it('Should send a notification to video owner on blacklist', async function () {
469 this.timeout(10000) 481 this.timeout(10000)
470 482
471 const videoName = 'local video 130' 483 const name = 'video for abuse ' + uuidv4()
472 484 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name })
473 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: videoName })
474 const uuid = resVideo.body.video.uuid 485 const uuid = resVideo.body.video.uuid
475 486
476 await addVideoToBlacklist(servers[0].url, servers[0].accessToken, uuid) 487 await addVideoToBlacklist(servers[0].url, servers[0].accessToken, uuid)
477 488
478 await waitJobs(servers) 489 await waitJobs(servers)
479 await checkNewBlacklistOnMyVideo(baseParams, uuid, videoName, 'blacklist') 490 await checkNewBlacklistOnMyVideo(baseParams, uuid, name, 'blacklist')
480 }) 491 })
481 492
482 it('Should send a notification to video owner on unblacklist', async function () { 493 it('Should send a notification to video owner on unblacklist', async function () {
483 this.timeout(10000) 494 this.timeout(10000)
484 495
485 const videoName = 'local video 130' 496 const name = 'video for abuse ' + uuidv4()
486 497 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name })
487 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: videoName })
488 const uuid = resVideo.body.video.uuid 498 const uuid = resVideo.body.video.uuid
489 499
490 await addVideoToBlacklist(servers[0].url, servers[0].accessToken, uuid) 500 await addVideoToBlacklist(servers[0].url, servers[0].accessToken, uuid)
@@ -494,38 +504,187 @@ describe('Test users notifications', function () {
494 await waitJobs(servers) 504 await waitJobs(servers)
495 505
496 await wait(500) 506 await wait(500)
497 await checkNewBlacklistOnMyVideo(baseParams, uuid, videoName, 'unblacklist') 507 await checkNewBlacklistOnMyVideo(baseParams, uuid, name, 'unblacklist')
508 })
509 })
510
511 describe('My video is published', function () {
512 let baseParams: CheckerBaseParams
513
514 before(() => {
515 baseParams = {
516 server: servers[1],
517 emails,
518 socketNotifications: adminNotificationsServer2,
519 token: servers[1].accessToken
520 }
521 })
522
523 it('Should not send a notification if transcoding is not enabled', async function () {
524 const { name, uuid } = await uploadVideoByLocalAccount(servers)
525 await waitJobs(servers)
526
527 await checkVideoIsPublished(baseParams, name, uuid, 'absence')
528 })
529
530 it('Should not send a notification if the wait transcoding is false', async function () {
531 this.timeout(50000)
532
533 await uploadVideoByRemoteAccount(servers, { waitTranscoding: false })
534 await waitJobs(servers)
535
536 const notification = await getLastNotification(servers[ 0 ].url, userAccessToken)
537 if (notification) {
538 expect(notification.type).to.not.equal(UserNotificationType.MY_VIDEO_PUBLISHED)
539 }
540 })
541
542 it('Should send a notification even if the video is not transcoded in other resolutions', async function () {
543 this.timeout(50000)
544
545 const { name, uuid } = await uploadVideoByRemoteAccount(servers, { waitTranscoding: true, fixture: 'video_short_240p.mp4' })
546 await waitJobs(servers)
547
548 await checkVideoIsPublished(baseParams, name, uuid, 'presence')
549 })
550
551 it('Should send a notification with a transcoded video', async function () {
552 this.timeout(50000)
553
554 const { name, uuid } = await uploadVideoByRemoteAccount(servers, { waitTranscoding: true })
555 await waitJobs(servers)
556
557 await checkVideoIsPublished(baseParams, name, uuid, 'presence')
558 })
559
560 it('Should send a notification when an imported video is transcoded', async function () {
561 this.timeout(50000)
562
563 const name = 'video import ' + uuidv4()
564
565 const attributes = {
566 name,
567 channelId,
568 privacy: VideoPrivacy.PUBLIC,
569 targetUrl: getYoutubeVideoUrl(),
570 waitTranscoding: true
571 }
572 const res = await importVideo(servers[1].url, servers[1].accessToken, attributes)
573 const uuid = res.body.video.uuid
574
575 await waitJobs(servers)
576 await checkVideoIsPublished(baseParams, name, uuid, 'presence')
577 })
578
579 it('Should send a notification when the scheduled update has been proceeded', async function () {
580 this.timeout(70000)
581
582 // In 2 seconds
583 let updateAt = new Date(new Date().getTime() + 2000)
584
585 const data = {
586 privacy: VideoPrivacy.PRIVATE,
587 scheduleUpdate: {
588 updateAt: updateAt.toISOString(),
589 privacy: VideoPrivacy.PUBLIC
590 }
591 }
592 const { name, uuid } = await uploadVideoByRemoteAccount(servers, data)
593
594 await wait(6000)
595 await checkVideoIsPublished(baseParams, name, uuid, 'presence')
596 })
597 })
598
599 describe('My video is imported', function () {
600 let baseParams: CheckerBaseParams
601
602 before(() => {
603 baseParams = {
604 server: servers[0],
605 emails,
606 socketNotifications: adminNotifications,
607 token: servers[0].accessToken
608 }
609 })
610
611 it('Should send a notification when the video import failed', async function () {
612 this.timeout(70000)
613
614 const name = 'video import ' + uuidv4()
615
616 const attributes = {
617 name,
618 channelId,
619 privacy: VideoPrivacy.PRIVATE,
620 targetUrl: getBadVideoUrl()
621 }
622 const res = await importVideo(servers[0].url, servers[0].accessToken, attributes)
623 const uuid = res.body.video.uuid
624
625 await waitJobs(servers)
626 await checkMyVideoImportIsFinished(baseParams, name, uuid, getBadVideoUrl(), false, 'presence')
627 })
628
629 it('Should send a notification when the video import succeeded', async function () {
630 this.timeout(70000)
631
632 const name = 'video import ' + uuidv4()
633
634 const attributes = {
635 name,
636 channelId,
637 privacy: VideoPrivacy.PRIVATE,
638 targetUrl: getYoutubeVideoUrl()
639 }
640 const res = await importVideo(servers[0].url, servers[0].accessToken, attributes)
641 const uuid = res.body.video.uuid
642
643 await waitJobs(servers)
644 await checkMyVideoImportIsFinished(baseParams, name, uuid, getYoutubeVideoUrl(), true, 'presence')
498 }) 645 })
499 }) 646 })
500 647
501 describe('Mark as read', function () { 648 describe('Mark as read', function () {
502 it('Should mark as read some notifications', async function () { 649 it('Should mark as read some notifications', async function () {
503 const res = await getUserNotifications(servers[0].url, userAccessToken, 2, 3) 650 const res = await getUserNotifications(servers[ 0 ].url, userAccessToken, 2, 3)
504 const ids = res.body.data.map(n => n.id) 651 const ids = res.body.data.map(n => n.id)
505 652
506 await markAsReadNotifications(servers[0].url, userAccessToken, ids) 653 await markAsReadNotifications(servers[ 0 ].url, userAccessToken, ids)
507 }) 654 })
508 655
509 it('Should have the notifications marked as read', async function () { 656 it('Should have the notifications marked as read', async function () {
510 const res = await getUserNotifications(servers[0].url, userAccessToken, 0, 10) 657 const res = await getUserNotifications(servers[ 0 ].url, userAccessToken, 0, 10)
658
659 const notifications = res.body.data as UserNotification[]
660 expect(notifications[ 0 ].read).to.be.false
661 expect(notifications[ 1 ].read).to.be.false
662 expect(notifications[ 2 ].read).to.be.true
663 expect(notifications[ 3 ].read).to.be.true
664 expect(notifications[ 4 ].read).to.be.true
665 expect(notifications[ 5 ].read).to.be.false
666 })
667
668 it('Should only list read notifications', async function () {
669 const res = await getUserNotifications(servers[ 0 ].url, userAccessToken, 0, 10, false)
511 670
512 const notifications = res.body.data as UserNotification[] 671 const notifications = res.body.data as UserNotification[]
513 expect(notifications[0].read).to.be.false 672 for (const notification of notifications) {
514 expect(notifications[1].read).to.be.false 673 expect(notification.read).to.be.true
515 expect(notifications[2].read).to.be.true 674 }
516 expect(notifications[3].read).to.be.true 675 })
517 expect(notifications[4].read).to.be.true 676
518 expect(notifications[5].read).to.be.false 677 it('Should only list unread notifications', async function () {
678 const res = await getUserNotifications(servers[ 0 ].url, userAccessToken, 0, 10, true)
679
680 const notifications = res.body.data as UserNotification[]
681 for (const notification of notifications) {
682 expect(notification.read).to.be.false
683 }
519 }) 684 })
520 }) 685 })
521 686
522 describe('Notification settings', function () { 687 describe('Notification settings', function () {
523 const baseUpdateNotificationParams = {
524 newCommentOnMyVideo: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL,
525 newVideoFromSubscription: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL,
526 videoAbuseAsModerator: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL,
527 blacklistOnMyVideo: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL
528 }
529 let baseParams: CheckerBaseParams 688 let baseParams: CheckerBaseParams
530 689
531 before(() => { 690 before(() => {
@@ -538,7 +697,7 @@ describe('Test users notifications', function () {
538 }) 697 })
539 698
540 it('Should not have notifications', async function () { 699 it('Should not have notifications', async function () {
541 await updateMyNotificationSettings(servers[0].url, userAccessToken, immutableAssign(baseUpdateNotificationParams, { 700 await updateMyNotificationSettings(servers[0].url, userAccessToken, immutableAssign(allNotificationSettings, {
542 newVideoFromSubscription: UserNotificationSettingValue.NONE 701 newVideoFromSubscription: UserNotificationSettingValue.NONE
543 })) 702 }))
544 703
@@ -548,16 +707,14 @@ describe('Test users notifications', function () {
548 expect(info.notificationSettings.newVideoFromSubscription).to.equal(UserNotificationSettingValue.NONE) 707 expect(info.notificationSettings.newVideoFromSubscription).to.equal(UserNotificationSettingValue.NONE)
549 } 708 }
550 709
551 const videoNameId = 42 710 const { name, uuid } = await uploadVideoByLocalAccount(servers)
552 const videoName = 'local video ' + videoNameId
553 const uuid = await uploadVideoByLocalAccount(servers, videoNameId)
554 711
555 const check = { web: true, mail: true } 712 const check = { web: true, mail: true }
556 await checkNewVideoFromSubscription(immutableAssign(baseParams, { check }), videoName, uuid, 'absence') 713 await checkNewVideoFromSubscription(immutableAssign(baseParams, { check }), name, uuid, 'absence')
557 }) 714 })
558 715
559 it('Should only have web notifications', async function () { 716 it('Should only have web notifications', async function () {
560 await updateMyNotificationSettings(servers[0].url, userAccessToken, immutableAssign(baseUpdateNotificationParams, { 717 await updateMyNotificationSettings(servers[0].url, userAccessToken, immutableAssign(allNotificationSettings, {
561 newVideoFromSubscription: UserNotificationSettingValue.WEB_NOTIFICATION 718 newVideoFromSubscription: UserNotificationSettingValue.WEB_NOTIFICATION
562 })) 719 }))
563 720
@@ -567,23 +724,21 @@ describe('Test users notifications', function () {
567 expect(info.notificationSettings.newVideoFromSubscription).to.equal(UserNotificationSettingValue.WEB_NOTIFICATION) 724 expect(info.notificationSettings.newVideoFromSubscription).to.equal(UserNotificationSettingValue.WEB_NOTIFICATION)
568 } 725 }
569 726
570 const videoNameId = 52 727 const { name, uuid } = await uploadVideoByLocalAccount(servers)
571 const videoName = 'local video ' + videoNameId
572 const uuid = await uploadVideoByLocalAccount(servers, videoNameId)
573 728
574 { 729 {
575 const check = { mail: true, web: false } 730 const check = { mail: true, web: false }
576 await checkNewVideoFromSubscription(immutableAssign(baseParams, { check }), videoName, uuid, 'absence') 731 await checkNewVideoFromSubscription(immutableAssign(baseParams, { check }), name, uuid, 'absence')
577 } 732 }
578 733
579 { 734 {
580 const check = { mail: false, web: true } 735 const check = { mail: false, web: true }
581 await checkNewVideoFromSubscription(immutableAssign(baseParams, { check }), videoName, uuid, 'presence') 736 await checkNewVideoFromSubscription(immutableAssign(baseParams, { check }), name, uuid, 'presence')
582 } 737 }
583 }) 738 })
584 739
585 it('Should only have mail notifications', async function () { 740 it('Should only have mail notifications', async function () {
586 await updateMyNotificationSettings(servers[0].url, userAccessToken, immutableAssign(baseUpdateNotificationParams, { 741 await updateMyNotificationSettings(servers[0].url, userAccessToken, immutableAssign(allNotificationSettings, {
587 newVideoFromSubscription: UserNotificationSettingValue.EMAIL 742 newVideoFromSubscription: UserNotificationSettingValue.EMAIL
588 })) 743 }))
589 744
@@ -593,23 +748,21 @@ describe('Test users notifications', function () {
593 expect(info.notificationSettings.newVideoFromSubscription).to.equal(UserNotificationSettingValue.EMAIL) 748 expect(info.notificationSettings.newVideoFromSubscription).to.equal(UserNotificationSettingValue.EMAIL)
594 } 749 }
595 750
596 const videoNameId = 62 751 const { name, uuid } = await uploadVideoByLocalAccount(servers)
597 const videoName = 'local video ' + videoNameId
598 const uuid = await uploadVideoByLocalAccount(servers, videoNameId)
599 752
600 { 753 {
601 const check = { mail: false, web: true } 754 const check = { mail: false, web: true }
602 await checkNewVideoFromSubscription(immutableAssign(baseParams, { check }), videoName, uuid, 'absence') 755 await checkNewVideoFromSubscription(immutableAssign(baseParams, { check }), name, uuid, 'absence')
603 } 756 }
604 757
605 { 758 {
606 const check = { mail: true, web: false } 759 const check = { mail: true, web: false }
607 await checkNewVideoFromSubscription(immutableAssign(baseParams, { check }), videoName, uuid, 'presence') 760 await checkNewVideoFromSubscription(immutableAssign(baseParams, { check }), name, uuid, 'presence')
608 } 761 }
609 }) 762 })
610 763
611 it('Should have email and web notifications', async function () { 764 it('Should have email and web notifications', async function () {
612 await updateMyNotificationSettings(servers[0].url, userAccessToken, immutableAssign(baseUpdateNotificationParams, { 765 await updateMyNotificationSettings(servers[0].url, userAccessToken, immutableAssign(allNotificationSettings, {
613 newVideoFromSubscription: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL 766 newVideoFromSubscription: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL
614 })) 767 }))
615 768
@@ -619,11 +772,9 @@ describe('Test users notifications', function () {
619 expect(info.notificationSettings.newVideoFromSubscription).to.equal(UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL) 772 expect(info.notificationSettings.newVideoFromSubscription).to.equal(UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL)
620 } 773 }
621 774
622 const videoNameId = 72 775 const { name, uuid } = await uploadVideoByLocalAccount(servers)
623 const videoName = 'local video ' + videoNameId
624 const uuid = await uploadVideoByLocalAccount(servers, videoNameId)
625 776
626 await checkNewVideoFromSubscription(baseParams, videoName, uuid, 'presence') 777 await checkNewVideoFromSubscription(baseParams, name, uuid, 'presence')
627 }) 778 })
628 }) 779 })
629 780
diff --git a/server/tests/fixtures/video_short_240p.mp4 b/server/tests/fixtures/video_short_240p.mp4
new file mode 100644
index 000000000..db074940b
--- /dev/null
+++ b/server/tests/fixtures/video_short_240p.mp4
Binary files differ
diff --git a/shared/models/users/user-notification-setting.model.ts b/shared/models/users/user-notification-setting.model.ts
index 7cecd70a2..55d351abf 100644
--- a/shared/models/users/user-notification-setting.model.ts
+++ b/shared/models/users/user-notification-setting.model.ts
@@ -10,4 +10,6 @@ export interface UserNotificationSetting {
10 newCommentOnMyVideo: UserNotificationSettingValue 10 newCommentOnMyVideo: UserNotificationSettingValue
11 videoAbuseAsModerator: UserNotificationSettingValue 11 videoAbuseAsModerator: UserNotificationSettingValue
12 blacklistOnMyVideo: UserNotificationSettingValue 12 blacklistOnMyVideo: UserNotificationSettingValue
13 myVideoPublished: UserNotificationSettingValue
14 myVideoImportFinished: UserNotificationSettingValue
13} 15}
diff --git a/shared/models/users/user-notification.model.ts b/shared/models/users/user-notification.model.ts
index 39beb2350..ee9ac275a 100644
--- a/shared/models/users/user-notification.model.ts
+++ b/shared/models/users/user-notification.model.ts
@@ -3,10 +3,13 @@ export enum UserNotificationType {
3 NEW_COMMENT_ON_MY_VIDEO = 2, 3 NEW_COMMENT_ON_MY_VIDEO = 2,
4 NEW_VIDEO_ABUSE_FOR_MODERATORS = 3, 4 NEW_VIDEO_ABUSE_FOR_MODERATORS = 3,
5 BLACKLIST_ON_MY_VIDEO = 4, 5 BLACKLIST_ON_MY_VIDEO = 4,
6 UNBLACKLIST_ON_MY_VIDEO = 5 6 UNBLACKLIST_ON_MY_VIDEO = 5,
7 MY_VIDEO_PUBLISHED = 6,
8 MY_VIDEO_IMPORT_SUCCESS = 7,
9 MY_VIDEO_IMPORT_ERROR = 8
7} 10}
8 11
9interface VideoInfo { 12export interface VideoInfo {
10 id: number 13 id: number
11 uuid: string 14 uuid: string
12 name: string 15 name: string
@@ -24,12 +27,22 @@ export interface UserNotification {
24 } 27 }
25 } 28 }
26 29
30 videoImport?: {
31 id: number
32 video?: VideoInfo
33 torrentName?: string
34 magnetUri?: string
35 targetUrl?: string
36 }
37
27 comment?: { 38 comment?: {
28 id: number 39 id: number
40 threadId: number
29 account: { 41 account: {
30 id: number 42 id: number
31 displayName: string 43 displayName: string
32 } 44 }
45 video: VideoInfo
33 } 46 }
34 47
35 videoAbuse?: { 48 videoAbuse?: {
diff --git a/shared/utils/users/user-notifications.ts b/shared/utils/users/user-notifications.ts
index dbe87559e..75d52023a 100644
--- a/shared/utils/users/user-notifications.ts
+++ b/shared/utils/users/user-notifications.ts
@@ -4,6 +4,7 @@ import { makeGetRequest, makePostBodyRequest, makePutBodyRequest } from '../requ
4import { UserNotification, UserNotificationSetting, UserNotificationType } from '../../models/users' 4import { UserNotification, UserNotificationSetting, UserNotificationType } from '../../models/users'
5import { ServerInfo } from '..' 5import { ServerInfo } from '..'
6import { expect } from 'chai' 6import { expect } from 'chai'
7import { inspect } from 'util'
7 8
8function updateMyNotificationSettings (url: string, token: string, settings: UserNotificationSetting, statusCodeExpected = 204) { 9function updateMyNotificationSettings (url: string, token: string, settings: UserNotificationSetting, statusCodeExpected = 204) {
9 const path = '/api/v1/users/me/notification-settings' 10 const path = '/api/v1/users/me/notification-settings'
@@ -17,7 +18,15 @@ function updateMyNotificationSettings (url: string, token: string, settings: Use
17 }) 18 })
18} 19}
19 20
20function getUserNotifications (url: string, token: string, start: number, count: number, sort = '-createdAt', statusCodeExpected = 200) { 21function getUserNotifications (
22 url: string,
23 token: string,
24 start: number,
25 count: number,
26 unread?: boolean,
27 sort = '-createdAt',
28 statusCodeExpected = 200
29) {
21 const path = '/api/v1/users/me/notifications' 30 const path = '/api/v1/users/me/notifications'
22 31
23 return makeGetRequest({ 32 return makeGetRequest({
@@ -27,7 +36,8 @@ function getUserNotifications (url: string, token: string, start: number, count:
27 query: { 36 query: {
28 start, 37 start,
29 count, 38 count,
30 sort 39 sort,
40 unread
31 }, 41 },
32 statusCodeExpected 42 statusCodeExpected
33 }) 43 })
@@ -46,7 +56,7 @@ function markAsReadNotifications (url: string, token: string, ids: number[], sta
46} 56}
47 57
48async function getLastNotification (serverUrl: string, accessToken: string) { 58async function getLastNotification (serverUrl: string, accessToken: string) {
49 const res = await getUserNotifications(serverUrl, accessToken, 0, 1, '-createdAt') 59 const res = await getUserNotifications(serverUrl, accessToken, 0, 1, undefined, '-createdAt')
50 60
51 if (res.body.total === 0) return undefined 61 if (res.body.total === 0) return undefined
52 62
@@ -65,21 +75,33 @@ type CheckerType = 'presence' | 'absence'
65 75
66async function checkNotification ( 76async function checkNotification (
67 base: CheckerBaseParams, 77 base: CheckerBaseParams,
68 lastNotificationChecker: (notification: UserNotification) => void, 78 notificationChecker: (notification: UserNotification, type: CheckerType) => void,
69 socketNotificationFinder: (notification: UserNotification) => boolean,
70 emailNotificationFinder: (email: object) => boolean, 79 emailNotificationFinder: (email: object) => boolean,
71 checkType: 'presence' | 'absence' 80 checkType: CheckerType
72) { 81) {
73 const check = base.check || { web: true, mail: true } 82 const check = base.check || { web: true, mail: true }
74 83
75 if (check.web) { 84 if (check.web) {
76 const notification = await getLastNotification(base.server.url, base.token) 85 const notification = await getLastNotification(base.server.url, base.token)
77 lastNotificationChecker(notification)
78 86
79 const socketNotification = base.socketNotifications.find(n => socketNotificationFinder(n)) 87 if (notification || checkType !== 'absence') {
88 notificationChecker(notification, checkType)
89 }
80 90
81 if (checkType === 'presence') expect(socketNotification, 'The socket notification is absent.').to.not.be.undefined 91 const socketNotification = base.socketNotifications.find(n => {
82 else expect(socketNotification, 'The socket notification is present.').to.be.undefined 92 try {
93 notificationChecker(n, 'presence')
94 return true
95 } catch {
96 return false
97 }
98 })
99
100 if (checkType === 'presence') {
101 expect(socketNotification, 'The socket notification is absent. ' + inspect(base.socketNotifications)).to.not.be.undefined
102 } else {
103 expect(socketNotification, 'The socket notification is present. ' + inspect(socketNotification)).to.be.undefined
104 }
83 } 105 }
84 106
85 if (check.mail) { 107 if (check.mail) {
@@ -89,45 +111,127 @@ async function checkNotification (
89 .reverse() 111 .reverse()
90 .find(e => emailNotificationFinder(e)) 112 .find(e => emailNotificationFinder(e))
91 113
92 if (checkType === 'presence') expect(email, 'The email is present.').to.not.be.undefined 114 if (checkType === 'presence') {
93 else expect(email, 'The email is absent.').to.be.undefined 115 expect(email, 'The email is absent. ' + inspect(base.emails)).to.not.be.undefined
116 } else {
117 expect(email, 'The email is present. ' + inspect(email)).to.be.undefined
118 }
94 } 119 }
95} 120}
96 121
122function checkVideo (video: any, videoName?: string, videoUUID?: string) {
123 expect(video.name).to.be.a('string')
124 expect(video.name).to.not.be.empty
125 if (videoName) expect(video.name).to.equal(videoName)
126
127 expect(video.uuid).to.be.a('string')
128 expect(video.uuid).to.not.be.empty
129 if (videoUUID) expect(video.uuid).to.equal(videoUUID)
130
131 expect(video.id).to.be.a('number')
132}
133
134function checkActor (channel: any) {
135 expect(channel.id).to.be.a('number')
136 expect(channel.displayName).to.be.a('string')
137 expect(channel.displayName).to.not.be.empty
138}
139
140function checkComment (comment: any, commentId: number, threadId: number) {
141 expect(comment.id).to.equal(commentId)
142 expect(comment.threadId).to.equal(threadId)
143}
144
97async function checkNewVideoFromSubscription (base: CheckerBaseParams, videoName: string, videoUUID: string, type: CheckerType) { 145async function checkNewVideoFromSubscription (base: CheckerBaseParams, videoName: string, videoUUID: string, type: CheckerType) {
98 const notificationType = UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION 146 const notificationType = UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION
99 147
100 function lastNotificationChecker (notification: UserNotification) { 148 function notificationChecker (notification: UserNotification, type: CheckerType) {
101 if (type === 'presence') { 149 if (type === 'presence') {
102 expect(notification).to.not.be.undefined 150 expect(notification).to.not.be.undefined
103 expect(notification.type).to.equal(notificationType) 151 expect(notification.type).to.equal(notificationType)
104 expect(notification.video.name).to.equal(videoName) 152
153 checkVideo(notification.video, videoName, videoUUID)
154 checkActor(notification.video.channel)
105 } else { 155 } else {
106 expect(notification.video).to.satisfy(v => v === undefined || v.name !== videoName) 156 expect(notification.video).to.satisfy(v => v === undefined || v.name !== videoName)
107 } 157 }
108 } 158 }
109 159
110 function socketFinder (notification: UserNotification) { 160 function emailFinder (email: object) {
111 return notification.type === notificationType && notification.video.name === videoName 161 return email[ 'text' ].indexOf(videoUUID) !== -1
162 }
163
164 await checkNotification(base, notificationChecker, emailFinder, type)
165}
166
167async function checkVideoIsPublished (base: CheckerBaseParams, videoName: string, videoUUID: string, type: CheckerType) {
168 const notificationType = UserNotificationType.MY_VIDEO_PUBLISHED
169
170 function notificationChecker (notification: UserNotification, type: CheckerType) {
171 if (type === 'presence') {
172 expect(notification).to.not.be.undefined
173 expect(notification.type).to.equal(notificationType)
174
175 checkVideo(notification.video, videoName, videoUUID)
176 checkActor(notification.video.channel)
177 } else {
178 expect(notification.video).to.satisfy(v => v === undefined || v.name !== videoName)
179 }
112 } 180 }
113 181
114 function emailFinder (email: object) { 182 function emailFinder (email: object) {
115 return email[ 'text' ].indexOf(videoUUID) !== -1 183 const text: string = email[ 'text' ]
184 return text.includes(videoUUID) && text.includes('Your video')
116 } 185 }
117 186
118 await checkNotification(base, lastNotificationChecker, socketFinder, emailFinder, type) 187 await checkNotification(base, notificationChecker, emailFinder, type)
188}
189
190async function checkMyVideoImportIsFinished (
191 base: CheckerBaseParams,
192 videoName: string,
193 videoUUID: string,
194 url: string,
195 success: boolean,
196 type: CheckerType
197) {
198 const notificationType = success ? UserNotificationType.MY_VIDEO_IMPORT_SUCCESS : UserNotificationType.MY_VIDEO_IMPORT_ERROR
199
200 function notificationChecker (notification: UserNotification, type: CheckerType) {
201 if (type === 'presence') {
202 expect(notification).to.not.be.undefined
203 expect(notification.type).to.equal(notificationType)
204
205 expect(notification.videoImport.targetUrl).to.equal(url)
206
207 if (success) checkVideo(notification.videoImport.video, videoName, videoUUID)
208 } else {
209 expect(notification.videoImport).to.satisfy(i => i === undefined || i.targetUrl !== url)
210 }
211 }
212
213 function emailFinder (email: object) {
214 const text: string = email[ 'text' ]
215 const toFind = success ? ' finished' : ' error'
216
217 return text.includes(url) && text.includes(toFind)
218 }
219
220 await checkNotification(base, notificationChecker, emailFinder, type)
119} 221}
120 222
121let lastEmailCount = 0 223let lastEmailCount = 0
122async function checkNewCommentOnMyVideo (base: CheckerBaseParams, uuid: string, commentId: number, threadId: number, type: CheckerType) { 224async function checkNewCommentOnMyVideo (base: CheckerBaseParams, uuid: string, commentId: number, threadId: number, type: CheckerType) {
123 const notificationType = UserNotificationType.NEW_COMMENT_ON_MY_VIDEO 225 const notificationType = UserNotificationType.NEW_COMMENT_ON_MY_VIDEO
124 226
125 function lastNotificationChecker (notification: UserNotification) { 227 function notificationChecker (notification: UserNotification, type: CheckerType) {
126 if (type === 'presence') { 228 if (type === 'presence') {
127 expect(notification).to.not.be.undefined 229 expect(notification).to.not.be.undefined
128 expect(notification.type).to.equal(notificationType) 230 expect(notification.type).to.equal(notificationType)
129 expect(notification.comment.id).to.equal(commentId) 231
130 expect(notification.comment.account.displayName).to.equal('root') 232 checkComment(notification.comment, commentId, threadId)
233 checkActor(notification.comment.account)
234 checkVideo(notification.comment.video, undefined, uuid)
131 } else { 235 } else {
132 expect(notification).to.satisfy((n: UserNotification) => { 236 expect(notification).to.satisfy((n: UserNotification) => {
133 return n === undefined || n.comment === undefined || n.comment.id !== commentId 237 return n === undefined || n.comment === undefined || n.comment.id !== commentId
@@ -135,18 +239,12 @@ async function checkNewCommentOnMyVideo (base: CheckerBaseParams, uuid: string,
135 } 239 }
136 } 240 }
137 241
138 function socketFinder (notification: UserNotification) {
139 return notification.type === notificationType &&
140 notification.comment.id === commentId &&
141 notification.comment.account.displayName === 'root'
142 }
143
144 const commentUrl = `http://localhost:9001/videos/watch/${uuid};threadId=${threadId}` 242 const commentUrl = `http://localhost:9001/videos/watch/${uuid};threadId=${threadId}`
145 function emailFinder (email: object) { 243 function emailFinder (email: object) {
146 return email[ 'text' ].indexOf(commentUrl) !== -1 244 return email[ 'text' ].indexOf(commentUrl) !== -1
147 } 245 }
148 246
149 await checkNotification(base, lastNotificationChecker, socketFinder, emailFinder, type) 247 await checkNotification(base, notificationChecker, emailFinder, type)
150 248
151 if (type === 'presence') { 249 if (type === 'presence') {
152 // We cannot detect email duplicates, so check we received another email 250 // We cannot detect email duplicates, so check we received another email
@@ -158,12 +256,13 @@ async function checkNewCommentOnMyVideo (base: CheckerBaseParams, uuid: string,
158async function checkNewVideoAbuseForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) { 256async function checkNewVideoAbuseForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) {
159 const notificationType = UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS 257 const notificationType = UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS
160 258
161 function lastNotificationChecker (notification: UserNotification) { 259 function notificationChecker (notification: UserNotification, type: CheckerType) {
162 if (type === 'presence') { 260 if (type === 'presence') {
163 expect(notification).to.not.be.undefined 261 expect(notification).to.not.be.undefined
164 expect(notification.type).to.equal(notificationType) 262 expect(notification.type).to.equal(notificationType)
165 expect(notification.videoAbuse.video.uuid).to.equal(videoUUID) 263
166 expect(notification.videoAbuse.video.name).to.equal(videoName) 264 expect(notification.videoAbuse.id).to.be.a('number')
265 checkVideo(notification.videoAbuse.video, videoName, videoUUID)
167 } else { 266 } else {
168 expect(notification).to.satisfy((n: UserNotification) => { 267 expect(notification).to.satisfy((n: UserNotification) => {
169 return n === undefined || n.videoAbuse === undefined || n.videoAbuse.video.uuid !== videoUUID 268 return n === undefined || n.videoAbuse === undefined || n.videoAbuse.video.uuid !== videoUUID
@@ -171,16 +270,12 @@ async function checkNewVideoAbuseForModerators (base: CheckerBaseParams, videoUU
171 } 270 }
172 } 271 }
173 272
174 function socketFinder (notification: UserNotification) {
175 return notification.type === notificationType && notification.videoAbuse.video.uuid === videoUUID
176 }
177
178 function emailFinder (email: object) { 273 function emailFinder (email: object) {
179 const text = email[ 'text' ] 274 const text = email[ 'text' ]
180 return text.indexOf(videoUUID) !== -1 && text.indexOf('abuse') !== -1 275 return text.indexOf(videoUUID) !== -1 && text.indexOf('abuse') !== -1
181 } 276 }
182 277
183 await checkNotification(base, lastNotificationChecker, socketFinder, emailFinder, type) 278 await checkNotification(base, notificationChecker, emailFinder, type)
184} 279}
185 280
186async function checkNewBlacklistOnMyVideo ( 281async function checkNewBlacklistOnMyVideo (
@@ -193,18 +288,13 @@ async function checkNewBlacklistOnMyVideo (
193 ? UserNotificationType.BLACKLIST_ON_MY_VIDEO 288 ? UserNotificationType.BLACKLIST_ON_MY_VIDEO
194 : UserNotificationType.UNBLACKLIST_ON_MY_VIDEO 289 : UserNotificationType.UNBLACKLIST_ON_MY_VIDEO
195 290
196 function lastNotificationChecker (notification: UserNotification) { 291 function notificationChecker (notification: UserNotification) {
197 expect(notification).to.not.be.undefined 292 expect(notification).to.not.be.undefined
198 expect(notification.type).to.equal(notificationType) 293 expect(notification.type).to.equal(notificationType)
199 294
200 const video = blacklistType === 'blacklist' ? notification.videoBlacklist.video : notification.video 295 const video = blacklistType === 'blacklist' ? notification.videoBlacklist.video : notification.video
201 296
202 expect(video.uuid).to.equal(videoUUID) 297 checkVideo(video, videoName, videoUUID)
203 expect(video.name).to.equal(videoName)
204 }
205
206 function socketFinder (notification: UserNotification) {
207 return notification.type === notificationType && (notification.video || notification.videoBlacklist.video).uuid === videoUUID
208 } 298 }
209 299
210 function emailFinder (email: object) { 300 function emailFinder (email: object) {
@@ -212,7 +302,7 @@ async function checkNewBlacklistOnMyVideo (
212 return text.indexOf(videoUUID) !== -1 && text.indexOf(' ' + blacklistType) !== -1 302 return text.indexOf(videoUUID) !== -1 && text.indexOf(' ' + blacklistType) !== -1
213 } 303 }
214 304
215 await checkNotification(base, lastNotificationChecker, socketFinder, emailFinder, 'presence') 305 await checkNotification(base, notificationChecker, emailFinder, 'presence')
216} 306}
217 307
218// --------------------------------------------------------------------------- 308// ---------------------------------------------------------------------------
@@ -221,6 +311,8 @@ export {
221 CheckerBaseParams, 311 CheckerBaseParams,
222 CheckerType, 312 CheckerType,
223 checkNotification, 313 checkNotification,
314 checkMyVideoImportIsFinished,
315 checkVideoIsPublished,
224 checkNewVideoFromSubscription, 316 checkNewVideoFromSubscription,
225 checkNewCommentOnMyVideo, 317 checkNewCommentOnMyVideo,
226 checkNewBlacklistOnMyVideo, 318 checkNewBlacklistOnMyVideo,
diff --git a/shared/utils/videos/video-imports.ts b/shared/utils/videos/video-imports.ts
index 3fa49b432..ec77cdcda 100644
--- a/shared/utils/videos/video-imports.ts
+++ b/shared/utils/videos/video-imports.ts
@@ -11,6 +11,10 @@ function getMagnetURI () {
11 return 'magnet:?xs=https%3A%2F%2Fpeertube2.cpy.re%2Fstatic%2Ftorrents%2Fb209ca00-c8bb-4b2b-b421-1ede169f3dbc-720.torrent&xt=urn:btih:0f498834733e8057ed5c6f2ee2b4efd8d84a76ee&dn=super+peertube2+video&tr=wss%3A%2F%2Fpeertube2.cpy.re%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fpeertube2.cpy.re%2Ftracker%2Fannounce&ws=https%3A%2F%2Fpeertube2.cpy.re%2Fstatic%2Fwebseed%2Fb209ca00-c8bb-4b2b-b421-1ede169f3dbc-720.mp4' 11 return 'magnet:?xs=https%3A%2F%2Fpeertube2.cpy.re%2Fstatic%2Ftorrents%2Fb209ca00-c8bb-4b2b-b421-1ede169f3dbc-720.torrent&xt=urn:btih:0f498834733e8057ed5c6f2ee2b4efd8d84a76ee&dn=super+peertube2+video&tr=wss%3A%2F%2Fpeertube2.cpy.re%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fpeertube2.cpy.re%2Ftracker%2Fannounce&ws=https%3A%2F%2Fpeertube2.cpy.re%2Fstatic%2Fwebseed%2Fb209ca00-c8bb-4b2b-b421-1ede169f3dbc-720.mp4'
12} 12}
13 13
14function getBadVideoUrl () {
15 return 'https://download.cpy.re/peertube/bad_video.mp4'
16}
17
14function importVideo (url: string, token: string, attributes: VideoImportCreate) { 18function importVideo (url: string, token: string, attributes: VideoImportCreate) {
15 const path = '/api/v1/videos/imports' 19 const path = '/api/v1/videos/imports'
16 20
@@ -45,6 +49,7 @@ function getMyVideoImports (url: string, token: string, sort?: string) {
45// --------------------------------------------------------------------------- 49// ---------------------------------------------------------------------------
46 50
47export { 51export {
52 getBadVideoUrl,
48 getYoutubeVideoUrl, 53 getYoutubeVideoUrl,
49 importVideo, 54 importVideo,
50 getMagnetURI, 55 getMagnetURI,