]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Add import finished and video published notifs
authorChocobozzz <me@florianbigard.com>
Wed, 2 Jan 2019 15:37:43 +0000 (16:37 +0100)
committerChocobozzz <chocobozzz@cpy.re>
Wed, 9 Jan 2019 10:15:15 +0000 (11:15 +0100)
23 files changed:
server/controllers/api/users/my-notifications.ts
server/initializers/migrations/0315-user-notifications.ts
server/lib/emailer.ts
server/lib/job-queue/handlers/video-file.ts
server/lib/job-queue/handlers/video-import.ts
server/lib/notifier.ts
server/lib/schedulers/update-videos-scheduler.ts
server/lib/user.ts
server/middlewares/validators/user-notifications.ts
server/models/account/account-blocklist.ts
server/models/account/user-notification-setting.ts
server/models/account/user-notification.ts
server/models/account/user.ts
server/models/video/video-file.ts
server/models/video/video-import.ts
server/models/video/video.ts
server/tests/api/check-params/user-notifications.ts
server/tests/api/users/user-notifications.ts
server/tests/fixtures/video_short_240p.mp4 [new file with mode: 0644]
shared/models/users/user-notification-setting.model.ts
shared/models/users/user-notification.model.ts
shared/utils/users/user-notifications.ts
shared/utils/videos/video-imports.ts

index cef1d237c0f3725c859ef3ea69c6a8ac15e40e7f..4b81777a47d27e019622a7ddd137567179c6c1eb 100644 (file)
@@ -14,10 +14,11 @@ import { getFormattedObjects } from '../../../helpers/utils'
 import { UserNotificationModel } from '../../../models/account/user-notification'
 import { meRouter } from './me'
 import {
+  listUserNotificationsValidator,
   markAsReadUserNotificationsValidator,
   updateNotificationSettingsValidator
 } from '../../../middlewares/validators/user-notifications'
-import { UserNotificationSetting } from '../../../../shared/models/users'
+import { UserNotificationSetting, UserNotificationSettingValue } from '../../../../shared/models/users'
 import { UserNotificationSettingModel } from '../../../models/account/user-notification-setting'
 
 const myNotificationsRouter = express.Router()
@@ -34,6 +35,7 @@ myNotificationsRouter.get('/me/notifications',
   userNotificationsSortValidator,
   setDefaultSort,
   setDefaultPagination,
+  listUserNotificationsValidator,
   asyncMiddleware(listUserNotifications)
 )
 
