diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index 94ed08fed..33521a8c1 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts @@ -264,7 +264,6 @@ async function addVideo (req: express.Request, res: express.Response) { } await federateVideoIfNeeded(video, true, t) - Notifier.Instance.notifyOnNewVideo(video) auditLogger.create(getAuditIdFromRes(res), new VideoAuditView(videoCreated.toFormattedDetailsJSON())) logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoCreated.uuid) @@ -272,6 +271,8 @@ async function addVideo (req: express.Request, res: express.Response) { return videoCreated }) + Notifier.Instance.notifyOnNewVideo(videoCreated) + if (video.state === VideoState.TO_TRANSCODE) { // Put uuid because we don't have id auto incremented for now const dataInput = { @@ -311,10 +312,8 @@ async function updateVideo (req: express.Request, res: express.Response) { } try { - await sequelizeTypescript.transaction(async t => { - const sequelizeOptions = { - transaction: t - } + const videoInstanceUpdated = await sequelizeTypescript.transaction(async t => { + const sequelizeOptions = { transaction: t } const oldVideoChannel = videoInstance.VideoChannel if (videoInfoToUpdate.name !== undefined) videoInstance.set('name', videoInfoToUpdate.name) @@ -367,17 +366,19 @@ async function updateVideo (req: express.Request, res: express.Response) { const isNewVideo = wasPrivateVideo && videoInstanceUpdated.privacy !== VideoPrivacy.PRIVATE await federateVideoIfNeeded(videoInstanceUpdated, isNewVideo, t) - if (wasUnlistedVideo || wasPrivateVideo) { - Notifier.Instance.notifyOnNewVideo(videoInstanceUpdated) - } - auditLogger.update( getAuditIdFromRes(res), new VideoAuditView(videoInstanceUpdated.toFormattedDetailsJSON()), oldVideoAuditView ) logger.info('Video with name %s and uuid %s updated.', videoInstance.name, videoInstance.uuid) + + return videoInstanceUpdated }) + + if (wasUnlistedVideo || wasPrivateVideo) { + Notifier.Instance.notifyOnNewVideo(videoInstanceUpdated) + } } catch (err) { // Force fields we want to update // If the transaction is retried, sequelize will think the object has not changed diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index fcfaf71a0..91e74f6c7 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts @@ -16,7 +16,7 @@ let config: IConfig = require('config') // --------------------------------------------------------------------------- -const LAST_MIGRATION_VERSION = 310 +const LAST_MIGRATION_VERSION = 315 // --------------------------------------------------------------------------- diff --git a/server/initializers/migrations/0315-user-notifications.ts b/server/initializers/migrations/0315-user-notifications.ts new file mode 100644 index 000000000..2bd9c657d --- /dev/null +++ b/server/initializers/migrations/0315-user-notifications.ts @@ -0,0 +1,41 @@ +import * as Sequelize from 'sequelize' + +async function up (utils: { + transaction: Sequelize.Transaction, + queryInterface: Sequelize.QueryInterface, + sequelize: Sequelize.Sequelize +}): Promise { + + { + const query = ` +CREATE TABLE IF NOT EXISTS "userNotificationSetting" ("id" SERIAL, +"newVideoFromSubscription" INTEGER NOT NULL DEFAULT NULL, +"newCommentOnMyVideo" INTEGER NOT NULL DEFAULT NULL, +"videoAbuseAsModerator" INTEGER NOT NULL DEFAULT NULL, +"blacklistOnMyVideo" 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, +PRIMARY KEY ("id")) +` + await utils.sequelize.query(query) + } + + { + const query = 'INSERT INTO "userNotificationSetting" ' + + '("newVideoFromSubscription", "newCommentOnMyVideo", "videoAbuseAsModerator", "blacklistOnMyVideo", ' + + '"userId", "createdAt", "updatedAt") ' + + '(SELECT 2, 2, 4, 4, id, NOW(), NOW() FROM "user")' + + await utils.sequelize.query(query) + } +} + +function down (options) { + throw new Error('Not implemented.') +} + +export { + up, + down +} diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts index 5794988a5..893768769 100644 --- a/server/lib/activitypub/videos.ts +++ b/server/lib/activitypub/videos.ts @@ -204,19 +204,17 @@ async function updateVideoFromAP (options: { overrideTo?: string[] }) { logger.debug('Updating remote video "%s".', options.videoObject.uuid) + let videoFieldsSave: any + const wasPrivateVideo = options.video.privacy === VideoPrivacy.PRIVATE + const wasUnlistedVideo = options.video.privacy === VideoPrivacy.UNLISTED try { await sequelizeTypescript.transaction(async t => { - const sequelizeOptions = { - transaction: t - } + const sequelizeOptions = { transaction: t } videoFieldsSave = options.video.toJSON() - const wasPrivateVideo = options.video.privacy === VideoPrivacy.PRIVATE - const wasUnlistedVideo = options.video.privacy === VideoPrivacy.UNLISTED - // Check actor has the right to update the video const videoChannel = options.video.VideoChannel if (videoChannel.Account.id !== options.account.id) { @@ -281,15 +279,13 @@ async function updateVideoFromAP (options: { }) options.video.VideoCaptions = await Promise.all(videoCaptionsPromises) } - - { - // Notify our users? - if (wasPrivateVideo || wasUnlistedVideo) { - Notifier.Instance.notifyOnNewVideo(options.video) - } - } }) + // Notify our users? + if (wasPrivateVideo || wasUnlistedVideo) { + Notifier.Instance.notifyOnNewVideo(options.video) + } + logger.info('Remote video with uuid %s updated', options.videoObject.uuid) } catch (err) { if (options.video !== undefined && videoFieldsSave !== undefined) { diff --git a/server/lib/job-queue/handlers/video-file.ts b/server/lib/job-queue/handlers/video-file.ts index 959cc04fa..480d324dc 100644 --- a/server/lib/job-queue/handlers/video-file.ts +++ b/server/lib/job-queue/handlers/video-file.ts @@ -1,5 +1,5 @@ import * as Bull from 'bull' -import { VideoResolution, VideoState, Job } from '../../../../shared' +import { VideoResolution, VideoState } from '../../../../shared' import { logger } from '../../../helpers/logger' import { VideoModel } from '../../../models/video/video' import { JobQueue } from '../job-queue' @@ -8,7 +8,7 @@ import { retryTransactionWrapper } from '../../../helpers/database-utils' import { sequelizeTypescript } from '../../../initializers' import * as Bluebird from 'bluebird' import { computeResolutionsToTranscode } from '../../../helpers/ffmpeg-utils' -import { importVideoFile, transcodeOriginalVideofile, optimizeVideofile } from '../../video-transcoding' +import { importVideoFile, optimizeVideofile, transcodeOriginalVideofile } from '../../video-transcoding' import { Notifier } from '../../notifier' export type VideoFilePayload = { @@ -68,7 +68,7 @@ async function processVideoFile (job: Bull.Job) { async function onVideoFileTranscoderOrImportSuccess (video: VideoModel) { if (video === undefined) return undefined - return sequelizeTypescript.transaction(async t => { + const { videoDatabase, isNewVideo } = 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 @@ -87,10 +87,11 @@ async function onVideoFileTranscoderOrImportSuccess (video: VideoModel) { // If the video was not published, we consider it is a new one for other instances await federateVideoIfNeeded(videoDatabase, isNewVideo, t) - if (isNewVideo) Notifier.Instance.notifyOnNewVideo(video) - return undefined + return { videoDatabase, isNewVideo } }) + + if (isNewVideo) Notifier.Instance.notifyOnNewVideo(videoDatabase) } async function onVideoFileOptimizerSuccess (videoArg: VideoModel, isNewVideo: boolean) { @@ -99,7 +100,7 @@ async function onVideoFileOptimizerSuccess (videoArg: VideoModel, isNewVideo: bo // Outside the transaction (IO on disk) const { videoFileResolution } = await videoArg.getOriginalFileResolution() - return sequelizeTypescript.transaction(async t => { + const videoDatabase = 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 @@ -137,8 +138,11 @@ async function onVideoFileOptimizerSuccess (videoArg: VideoModel, isNewVideo: bo } await federateVideoIfNeeded(videoDatabase, isNewVideo, t) - if (isNewVideo) Notifier.Instance.notifyOnNewVideo(videoDatabase) + + return videoDatabase }) + + if (isNewVideo) Notifier.Instance.notifyOnNewVideo(videoDatabase) } // --------------------------------------------------------------------------- diff --git a/server/lib/job-queue/handlers/video-import.ts b/server/lib/job-queue/handlers/video-import.ts index 82edb8d5c..29cd1198c 100644 --- a/server/lib/job-queue/handlers/video-import.ts +++ b/server/lib/job-queue/handlers/video-import.ts @@ -180,12 +180,11 @@ async function processFile (downloader: () => Promise, videoImport: Vide // Update video DB object video.duration = duration video.state = CONFIG.TRANSCODING.ENABLED ? VideoState.TO_TRANSCODE : VideoState.PUBLISHED - const videoUpdated = await video.save({ transaction: t }) + await video.save({ transaction: t }) // Now we can federate the video (reload from database, we need more attributes) const videoForFederation = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t) await federateVideoIfNeeded(videoForFederation, true, t) - Notifier.Instance.notifyOnNewVideo(videoForFederation) // Update video import object videoImport.state = VideoImportState.SUCCESS @@ -193,10 +192,12 @@ async function processFile (downloader: () => Promise, videoImport: Vide logger.info('Video %s imported.', video.uuid) - videoImportUpdated.Video = videoUpdated + videoImportUpdated.Video = videoForFederation return videoImportUpdated }) + Notifier.Instance.notifyOnNewVideo(videoImportUpdated.Video) + // Create transcoding jobs? if (videoImportUpdated.Video.state === VideoState.TO_TRANSCODE) { // Put uuid because we don't have id auto incremented for now diff --git a/server/tests/api/users/user-notifications.ts b/server/tests/api/users/user-notifications.ts index ea35e6390..09c0479fd 100644 --- a/server/tests/api/users/user-notifications.ts +++ b/server/tests/api/users/user-notifications.ts @@ -152,6 +152,8 @@ describe('Test users notifications', function () { const videoName = 'remote video ' + videoNameId const uuid = await uploadVideoByRemoteAccount(servers, videoNameId) + await waitJobs(servers) + await checkNewVideoFromSubscription(baseParams, videoName, uuid, 'presence') }) @@ -194,6 +196,7 @@ describe('Test users notifications', function () { } } const uuid = await uploadVideoByRemoteAccount(servers, videoNameId, data) + await waitJobs(servers) await wait(6000) await checkNewVideoFromSubscription(baseParams, videoName, uuid, 'presence') @@ -245,6 +248,7 @@ describe('Test users notifications', function () { const data = { privacy: VideoPrivacy.PRIVATE } const uuid = await uploadVideoByRemoteAccount(servers, videoNameId, data) + await waitJobs(servers) await checkNewVideoFromSubscription(baseParams, videoName, uuid, 'absence') @@ -276,6 +280,7 @@ describe('Test users notifications', function () { const data = { privacy: VideoPrivacy.PRIVATE } const uuid = await uploadVideoByRemoteAccount(servers, videoNameId, data) + await waitJobs(servers) await updateVideo(servers[1].url, servers[1].accessToken, uuid, { privacy: VideoPrivacy.UNLISTED })