1
0
Fork 0

Add import finished and video published notifs

This commit is contained in:
Chocobozzz 2019-01-02 16:37:43 +01:00 committed by Chocobozzz
parent 6e7e63b83f
commit dc13348070
23 changed files with 815 additions and 251 deletions

View file

@ -14,10 +14,11 @@ import { getFormattedObjects } from '../../../helpers/utils'
import { UserNotificationModel } from '../../../models/account/user-notification' import { UserNotificationModel } from '../../../models/account/user-notification'
import { meRouter } from './me' import { meRouter } from './me'
import { import {
listUserNotificationsValidator,
markAsReadUserNotificationsValidator, markAsReadUserNotificationsValidator,
updateNotificationSettingsValidator updateNotificationSettingsValidator
} from '../../../middlewares/validators/user-notifications' } 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' import { UserNotificationSettingModel } from '../../../models/account/user-notification-setting'
const myNotificationsRouter = express.Router() const myNotificationsRouter = express.Router()
@ -34,6 +35,7 @@ myNotificationsRouter.get('/me/notifications',
userNotificationsSortValidator, userNotificationsSortValidator,
setDefaultSort, setDefaultSort,
setDefaultPagination, setDefaultPagination,
listUserNotificationsValidator,
asyncMiddleware(listUserNotifications) asyncMiddleware(listUserNotifications)
) )
@ -61,7 +63,11 @@ async function updateNotificationSettings (req: express.Request, res: express.Re
await UserNotificationSettingModel.update({ await UserNotificationSettingModel.update({
newVideoFromSubscription: body.newVideoFromSubscription, newVideoFromSubscription: body.newVideoFromSubscription,
newCommentOnMyVideo: body.newCommentOnMyVideo newCommentOnMyVideo: body.newCommentOnMyVideo,
videoAbuseAsModerator: body.videoAbuseAsModerator,
blacklistOnMyVideo: body.blacklistOnMyVideo,
myVideoPublished: body.myVideoPublished,
myVideoImportFinished: body.myVideoImportFinished
}, query) }, query)
return res.status(204).end() 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) { async function listUserNotifications (req: express.Request, res: express.Response) {
const user: UserModel = res.locals.oauth.token.User 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)) return res.json(getFormattedObjects(resultList.data, resultList.total))
} }

View file

@ -13,6 +13,8 @@ CREATE TABLE IF NOT EXISTS "userNotificationSetting" ("id" SERIAL,
"newCommentOnMyVideo" INTEGER NOT NULL DEFAULT NULL, "newCommentOnMyVideo" INTEGER NOT NULL DEFAULT NULL,
"videoAbuseAsModerator" INTEGER NOT NULL DEFAULT NULL, "videoAbuseAsModerator" INTEGER NOT NULL DEFAULT NULL,
"blacklistOnMyVideo" 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, "userId" INTEGER REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
"updatedAt" 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" ' + const query = 'INSERT INTO "userNotificationSetting" ' +
'("newVideoFromSubscription", "newCommentOnMyVideo", "videoAbuseAsModerator", "blacklistOnMyVideo", ' + '("newVideoFromSubscription", "newCommentOnMyVideo", "videoAbuseAsModerator", "blacklistOnMyVideo", ' +
'"userId", "createdAt", "updatedAt") ' + '"myVideoPublished", "myVideoImportFinished", "userId", "createdAt", "updatedAt") ' +
'(SELECT 2, 2, 4, 4, id, NOW(), NOW() FROM "user")' '(SELECT 2, 2, 4, 4, 2, 2, id, NOW(), NOW() FROM "user")'
await utils.sequelize.query(query) await utils.sequelize.query(query)
} }

View file

@ -10,6 +10,7 @@ import { readFileSync } from 'fs-extra'
import { VideoCommentModel } from '../models/video/video-comment' import { VideoCommentModel } from '../models/video/video-comment'
import { VideoAbuseModel } from '../models/video/video-abuse' import { VideoAbuseModel } from '../models/video/video-abuse'
import { VideoBlacklistModel } from '../models/video/video-blacklist' import { VideoBlacklistModel } from '../models/video/video-blacklist'
import { VideoImportModel } from '../models/video/video-import'
class Emailer { class Emailer {
@ -102,6 +103,66 @@ class Emailer {
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) 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) { addNewCommentOnMyVideoNotification (to: string[], comment: VideoCommentModel) {
const accountName = comment.Account.getDisplayName() const accountName = comment.Account.getDisplayName()
const video = comment.Video const video = comment.Video

View file

@ -68,17 +68,17 @@ async function processVideoFile (job: Bull.Job) {
async function onVideoFileTranscoderOrImportSuccess (video: VideoModel) { async function onVideoFileTranscoderOrImportSuccess (video: VideoModel) {
if (video === undefined) return undefined 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 // Maybe the video changed in database, refresh it
let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t) let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t)
// Video does not exist anymore // Video does not exist anymore
if (!videoDatabase) return undefined if (!videoDatabase) return undefined
let isNewVideo = false let videoPublished = false
// We transcoded the video file in another format, now we can publish it // We transcoded the video file in another format, now we can publish it
if (videoDatabase.state !== VideoState.PUBLISHED) { if (videoDatabase.state !== VideoState.PUBLISHED) {
isNewVideo = true videoPublished = true
videoDatabase.state = VideoState.PUBLISHED videoDatabase.state = VideoState.PUBLISHED
videoDatabase.publishedAt = new Date() 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 // 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) { async function onVideoFileOptimizerSuccess (videoArg: VideoModel, isNewVideo: boolean) {
@ -100,7 +103,7 @@ async function onVideoFileOptimizerSuccess (videoArg: VideoModel, isNewVideo: bo
// Outside the transaction (IO on disk) // Outside the transaction (IO on disk)
const { videoFileResolution } = await videoArg.getOriginalFileResolution() 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 // Maybe the video changed in database, refresh it
let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoArg.uuid, t) let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoArg.uuid, t)
// Video does not exist anymore // Video does not exist anymore
@ -113,6 +116,8 @@ async function onVideoFileOptimizerSuccess (videoArg: VideoModel, isNewVideo: bo
{ resolutions: resolutionsEnabled } { resolutions: resolutionsEnabled }
) )
let videoPublished = false
if (resolutionsEnabled.length !== 0) { if (resolutionsEnabled.length !== 0) {
const tasks: Bluebird<Bull.Job<any>>[] = [] 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 }) logger.info('Transcoding jobs created for uuid %s.', videoDatabase.uuid, { resolutionsEnabled })
} else { } else {
videoPublished = true
// No transcoding to do, it's now published // No transcoding to do, it's now published
videoDatabase.state = VideoState.PUBLISHED videoDatabase.state = VideoState.PUBLISHED
videoDatabase = await videoDatabase.save({ transaction: t }) videoDatabase = await videoDatabase.save({ transaction: t })
@ -139,10 +146,11 @@ async function onVideoFileOptimizerSuccess (videoArg: VideoModel, isNewVideo: bo
await federateVideoIfNeeded(videoDatabase, isNewVideo, t) await federateVideoIfNeeded(videoDatabase, isNewVideo, t)
return videoDatabase return { videoDatabase, videoPublished }
}) })
if (isNewVideo) Notifier.Instance.notifyOnNewVideo(videoDatabase) if (isNewVideo) Notifier.Instance.notifyOnNewVideo(videoDatabase)
if (videoPublished) Notifier.Instance.notifyOnPendingVideoPublished(videoDatabase)
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View file

@ -197,6 +197,7 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide
}) })
Notifier.Instance.notifyOnNewVideo(videoImportUpdated.Video) Notifier.Instance.notifyOnNewVideo(videoImportUpdated.Video)
Notifier.Instance.notifyOnFinishedVideoImport(videoImportUpdated, true)
// Create transcoding jobs? // Create transcoding jobs?
if (videoImportUpdated.Video.state === VideoState.TO_TRANSCODE) { if (videoImportUpdated.Video.state === VideoState.TO_TRANSCODE) {
@ -220,6 +221,8 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide
videoImport.state = VideoImportState.FAILED videoImport.state = VideoImportState.FAILED
await videoImport.save() await videoImport.save()
Notifier.Instance.notifyOnFinishedVideoImport(videoImport, false)
throw err throw err
} }
} }