@@ -61,7 +63,11 @@ async function updateNotificationSettings (req: express.Request, res: express.Re
 
   await UserNotificationSettingModel.update({
     newVideoFromSubscription: body.newVideoFromSubscription,
-    newCommentOnMyVideo: body.newCommentOnMyVideo
+    newCommentOnMyVideo: body.newCommentOnMyVideo,
+    videoAbuseAsModerator: body.videoAbuseAsModerator,
+    blacklistOnMyVideo: body.blacklistOnMyVideo,
+    myVideoPublished: body.myVideoPublished,
+    myVideoImportFinished: body.myVideoImportFinished
   }, query)
 
   return res.status(204).end()
@@ -70,7 +76,7 @@ async function updateNotificationSettings (req: express.Request, res: express.Re
 async function listUserNotifications (req: express.Request, res: express.Response) {
   const user: UserModel = res.locals.oauth.token.User
 
-  const resultList = await UserNotificationModel.listForApi(user.id, req.query.start, req.query.count, req.query.sort)
+  const resultList = await UserNotificationModel.listForApi(user.id, req.query.start, req.query.count, req.query.sort, req.query.unread)
 
   return res.json(getFormattedObjects(resultList.data, resultList.total))
 }
index 2bd9c657d40d236e5e2f6754f08c164fc4db9c3b..8c54c5d6c9e5d48067b314f05188ed3f36c90d82 100644 (file)
@@ -13,6 +13,8 @@ CREATE TABLE IF NOT EXISTS "userNotificationSetting" ("id" SERIAL,
 "newCommentOnMyVideo" INTEGER NOT NULL DEFAULT NULL,
 "videoAbuseAsModerator" INTEGER NOT NULL DEFAULT NULL,
 "blacklistOnMyVideo" INTEGER NOT NULL DEFAULT NULL,
+"myVideoPublished" INTEGER NOT NULL DEFAULT NULL,
+"myVideoImportFinished" INTEGER NOT NULL DEFAULT NULL,
 "userId" INTEGER REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
 "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
 "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
@@ -24,8 +26,8 @@ PRIMARY KEY ("id"))
   {
     const query = 'INSERT INTO "userNotificationSetting" ' +
       '("newVideoFromSubscription", "newCommentOnMyVideo", "videoAbuseAsModerator", "blacklistOnMyVideo", ' +
-      '"userId", "createdAt", "updatedAt") ' +
-      '(SELECT 2, 2, 4, 4, id, NOW(), NOW() FROM "user")'
+      '"myVideoPublished", "myVideoImportFinished", "userId", "createdAt", "updatedAt") ' +
+      '(SELECT 2, 2, 4, 4, 2, 2, id, NOW(), NOW() FROM "user")'
 
     await utils.sequelize.query(query)
   }
index d766e655b2d902763320a7800a665650a5f30486..6dc8f2adf094c6799c00b585875249e136f50955 100644 (file)
@@ -10,6 +10,7 @@ import { readFileSync } from 'fs-extra'
 import { VideoCommentModel } from '../models/video/video-comment'
 import { VideoAbuseModel } from '../models/video/video-abuse'
 import { VideoBlacklistModel } from '../models/video/video-blacklist'
+import { VideoImportModel } from '../models/video/video-import'
 
 class Emailer {
 
@@ -102,6 +103,66 @@ class Emailer {
     return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
   }
 
+  myVideoPublishedNotification (to: string[], video: VideoModel) {
+    const videoUrl = CONFIG.WEBSERVER.URL + video.getWatchStaticPath()
+
+    const text = `Hi dear user,\n\n` +
+      `Your video ${video.name} has been published.` +
+      `\n\n` +
+      `You can view it on ${videoUrl} ` +
+      `\n\n` +
+      `Cheers,\n` +
+      `PeerTube.`
+
+    const emailPayload: EmailPayload = {
+      to,
+      subject: `Your video ${video.name} is published`,
+      text
+    }
+
+    return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
+  }
+
+  myVideoImportSuccessNotification (to: string[], videoImport: VideoImportModel) {
+    const videoUrl = CONFIG.WEBSERVER.URL + videoImport.Video.getWatchStaticPath()
+
+    const text = `Hi dear user,\n\n` +
+      `Your video import ${videoImport.getTargetIdentifier()} is finished.` +
+      `\n\n` +
+      `You can view the imported video on ${videoUrl} ` +
+      `\n\n` +
+      `Cheers,\n` +
+      `PeerTube.`
+
+    const emailPayload: EmailPayload = {
+      to,
+      subject: `Your video import ${videoImport.getTargetIdentifier()} is finished`,
+      text
+    }
+
+    return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
+  }
+
+  myVideoImportErrorNotification (to: string[], videoImport: VideoImportModel) {
+    const importUrl = CONFIG.WEBSERVER.URL + '/my-account/video-imports'
+
+    const text = `Hi dear user,\n\n` +
+      `Your video import ${videoImport.getTargetIdentifier()} encountered an error.` +
+      `\n\n` +
+      `See your videos import dashboard for more information: ${importUrl}` +
+      `\n\n` +
+      `Cheers,\n` +
+      `PeerTube.`
+
+    const emailPayload: EmailPayload = {
+      to,
+      subject: `Your video import ${videoImport.getTargetIdentifier()} encountered an error`,
+      text
+    }
+
+    return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
+  }
+
   addNewCommentOnMyVideoNotification (to: string[], comment: VideoCommentModel) {
     const accountName = comment.Account.getDisplayName()
     const video = comment.Video
index 480d324dc290dae43a2fa4cb54147c096dc3fe3d..593e43cc5fe95abeb151e932a3bc1da293930584 100644 (file)
@@ -68,17 +68,17 @@ async function processVideoFile (job: Bull.Job) {
 async function onVideoFileTranscoderOrImportSuccess (video: VideoModel) {
   if (video === undefined) return undefined
 
-  const { videoDatabase, isNewVideo } = await sequelizeTypescript.transaction(async t => {
+  const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => {
     // Maybe the video changed in database, refresh it
     let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t)
     // Video does not exist anymore
     if (!videoDatabase) return undefined
 
-    let isNewVideo = false
+    let videoPublished = false
 
     // We transcoded the video file in another format, now we can publish it
     if (videoDatabase.state !== VideoState.PUBLISHED) {
-      isNewVideo = true
+      videoPublished = true
 
       videoDatabase.state = VideoState.PUBLISHED
       videoDatabase.publishedAt = new Date()
@@ -86,12 +86,15 @@ async function onVideoFileTranscoderOrImportSuccess (video: VideoModel) {
     }
 
     // If the video was not published, we consider it is a new one for other instances
-    await federateVideoIfNeeded(videoDatabase, isNewVideo, t)
+    await federateVideoIfNeeded(videoDatabase, videoPublished, t)
 
-    return { videoDatabase, isNewVideo }
+    return { videoDatabase, videoPublished }
   })
 
-  if (isNewVideo) Notifier.Instance.notifyOnNewVideo(videoDatabase)
+  if (videoPublished) {
+    Notifier.Instance.notifyOnNewVideo(videoDatabase)
+    Notifier.Instance.notifyOnPendingVideoPublished(videoDatabase)
+  }
 }
 
 async function onVideoFileOptimizerSuccess (videoArg: VideoModel, isNewVideo: boolean) {
@@ -100,7 +103,7 @@ async function onVideoFileOptimizerSuccess (videoArg: VideoModel, isNewVideo: bo
   // Outside the transaction (IO on disk)
   const { videoFileResolution } = await videoArg.getOriginalFileResolution()
 
-  const videoDatabase = await sequelizeTypescript.transaction(async t => {
+  const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => {
     // Maybe the video changed in database, refresh it
     let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoArg.uuid, t)
     // Video does not exist anymore
@@ -113,6 +116,8 @@ async function onVideoFileOptimizerSuccess (videoArg: VideoModel, isNewVideo: bo
       { resolutions: resolutionsEnabled }
     )
 
+    let videoPublished = false
+
     if (resolutionsEnabled.length !== 0) {
       const tasks: Bluebird<Bull.Job<any>>[] = []
 
@@ -130,6 +135,8 @@ async function onVideoFileOptimizerSuccess (videoArg: VideoModel, isNewVideo: bo
 
       logger.info('Transcoding jobs created for uuid %s.', videoDatabase.uuid, { resolutionsEnabled })
     } else {
+      videoPublished = true
+
       // No transcoding to do, it's now published
       videoDatabase.state = VideoState.PUBLISHED
       videoDatabase = await videoDatabase.save({ transaction: t })
@@ -139,10 +146,11 @@ async function onVideoFileOptimizerSuccess (videoArg: VideoModel, isNewVideo: bo
 
     await federateVideoIfNeeded(videoDatabase, isNewVideo, t)
 
-    return videoDatabase
+    return { videoDatabase, videoPublished }
   })
 
   if (isNewVideo) Notifier.Instance.notifyOnNewVideo(videoDatabase)
+  if (videoPublished) Notifier.Instance.notifyOnPendingVideoPublished(videoDatabase)
 }
 
 // ---------------------------------------------------------------------------
index 29cd1198c7fedcddeabac4a2b0b21c1f782ca892..12004dcd7ef5fb7af316bb2c23da7163f92343df 100644 (file)
@@ -197,6 +197,7 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide
     })
 
     Notifier.Instance.notifyOnNewVideo(videoImportUpdated.Video)
+    Notifier.Instance.notifyOnFinishedVideoImport(videoImportUpdated, true)
 
     // Create transcoding jobs?
     if (videoImportUpdated.Video.state === VideoState.TO_TRANSCODE) {
@@ -220,6 +221,8 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide
     videoImport.state = VideoImportState.FAILED
     await videoImport.save()
 
+    Notifier.Instance.notifyOnFinishedVideoImport(videoImport, false)
+
     throw err
   }
 }
index a21b50b2dfa5222a3e58c42f233d03420e82bcef..11b0937e9c72fb30a26ce3e41b13240a699a2fae 100644 (file)
@@ -11,6 +11,8 @@ import { VideoPrivacy, VideoState } from '../../shared/models/videos'
 import { VideoAbuseModel } from '../models/video/video-abuse'
 import { VideoBlacklistModel } from '../models/video/video-blacklist'
 import * as Bluebird from 'bluebird'
+import { VideoImportModel } from '../models/video/video-import'
+import { AccountBlocklistModel } from '../models/account/account-blocklist'
 
 class Notifier {
 
@@ -26,6 +28,14 @@ class Notifier {
       .catch(err => logger.error('Cannot notify subscribers of new video %s.', video.url, { err }))
   }
 
+  notifyOnPendingVideoPublished (video: VideoModel): void {
+    // Only notify on public videos that has been published while the user waited transcoding/scheduled update
+    if (video.waitTranscoding === false && !video.ScheduleVideoUpdate) return
+
+    this.notifyOwnedVideoHasBeenPublished(video)
+        .catch(err => logger.error('Cannot notify owner that its video %s has been published.', video.url, { err }))
+  }
+
   notifyOnNewComment (comment: VideoCommentModel): void {
     this.notifyVideoOwnerOfNewComment(comment)
         .catch(err => logger.error('Cannot notify of new comment %s.', comment.url, { err }))
@@ -46,6 +56,11 @@ class Notifier {
         .catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', video.url, { err }))
   }
 
+  notifyOnFinishedVideoImport (videoImport: VideoImportModel, success: boolean): void {
+    this.notifyOwnerVideoImportIsFinished(videoImport, success)
+      .catch(err => logger.error('Cannot notify owner that its video import %s is finished.', videoImport.getTargetIdentifier(), { err }))
+  }
+
   private async notifySubscribersOfNewVideo (video: VideoModel) {
     // List all followers that are users
     const users = await UserModel.listUserSubscribersOf(video.VideoChannel.actorId)
@@ -80,6 +95,9 @@ class Notifier {
     // Not our user or user comments its own video
     if (!user || comment.Account.userId === user.id) return
 
+    const accountMuted = await AccountBlocklistModel.isAccountMutedBy(user.Account.id, comment.accountId)
+    if (accountMuted) return
+
     logger.info('Notifying user %s of new comment %s.', user.username, comment.url)
 
     function settingGetter (user: UserModel) {
@@ -188,6 +206,64 @@ class Notifier {
     return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
   }
 
+  private async notifyOwnedVideoHasBeenPublished (video: VideoModel) {
+    const user = await UserModel.loadByVideoId(video.id)
+    if (!user) return
+
+    logger.info('Notifying user %s of the publication of its video %s.', user.username, video.url)
+
+    function settingGetter (user: UserModel) {
+      return user.NotificationSetting.myVideoPublished
+    }
+
+    async function notificationCreator (user: UserModel) {
+      const notification = await UserNotificationModel.create({
+        type: UserNotificationType.MY_VIDEO_PUBLISHED,
+        userId: user.id,
+        videoId: video.id
+      })
+      notification.Video = video
+
+      return notification
+    }
+
+    function emailSender (emails: string[]) {
+      return Emailer.Instance.myVideoPublishedNotification(emails, video)
+    }
+
+    return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
+  }
+
+  private async notifyOwnerVideoImportIsFinished (videoImport: VideoImportModel, success: boolean) {
+    const user = await UserModel.loadByVideoImportId(videoImport.id)
+    if (!user) return
+
+    logger.info('Notifying user %s its video import %s is finished.', user.username, videoImport.getTargetIdentifier())
+
+    function settingGetter (user: UserModel) {
+      return user.NotificationSetting.myVideoImportFinished
+    }
+
+    async function notificationCreator (user: UserModel) {
+      const notification = await UserNotificationModel.create({
+        type: success ? UserNotificationType.MY_VIDEO_IMPORT_SUCCESS : UserNotificationType.MY_VIDEO_IMPORT_ERROR,
+        userId: user.id,
+        videoImportId: videoImport.id
+      })
+      notification.VideoImport = videoImport
+
+      return notification
+    }
+
+    function emailSender (emails: string[]) {
+      return success
+        ? Emailer.Instance.myVideoImportSuccessNotification(emails, videoImport)
+        : Emailer.Instance.myVideoImportErrorNotification(emails, videoImport)
+    }
+
+    return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
+  }
+
   private async notify (options: {
     users: UserModel[],
     notificationCreator: (user: UserModel) => Promise<UserNotificationModel>,
index b7fb029f1bd6d6fd9c89dd154d01b298a694ee3c..2618a5857d24aa77a0c50e6c02693fb3513f4dc3 100644 (file)
@@ -6,6 +6,7 @@ import { federateVideoIfNeeded } from '../activitypub'
 import { SCHEDULER_INTERVALS_MS, sequelizeTypescript } from '../../initializers'
 import { VideoPrivacy } from '../../../shared/models/videos'
 import { Notifier } from '../notifier'
+import { VideoModel } from '../../models/video/video'
 
 export class UpdateVideosScheduler extends AbstractScheduler {
 
@@ -24,8 +25,9 @@ export class UpdateVideosScheduler extends AbstractScheduler {
   private async updateVideos () {
     if (!await ScheduleVideoUpdateModel.areVideosToUpdate()) return undefined
 
-    return sequelizeTypescript.transaction(async t => {
+    const publishedVideos = await sequelizeTypescript.transaction(async t => {
       const schedules = await ScheduleVideoUpdateModel.listVideosToUpdate(t)
+      const publishedVideos: VideoModel[] = []
 
       for (const schedule of schedules) {
         const video = schedule.Video
@@ -42,13 +44,21 @@ export class UpdateVideosScheduler extends AbstractScheduler {
           await federateVideoIfNeeded(video, isNewVideo, t)
 
           if (oldPrivacy === VideoPrivacy.UNLISTED || oldPrivacy === VideoPrivacy.PRIVATE) {
-            Notifier.Instance.notifyOnNewVideo(video)
+            video.ScheduleVideoUpdate = schedule
+            publishedVideos.push(video)
           }
         }
 
         await schedule.destroy({ transaction: t })
       }
+
+      return publishedVideos
     })
+
+    for (const v of publishedVideos) {
+      Notifier.Instance.notifyOnNewVideo(v)
+      Notifier.Instance.notifyOnPendingVideoPublished(v)
+    }
   }
 
   static get Instance () {
index 72127819c0d5bff9e561f772d825f28626c57b1c..481571828265c05dee3170a0115968d9839e6ce6 100644 (file)
@@ -100,6 +100,8 @@ function createDefaultUserNotificationSettings (user: UserModel, t: Sequelize.Tr
     userId: user.id,
     newVideoFromSubscription: UserNotificationSettingValue.WEB_NOTIFICATION,
     newCommentOnMyVideo: UserNotificationSettingValue.WEB_NOTIFICATION,
+    myVideoImportFinished: UserNotificationSettingValue.WEB_NOTIFICATION,
+    myVideoPublished: UserNotificationSettingValue.WEB_NOTIFICATION,
     videoAbuseAsModerator: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL,
     blacklistOnMyVideo: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL
   }, { transaction: t })
index 8202f307e5ebfee4170137dc052fd8e9338bf472..1c31f0a73b001b5330ae9ddbb635cc23b449d354 100644 (file)
@@ -1,11 +1,26 @@
 import * as express from 'express'
 import 'express-validator'
-import { body } from 'express-validator/check'
+import { body, query } from 'express-validator/check'
 import { logger } from '../../helpers/logger'
 import { areValidationErrors } from './utils'
 import { isUserNotificationSettingValid } from '../../helpers/custom-validators/user-notifications'
 import { isIntArray } from '../../helpers/custom-validators/misc'
 
+const listUserNotificationsValidator = [
+  query('unread')
+    .optional()
+    .toBoolean()
+    .isBoolean().withMessage('Should have a valid unread boolean'),
+
+  (req: express.Request, res: express.Response, next: express.NextFunction) => {
+    logger.debug('Checking listUserNotificationsValidator parameters', { parameters: req.query })
+
+    if (areValidationErrors(req, res)) return
+
+    return next()
+  }
+]
+
 const updateNotificationSettingsValidator = [
   body('newVideoFromSubscription')
     .custom(isUserNotificationSettingValid).withMessage('Should have a valid new video from subscription notification setting'),
@@ -41,6 +56,7 @@ const markAsReadUserNotificationsValidator = [
 // ---------------------------------------------------------------------------
 
 export {
+  listUserNotificationsValidator,
   updateNotificationSettingsValidator,
   markAsReadUserNotificationsValidator
 }
index fa281923547be9764d1395d474a25c0b13fe9666..54ac290c4b69eb13873a9bf8e86d1864afde604b 100644 (file)
@@ -72,6 +72,21 @@ export class AccountBlocklistModel extends Model<AccountBlocklistModel> {
   })
   BlockedAccount: AccountModel
 
+  static isAccountMutedBy (accountId: number, targetAccountId: number) {
+    const query = {
+      attributes: [ 'id' ],
+      where: {
+        accountId,
+        targetAccountId
+      },
+      raw: true
+    }
+
+    return AccountBlocklistModel.unscoped()
+                                .findOne(query)
+                                .then(a => !!a)
+  }
+
   static loadByAccountAndTarget (accountId: number, targetAccountId: number) {
     const query = {
       where: {
index bc24b1e3360df8cf4e435e2e5bd1af629df3f8d3..6470defa75c927319dc203e0b9ecb95e02e67c59 100644 (file)
@@ -65,6 +65,24 @@ export class UserNotificationSettingModel extends Model<UserNotificationSettingM
   @Column
   blacklistOnMyVideo: UserNotificationSettingValue
 
+  @AllowNull(false)
+  @Default(null)
+  @Is(
+    'UserNotificationSettingMyVideoPublished',
+    value => throwIfNotValid(value, isUserNotificationSettingValid, 'myVideoPublished')
+  )
+  @Column
+  myVideoPublished: UserNotificationSettingValue
+
+  @AllowNull(false)
+  @Default(null)
+  @Is(
+    'UserNotificationSettingMyVideoImportFinished',
+    value => throwIfNotValid(value, isUserNotificationSettingValid, 'myVideoImportFinished')
+  )
+  @Column
+  myVideoImportFinished: UserNotificationSettingValue
+
   @ForeignKey(() => UserModel)
   @Column
   userId: number
@@ -94,7 +112,9 @@ export class UserNotificationSettingModel extends Model<UserNotificationSettingM
       newCommentOnMyVideo: this.newCommentOnMyVideo,
       newVideoFromSubscription: this.newVideoFromSubscription,
       videoAbuseAsModerator: this.videoAbuseAsModerator,
-      blacklistOnMyVideo: this.blacklistOnMyVideo
+      blacklistOnMyVideo: this.blacklistOnMyVideo,
+      myVideoPublished: this.myVideoPublished,
+      myVideoImportFinished: this.myVideoImportFinished
     }
   }
 }
index e22f0d57f661767296704fabe439baf4f98c664c..25124437418e4f4ddbe52bc56a199861ecee9c65 100644 (file)
@@ -1,4 +1,17 @@
-import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
+import {
+  AllowNull,
+  BelongsTo,
+  Column,
+  CreatedAt,
+  Default,
+  ForeignKey,
+  IFindOptions,
+  Is,
+  Model,
+  Scopes,
+  Table,
+  UpdatedAt
+} from 'sequelize-typescript'
 import { UserNotification, UserNotificationType } from '../../../shared'
 import { getSort, throwIfNotValid } from '../utils'
 import { isBooleanValid } from '../../helpers/custom-validators/misc'
@@ -11,66 +24,68 @@ import { VideoChannelModel } from '../video/video-channel'
 import { AccountModel } from './account'
 import { VideoAbuseModel } from '../video/video-abuse'
 import { VideoBlacklistModel } from '../video/video-blacklist'
+import { VideoImportModel } from '../video/video-import'
 
 enum ScopeNames {
   WITH_ALL = 'WITH_ALL'
 }
 
+function buildVideoInclude (required: boolean) {
+  return {
+    attributes: [ 'id', 'uuid', 'name' ],
+    model: () => VideoModel.unscoped(),
+    required
+  }
+}
+
+function buildChannelInclude () {
+  return {
+    required: true,
+    attributes: [ 'id', 'name' ],
+    model: () => VideoChannelModel.unscoped()
+  }
+}
+
+function buildAccountInclude () {
+  return {
+    required: true,
+    attributes: [ 'id', 'name' ],
+    model: () => AccountModel.unscoped()
+  }
+}
+
 @Scopes({
   [ScopeNames.WITH_ALL]: {
     include: [
+      Object.assign(buildVideoInclude(false), {
+        include: [ buildChannelInclude() ]
+      }),
       {
-        attributes: [ 'id', 'uuid', 'name' ],
-        model: () => VideoModel.unscoped(),
-        required: false,
-        include: [
-          {
-            required: true,
-            attributes: [ 'id', 'name' ],
-            model: () => VideoChannelModel.unscoped()
-          }
-        ]
-      },
-      {
-        attributes: [ 'id' ],
+        attributes: [ 'id', 'originCommentId' ],
         model: () => VideoCommentModel.unscoped(),
         required: false,
         include: [
-          {
-            required: true,
-            attributes: [ 'id', 'name' ],
-            model: () => AccountModel.unscoped()
-          },
-          {
-            required: true,
-            attributes: [ 'id', 'uuid', 'name' ],
-            model: () => VideoModel.unscoped()
-          }
+          buildAccountInclude(),
+          buildVideoInclude(true)
         ]
       },
       {
         attributes: [ 'id' ],
         model: () => VideoAbuseModel.unscoped(),
         required: false,
-        include: [
-          {
-            required: true,
-            attributes: [ 'id', 'uuid', 'name' ],
-            model: () => VideoModel.unscoped()
-          }
-        ]
+        include: [ buildVideoInclude(true) ]
       },
       {
         attributes: [ 'id' ],
         model: () => VideoBlacklistModel.unscoped(),
         required: false,
-        include: [
-          {
-            required: true,
-            attributes: [ 'id', 'uuid', 'name' ],
-            model: () => VideoModel.unscoped()
-          }
-        ]
+        include: [ buildVideoInclude(true) ]
+      },
+      {
+        attributes: [ 'id', 'magnetUri', 'targetUrl', 'torrentName' ],
+        model: () => VideoImportModel.unscoped(),
+        required: false,
+        include: [ buildVideoInclude(false) ]
       }
     ]
   }
@@ -166,8 +181,20 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
   })
   VideoBlacklist: VideoBlacklistModel
 
-  static listForApi (userId: number, start: number, count: number, sort: string) {
-    const query = {
+  @ForeignKey(() => VideoImportModel)
+  @Column
+  videoImportId: number
+
+  @BelongsTo(() => VideoImportModel, {
+    foreignKey: {
+      allowNull: true
+    },
+    onDelete: 'cascade'
+  })
+  VideoImport: VideoImportModel
+
+  static listForApi (userId: number, start: number, count: number, sort: string, unread?: boolean) {
+    const query: IFindOptions<UserNotificationModel> = {
       offset: start,
       limit: count,
       order: getSort(sort),
@@ -176,6 +203,8 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
       }
     }
 
+    if (unread !== undefined) query.where['read'] = !unread
+
     return UserNotificationModel.scope(ScopeNames.WITH_ALL)
                                 .findAndCountAll(query)
                                 .then(({ rows, count }) => {
@@ -200,45 +229,39 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
   }
 
   toFormattedJSON (): UserNotification {
-    const video = this.Video ? {
-      id: this.Video.id,
-      uuid: this.Video.uuid,
-      name: this.Video.name,
+    const video = this.Video ? Object.assign(this.formatVideo(this.Video), {
       channel: {
         id: this.Video.VideoChannel.id,
         displayName: this.Video.VideoChannel.getDisplayName()
       }
+    }) : undefined
+
+    const videoImport = this.VideoImport ? {
+      id: this.VideoImport.id,
+      video: this.VideoImport.Video ? this.formatVideo(this.VideoImport.Video) : undefined,
+      torrentName: this.VideoImport.torrentName,
+      magnetUri: this.VideoImport.magnetUri,
+      targetUrl: this.VideoImport.targetUrl
     } : undefined
 
     const comment = this.Comment ? {
       id: this.Comment.id,
+      threadId: this.Comment.getThreadId(),
       account: {
         id: this.Comment.Account.id,
         displayName: this.Comment.Account.getDisplayName()
       },
-      video: {
-        id: this.Comment.Video.id,
-        uuid: this.Comment.Video.uuid,
-        name: this.Comment.Video.name
-      }
+      video: this.formatVideo(this.Comment.Video)
     } : undefined
 
     const videoAbuse = this.VideoAbuse ? {
       id: this.VideoAbuse.id,
-      video: {
-        id: this.VideoAbuse.Video.id,
-        uuid: this.VideoAbuse.Video.uuid,
-        name: this.VideoAbuse.Video.name
-      }
+      video: this.formatVideo(this.VideoAbuse.Video)
     } : undefined
 
     const videoBlacklist = this.VideoBlacklist ? {
       id: this.VideoBlacklist.id,
-      video: {
-        id: this.VideoBlacklist.Video.id,
-        uuid: this.VideoBlacklist.Video.uuid,
-        name: this.VideoBlacklist.Video.name
-      }
+      video: this.formatVideo(this.VideoBlacklist.Video)
     } : undefined
 
     return {
@@ -246,6 +269,7 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
       type: this.type,
       read: this.read,
       video,
+      videoImport,
       comment,
       videoAbuse,
       videoBlacklist,
@@ -253,4 +277,12 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
       updatedAt: this.updatedAt.toISOString()
     }
   }
+
+  private formatVideo (video: VideoModel) {
+    return {
+      id: video.id,
+      uuid: video.uuid,
+      name: video.name
+    }
+  }
 }
index 55ec14d0568a2b378630a142c93f16cff399d38c..33f56f64193b5eecfca16b4892edb30d2ca13f2b 100644 (file)
@@ -48,6 +48,7 @@ import { UserNotificationSettingModel } from './user-notification-setting'
 import { VideoModel } from '../video/video'
 import { ActorModel } from '../activitypub/actor'
 import { ActorFollowModel } from '../activitypub/actor-follow'
+import { VideoImportModel } from '../video/video-import'
 
 enum ScopeNames {
   WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL'
@@ -186,6 +187,12 @@ export class UserModel extends Model<UserModel> {
   })
   NotificationSetting: UserNotificationSettingModel
 
+  @HasMany(() => VideoImportModel, {
+    foreignKey: 'userId',
+    onDelete: 'cascade'
+  })
+  VideoImports: VideoImportModel[]
+
   @HasMany(() => OAuthTokenModel, {
     foreignKey: 'userId',
     onDelete: 'cascade'
@@ -400,6 +407,23 @@ export class UserModel extends Model<UserModel> {
     return UserModel.findOne(query)
   }
 
+  static loadByVideoImportId (videoImportId: number) {
+    const query = {
+      include: [
+        {
+          required: true,
+          attributes: [ 'id' ],
+          model: VideoImportModel.unscoped(),
+          where: {
+            id: videoImportId
+          }
+        }
+      ]
+    }
+
+    return UserModel.findOne(query)
+  }
+
   static getOriginalVideoFileTotalFromUser (user: UserModel) {
     // Don't use sequelize because we need to use a sub query
     const query = UserModel.generateUserQuotaBaseSQL()
index 3fd2d5a99d66e42bb231f73aa221b729d1b305c3..0fd868cd61a33e9a5fdf2c649bc67e65b4376245 100644 (file)
@@ -1,4 +1,3 @@
-import { values } from 'lodash'
 import {
   AllowNull,
   BelongsTo,
@@ -20,7 +19,6 @@ import {
   isVideoFileSizeValid,
   isVideoFPSResolutionValid
 } from '../../helpers/custom-validators/videos'
-import { CONSTRAINTS_FIELDS } from '../../initializers'
 import { throwIfNotValid } from '../utils'
 import { VideoModel } from './video'
 import * as Sequelize from 'sequelize'
index 8d442b3f849940526fe5207afd157364f772dada..c723e57c0d20fe16f945b98db37811137f124418 100644 (file)
@@ -144,6 +144,10 @@ export class VideoImportModel extends Model<VideoImportModel> {
                            })
   }
 
+  getTargetIdentifier () {
+    return this.targetUrl || this.magnetUri || this.torrentName
+  }
+
   toFormattedJSON (): VideoImport {
     const videoFormatOptions = {
       completeDescription: true,
index fc200e5d1af159420b271d68b07e3d2873fbe995..80a6c78320e20bf5a63f7e71ac51bfc951721ee5 100644 (file)
@@ -94,6 +94,7 @@ import {
 import * as validator from 'validator'
 import { UserVideoHistoryModel } from '../account/user-video-history'
 import { UserModel } from '../account/user'
+import { VideoImportModel } from './video-import'
 
 // FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation
 const indexes: Sequelize.DefineIndexesOptions[] = [
@@ -785,6 +786,15 @@ export class VideoModel extends Model<VideoModel> {
   })
   VideoBlacklist: VideoBlacklistModel
 
+  @HasOne(() => VideoImportModel, {
+    foreignKey: {
+      name: 'videoId',
+      allowNull: true
+    },
+    onDelete: 'set null'
+  })
+  VideoImport: VideoImportModel
+
   @HasMany(() => VideoCaptionModel, {
     foreignKey: {
       name: 'videoId',
index 3ae36ddb325434b87a6ad9edfe684353bca62ed1..4f21f7b9520807a5479912082fb81d85317ef59c 100644 (file)
@@ -52,6 +52,18 @@ describe('Test user notifications API validators', function () {
       await checkBadSortPagination(server.url, path, server.accessToken)
     })
 
+    it('Should fail with an incorrect unread parameter', async function () {
+      await makeGetRequest({
+        url: server.url,
+        path,
+        query: {
+          unread: 'toto'
+        },
+        token: server.accessToken,
+        statusCodeExpected: 200
+      })
+    })
+
     it('Should fail with a non authenticated user', async function () {
       await makeGetRequest({
         url: server.url,
@@ -125,7 +137,9 @@ describe('Test user notifications API validators', function () {
       newVideoFromSubscription: UserNotificationSettingValue.WEB_NOTIFICATION,
       newCommentOnMyVideo: UserNotificationSettingValue.WEB_NOTIFICATION,
       videoAbuseAsModerator: UserNotificationSettingValue.WEB_NOTIFICATION,
-      blacklistOnMyVideo: UserNotificationSettingValue.WEB_NOTIFICATION
+      blacklistOnMyVideo: UserNotificationSettingValue.WEB_NOTIFICATION,
+      myVideoImportFinished: UserNotificationSettingValue.WEB_NOTIFICATION,
+      myVideoPublished: UserNotificationSettingValue.WEB_NOTIFICATION
     }
 
     it('Should fail with missing fields', async function () {
index 09c0479fdf79c45fda9530490ce00926a64b5a59..e4966dbf570e4f5d7e87e32efd33393103640c67 100644 (file)
@@ -29,33 +29,46 @@ import {
   getLastNotification,
   getUserNotifications,
   markAsReadNotifications,
-  updateMyNotificationSettings
+  updateMyNotificationSettings,
+  checkVideoIsPublished, checkMyVideoImportIsFinished
 } from '../../../../shared/utils/users/user-notifications'
-import { User, UserNotification, UserNotificationSettingValue } from '../../../../shared/models/users'
+import {
+  User,
+  UserNotification,
+  UserNotificationSetting,
+  UserNotificationSettingValue,
+  UserNotificationType
+} from '../../../../shared/models/users'
 import { MockSmtpServer } from '../../../../shared/utils/miscs/email'
 import { addUserSubscription } from '../../../../shared/utils/users/user-subscriptions'
 import { VideoPrivacy } from '../../../../shared/models/videos'
-import { getYoutubeVideoUrl, importVideo } from '../../../../shared/utils/videos/video-imports'
+import { getYoutubeVideoUrl, importVideo, getBadVideoUrl } from '../../../../shared/utils/videos/video-imports'
 import { addVideoCommentReply, addVideoCommentThread } from '../../../../shared/utils/videos/video-comments'
+import * as uuidv4 from 'uuid/v4'
+import { addAccountToAccountBlocklist, removeAccountFromAccountBlocklist } from '../../../../shared/utils/users/blocklist'
 
 const expect = chai.expect
 
-async function uploadVideoByRemoteAccount (servers: ServerInfo[], videoNameId: number, additionalParams: any = {}) {
-  const data = Object.assign({ name: 'remote video ' + videoNameId }, additionalParams)
+async function uploadVideoByRemoteAccount (servers: ServerInfo[], additionalParams: any = {}) {
+  const name = 'remote video ' + uuidv4()
+
+  const data = Object.assign({ name }, additionalParams)
   const res = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, data)
 
   await waitJobs(servers)
 
-  return res.body.video.uuid
+  return { uuid: res.body.video.uuid, name }
 }
 
-async function uploadVideoByLocalAccount (servers: ServerInfo[], videoNameId: number, additionalParams: any = {}) {
-  const data = Object.assign({ name: 'local video ' + videoNameId }, additionalParams)
+async function uploadVideoByLocalAccount (servers: ServerInfo[], additionalParams: any = {}) {
+  const name = 'local video ' + uuidv4()
+
+  const data = Object.assign({ name }, additionalParams)
   const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, data)
 
   await waitJobs(servers)
 
-  return res.body.video.uuid
+  return { uuid: res.body.video.uuid, name }
 }
 
 describe('Test users notifications', function () {
@@ -63,7 +76,18 @@ describe('Test users notifications', function () {
   let userAccessToken: string
   let userNotifications: UserNotification[] = []
   let adminNotifications: UserNotification[] = []
+  let adminNotificationsServer2: UserNotification[] = []
   const emails: object[] = []
+  let channelId: number
+
+  const allNotificationSettings: UserNotificationSetting = {
+    myVideoPublished: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL,
+    myVideoImportFinished: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL,
+    newCommentOnMyVideo: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL,
+    newVideoFromSubscription: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL,
+    videoAbuseAsModerator: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL,
+    blacklistOnMyVideo: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL
+  }
 
   before(async function () {
     this.timeout(120000)
@@ -94,12 +118,9 @@ describe('Test users notifications', function () {
     await createUser(servers[0].url, servers[0].accessToken, user.username, user.password, 10 * 1000 * 1000)
     userAccessToken = await userLogin(servers[0], user)
 
-    await updateMyNotificationSettings(servers[0].url, userAccessToken, {
-      newCommentOnMyVideo: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL,
-      newVideoFromSubscription: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL,
-      blacklistOnMyVideo: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL,
-      videoAbuseAsModerator: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL
-    })
+    await updateMyNotificationSettings(servers[0].url, userAccessToken, allNotificationSettings)
+    await updateMyNotificationSettings(servers[0].url, servers[0].accessToken, allNotificationSettings)
+    await updateMyNotificationSettings(servers[1].url, servers[1].accessToken, allNotificationSettings)
 
     {
       const socket = getUserNotificationSocket(servers[ 0 ].url, userAccessToken)
@@ -109,6 +130,15 @@ describe('Test users notifications', function () {
       const socket = getUserNotificationSocket(servers[ 0 ].url, servers[0].accessToken)
       socket.on('new-notification', n => adminNotifications.push(n))
     }
+    {
+      const socket = getUserNotificationSocket(servers[ 1 ].url, servers[1].accessToken)
+      socket.on('new-notification', n => adminNotificationsServer2.push(n))
+    }
+
+    {
+      const resChannel = await getMyUserInformation(servers[0].url, servers[0].accessToken)
+      channelId = resChannel.body.videoChannels[0].id
+    }
   })
 
   describe('New video from my subscription notification', function () {
@@ -124,7 +154,7 @@ describe('Test users notifications', function () {
     })
 
     it('Should not send notifications if the user does not follow the video publisher', async function () {
-      await uploadVideoByLocalAccount(servers, 1)
+      await uploadVideoByLocalAccount(servers)
 
       const notification = await getLastNotification(servers[ 0 ].url, userAccessToken)
       expect(notification).to.be.undefined
@@ -136,11 +166,8 @@ describe('Test users notifications', function () {
     it('Should send a new video notification if the user follows the local video publisher', async function () {
       await addUserSubscription(servers[0].url, userAccessToken, 'root_channel@localhost:9001')
 
-      const videoNameId = 10
-      const videoName = 'local video ' + videoNameId
-
-      const uuid = await uploadVideoByLocalAccount(servers, videoNameId)
-      await checkNewVideoFromSubscription(baseParams, videoName, uuid, 'presence')
+      const { name, uuid } = await uploadVideoByLocalAccount(servers)
+      await checkNewVideoFromSubscription(baseParams, name, uuid, 'presence')
     })
 
     it('Should send a new video notification from a remote account', async function () {
@@ -148,21 +175,13 @@ describe('Test users notifications', function () {
 
       await addUserSubscription(servers[0].url, userAccessToken, 'root_channel@localhost:9002')
 
-      const videoNameId = 20
-      const videoName = 'remote video ' + videoNameId
-
-      const uuid = await uploadVideoByRemoteAccount(servers, videoNameId)
-      await waitJobs(servers)
-
-      await checkNewVideoFromSubscription(baseParams, videoName, uuid, 'presence')
+      const { name, uuid } = await uploadVideoByRemoteAccount(servers)
+      await checkNewVideoFromSubscription(baseParams, name, uuid, 'presence')
     })
 
     it('Should send a new video notification on a scheduled publication', async function () {
       this.timeout(20000)
 
-      const videoNameId = 30
-      const videoName = 'local video ' + videoNameId
-
       // In 2 seconds
       let updateAt = new Date(new Date().getTime() + 2000)
 
@@ -173,18 +192,15 @@ describe('Test users notifications', function () {
           privacy: VideoPrivacy.PUBLIC
         }
       }
-      const uuid = await uploadVideoByLocalAccount(servers, videoNameId, data)
+      const { name, uuid } = await uploadVideoByLocalAccount(servers, data)
 
       await wait(6000)
-      await checkNewVideoFromSubscription(baseParams, videoName, uuid, 'presence')
+      await checkNewVideoFromSubscription(baseParams, name, uuid, 'presence')
     })
 
     it('Should send a new video notification on a remote scheduled publication', async function () {
       this.timeout(20000)
 
-      const videoNameId = 40
-      const videoName = 'remote video ' + videoNameId
-
       // In 2 seconds
       let updateAt = new Date(new Date().getTime() + 2000)
 
@@ -195,19 +211,16 @@ describe('Test users notifications', function () {
           privacy: VideoPrivacy.PUBLIC
         }
       }
-      const uuid = await uploadVideoByRemoteAccount(servers, videoNameId, data)
+      const { name, uuid } = await uploadVideoByRemoteAccount(servers, data)
       await waitJobs(servers)
 
       await wait(6000)
-      await checkNewVideoFromSubscription(baseParams, videoName, uuid, 'presence')
+      await checkNewVideoFromSubscription(baseParams, name, uuid, 'presence')
     })
 
     it('Should not send a notification before the video is published', async function () {
       this.timeout(20000)
 
-      const videoNameId = 50
-      const videoName = 'local video ' + videoNameId
-
       let updateAt = new Date(new Date().getTime() + 100000)
 
       const data = {
@@ -217,86 +230,70 @@ describe('Test users notifications', function () {
           privacy: VideoPrivacy.PUBLIC
         }
       }
-      const uuid = await uploadVideoByLocalAccount(servers, videoNameId, data)
+      const { name, uuid } = await uploadVideoByLocalAccount(servers, data)
 
       await wait(6000)
-      await checkNewVideoFromSubscription(baseParams, videoName, uuid, 'absence')
+      await checkNewVideoFromSubscription(baseParams, name, uuid, 'absence')
     })
 
     it('Should send a new video notification when a video becomes public', async function () {
       this.timeout(10000)
 
-      const videoNameId = 60
-      const videoName = 'local video ' + videoNameId
-
       const data = { privacy: VideoPrivacy.PRIVATE }
-      const uuid = await uploadVideoByLocalAccount(servers, videoNameId, data)
+      const { name, uuid } = await uploadVideoByLocalAccount(servers, data)
 
-      await checkNewVideoFromSubscription(baseParams, videoName, uuid, 'absence')
+      await checkNewVideoFromSubscription(baseParams, name, uuid, 'absence')
 
       await updateVideo(servers[0].url, servers[0].accessToken, uuid, { privacy: VideoPrivacy.PUBLIC })
 
       await wait(500)
-      await checkNewVideoFromSubscription(baseParams, videoName, uuid, 'presence')
+      await checkNewVideoFromSubscription(baseParams, name, uuid, 'presence')
     })
 
     it('Should send a new video notification when a remote video becomes public', async function () {
       this.timeout(20000)
 
-      const videoNameId = 70
-      const videoName = 'remote video ' + videoNameId
-
       const data = { privacy: VideoPrivacy.PRIVATE }
-      const uuid = await uploadVideoByRemoteAccount(servers, videoNameId, data)
-      await waitJobs(servers)
+      const { name, uuid } = await uploadVideoByRemoteAccount(servers, data)
 
-      await checkNewVideoFromSubscription(baseParams, videoName, uuid, 'absence')
+      await checkNewVideoFromSubscription(baseParams, name, uuid, 'absence')
 
       await updateVideo(servers[1].url, servers[1].accessToken, uuid, { privacy: VideoPrivacy.PUBLIC })
 
       await waitJobs(servers)
-      await checkNewVideoFromSubscription(baseParams, videoName, uuid, 'presence')
+      await checkNewVideoFromSubscription(baseParams, name, uuid, 'presence')
     })
 
     it('Should not send a new video notification when a video becomes unlisted', async function () {
       this.timeout(20000)
 
-      const videoNameId = 80
-      const videoName = 'local video ' + videoNameId
-
       const data = { privacy: VideoPrivacy.PRIVATE }
-      const uuid = await uploadVideoByLocalAccount(servers, videoNameId, data)
+      const { name, uuid } = await uploadVideoByLocalAccount(servers, data)
 
       await updateVideo(servers[0].url, servers[0].accessToken, uuid, { privacy: VideoPrivacy.UNLISTED })
 
-      await checkNewVideoFromSubscription(baseParams, videoName, uuid, 'absence')
+      await checkNewVideoFromSubscription(baseParams, name, uuid, 'absence')
     })
 
     it('Should not send a new video notification when a remote video becomes unlisted', async function () {
       this.timeout(20000)
 
-      const videoNameId = 90
-      const videoName = 'remote video ' + videoNameId
-
       const data = { privacy: VideoPrivacy.PRIVATE }
-      const uuid = await uploadVideoByRemoteAccount(servers, videoNameId, data)
-      await waitJobs(servers)
+      const { name, uuid } = await uploadVideoByRemoteAccount(servers, data)
 
       await updateVideo(servers[1].url, servers[1].accessToken, uuid, { privacy: VideoPrivacy.UNLISTED })
 
       await waitJobs(servers)
-      await checkNewVideoFromSubscription(baseParams, videoName, uuid, 'absence')
+      await checkNewVideoFromSubscription(baseParams, name, uuid, 'absence')
     })
 
     it('Should send a new video notification after a video import', async function () {
       this.timeout(30000)
 
-      const resChannel = await getMyUserInformation(servers[0].url, servers[0].accessToken)
-      const channelId = resChannel.body.videoChannels[0].id
-      const videoName = 'local video 100'
+      const name = 'video import ' + uuidv4()
 
       const attributes = {
-        name: videoName,
+        name,
         channelId,
         privacy: VideoPrivacy.PUBLIC,
         targetUrl: getYoutubeVideoUrl()
@@ -306,7 +303,7 @@ describe('Test users notifications', function () {
 
       await waitJobs(servers)
 
-      await checkNewVideoFromSubscription(baseParams, videoName, uuid, 'presence')
+      await checkNewVideoFromSubscription(baseParams, name, uuid, 'presence')
     })
   })
 
@@ -348,6 +345,23 @@ describe('Test users notifications', function () {
       await checkNewCommentOnMyVideo(baseParams, uuid, commentId, commentId, 'absence')
     })
 
+    it('Should not send a new comment notification if the account is muted', async function () {
+      this.timeout(10000)
+
+      await addAccountToAccountBlocklist(servers[ 0 ].url, userAccessToken, 'root')
+
+      const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: 'super video' })
+      const uuid = resVideo.body.video.uuid
+
+      const resComment = await addVideoCommentThread(servers[0].url, servers[0].accessToken, uuid, 'comment')
+      const commentId = resComment.body.comment.id
+
+      await wait(500)
+      await checkNewCommentOnMyVideo(baseParams, uuid, commentId, commentId, 'absence')
+
+      await removeAccountFromAccountBlocklist(servers[ 0 ].url, userAccessToken, 'root')
+    })
+
     it('Should send a new comment notification after a local comment on my video', async function () {
       this.timeout(10000)
 
@@ -425,23 +439,21 @@ describe('Test users notifications', function () {
     it('Should send a notification to moderators on local video abuse', async function () {
       this.timeout(10000)
 
-      const videoName = 'local video 110'
-
-      const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: videoName })
+      const name = 'video for abuse ' + uuidv4()
+      const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name })
       const uuid = resVideo.body.video.uuid
 
       await reportVideoAbuse(servers[0].url, servers[0].accessToken, uuid, 'super reason')
 
       await waitJobs(servers)
-      await checkNewVideoAbuseForModerators(baseParams, uuid, videoName, 'presence')
+      await checkNewVideoAbuseForModerators(baseParams, uuid, name, 'presence')
     })
 
     it('Should send a notification to moderators on remote video abuse', async function () {
       this.timeout(10000)
 
-      const videoName = 'remote video 120'
-
-      const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: videoName })
+      const name = 'video for abuse ' + uuidv4()
+      const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name })
       const uuid = resVideo.body.video.uuid
 
       await waitJobs(servers)
@@ -449,7 +461,7 @@ describe('Test users notifications', function () {
       await reportVideoAbuse(servers[1].url, servers[1].accessToken, uuid, 'super reason')
 
       await waitJobs(servers)
-      await checkNewVideoAbuseForModerators(baseParams, uuid, videoName, 'presence')
+      await checkNewVideoAbuseForModerators(baseParams, uuid, name, 'presence')
     })
   })
 
@@ -468,23 +480,21 @@ describe('Test users notifications', function () {
     it('Should send a notification to video owner on blacklist', async function () {
       this.timeout(10000)
 
-      const videoName = 'local video 130'
-
-      const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: videoName })
+      const name = 'video for abuse ' + uuidv4()
+      const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name })
       const uuid = resVideo.body.video.uuid
 
       await addVideoToBlacklist(servers[0].url, servers[0].accessToken, uuid)
 
       await waitJobs(servers)
-      await checkNewBlacklistOnMyVideo(baseParams, uuid, videoName, 'blacklist')
+      await checkNewBlacklistOnMyVideo(baseParams, uuid, name, 'blacklist')
     })
 
     it('Should send a notification to video owner on unblacklist', async function () {
       this.timeout(10000)
 
-      const videoName = 'local video 130'
-
-      const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: videoName })
+      const name = 'video for abuse ' + uuidv4()
+      const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name })
       const uuid = resVideo.body.video.uuid
 
       await addVideoToBlacklist(servers[0].url, servers[0].accessToken, uuid)
@@ -494,38 +504,187 @@ describe('Test users notifications', function () {
       await waitJobs(servers)
 
       await wait(500)
-      await checkNewBlacklistOnMyVideo(baseParams, uuid, videoName, 'unblacklist')
+      await checkNewBlacklistOnMyVideo(baseParams, uuid, name, 'unblacklist')
+    })
+  })
+
+  describe('My video is published', function () {
+    let baseParams: CheckerBaseParams
+
+    before(() => {
+      baseParams = {
+        server: servers[1],
+        emails,
+        socketNotifications: adminNotificationsServer2,
+        token: servers[1].accessToken
+      }
+    })
+
+    it('Should not send a notification if transcoding is not enabled', async function () {
+      const { name, uuid } = await uploadVideoByLocalAccount(servers)
+      await waitJobs(servers)
+
+      await checkVideoIsPublished(baseParams, name, uuid, 'absence')
+    })
+
+    it('Should not send a notification if the wait transcoding is false', async function () {
+      this.timeout(50000)
+
+      await uploadVideoByRemoteAccount(servers, { waitTranscoding: false })
+      await waitJobs(servers)
+
+      const notification = await getLastNotification(servers[ 0 ].url, userAccessToken)
+      if (notification) {
+        expect(notification.type).to.not.equal(UserNotificationType.MY_VIDEO_PUBLISHED)
+      }
+    })
+
+    it('Should send a notification even if the video is not transcoded in other resolutions', async function () {
+      this.timeout(50000)
+
+      const { name, uuid } = await uploadVideoByRemoteAccount(servers, { waitTranscoding: true, fixture: 'video_short_240p.mp4' })
+      await waitJobs(servers)
+
+      await checkVideoIsPublished(baseParams, name, uuid, 'presence')
+    })
+
+    it('Should send a notification with a transcoded video', async function () {
+      this.timeout(50000)
+
+      const { name, uuid } = await uploadVideoByRemoteAccount(servers, { waitTranscoding: true })
+      await waitJobs(servers)
+
+      await checkVideoIsPublished(baseParams, name, uuid, 'presence')
+    })
+
+    it('Should send a notification when an imported video is transcoded', async function () {
+      this.timeout(50000)
+
+      const name = 'video import ' + uuidv4()
+
+      const attributes = {
+        name,
+        channelId,
+        privacy: VideoPrivacy.PUBLIC,
+        targetUrl: getYoutubeVideoUrl(),
+        waitTranscoding: true
+      }
+      const res = await importVideo(servers[1].url, servers[1].accessToken, attributes)
+      const uuid = res.body.video.uuid
+
+      await waitJobs(servers)
+      await checkVideoIsPublished(baseParams, name, uuid, 'presence')
+    })
+
+    it('Should send a notification when the scheduled update has been proceeded', async function () {
+      this.timeout(70000)
+
+      // In 2 seconds
+      let updateAt = new Date(new Date().getTime() + 2000)
+
+      const data = {
+        privacy: VideoPrivacy.PRIVATE,
+        scheduleUpdate: {
+          updateAt: updateAt.toISOString(),
+          privacy: VideoPrivacy.PUBLIC
+        }
+      }
+      const { name, uuid } = await uploadVideoByRemoteAccount(servers, data)
+
+      await wait(6000)
+      await checkVideoIsPublished(baseParams, name, uuid, 'presence')
+    })
+  })
+
+  describe('My video is imported', function () {
+    let baseParams: CheckerBaseParams
+
+    before(() => {
+      baseParams = {
+        server: servers[0],
+        emails,
+        socketNotifications: adminNotifications,
+        token: servers[0].accessToken
+      }
+    })
+
+    it('Should send a notification when the video import failed', async function () {
+      this.timeout(70000)
+
+      const name = 'video import ' + uuidv4()
+
+      const attributes = {
+        name,
+        channelId,
+        privacy: VideoPrivacy.PRIVATE,
+        targetUrl: getBadVideoUrl()
+      }
+      const res = await importVideo(servers[0].url, servers[0].accessToken, attributes)
+      const uuid = res.body.video.uuid
+
+      await waitJobs(servers)
+      await checkMyVideoImportIsFinished(baseParams, name, uuid, getBadVideoUrl(), false, 'presence')
+    })
+
+    it('Should send a notification when the video import succeeded', async function () {
+      this.timeout(70000)
+
+      const name = 'video import ' + uuidv4()
+
+      const attributes = {
+        name,
+        channelId,
+        privacy: VideoPrivacy.PRIVATE,
+        targetUrl: getYoutubeVideoUrl()
+      }
+      const res = await importVideo(servers[0].url, servers[0].accessToken, attributes)
+      const uuid = res.body.video.uuid
+
+      await waitJobs(servers)
+      await checkMyVideoImportIsFinished(baseParams, name, uuid, getYoutubeVideoUrl(), true, 'presence')
     })
   })
 
   describe('Mark as read', function () {
     it('Should mark as read some notifications', async function () {
-      const res = await getUserNotifications(servers[0].url, userAccessToken, 2, 3)
+      const res = await getUserNotifications(servers[ 0 ].url, userAccessToken, 2, 3)
       const ids = res.body.data.map(n => n.id)
 
-      await markAsReadNotifications(servers[0].url, userAccessToken, ids)
+      await markAsReadNotifications(servers[ 0 ].url, userAccessToken, ids)
     })
 
     it('Should have the notifications marked as read', async function () {
-      const res = await getUserNotifications(servers[0].url, userAccessToken, 0, 10)
+      const res = await getUserNotifications(servers[ 0 ].url, userAccessToken, 0, 10)
+
+      const notifications = res.body.data as UserNotification[]
+      expect(notifications[ 0 ].read).to.be.false
+      expect(notifications[ 1 ].read).to.be.false
+      expect(notifications[ 2 ].read).to.be.true
+      expect(notifications[ 3 ].read).to.be.true
+      expect(notifications[ 4 ].read).to.be.true
+      expect(notifications[ 5 ].read).to.be.false
+    })
+
+    it('Should only list read notifications', async function () {
+      const res = await getUserNotifications(servers[ 0 ].url, userAccessToken, 0, 10, false)
 
       const notifications = res.body.data as UserNotification[]
-      expect(notifications[0].read).to.be.false
-      expect(notifications[1].read).to.be.false
-      expect(notifications[2].read).to.be.true
-      expect(notifications[3].read).to.be.true
-      expect(notifications[4].read).to.be.true
-      expect(notifications[5].read).to.be.false
+      for (const notification of notifications) {
+        expect(notification.read).to.be.true
+      }
+    })
+
+    it('Should only list unread notifications', async function () {
+      const res = await getUserNotifications(servers[ 0 ].url, userAccessToken, 0, 10, true)
+
+      const notifications = res.body.data as UserNotification[]
+      for (const notification of notifications) {
+        expect(notification.read).to.be.false
+      }
     })
   })
 
   describe('Notification settings', function () {
-    const baseUpdateNotificationParams = {
-      newCommentOnMyVideo: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL,
-      newVideoFromSubscription: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL,
-      videoAbuseAsModerator: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL,
-      blacklistOnMyVideo: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL
-    }
     let baseParams: CheckerBaseParams
 
     before(() => {
@@ -538,7 +697,7 @@ describe('Test users notifications', function () {
     })
 
     it('Should not have notifications', async function () {
-      await updateMyNotificationSettings(servers[0].url, userAccessToken, immutableAssign(baseUpdateNotificationParams, {
+      await updateMyNotificationSettings(servers[0].url, userAccessToken, immutableAssign(allNotificationSettings, {
         newVideoFromSubscription: UserNotificationSettingValue.NONE
       }))
 
@@ -548,16 +707,14 @@ describe('Test users notifications', function () {
         expect(info.notificationSettings.newVideoFromSubscription).to.equal(UserNotificationSettingValue.NONE)
       }
 
-      const videoNameId = 42
-      const videoName = 'local video ' + videoNameId
-      const uuid = await uploadVideoByLocalAccount(servers, videoNameId)
+      const { name, uuid } = await uploadVideoByLocalAccount(servers)
 
       const check = { web: true, mail: true }
-      await checkNewVideoFromSubscription(immutableAssign(baseParams, { check }), videoName, uuid, 'absence')
+      await checkNewVideoFromSubscription(immutableAssign(baseParams, { check }), name, uuid, 'absence')
     })
 
     it('Should only have web notifications', async function () {
-      await updateMyNotificationSettings(servers[0].url, userAccessToken, immutableAssign(baseUpdateNotificationParams, {
+      await updateMyNotificationSettings(servers[0].url, userAccessToken, immutableAssign(allNotificationSettings, {
         newVideoFromSubscription: UserNotificationSettingValue.WEB_NOTIFICATION
       }))
 
@@ -567,23 +724,21 @@ describe('Test users notifications', function () {
         expect(info.notificationSettings.newVideoFromSubscription).to.equal(UserNotificationSettingValue.WEB_NOTIFICATION)
       }
 
-      const videoNameId = 52
-      const videoName = 'local video ' + videoNameId
-      const uuid = await uploadVideoByLocalAccount(servers, videoNameId)
+      const { name, uuid } = await uploadVideoByLocalAccount(servers)
 
       {
         const check = { mail: true, web: false }
-        await checkNewVideoFromSubscription(immutableAssign(baseParams, { check }), videoName, uuid, 'absence')
+        await checkNewVideoFromSubscription(immutableAssign(baseParams, { check }), name, uuid, 'absence')
       }
 
       {
         const check = { mail: false, web: true }
-        await checkNewVideoFromSubscription(immutableAssign(baseParams, { check }), videoName, uuid, 'presence')
+        await checkNewVideoFromSubscription(immutableAssign(baseParams, { check }), name, uuid, 'presence')
       }
     })
 
     it('Should only have mail notifications', async function () {
-      await updateMyNotificationSettings(servers[0].url, userAccessToken, immutableAssign(baseUpdateNotificationParams, {
+      await updateMyNotificationSettings(servers[0].url, userAccessToken, immutableAssign(allNotificationSettings, {
         newVideoFromSubscription: UserNotificationSettingValue.EMAIL
       }))
 
@@ -593,23 +748,21 @@ describe('Test users notifications', function () {
         expect(info.notificationSettings.newVideoFromSubscription).to.equal(UserNotificationSettingValue.EMAIL)
       }
 
-      const videoNameId = 62
-      const videoName = 'local video ' + videoNameId
-      const uuid = await uploadVideoByLocalAccount(servers, videoNameId)
+      const { name, uuid } = await uploadVideoByLocalAccount(servers)
 
       {
         const check = { mail: false, web: true }
-        await checkNewVideoFromSubscription(immutableAssign(baseParams, { check }), videoName, uuid, 'absence')
+        await checkNewVideoFromSubscription(immutableAssign(baseParams, { check }), name, uuid, 'absence')
       }
 
       {
         const check = { mail: true, web: false }
-        await checkNewVideoFromSubscription(immutableAssign(baseParams, { check }), videoName, uuid, 'presence')
+        await checkNewVideoFromSubscription(immutableAssign(baseParams, { check }), name, uuid, 'presence')
       }
     })
 
     it('Should have email and web notifications', async function () {
-      await updateMyNotificationSettings(servers[0].url, userAccessToken, immutableAssign(baseUpdateNotificationParams, {
+      await updateMyNotificationSettings(servers[0].url, userAccessToken, immutableAssign(allNotificationSettings, {
         newVideoFromSubscription: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL
       }))
 
@@ -619,11 +772,9 @@ describe('Test users notifications', function () {
         expect(info.notificationSettings.newVideoFromSubscription).to.equal(UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL)
       }
 
-      const videoNameId = 72
-      const videoName = 'local video ' + videoNameId
-      const uuid = await uploadVideoByLocalAccount(servers, videoNameId)
+      const { name, uuid } = await uploadVideoByLocalAccount(servers)
 
-      await checkNewVideoFromSubscription(baseParams, videoName, uuid, 'presence')
+      await checkNewVideoFromSubscription(baseParams, name, uuid, 'presence')
     })
   })
 
diff --git a/server/tests/fixtures/video_short_240p.mp4 b/server/tests/fixtures/video_short_240p.mp4
new file mode 100644 (file)
index 0000000..db07494
Binary files /dev/null and b/server/tests/fixtures/video_short_240p.mp4 differ
index 7cecd70a27739389373a9d81d8e2cc5aa8631904..55d351abfe582c10303f49577182cbfd5f54e161 100644 (file)
@@ -10,4 +10,6 @@ export interface UserNotificationSetting {
   newCommentOnMyVideo: UserNotificationSettingValue
   videoAbuseAsModerator: UserNotificationSettingValue
   blacklistOnMyVideo: UserNotificationSettingValue
+  myVideoPublished: UserNotificationSettingValue
+  myVideoImportFinished: UserNotificationSettingValue
 }
index 39beb2350e08a3a51e44b68eb2e87d3d918172d5..ee9ac275abb9b4c617bae5f10df7f2c60ad338ae 100644 (file)
@@ -3,10 +3,13 @@ export enum UserNotificationType {
   NEW_COMMENT_ON_MY_VIDEO = 2,
   NEW_VIDEO_ABUSE_FOR_MODERATORS = 3,
   BLACKLIST_ON_MY_VIDEO = 4,
-  UNBLACKLIST_ON_MY_VIDEO = 5
+  UNBLACKLIST_ON_MY_VIDEO = 5,
+  MY_VIDEO_PUBLISHED = 6,
+  MY_VIDEO_IMPORT_SUCCESS = 7,
+  MY_VIDEO_IMPORT_ERROR = 8
 }
 
-interface VideoInfo {
+export interface VideoInfo {
   id: number
   uuid: string
   name: string
@@ -24,12 +27,22 @@ export interface UserNotification {
     }
   }
 
+  videoImport?: {
+    id: number
+    video?: VideoInfo
+    torrentName?: string
+    magnetUri?: string
+    targetUrl?: string
+  }
+
   comment?: {
     id: number
+    threadId: number
     account: {
       id: number
       displayName: string
     }
+    video: VideoInfo
   }
 
   videoAbuse?: {
index dbe87559ef0067105a8c175bfb8b94b57f897254..75d52023a31904b0a4a7179b358dc3a2dbdc43a7 100644 (file)
@@ -4,6 +4,7 @@ import { makeGetRequest, makePostBodyRequest, makePutBodyRequest } from '../requ
 import { UserNotification, UserNotificationSetting, UserNotificationType } from '../../models/users'
 import { ServerInfo } from '..'
 import { expect } from 'chai'
+import { inspect } from 'util'
 
 function updateMyNotificationSettings (url: string, token: string, settings: UserNotificationSetting, statusCodeExpected = 204) {
   const path = '/api/v1/users/me/notification-settings'
@@ -17,7 +18,15 @@ function updateMyNotificationSettings (url: string, token: string, settings: Use
   })
 }
 
-function getUserNotifications (url: string, token: string, start: number, count: number, sort = '-createdAt', statusCodeExpected = 200) {
+function getUserNotifications (
+  url: string,
+  token: string,
+  start: number,
+  count: number,
+  unread?: boolean,
+  sort = '-createdAt',
+  statusCodeExpected = 200
+) {
   const path = '/api/v1/users/me/notifications'
 
   return makeGetRequest({
@@ -27,7 +36,8 @@ function getUserNotifications (url: string, token: string, start: number, count:
     query: {
       start,
       count,
-      sort
+      sort,
+      unread
     },
     statusCodeExpected
   })
@@ -46,7 +56,7 @@ function markAsReadNotifications (url: string, token: string, ids: number[], sta
 }
 
 async function getLastNotification (serverUrl: string, accessToken: string) {
-  const res = await getUserNotifications(serverUrl, accessToken, 0, 1, '-createdAt')
+  const res = await getUserNotifications(serverUrl, accessToken, 0, 1, undefined, '-createdAt')
 
   if (res.body.total === 0) return undefined
 
@@ -65,21 +75,33 @@ type CheckerType = 'presence' | 'absence'
 
 async function checkNotification (
   base: CheckerBaseParams,
-  lastNotificationChecker: (notification: UserNotification) => void,
-  socketNotificationFinder: (notification: UserNotification) => boolean,
+  notificationChecker: (notification: UserNotification, type: CheckerType) => void,
   emailNotificationFinder: (email: object) => boolean,
-  checkType: 'presence' | 'absence'
+  checkType: CheckerType
 ) {
   const check = base.check || { web: true, mail: true }
 
   if (check.web) {
     const notification = await getLastNotification(base.server.url, base.token)
-    lastNotificationChecker(notification)
 
-    const socketNotification = base.socketNotifications.find(n => socketNotificationFinder(n))
+    if (notification || checkType !== 'absence') {
+      notificationChecker(notification, checkType)
+    }
 
-    if (checkType === 'presence') expect(socketNotification, 'The socket notification is absent.').to.not.be.undefined
-    else expect(socketNotification, 'The socket notification is present.').to.be.undefined
+    const socketNotification = base.socketNotifications.find(n => {
+      try {
+        notificationChecker(n, 'presence')
+        return true
+      } catch {
+        return false
+      }
+    })
+
+    if (checkType === 'presence') {
+      expect(socketNotification, 'The socket notification is absent. ' + inspect(base.socketNotifications)).to.not.be.undefined
+    } else {
+      expect(socketNotification, 'The socket notification is present. ' + inspect(socketNotification)).to.be.undefined
+    }
   }
 
   if (check.mail) {
@@ -89,45 +111,127 @@ async function checkNotification (
                       .reverse()
                       .find(e => emailNotificationFinder(e))
 
-    if (checkType === 'presence') expect(email, 'The email is present.').to.not.be.undefined
-    else expect(email, 'The email is absent.').to.be.undefined
+    if (checkType === 'presence') {
+      expect(email, 'The email is absent. ' + inspect(base.emails)).to.not.be.undefined
+    } else {
+      expect(email, 'The email is present. ' + inspect(email)).to.be.undefined
+    }
   }
 }
 
+function checkVideo (video: any, videoName?: string, videoUUID?: string) {
+  expect(video.name).to.be.a('string')
+  expect(video.name).to.not.be.empty
+  if (videoName) expect(video.name).to.equal(videoName)
+
+  expect(video.uuid).to.be.a('string')
+  expect(video.uuid).to.not.be.empty
+  if (videoUUID) expect(video.uuid).to.equal(videoUUID)
+
+  expect(video.id).to.be.a('number')
+}
+
+function checkActor (channel: any) {
+  expect(channel.id).to.be.a('number')
+  expect(channel.displayName).to.be.a('string')
+  expect(channel.displayName).to.not.be.empty
+}
+
+function checkComment (comment: any, commentId: number, threadId: number) {
+  expect(comment.id).to.equal(commentId)
+  expect(comment.threadId).to.equal(threadId)
+}
+
 async function checkNewVideoFromSubscription (base: CheckerBaseParams, videoName: string, videoUUID: string, type: CheckerType) {
   const notificationType = UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION
 
-  function lastNotificationChecker (notification: UserNotification) {
+  function notificationChecker (notification: UserNotification, type: CheckerType) {
     if (type === 'presence') {
       expect(notification).to.not.be.undefined
       expect(notification.type).to.equal(notificationType)
-      expect(notification.video.name).to.equal(videoName)
+
+      checkVideo(notification.video, videoName, videoUUID)
+      checkActor(notification.video.channel)
     } else {
       expect(notification.video).to.satisfy(v => v === undefined || v.name !== videoName)
     }
   }
 
-  function socketFinder (notification: UserNotification) {
-    return notification.type === notificationType && notification.video.name === videoName
+  function emailFinder (email: object) {
+    return email[ 'text' ].indexOf(videoUUID) !== -1
+  }
+
+  await checkNotification(base, notificationChecker, emailFinder, type)
+}
+
+async function checkVideoIsPublished (base: CheckerBaseParams, videoName: string, videoUUID: string, type: CheckerType) {
+  const notificationType = UserNotificationType.MY_VIDEO_PUBLISHED
+
+  function notificationChecker (notification: UserNotification, type: CheckerType) {
+    if (type === 'presence') {
+      expect(notification).to.not.be.undefined
+      expect(notification.type).to.equal(notificationType)
+
+      checkVideo(notification.video, videoName, videoUUID)
+      checkActor(notification.video.channel)
+    } else {
+      expect(notification.video).to.satisfy(v => v === undefined || v.name !== videoName)
+    }
   }
 
   function emailFinder (email: object) {
-    return email[ 'text' ].indexOf(videoUUID) !== -1
+    const text: string = email[ 'text' ]
+    return text.includes(videoUUID) && text.includes('Your video')
   }
 
-  await checkNotification(base, lastNotificationChecker, socketFinder, emailFinder, type)
+  await checkNotification(base, notificationChecker, emailFinder, type)
+}
+
+async function checkMyVideoImportIsFinished (
+  base: CheckerBaseParams,
+  videoName: string,
+  videoUUID: string,
+  url: string,
+  success: boolean,
+  type: CheckerType
+) {
+  const notificationType = success ? UserNotificationType.MY_VIDEO_IMPORT_SUCCESS : UserNotificationType.MY_VIDEO_IMPORT_ERROR
+
+  function notificationChecker (notification: UserNotification, type: CheckerType) {
+    if (type === 'presence') {
+      expect(notification).to.not.be.undefined
+      expect(notification.type).to.equal(notificationType)
+
+      expect(notification.videoImport.targetUrl).to.equal(url)
+
+      if (success) checkVideo(notification.videoImport.video, videoName, videoUUID)
+    } else {
+      expect(notification.videoImport).to.satisfy(i => i === undefined || i.targetUrl !== url)
+    }
+  }
+
+  function emailFinder (email: object) {
+    const text: string = email[ 'text' ]
+    const toFind = success ? ' finished' : ' error'
+
+    return text.includes(url) && text.includes(toFind)
+  }
+
+  await checkNotification(base, notificationChecker, emailFinder, type)
 }
 
 let lastEmailCount = 0
 async function checkNewCommentOnMyVideo (base: CheckerBaseParams, uuid: string, commentId: number, threadId: number, type: CheckerType) {
   const notificationType = UserNotificationType.NEW_COMMENT_ON_MY_VIDEO
 
-  function lastNotificationChecker (notification: UserNotification) {
+  function notificationChecker (notification: UserNotification, type: CheckerType) {
     if (type === 'presence') {
       expect(notification).to.not.be.undefined
       expect(notification.type).to.equal(notificationType)
-      expect(notification.comment.id).to.equal(commentId)
-      expect(notification.comment.account.displayName).to.equal('root')
+
+      checkComment(notification.comment, commentId, threadId)
+      checkActor(notification.comment.account)
+      checkVideo(notification.comment.video, undefined, uuid)
     } else {
       expect(notification).to.satisfy((n: UserNotification) => {
         return n === undefined || n.comment === undefined || n.comment.id !== commentId
@@ -135,18 +239,12 @@ async function checkNewCommentOnMyVideo (base: CheckerBaseParams, uuid: string,
     }
   }
 
-  function socketFinder (notification: UserNotification) {
-    return notification.type === notificationType &&
-      notification.comment.id === commentId &&
-      notification.comment.account.displayName === 'root'
-  }
-
   const commentUrl = `http://localhost:9001/videos/watch/${uuid};threadId=${threadId}`
   function emailFinder (email: object) {
     return email[ 'text' ].indexOf(commentUrl) !== -1
   }
 
-  await checkNotification(base, lastNotificationChecker, socketFinder, emailFinder, type)
+  await checkNotification(base, notificationChecker, emailFinder, type)
 
   if (type === 'presence') {
     // We cannot detect email duplicates, so check we received another email
@@ -158,12 +256,13 @@ async function checkNewCommentOnMyVideo (base: CheckerBaseParams, uuid: string,
 async function checkNewVideoAbuseForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) {
   const notificationType = UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS
 
-  function lastNotificationChecker (notification: UserNotification) {
+  function notificationChecker (notification: UserNotification, type: CheckerType) {
     if (type === 'presence') {
       expect(notification).to.not.be.undefined
       expect(notification.type).to.equal(notificationType)
-      expect(notification.videoAbuse.video.uuid).to.equal(videoUUID)
-      expect(notification.videoAbuse.video.name).to.equal(videoName)
+
+      expect(notification.videoAbuse.id).to.be.a('number')
+      checkVideo(notification.videoAbuse.video, videoName, videoUUID)
     } else {
       expect(notification).to.satisfy((n: UserNotification) => {
         return n === undefined || n.videoAbuse === undefined || n.videoAbuse.video.uuid !== videoUUID
@@ -171,16 +270,12 @@ async function checkNewVideoAbuseForModerators (base: CheckerBaseParams, videoUU
     }
   }
 
-  function socketFinder (notification: UserNotification) {
-    return notification.type === notificationType && notification.videoAbuse.video.uuid === videoUUID
-  }
-
   function emailFinder (email: object) {
     const text = email[ 'text' ]
     return text.indexOf(videoUUID) !== -1 && text.indexOf('abuse') !== -1
   }
 
-  await checkNotification(base, lastNotificationChecker, socketFinder, emailFinder, type)
+  await checkNotification(base, notificationChecker, emailFinder, type)
 }
 
 async function checkNewBlacklistOnMyVideo (
@@ -193,18 +288,13 @@ async function checkNewBlacklistOnMyVideo (
     ? UserNotificationType.BLACKLIST_ON_MY_VIDEO
     : UserNotificationType.UNBLACKLIST_ON_MY_VIDEO
 
-  function lastNotificationChecker (notification: UserNotification) {
+  function notificationChecker (notification: UserNotification) {
     expect(notification).to.not.be.undefined
     expect(notification.type).to.equal(notificationType)
 
     const video = blacklistType === 'blacklist' ? notification.videoBlacklist.video : notification.video
 
-    expect(video.uuid).to.equal(videoUUID)
-    expect(video.name).to.equal(videoName)
-  }
-
-  function socketFinder (notification: UserNotification) {
-    return notification.type === notificationType && (notification.video || notification.videoBlacklist.video).uuid === videoUUID
+    checkVideo(video, videoName, videoUUID)
   }
 
   function emailFinder (email: object) {
@@ -212,7 +302,7 @@ async function checkNewBlacklistOnMyVideo (
     return text.indexOf(videoUUID) !== -1 && text.indexOf(' ' + blacklistType) !== -1
   }
 
-  await checkNotification(base, lastNotificationChecker, socketFinder, emailFinder, 'presence')
+  await checkNotification(base, notificationChecker, emailFinder, 'presence')
 }
 
 // ---------------------------------------------------------------------------
@@ -221,6 +311,8 @@ export {
   CheckerBaseParams,
   CheckerType,
   checkNotification,
+  checkMyVideoImportIsFinished,
+  checkVideoIsPublished,
   checkNewVideoFromSubscription,
   checkNewCommentOnMyVideo,
   checkNewBlacklistOnMyVideo,
index 3fa49b4320fad5da696afd9d0544a1bc0541790d..ec77cdcda6503c770b2a3c6120f197368aa3b3e3 100644 (file)
@@ -11,6 +11,10 @@ function getMagnetURI () {
   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'
 }
 
+function getBadVideoUrl () {
+  return 'https://download.cpy.re/peertube/bad_video.mp4'
+}
+
 function importVideo (url: string, token: string, attributes: VideoImportCreate) {
   const path = '/api/v1/videos/imports'
 
@@ -45,6 +49,7 @@ function getMyVideoImports (url: string, token: string, sort?: string) {
 // ---------------------------------------------------------------------------
 
 export {
+  getBadVideoUrl,
   getYoutubeVideoUrl,
   importVideo,
   getMagnetURI,