diff --git a/server/helpers/activitypub.ts b/server/helpers/activitypub.ts index 62d78373e..e850efe13 100644 --- a/server/helpers/activitypub.ts +++ b/server/helpers/activitypub.ts @@ -34,7 +34,8 @@ function activityPubContextify (data: T) { expires: 'sc:expires', support: 'sc:Text', CacheFile: 'pt:CacheFile', - Infohash: 'pt:Infohash' + Infohash: 'pt:Infohash', + originallyPublishedAt: 'sc:DateTime' }, { likes: { diff --git a/server/helpers/custom-validators/activitypub/videos.ts b/server/helpers/custom-validators/activitypub/videos.ts index 53ad0588d..d94333151 100644 --- a/server/helpers/custom-validators/activitypub/videos.ts +++ b/server/helpers/custom-validators/activitypub/videos.ts @@ -54,6 +54,7 @@ function sanitizeAndCheckVideoTorrentObject (video: any) { isBooleanValid(video.downloadEnabled) && isDateValid(video.published) && isDateValid(video.updated) && + (!video.originallyPublishedAt || isDateValid(video.originallyPublishedAt)) && (!video.content || isRemoteVideoContentValid(video.mediaType, video.content)) && isRemoteVideoIconValid(video.icon) && video.url.length !== 0 && diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts index 710929aac..9ca0502a4 100644 --- a/server/lib/activitypub/videos.ts +++ b/server/lib/activitypub/videos.ts @@ -249,6 +249,7 @@ async function updateVideoFromAP (options: { options.video.set('duration', videoData.duration) options.video.set('createdAt', videoData.createdAt) options.video.set('publishedAt', videoData.publishedAt) + options.video.set('originallyPublishedAt', videoData.originallyPublishedAt) options.video.set('privacy', videoData.privacy) options.video.set('channelId', videoData.channelId) options.video.set('views', videoData.views) @@ -511,6 +512,7 @@ async function videoActivityObjectToDBAttributes ( duration: parseInt(duration, 10), createdAt: new Date(videoObject.published), publishedAt: new Date(videoObject.published), + originallyPublishedAt: videoObject.originallyPublishedAt ? new Date(videoObject.originallyPublishedAt) : null, // FIXME: updatedAt does not seems to be considered by Sequelize updatedAt: new Date(videoObject.updated), views: videoObject.views, diff --git a/server/models/video/video-format-utils.ts b/server/models/video/video-format-utils.ts index c63285e25..a62335333 100644 --- a/server/models/video/video-format-utils.ts +++ b/server/models/video/video-format-utils.ts @@ -324,9 +324,7 @@ function videoModelToActivityPubObject (video: VideoModel): VideoTorrentObject { commentsEnabled: video.commentsEnabled, downloadEnabled: video.downloadEnabled, published: video.publishedAt.toISOString(), - originallyPublishedAt: video.originallyPublishedAt ? - video.originallyPublishedAt.toISOString() : - null, + originallyPublishedAt: video.originallyPublishedAt ? video.originallyPublishedAt.toISOString() : null, updated: video.updatedAt.toISOString(), mediaType: 'text/markdown', content: video.getTruncatedDescription(), diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 73626b6a0..215e26d7d 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts @@ -40,7 +40,7 @@ import { isVideoDurationValid, isVideoLanguageValid, isVideoLicenceValid, - isVideoNameValid, + isVideoNameValid, isVideoOriginallyPublishedAtValid, isVideoPrivacyValid, isVideoStateValid, isVideoSupportValid @@ -103,10 +103,17 @@ const indexes: Sequelize.DefineIndexesOptions[] = [ { fields: [ 'createdAt' ] }, { fields: [ 'publishedAt' ] }, - { fields: [ 'originallyPublishedAt' ] }, { fields: [ 'duration' ] }, { fields: [ 'views' ] }, { fields: [ 'channelId' ] }, + { + fields: [ 'originallyPublishedAt' ], + where: { + originallyPublishedAt: { + [Sequelize.Op.ne]: null + } + } + }, { fields: [ 'category' ], // We don't care videos with an unknown category where: { @@ -741,6 +748,8 @@ export class VideoModel extends Model { @Column publishedAt: Date + @AllowNull(true) + @Default(null) @Column originallyPublishedAt: Date diff --git a/server/tests/api/check-params/videos.ts b/server/tests/api/check-params/videos.ts index 878ffe025..3eccaee44 100644 --- a/server/tests/api/check-params/videos.ts +++ b/server/tests/api/check-params/videos.ts @@ -185,7 +185,8 @@ describe('Test videos API validator', function () { support: 'my super support text', tags: [ 'tag1', 'tag2' ], privacy: VideoPrivacy.PUBLIC, - channelId: channelId + channelId: channelId, + originallyPublishedAt: new Date().toISOString() } }) @@ -313,6 +314,13 @@ describe('Test videos API validator', function () { await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) }) + it('Should fail with a bad originally published at attribute', async function () { + const fields = immutableAssign(baseCorrectParams, { 'originallyPublishedAt': 'toto' }) + const attaches = baseCorrectAttaches + + await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) + }) + it('Should fail without an input file', async function () { const fields = baseCorrectParams const attaches = {} @@ -534,6 +542,12 @@ describe('Test videos API validator', function () { await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields }) }) + it('Should fail with a bad originally published at param', async function () { + const fields = immutableAssign(baseCorrectParams, { originallyPublishedAt: 'toto' }) + + await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields }) + }) + it('Should fail with an incorrect thumbnail file', async function () { const fields = baseCorrectParams const attaches = { diff --git a/server/tests/api/videos/multiple-servers.ts b/server/tests/api/videos/multiple-servers.ts index 1b471ba79..7e2fcb630 100644 --- a/server/tests/api/videos/multiple-servers.ts +++ b/server/tests/api/videos/multiple-servers.ts @@ -98,6 +98,7 @@ describe('Test multiple servers', function () { nsfw: true, description: 'my super description for server 1', support: 'my super support text for server 1', + originallyPublishedAt: '2019-02-10T13:38:14.449Z', tags: [ 'tag1p1', 'tag2p1' ], channelId: videoChannelId, fixture: 'video_short1.webm' @@ -118,6 +119,7 @@ describe('Test multiple servers', function () { nsfw: true, description: 'my super description for server 1', support: 'my super support text for server 1', + originallyPublishedAt: '2019-02-10T13:38:14.449Z', account: { name: 'root', host: 'localhost:9001' @@ -625,6 +627,7 @@ describe('Test multiple servers', function () { support: 'my super support text updated', tags: [ 'tag_up_1', 'tag_up_2' ], thumbnailfile: 'thumbnail.jpg', + originallyPublishedAt: '2019-02-11T13:38:14.449Z', previewfile: 'preview.jpg' } @@ -652,6 +655,7 @@ describe('Test multiple servers', function () { nsfw: true, description: 'my super description updated', support: 'my super support text updated', + originallyPublishedAt: '2019-02-11T13:38:14.449Z', account: { name: 'root', host: 'localhost:9003' @@ -983,7 +987,7 @@ describe('Test multiple servers', function () { isLocal, duration: 5, commentsEnabled: false, - downloadEnabled: false, + downloadEnabled: true, tags: [ ], privacy: VideoPrivacy.PUBLIC, channel: { diff --git a/shared/utils/videos/videos.ts b/shared/utils/videos/videos.ts index 39c808d1f..16ecbfe84 100644 --- a/shared/utils/videos/videos.ts +++ b/shared/utils/videos/videos.ts @@ -31,6 +31,7 @@ type VideoAttributes = { downloadEnabled?: boolean waitTranscoding?: boolean description?: string + originallyPublishedAt?: string tags?: string[] channelId?: number privacy?: VideoPrivacy @@ -349,6 +350,9 @@ async function uploadVideo (url: string, accessToken: string, videoAttributesArg if (attributes.licence !== undefined) { req.field('licence', attributes.licence.toString()) } + if (attributes.originallyPublishedAt !== undefined) { + req.field('originallyPublishedAt', attributes.originallyPublishedAt) + } for (let i = 0; i < attributes.tags.length; i++) { req.field('tags[' + i + ']', attributes.tags[i]) @@ -384,6 +388,7 @@ function updateVideo (url: string, accessToken: string, id: number | string, att if (attributes.nsfw !== undefined) body['nsfw'] = JSON.stringify(attributes.nsfw) if (attributes.commentsEnabled !== undefined) body['commentsEnabled'] = JSON.stringify(attributes.commentsEnabled) if (attributes.downloadEnabled !== undefined) body['downloadEnabled'] = JSON.stringify(attributes.downloadEnabled) + if (attributes.originallyPublishedAt !== undefined) body['originallyPublishedAt'] = attributes.originallyPublishedAt if (attributes.description) body['description'] = attributes.description if (attributes.tags) body['tags'] = attributes.tags if (attributes.privacy) body['privacy'] = attributes.privacy @@ -453,6 +458,7 @@ async function completeVideoCheck ( description: string publishedAt?: string support: string + originallyPublishedAt?: string, account: { name: string host: string @@ -510,6 +516,12 @@ async function completeVideoCheck ( expect(video.publishedAt).to.equal(attributes.publishedAt) } + if (attributes.originallyPublishedAt) { + expect(video.originallyPublishedAt).to.equal(attributes.originallyPublishedAt) + } else { + expect(video.originallyPublishedAt).to.be.null + } + const res = await getVideo(url, video.uuid) const videoDetails: VideoDetails = res.body