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()
userNotificationsSortValidator,
setDefaultSort,
setDefaultPagination,
+ listUserNotificationsValidator,
asyncMiddleware(listUserNotifications)
)
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()
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))
}
"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,
{
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)
}
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 {
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
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()
}
// 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) {
// 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
{ resolutions: resolutionsEnabled }
)
+ let videoPublished = false
+
if (resolutionsEnabled.length !== 0) {
const tasks: Bluebird<Bull.Job<any>>[] = []
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 })
await federateVideoIfNeeded(videoDatabase, isNewVideo, t)
- return videoDatabase
+ return { videoDatabase, videoPublished }
})
if (isNewVideo) Notifier.Instance.notifyOnNewVideo(videoDatabase)
+ if (videoPublished) Notifier.Instance.notifyOnPendingVideoPublished(videoDatabase)
}
// ---------------------------------------------------------------------------
})
Notifier.Instance.notifyOnNewVideo(videoImportUpdated.Video)
+ Notifier.Instance.notifyOnFinishedVideoImport(videoImportUpdated, true)
// Create transcoding jobs?
if (videoImportUpdated.Video.state === VideoState.TO_TRANSCODE) {
videoImport.state = VideoImportState.FAILED
await videoImport.save()
+ Notifier.Instance.notifyOnFinishedVideoImport(videoImport, false)
+
throw err
}
}
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 {
.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 }))
.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)
// 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) {
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>,
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 {
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
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 () {
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 })
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'),
// ---------------------------------------------------------------------------
export {
+ listUserNotificationsValidator,
updateNotificationSettingsValidator,
markAsReadUserNotificationsValidator
}
})
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: {
@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
newCommentOnMyVideo: this.newCommentOnMyVideo,
newVideoFromSubscription: this.newVideoFromSubscription,
videoAbuseAsModerator: this.videoAbuseAsModerator,
- blacklistOnMyVideo: this.blacklistOnMyVideo
+ blacklistOnMyVideo: this.blacklistOnMyVideo,
+ myVideoPublished: this.myVideoPublished,
+ myVideoImportFinished: this.myVideoImportFinished
}
}
}
-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'
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) ]
}
]
}
})
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),
}
}
+ if (unread !== undefined) query.where['read'] = !unread
+
return UserNotificationModel.scope(ScopeNames.WITH_ALL)
.findAndCountAll(query)
.then(({ rows, count }) => {
}
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 {
type: this.type,
read: this.read,
video,
+ videoImport,
comment,
videoAbuse,
videoBlacklist,
updatedAt: this.updatedAt.toISOString()
}
}
+
+ private formatVideo (video: VideoModel) {
+ return {
+ id: video.id,
+ uuid: video.uuid,
+ name: video.name
+ }
+ }
}
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'
})
NotificationSetting: UserNotificationSettingModel
+ @HasMany(() => VideoImportModel, {
+ foreignKey: 'userId',
+ onDelete: 'cascade'
+ })
+ VideoImports: VideoImportModel[]
+
@HasMany(() => OAuthTokenModel, {
foreignKey: 'userId',
onDelete: 'cascade'
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()
-import { values } from 'lodash'
import {
AllowNull,
BelongsTo,
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'
})
}
+ getTargetIdentifier () {
+ return this.targetUrl || this.magnetUri || this.torrentName
+ }
+
toFormattedJSON (): VideoImport {
const videoFormatOptions = {
completeDescription: true,
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[] = [
})
VideoBlacklist: VideoBlacklistModel
+ @HasOne(() => VideoImportModel, {
+ foreignKey: {
+ name: 'videoId',
+ allowNull: true
+ },
+ onDelete: 'set null'
+ })
+ VideoImport: VideoImportModel
+
@HasMany(() => VideoCaptionModel, {
foreignKey: {
name: 'videoId',
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,
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 () {
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 () {
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)
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)
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 () {
})
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
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 () {
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)
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)
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 = {
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()
await waitJobs(servers)
- await checkNewVideoFromSubscription(baseParams, videoName, uuid, 'presence')
+ await checkNewVideoFromSubscription(baseParams, name, uuid, 'presence')
})
})
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)
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)
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')
})
})
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)
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(() => {
})
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
}))
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
}))
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
}))
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
}))
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')
})
})
newCommentOnMyVideo: UserNotificationSettingValue
videoAbuseAsModerator: UserNotificationSettingValue
blacklistOnMyVideo: UserNotificationSettingValue
+ myVideoPublished: UserNotificationSettingValue
+ myVideoImportFinished: UserNotificationSettingValue
}
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
}
}
+ videoImport?: {
+ id: number
+ video?: VideoInfo
+ torrentName?: string
+ magnetUri?: string
+ targetUrl?: string
+ }
+
comment?: {
id: number
+ threadId: number
account: {
id: number
displayName: string
}
+ video: VideoInfo
}
videoAbuse?: {
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'
})
}
-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({
query: {
start,
count,
- sort
+ sort,
+ unread
},
statusCodeExpected
})
}
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
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) {
.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
}
}
- 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
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
}
}
- 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 (
? 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) {
return text.indexOf(videoUUID) !== -1 && text.indexOf(' ' + blacklistType) !== -1
}
- await checkNotification(base, lastNotificationChecker, socketFinder, emailFinder, 'presence')
+ await checkNotification(base, notificationChecker, emailFinder, 'presence')
}
// ---------------------------------------------------------------------------
CheckerBaseParams,
CheckerType,
checkNotification,
+ checkMyVideoImportIsFinished,
+ checkVideoIsPublished,
checkNewVideoFromSubscription,
checkNewCommentOnMyVideo,
checkNewBlacklistOnMyVideo,
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'
// ---------------------------------------------------------------------------
export {
+ getBadVideoUrl,
getYoutubeVideoUrl,
importVideo,
getMagnetURI,