View file

@ -11,6 +11,8 @@ import { VideoPrivacy, VideoState } from '../../shared/models/videos'
import { VideoAbuseModel } from '../models/video/video-abuse' import { VideoAbuseModel } from '../models/video/video-abuse'
import { VideoBlacklistModel } from '../models/video/video-blacklist' import { VideoBlacklistModel } from '../models/video/video-blacklist'
import * as Bluebird from 'bluebird' import * as Bluebird from 'bluebird'
import { VideoImportModel } from '../models/video/video-import'
import { AccountBlocklistModel } from '../models/account/account-blocklist'
class Notifier { class Notifier {
@ -26,6 +28,14 @@ class Notifier {
.catch(err => logger.error('Cannot notify subscribers of new video %s.', video.url, { err })) .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 { notifyOnNewComment (comment: VideoCommentModel): void {
this.notifyVideoOwnerOfNewComment(comment) this.notifyVideoOwnerOfNewComment(comment)
.catch(err => logger.error('Cannot notify of new comment %s.', comment.url, { err })) .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 })) .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) { private async notifySubscribersOfNewVideo (video: VideoModel) {
// List all followers that are users // List all followers that are users
const users = await UserModel.listUserSubscribersOf(video.VideoChannel.actorId) const users = await UserModel.listUserSubscribersOf(video.VideoChannel.actorId)
@ -80,6 +95,9 @@ class Notifier {
// Not our user or user comments its own video // Not our user or user comments its own video
if (!user || comment.Account.userId === user.id) return 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) logger.info('Notifying user %s of new comment %s.', user.username, comment.url)
function settingGetter (user: UserModel) { function settingGetter (user: UserModel) {
@ -188,6 +206,64 @@ class Notifier {
return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) 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: { private async notify (options: {
users: UserModel[], users: UserModel[],
notificationCreator: (user: UserModel) => Promise<UserNotificationModel>, notificationCreator: (user: UserModel) => Promise<UserNotificationModel>,

View file

@ -6,6 +6,7 @@ import { federateVideoIfNeeded } from '../activitypub'
import { SCHEDULER_INTERVALS_MS, sequelizeTypescript } from '../../initializers' import { SCHEDULER_INTERVALS_MS, sequelizeTypescript } from '../../initializers'
import { VideoPrivacy } from '../../../shared/models/videos' import { VideoPrivacy } from '../../../shared/models/videos'
import { Notifier } from '../notifier' import { Notifier } from '../notifier'
import { VideoModel } from '../../models/video/video'
export class UpdateVideosScheduler extends AbstractScheduler { export class UpdateVideosScheduler extends AbstractScheduler {
@ -24,8 +25,9 @@ export class UpdateVideosScheduler extends AbstractScheduler {
private async updateVideos () { private async updateVideos () {
if (!await ScheduleVideoUpdateModel.areVideosToUpdate()) return undefined 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 schedules = await ScheduleVideoUpdateModel.listVideosToUpdate(t)
const publishedVideos: VideoModel[] = []
for (const schedule of schedules) { for (const schedule of schedules) {
const video = schedule.Video const video = schedule.Video
@ -42,13 +44,21 @@ export class UpdateVideosScheduler extends AbstractScheduler {
await federateVideoIfNeeded(video, isNewVideo, t) await federateVideoIfNeeded(video, isNewVideo, t)
if (oldPrivacy === VideoPrivacy.UNLISTED || oldPrivacy === VideoPrivacy.PRIVATE) { if (oldPrivacy === VideoPrivacy.UNLISTED || oldPrivacy === VideoPrivacy.PRIVATE) {
Notifier.Instance.notifyOnNewVideo(video) video.ScheduleVideoUpdate = schedule
publishedVideos.push(video)
} }
} }
await schedule.destroy({ transaction: t }) await schedule.destroy({ transaction: t })
} }
return publishedVideos
}) })
for (const v of publishedVideos) {
Notifier.Instance.notifyOnNewVideo(v)
Notifier.Instance.notifyOnPendingVideoPublished(v)
}
} }
static get Instance () { static get Instance () {

View file

@ -100,6 +100,8 @@ function createDefaultUserNotificationSettings (user: UserModel, t: Sequelize.Tr
userId: user.id, userId: user.id,
newVideoFromSubscription: UserNotificationSettingValue.WEB_NOTIFICATION, newVideoFromSubscription: UserNotificationSettingValue.WEB_NOTIFICATION,
newCommentOnMyVideo: UserNotificationSettingValue.WEB_NOTIFICATION, newCommentOnMyVideo: UserNotificationSettingValue.WEB_NOTIFICATION,
myVideoImportFinished: UserNotificationSettingValue.WEB_NOTIFICATION,
myVideoPublished: UserNotificationSettingValue.WEB_NOTIFICATION,
videoAbuseAsModerator: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL, videoAbuseAsModerator: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL,
blacklistOnMyVideo: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL blacklistOnMyVideo: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL
}, { transaction: t }) }, { transaction: t })

View file

@ -1,11 +1,26 @@
import * as express from 'express' import * as express from 'express'
import 'express-validator' import 'express-validator'
import { body } from 'express-validator/check' import { body, query } from 'express-validator/check'
import { logger } from '../../helpers/logger' import { logger } from '../../helpers/logger'
import { areValidationErrors } from './utils' import { areValidationErrors } from './utils'
import { isUserNotificationSettingValid } from '../../helpers/custom-validators/user-notifications' import { isUserNotificationSettingValid } from '../../helpers/custom-validators/user-notifications'
import { isIntArray } from '../../helpers/custom-validators/misc' 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 = [ const updateNotificationSettingsValidator = [
body('newVideoFromSubscription') body('newVideoFromSubscription')
.custom(isUserNotificationSettingValid).withMessage('Should have a valid new video from subscription notification setting'), .custom(isUserNotificationSettingValid).withMessage('Should have a valid new video from subscription notification setting'),
@ -41,6 +56,7 @@ const markAsReadUserNotificationsValidator = [
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
export { export {
listUserNotificationsValidator,
updateNotificationSettingsValidator, updateNotificationSettingsValidator,
markAsReadUserNotificationsValidator markAsReadUserNotificationsValidator
} }

View file

@ -72,6 +72,21 @@ export class AccountBlocklistModel extends Model<AccountBlocklistModel> {
}) })
BlockedAccount: AccountModel 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) { static loadByAccountAndTarget (accountId: number, targetAccountId: number) {
const query = { const query = {
where: { where: {

View file

@ -65,6 +65,24 @@ export class UserNotificationSettingModel extends Model<UserNotificationSettingM
@Column @Column
blacklistOnMyVideo: UserNotificationSettingValue 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) @ForeignKey(() => UserModel)
@Column @Column
userId: number userId: number
@ -94,7 +112,9 @@ export class UserNotificationSettingModel extends Model<UserNotificationSettingM
newCommentOnMyVideo: this.newCommentOnMyVideo, newCommentOnMyVideo: this.newCommentOnMyVideo,
newVideoFromSubscription: this.newVideoFromSubscription, newVideoFromSubscription: this.newVideoFromSubscription,
videoAbuseAsModerator: this.videoAbuseAsModerator, videoAbuseAsModerator: this.videoAbuseAsModerator,
blacklistOnMyVideo: this.blacklistOnMyVideo blacklistOnMyVideo: this.blacklistOnMyVideo,
myVideoPublished: this.myVideoPublished,
myVideoImportFinished: this.myVideoImportFinished
} }
} }
} }

View 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 { UserNotification, UserNotificationType } from '../../../shared'
import { getSort, throwIfNotValid } from '../utils' import { getSort, throwIfNotValid } from '../utils'
import { isBooleanValid } from '../../helpers/custom-validators/misc' import { isBooleanValid } from '../../helpers/custom-validators/misc'
@ -11,66 +24,68 @@ import { VideoChannelModel } from '../video/video-channel'
import { AccountModel } from './account' import { AccountModel } from './account'
import { VideoAbuseModel } from '../video/video-abuse' import { VideoAbuseModel } from '../video/video-abuse'
import { VideoBlacklistModel } from '../video/video-blacklist' import { VideoBlacklistModel } from '../video/video-blacklist'
import { VideoImportModel } from '../video/video-import'
enum ScopeNames { enum ScopeNames {
WITH_ALL = 'WITH_ALL' 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({ @Scopes({
[ScopeNames.WITH_ALL]: { [ScopeNames.WITH_ALL]: {
include: [ include: [
Object.assign(buildVideoInclude(false), {
include: [ buildChannelInclude() ]
}),
{ {
attributes: [ 'id', 'uuid', 'name' ], attributes: [ 'id', 'originCommentId' ],
model: () => VideoModel.unscoped(),
required: false,
include: [
{
required: true,
attributes: [ 'id', 'name' ],
model: () => VideoChannelModel.unscoped()
}
]
},
{
attributes: [ 'id' ],
model: () => VideoCommentModel.unscoped(), model: () => VideoCommentModel.unscoped(),
required: false, required: false,
include: [ include: [
{ buildAccountInclude(),
required: true, buildVideoInclude(true)
attributes: [ 'id', 'name' ],
model: () => AccountModel.unscoped()
},
{
required: true,
attributes: [ 'id', 'uuid', 'name' ],
model: () => VideoModel.unscoped()
}
] ]
}, },
{ {
attributes: [ 'id' ], attributes: [ 'id' ],
model: () => VideoAbuseModel.unscoped(), model: () => VideoAbuseModel.unscoped(),
required: false, required: false,
include: [ include: [ buildVideoInclude(true) ]
{
required: true,
attributes: [ 'id', 'uuid', 'name' ],
model: () => VideoModel.unscoped()
}
]
}, },
{ {
attributes: [ 'id' ], attributes: [ 'id' ],
model: () => VideoBlacklistModel.unscoped(), model: () => VideoBlacklistModel.unscoped(),
required: false, required: false,
include: [ include: [ buildVideoInclude(true) ]
{ },
required: true, {
attributes: [ 'id', 'uuid', 'name' ], attributes: [ 'id', 'magnetUri', 'targetUrl', 'torrentName' ],
model: () => VideoModel.unscoped() model: () => VideoImportModel.unscoped(),
} required: false,
] include: [ buildVideoInclude(false) ]
} }
] ]
} }
@ -166,8 +181,20 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
}) })
VideoBlacklist: VideoBlacklistModel VideoBlacklist: VideoBlacklistModel
static listForApi (userId: number, start: number, count: number, sort: string) { @ForeignKey(() => VideoImportModel)
const query = { @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, offset: start,
limit: count, limit: count,
order: getSort(sort), 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) return UserNotificationModel.scope(ScopeNames.WITH_ALL)
.findAndCountAll(query) .findAndCountAll(query)
.then(({ rows, count }) => { .then(({ rows, count }) => {
@ -200,45 +229,39 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
} }
toFormattedJSON (): UserNotification { toFormattedJSON (): UserNotification {
const video = this.Video ? { const video = this.Video ? Object.assign(this.formatVideo(this.Video), {
id: this.Video.id,
uuid: this.Video.uuid,
name: this.Video.name,
channel: { channel: {
id: this.Video.VideoChannel.id, id: this.Video.VideoChannel.id,
displayName: this.Video.VideoChannel.getDisplayName() 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 } : undefined
const comment = this.Comment ? { const comment = this.Comment ? {
id: this.Comment.id, id: this.Comment.id,
threadId: this.Comment.getThreadId(),
account: { account: {
id: this.Comment.Account.id, id: this.Comment.Account.id,
displayName: this.Comment.Account.getDisplayName() displayName: this.Comment.Account.getDisplayName()
}, },
video: { video: this.formatVideo(this.Comment.Video)
id: this.Comment.Video.id,
uuid: this.Comment.Video.uuid,
name: this.Comment.Video.name
}
} : undefined } : undefined
const videoAbuse = this.VideoAbuse ? { const videoAbuse = this.VideoAbuse ? {
id: this.VideoAbuse.id, id: this.VideoAbuse.id,
video: { video: this.formatVideo(this.VideoAbuse.Video)
id: this.VideoAbuse.Video.id,
uuid: this.VideoAbuse.Video.uuid,
name: this.VideoAbuse.Video.name
}
} : undefined } : undefined
const videoBlacklist = this.VideoBlacklist ? { const videoBlacklist = this.VideoBlacklist ? {
id: this.VideoBlacklist.id, id: this.VideoBlacklist.id,
video: { video: this.formatVideo(this.VideoBlacklist.Video)
id: this.VideoBlacklist.Video.id,
uuid: this.VideoBlacklist.Video.uuid,
name: this.VideoBlacklist.Video.name
}
} : undefined } : undefined
return { return {
@ -246,6 +269,7 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
type: this.type, type: this.type,
read: this.read, read: this.read,
video, video,
videoImport,
comment, comment,
videoAbuse, videoAbuse,
videoBlacklist, videoBlacklist,
@ -253,4 +277,12 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
updatedAt: this.updatedAt.toISOString() updatedAt: this.updatedAt.toISOString()
} }
} }
private formatVideo (video: VideoModel) {
return {
id: video.id,
uuid: video.uuid,
name: video.name
}
}
} }

View file

@ -48,6 +48,7 @@ import { UserNotificationSettingModel } from './user-notification-setting'
import { VideoModel } from '../video/video' import { VideoModel } from '../video/video'
import { ActorModel } from '../activitypub/actor' import { ActorModel } from '../activitypub/actor'
import { ActorFollowModel } from '../activitypub/actor-follow' import { ActorFollowModel } from '../activitypub/actor-follow'
import { VideoImportModel } from '../video/video-import'
enum ScopeNames { enum ScopeNames {
WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL' WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL'
@ -186,6 +187,12 @@ export class UserModel extends Model<UserModel> {
}) })
NotificationSetting: UserNotificationSettingModel NotificationSetting: UserNotificationSettingModel
@HasMany(() => VideoImportModel, {
foreignKey: 'userId',
onDelete: 'cascade'
})
VideoImports: VideoImportModel[]
@HasMany(() => OAuthTokenModel, { @HasMany(() => OAuthTokenModel, {
foreignKey: 'userId', foreignKey: 'userId',
onDelete: 'cascade' onDelete: 'cascade'
@ -400,6 +407,23 @@ export class UserModel extends Model<UserModel> {
return UserModel.findOne(query) 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) { static getOriginalVideoFileTotalFromUser (user: UserModel) {
// Don't use sequelize because we need to use a sub query // Don't use sequelize because we need to use a sub query
const query = UserModel.generateUserQuotaBaseSQL() const query = UserModel.generateUserQuotaBaseSQL()

View file

@ -1,4 +1,3 @@
import { values } from 'lodash'
import { import {
AllowNull, AllowNull,
BelongsTo, BelongsTo,
@ -20,7 +19,6 @@ import {
isVideoFileSizeValid, isVideoFileSizeValid,
isVideoFPSResolutionValid isVideoFPSResolutionValid
} from '../../helpers/custom-validators/videos' } from '../../helpers/custom-validators/videos'
import { CONSTRAINTS_FIELDS } from '../../initializers'
import { throwIfNotValid } from '../utils' import { throwIfNotValid } from '../utils'
import { VideoModel } from './video' import { VideoModel } from './video'
import * as Sequelize from 'sequelize' import * as Sequelize from 'sequelize'

View file

@ -144,6 +144,10 @@ export class VideoImportModel extends Model<VideoImportModel> {
}) })
} }
getTargetIdentifier () {
return this.targetUrl || this.magnetUri || this.torrentName
}
toFormattedJSON (): VideoImport { toFormattedJSON (): VideoImport {
const videoFormatOptions = { const videoFormatOptions = {
completeDescription: true, completeDescription: true,

View file

@ -94,6 +94,7 @@ import {
import * as validator from 'validator' import * as validator from 'validator'
import { UserVideoHistoryModel } from '../account/user-video-history' import { UserVideoHistoryModel } from '../account/user-video-history'
import { UserModel } from '../account/user' 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 // FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation
const indexes: Sequelize.DefineIndexesOptions[] = [ const indexes: Sequelize.DefineIndexesOptions[] = [
@ -785,6 +786,15 @@ export class VideoModel extends Model<VideoModel> {
}) })
VideoBlacklist: VideoBlacklistModel VideoBlacklist: VideoBlacklistModel
@HasOne(() => VideoImportModel, {
foreignKey: {
name: 'videoId',
allowNull: true
},
onDelete: 'set null'
})
VideoImport: VideoImportModel
@HasMany(() => VideoCaptionModel, { @HasMany(() => VideoCaptionModel, {
foreignKey: { foreignKey: {
name: 'videoId', name: 'videoId',

View file

@ -52,6 +52,18 @@ describe('Test user notifications API validators', function () {
await checkBadSortPagination(server.url, path, server.accessToken) 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 () { it('Should fail with a non authenticated user', async function () {
await makeGetRequest({ await makeGetRequest({
url: server.url, url: server.url,
@ -125,7 +137,9 @@ describe('Test user notifications API validators', function () {
newVideoFromSubscription: UserNotificationSettingValue.WEB_NOTIFICATION, newVideoFromSubscription: UserNotificationSettingValue.WEB_NOTIFICATION,
newCommentOnMyVideo: UserNotificationSettingValue.WEB_NOTIFICATION, newCommentOnMyVideo: UserNotificationSettingValue.WEB_NOTIFICATION,
videoAbuseAsModerator: 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 () { it('Should fail with missing fields', async function () {

View file

@ -29,33 +29,46 @@ import {
getLastNotification, getLastNotification,
getUserNotifications, getUserNotifications,
markAsReadNotifications, markAsReadNotifications,
updateMyNotificationSettings updateMyNotificationSettings,
checkVideoIsPublished, checkMyVideoImportIsFinished
} from '../../../../shared/utils/users/user-notifications' } 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 { MockSmtpServer } from '../../../../shared/utils/miscs/email'
import { addUserSubscription } from '../../../../shared/utils/users/user-subscriptions' import { addUserSubscription } from '../../../../shared/utils/users/user-subscriptions'
import { VideoPrivacy } from '../../../../shared/models/videos' 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 { 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 const expect = chai.expect
async function uploadVideoByRemoteAccount (servers: ServerInfo[], videoNameId: number, additionalParams: any = {}) { async function uploadVideoByRemoteAccount (servers: ServerInfo[], additionalParams: any = {}) {
const data = Object.assign({ name: 'remote video ' + videoNameId }, additionalParams) const name = 'remote video ' + uuidv4()
const data = Object.assign({ name }, additionalParams)
const res = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, data) const res = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, data)
await waitJobs(servers) await waitJobs(servers)
return res.body.video.uuid return { uuid: res.body.video.uuid, name }
} }
async function uploadVideoByLocalAccount (servers: ServerInfo[], videoNameId: number, additionalParams: any = {}) { async function uploadVideoByLocalAccount (servers: ServerInfo[], additionalParams: any = {}) {
const data = Object.assign({ name: 'local video ' + videoNameId }, additionalParams) const name = 'local video ' + uuidv4()
const data = Object.assign({ name }, additionalParams)
const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, data) const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, data)
await waitJobs(servers) await waitJobs(servers)
return res.body.video.uuid return { uuid: res.body.video.uuid, name }
} }
describe('Test users notifications', function () { describe('Test users notifications', function () {
@ -63,7 +76,18 @@ describe('Test users notifications', function () {
let userAccessToken: string let userAccessToken: string
let userNotifications: UserNotification[] = [] let userNotifications: UserNotification[] = []
let adminNotifications: UserNotification[] = [] let adminNotifications: UserNotification[] = []
let adminNotificationsServer2: UserNotification[] = []
const emails: object[] = [] 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 () { before(async function () {
this.timeout(120000) 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) await createUser(servers[0].url, servers[0].accessToken, user.username, user.password, 10 * 1000 * 1000)
userAccessToken = await userLogin(servers[0], user) userAccessToken = await userLogin(servers[0], user)
await updateMyNotificationSettings(servers[0].url, userAccessToken, { await updateMyNotificationSettings(servers[0].url, userAccessToken, allNotificationSettings)
newCommentOnMyVideo: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL, await updateMyNotificationSettings(servers[0].url, servers[0].accessToken, allNotificationSettings)
newVideoFromSubscription: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL, await updateMyNotificationSettings(servers[1].url, servers[1].accessToken, allNotificationSettings)
blacklistOnMyVideo: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL,
videoAbuseAsModerator: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL
})
{ {
const socket = getUserNotificationSocket(servers[ 0 ].url, userAccessToken) 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) const socket = getUserNotificationSocket(servers[ 0 ].url, servers[0].accessToken)
socket.on('new-notification', n => adminNotifications.push(n)) 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 () { 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 () { 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) const notification = await getLastNotification(servers[ 0 ].url, userAccessToken)
expect(notification).to.be.undefined 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 () { 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') await addUserSubscription(servers[0].url, userAccessToken, 'root_channel@localhost:9001')
const videoNameId = 10 const { name, uuid } = await uploadVideoByLocalAccount(servers)
const videoName = 'local video ' + videoNameId await checkNewVideoFromSubscription(baseParams, name, uuid, 'presence')
const uuid = await uploadVideoByLocalAccount(servers, videoNameId)
await checkNewVideoFromSubscription(baseParams, videoName, uuid, 'presence')
}) })
it('Should send a new video notification from a remote account', async function () { 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') await addUserSubscription(servers[0].url, userAccessToken, 'root_channel@localhost:9002')
const videoNameId = 20 const { name, uuid } = await uploadVideoByRemoteAccount(servers)
const videoName = 'remote video ' + videoNameId await checkNewVideoFromSubscription(baseParams, name, uuid, 'presence')
const uuid = await uploadVideoByRemoteAccount(servers, videoNameId)
await waitJobs(servers)
await checkNewVideoFromSubscription(baseParams, videoName, uuid, 'presence')
}) })
it('Should send a new video notification on a scheduled publication', async function () { it('Should send a new video notification on a scheduled publication', async function () {
this.timeout(20000) this.timeout(20000)
const videoNameId = 30
const videoName = 'local video ' + videoNameId
// In 2 seconds // In 2 seconds
let updateAt = new Date(new Date().getTime() + 2000) let updateAt = new Date(new Date().getTime() + 2000)
@ -173,18 +192,15 @@ describe('Test users notifications', function () {
privacy: VideoPrivacy.PUBLIC privacy: VideoPrivacy.PUBLIC
} }
} }
const uuid = await uploadVideoByLocalAccount(servers, videoNameId, data) const { name, uuid } = await uploadVideoByLocalAccount(servers, data)
await wait(6000) 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 () { it('Should send a new video notification on a remote scheduled publication', async function () {
this.timeout(20000) this.timeout(20000)
const videoNameId = 40
const videoName = 'remote video ' + videoNameId
// In 2 seconds // In 2 seconds
let updateAt = new Date(new Date().getTime() + 2000) let updateAt = new Date(new Date().getTime() + 2000)
@ -195,19 +211,16 @@ describe('Test users notifications', function () {
privacy: VideoPrivacy.PUBLIC privacy: VideoPrivacy.PUBLIC
} }
} }
const uuid = await uploadVideoByRemoteAccount(servers, videoNameId, data) const { name, uuid } = await uploadVideoByRemoteAccount(servers, data)
await waitJobs(servers) await waitJobs(servers)
await wait(6000) 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 () { it('Should not send a notification before the video is published', async function () {
this.timeout(20000) this.timeout(20000)
const videoNameId = 50
const videoName = 'local video ' + videoNameId
let updateAt = new Date(new Date().getTime() + 100000) let updateAt = new Date(new Date().getTime() + 100000)
const data = { const data = {
@ -217,86 +230,70 @@ describe('Test users notifications', function () {
privacy: VideoPrivacy.PUBLIC privacy: VideoPrivacy.PUBLIC
} }
} }
const uuid = await uploadVideoByLocalAccount(servers, videoNameId, data) const { name, uuid } = await uploadVideoByLocalAccount(servers, data)
await wait(6000) 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 () { it('Should send a new video notification when a video becomes public', async function () {
this.timeout(10000) this.timeout(10000)
const videoNameId = 60
const videoName = 'local video ' + videoNameId
const data = { privacy: VideoPrivacy.PRIVATE } 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 updateVideo(servers[0].url, servers[0].accessToken, uuid, { privacy: VideoPrivacy.PUBLIC })
await wait(500) 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 () { it('Should send a new video notification when a remote video becomes public', async function () {
this.timeout(20000) this.timeout(20000)
const videoNameId = 70
const videoName = 'remote video ' + videoNameId
const data = { privacy: VideoPrivacy.PRIVATE } const data = { privacy: VideoPrivacy.PRIVATE }
const uuid = await uploadVideoByRemoteAccount(servers, videoNameId, data) const { name, uuid } = await uploadVideoByRemoteAccount(servers, data)
await waitJobs(servers)
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 updateVideo(servers[1].url, servers[1].accessToken, uuid, { privacy: VideoPrivacy.PUBLIC })
await waitJobs(servers) 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 () { it('Should not send a new video notification when a video becomes unlisted', async function () {
this.timeout(20000) this.timeout(20000)
const videoNameId = 80
const videoName = 'local video ' + videoNameId
const data = { privacy: VideoPrivacy.PRIVATE } 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 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 () { it('Should not send a new video notification when a remote video becomes unlisted', async function () {
this.timeout(20000) this.timeout(20000)
const videoNameId = 90
const videoName = 'remote video ' + videoNameId
const data = { privacy: VideoPrivacy.PRIVATE } const data = { privacy: VideoPrivacy.PRIVATE }
const uuid = await uploadVideoByRemoteAccount(servers, videoNameId, data) const { name, uuid } = await uploadVideoByRemoteAccount(servers, data)
await waitJobs(servers)
await updateVideo(servers[1].url, servers[1].accessToken, uuid, { privacy: VideoPrivacy.UNLISTED }) await updateVideo(servers[1].url, servers[1].accessToken, uuid, { privacy: VideoPrivacy.UNLISTED })
await waitJobs(servers) 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 () { it('Should send a new video notification after a video import', async function () {
this.timeout(30000) this.timeout(30000)
const resChannel = await getMyUserInformation(servers[0].url, servers[0].accessToken) const name = 'video import ' + uuidv4()
const channelId = resChannel.body.videoChannels[0].id
const videoName = 'local video 100'
const attributes = { const attributes = {
name: videoName, name,
channelId, channelId,
privacy: VideoPrivacy.PUBLIC, privacy: VideoPrivacy.PUBLIC,
targetUrl: getYoutubeVideoUrl() targetUrl: getYoutubeVideoUrl()
@ -306,7 +303,7 @@ describe('Test users notifications', function () {
await waitJobs(servers) 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') 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 () { it('Should send a new comment notification after a local comment on my video', async function () {
this.timeout(10000) 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 () { it('Should send a notification to moderators on local video abuse', async function () {
this.timeout(10000) this.timeout(10000)
const videoName = 'local video 110' const name = 'video for abuse ' + uuidv4()
const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name })
const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: videoName })
const uuid = resVideo.body.video.uuid const uuid = resVideo.body.video.uuid
await reportVideoAbuse(servers[0].url, servers[0].accessToken, uuid, 'super reason') await reportVideoAbuse(servers[0].url, servers[0].accessToken, uuid, 'super reason')
await waitJobs(servers) 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 () { it('Should send a notification to moderators on remote video abuse', async function () {
this.timeout(10000) this.timeout(10000)
const videoName = 'remote video 120' const name = 'video for abuse ' + uuidv4()
const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name })
const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: videoName })
const uuid = resVideo.body.video.uuid const uuid = resVideo.body.video.uuid
await waitJobs(servers) await waitJobs(servers)
@ -449,7 +461,7 @@ describe('Test users notifications', function () {
await reportVideoAbuse(servers[1].url, servers[1].accessToken, uuid, 'super reason') await reportVideoAbuse(servers[1].url, servers[1].accessToken, uuid, 'super reason')
await waitJobs(servers) 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 () { it('Should send a notification to video owner on blacklist', async function () {
this.timeout(10000) this.timeout(10000)
const videoName = 'local video 130' const name = 'video for abuse ' + uuidv4()
const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name })
const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: videoName })
const uuid = resVideo.body.video.uuid const uuid = resVideo.body.video.uuid
await addVideoToBlacklist(servers[0].url, servers[0].accessToken, uuid) await addVideoToBlacklist(servers[0].url, servers[0].accessToken, uuid)
await waitJobs(servers) 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 () { it('Should send a notification to video owner on unblacklist', async function () {
this.timeout(10000) this.timeout(10000)
const videoName = 'local video 130' const name = 'video for abuse ' + uuidv4()
const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name })
const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: videoName })
const uuid = resVideo.body.video.uuid const uuid = resVideo.body.video.uuid
await addVideoToBlacklist(servers[0].url, servers[0].accessToken, uuid) await addVideoToBlacklist(servers[0].url, servers[0].accessToken, uuid)
@ -494,38 +504,187 @@ describe('Test users notifications', function () {
await waitJobs(servers) await waitJobs(servers)
await wait(500) 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 () { describe('Mark as read', function () {
it('Should mark as read some notifications', async 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) 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 () { 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[] const notifications = res.body.data as UserNotification[]
expect(notifications[0].read).to.be.false expect(notifications[ 0 ].read).to.be.false
expect(notifications[1].read).to.be.false expect(notifications[ 1 ].read).to.be.false
expect(notifications[2].read).to.be.true expect(notifications[ 2 ].read).to.be.true
expect(notifications[3].read).to.be.true expect(notifications[ 3 ].read).to.be.true
expect(notifications[4].read).to.be.true expect(notifications[ 4 ].read).to.be.true
expect(notifications[5].read).to.be.false 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[]
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 () { 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 let baseParams: CheckerBaseParams
before(() => { before(() => {
@ -538,7 +697,7 @@ describe('Test users notifications', function () {
}) })
it('Should not have notifications', async 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 newVideoFromSubscription: UserNotificationSettingValue.NONE
})) }))
@ -548,16 +707,14 @@ describe('Test users notifications', function () {
expect(info.notificationSettings.newVideoFromSubscription).to.equal(UserNotificationSettingValue.NONE) expect(info.notificationSettings.newVideoFromSubscription).to.equal(UserNotificationSettingValue.NONE)
} }
const videoNameId = 42 const { name, uuid } = await uploadVideoByLocalAccount(servers)
const videoName = 'local video ' + videoNameId
const uuid = await uploadVideoByLocalAccount(servers, videoNameId)
const check = { web: true, mail: true } 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 () { 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 newVideoFromSubscription: UserNotificationSettingValue.WEB_NOTIFICATION
})) }))
@ -567,23 +724,21 @@ describe('Test users notifications', function () {
expect(info.notificationSettings.newVideoFromSubscription).to.equal(UserNotificationSettingValue.WEB_NOTIFICATION) expect(info.notificationSettings.newVideoFromSubscription).to.equal(UserNotificationSettingValue.WEB_NOTIFICATION)
} }
const videoNameId = 52 const { name, uuid } = await uploadVideoByLocalAccount(servers)
const videoName = 'local video ' + videoNameId
const uuid = await uploadVideoByLocalAccount(servers, videoNameId)
{ {
const check = { mail: true, web: false } 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 } 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 () { 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 newVideoFromSubscription: UserNotificationSettingValue.EMAIL
})) }))
@ -593,23 +748,21 @@ describe('Test users notifications', function () {
expect(info.notificationSettings.newVideoFromSubscription).to.equal(UserNotificationSettingValue.EMAIL) expect(info.notificationSettings.newVideoFromSubscription).to.equal(UserNotificationSettingValue.EMAIL)
} }
const videoNameId = 62 const { name, uuid } = await uploadVideoByLocalAccount(servers)
const videoName = 'local video ' + videoNameId
const uuid = await uploadVideoByLocalAccount(servers, videoNameId)
{ {
const check = { mail: false, web: true } 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 } 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 () { 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 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) expect(info.notificationSettings.newVideoFromSubscription).to.equal(UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL)
} }
const videoNameId = 72 const { name, uuid } = await uploadVideoByLocalAccount(servers)
const videoName = 'local video ' + videoNameId
const uuid = await uploadVideoByLocalAccount(servers, videoNameId)
await checkNewVideoFromSubscription(baseParams, videoName, uuid, 'presence') await checkNewVideoFromSubscription(baseParams, name, uuid, 'presence')
}) })
}) })

Binary file not shown.

View file

@ -10,4 +10,6 @@ export interface UserNotificationSetting {
newCommentOnMyVideo: UserNotificationSettingValue newCommentOnMyVideo: UserNotificationSettingValue
videoAbuseAsModerator: UserNotificationSettingValue videoAbuseAsModerator: UserNotificationSettingValue
blacklistOnMyVideo: UserNotificationSettingValue blacklistOnMyVideo: UserNotificationSettingValue
myVideoPublished: UserNotificationSettingValue
myVideoImportFinished: UserNotificationSettingValue
} }

View file

@ -3,10 +3,13 @@ export enum UserNotificationType {
NEW_COMMENT_ON_MY_VIDEO = 2, NEW_COMMENT_ON_MY_VIDEO = 2,
NEW_VIDEO_ABUSE_FOR_MODERATORS = 3, NEW_VIDEO_ABUSE_FOR_MODERATORS = 3,
BLACKLIST_ON_MY_VIDEO = 4, 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 id: number
uuid: string uuid: string
name: string name: string
@ -24,12 +27,22 @@ export interface UserNotification {
} }
} }
videoImport?: {
id: number
video?: VideoInfo
torrentName?: string
magnetUri?: string
targetUrl?: string
}
comment?: { comment?: {
id: number id: number
threadId: number
account: { account: {
id: number id: number
displayName: string displayName: string
} }
video: VideoInfo
} }
videoAbuse?: { videoAbuse?: {

View file

@ -4,6 +4,7 @@ import { makeGetRequest, makePostBodyRequest, makePutBodyRequest } from '../requ
import { UserNotification, UserNotificationSetting, UserNotificationType } from '../../models/users' import { UserNotification, UserNotificationSetting, UserNotificationType } from '../../models/users'
import { ServerInfo } from '..' import { ServerInfo } from '..'
import { expect } from 'chai' import { expect } from 'chai'
import { inspect } from 'util'
function updateMyNotificationSettings (url: string, token: string, settings: UserNotificationSetting, statusCodeExpected = 204) { function updateMyNotificationSettings (url: string, token: string, settings: UserNotificationSetting, statusCodeExpected = 204) {
const path = '/api/v1/users/me/notification-settings' 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' const path = '/api/v1/users/me/notifications'
return makeGetRequest({ return makeGetRequest({
@ -27,7 +36,8 @@ function getUserNotifications (url: string, token: string, start: number, count:
query: { query: {
start, start,
count, count,
sort sort,
unread
}, },
statusCodeExpected statusCodeExpected
}) })
@ -46,7 +56,7 @@ function markAsReadNotifications (url: string, token: string, ids: number[], sta
} }
async function getLastNotification (serverUrl: string, accessToken: string) { 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 if (res.body.total === 0) return undefined
@ -65,21 +75,33 @@ type CheckerType = 'presence' | 'absence'
async function checkNotification ( async function checkNotification (
base: CheckerBaseParams, base: CheckerBaseParams,
lastNotificationChecker: (notification: UserNotification) => void, notificationChecker: (notification: UserNotification, type: CheckerType) => void,
socketNotificationFinder: (notification: UserNotification) => boolean,
emailNotificationFinder: (email: object) => boolean, emailNotificationFinder: (email: object) => boolean,
checkType: 'presence' | 'absence' checkType: CheckerType
) { ) {
const check = base.check || { web: true, mail: true } const check = base.check || { web: true, mail: true }
if (check.web) { if (check.web) {
const notification = await getLastNotification(base.server.url, base.token) 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 const socketNotification = base.socketNotifications.find(n => {
else expect(socketNotification, 'The socket notification is present.').to.be.undefined 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) { if (check.mail) {
@ -89,45 +111,127 @@ async function checkNotification (
.reverse() .reverse()
.find(e => emailNotificationFinder(e)) .find(e => emailNotificationFinder(e))
if (checkType === 'presence') expect(email, 'The email is present.').to.not.be.undefined if (checkType === 'presence') {
else expect(email, 'The email is absent.').to.be.undefined 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) { async function checkNewVideoFromSubscription (base: CheckerBaseParams, videoName: string, videoUUID: string, type: CheckerType) {
const notificationType = UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION const notificationType = UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION
function lastNotificationChecker (notification: UserNotification) { function notificationChecker (notification: UserNotification, type: CheckerType) {
if (type === 'presence') { if (type === 'presence') {
expect(notification).to.not.be.undefined expect(notification).to.not.be.undefined
expect(notification.type).to.equal(notificationType) expect(notification.type).to.equal(notificationType)
expect(notification.video.name).to.equal(videoName)
checkVideo(notification.video, videoName, videoUUID)
checkActor(notification.video.channel)
} else { } else {
expect(notification.video).to.satisfy(v => v === undefined || v.name !== videoName) 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) { function emailFinder (email: object) {
return email[ 'text' ].indexOf(videoUUID) !== -1 return email[ 'text' ].indexOf(videoUUID) !== -1
} }
await checkNotification(base, lastNotificationChecker, socketFinder, emailFinder, type) 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) {
const text: string = email[ 'text' ]
return text.includes(videoUUID) && text.includes('Your video')
}
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 let lastEmailCount = 0
async function checkNewCommentOnMyVideo (base: CheckerBaseParams, uuid: string, commentId: number, threadId: number, type: CheckerType) { async function checkNewCommentOnMyVideo (base: CheckerBaseParams, uuid: string, commentId: number, threadId: number, type: CheckerType) {
const notificationType = UserNotificationType.NEW_COMMENT_ON_MY_VIDEO const notificationType = UserNotificationType.NEW_COMMENT_ON_MY_VIDEO
function lastNotificationChecker (notification: UserNotification) { function notificationChecker (notification: UserNotification, type: CheckerType) {
if (type === 'presence') { if (type === 'presence') {
expect(notification).to.not.be.undefined expect(notification).to.not.be.undefined
expect(notification.type).to.equal(notificationType) 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 { } else {
expect(notification).to.satisfy((n: UserNotification) => { expect(notification).to.satisfy((n: UserNotification) => {
return n === undefined || n.comment === undefined || n.comment.id !== commentId 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}` const commentUrl = `http://localhost:9001/videos/watch/${uuid};threadId=${threadId}`
function emailFinder (email: object) { function emailFinder (email: object) {
return email[ 'text' ].indexOf(commentUrl) !== -1 return email[ 'text' ].indexOf(commentUrl) !== -1
} }
await checkNotification(base, lastNotificationChecker, socketFinder, emailFinder, type) await checkNotification(base, notificationChecker, emailFinder, type)
if (type === 'presence') { if (type === 'presence') {
// We cannot detect email duplicates, so check we received another email // 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) { async function checkNewVideoAbuseForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) {
const notificationType = UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS const notificationType = UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS
function lastNotificationChecker (notification: UserNotification) { function notificationChecker (notification: UserNotification, type: CheckerType) {
if (type === 'presence') { if (type === 'presence') {
expect(notification).to.not.be.undefined expect(notification).to.not.be.undefined
expect(notification.type).to.equal(notificationType) 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 { } else {
expect(notification).to.satisfy((n: UserNotification) => { expect(notification).to.satisfy((n: UserNotification) => {
return n === undefined || n.videoAbuse === undefined || n.videoAbuse.video.uuid !== videoUUID 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) { function emailFinder (email: object) {
const text = email[ 'text' ] const text = email[ 'text' ]
return text.indexOf(videoUUID) !== -1 && text.indexOf('abuse') !== -1 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 ( async function checkNewBlacklistOnMyVideo (
@ -193,18 +288,13 @@ async function checkNewBlacklistOnMyVideo (
? UserNotificationType.BLACKLIST_ON_MY_VIDEO ? UserNotificationType.BLACKLIST_ON_MY_VIDEO
: UserNotificationType.UNBLACKLIST_ON_MY_VIDEO : UserNotificationType.UNBLACKLIST_ON_MY_VIDEO
function lastNotificationChecker (notification: UserNotification) { function notificationChecker (notification: UserNotification) {
expect(notification).to.not.be.undefined expect(notification).to.not.be.undefined
expect(notification.type).to.equal(notificationType) expect(notification.type).to.equal(notificationType)
const video = blacklistType === 'blacklist' ? notification.videoBlacklist.video : notification.video const video = blacklistType === 'blacklist' ? notification.videoBlacklist.video : notification.video
expect(video.uuid).to.equal(videoUUID) checkVideo(video, videoName, videoUUID)
expect(video.name).to.equal(videoName)
}
function socketFinder (notification: UserNotification) {
return notification.type === notificationType && (notification.video || notification.videoBlacklist.video).uuid === videoUUID
} }
function emailFinder (email: object) { function emailFinder (email: object) {
@ -212,7 +302,7 @@ async function checkNewBlacklistOnMyVideo (
return text.indexOf(videoUUID) !== -1 && text.indexOf(' ' + blacklistType) !== -1 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, CheckerBaseParams,
CheckerType, CheckerType,
checkNotification, checkNotification,
checkMyVideoImportIsFinished,
checkVideoIsPublished,
checkNewVideoFromSubscription, checkNewVideoFromSubscription,
checkNewCommentOnMyVideo, checkNewCommentOnMyVideo,
checkNewBlacklistOnMyVideo, checkNewBlacklistOnMyVideo,

View 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' 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) { function importVideo (url: string, token: string, attributes: VideoImportCreate) {
const path = '/api/v1/videos/imports' const path = '/api/v1/videos/imports'
@ -45,6 +49,7 @@ function getMyVideoImports (url: string, token: string, sort?: string) {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
export { export {
getBadVideoUrl,
getYoutubeVideoUrl, getYoutubeVideoUrl,
importVideo, importVideo,
getMagnetURI, getMagnetURI,