aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2018-06-15 16:52:15 +0200
committerChocobozzz <me@florianbigard.com>2018-06-15 18:20:56 +0200
commitbbe0f0645ca958d33a3f409b15166609733b663f (patch)
treeedcd5d702c73cda74a2177c4bdc08c616334337d /server
parent2baea0c77cc765f7cbca9c9a2f4272268892a35c (diff)
downloadPeerTube-bbe0f0645ca958d33a3f409b15166609733b663f.tar.gz
PeerTube-bbe0f0645ca958d33a3f409b15166609733b663f.tar.zst
PeerTube-bbe0f0645ca958d33a3f409b15166609733b663f.zip
Add ability to schedule video publication
Diffstat (limited to 'server')
-rw-r--r--server/initializers/constants.ts6
-rw-r--r--server/lib/schedulers/update-videos-scheduler.ts4
-rw-r--r--server/middlewares/validators/videos.ts2
-rw-r--r--server/models/video/schedule-video-update.ts23
-rw-r--r--server/models/video/video.ts33
-rw-r--r--server/tests/api/check-params/videos.ts29
-rw-r--r--server/tests/api/videos/video-schedule-update.ts26
7 files changed, 97 insertions, 26 deletions
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index 164378505..53902071c 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -8,8 +8,6 @@ import { VideoPrivacy } from '../../shared/models/videos'
8import { buildPath, isTestInstance, root, sanitizeHost, sanitizeUrl } from '../helpers/core-utils' 8import { buildPath, isTestInstance, root, sanitizeHost, sanitizeUrl } from '../helpers/core-utils'
9import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type' 9import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type'
10import { invert } from 'lodash' 10import { invert } from 'lodash'
11import { RemoveOldJobsScheduler } from '../lib/schedulers/remove-old-jobs-scheduler'
12import { UpdateVideosScheduler } from '../lib/schedulers/update-videos-scheduler'
13 11
14// Use a variable to reload the configuration if we need 12// Use a variable to reload the configuration if we need
15let config: IConfig = require('config') 13let config: IConfig = require('config')
@@ -98,8 +96,8 @@ const JOB_COMPLETED_LIFETIME = 60000 * 60 * 24 * 2 // 2 days
98// 1 hour 96// 1 hour
99let SCHEDULER_INTERVALS_MS = { 97let SCHEDULER_INTERVALS_MS = {
100 badActorFollow: 60000 * 60, // 1 hour 98 badActorFollow: 60000 * 60, // 1 hour
101 removeOldJobs: 60000 * 60, // 1 jour 99 removeOldJobs: 60000 * 60, // 1 hour
102 updateVideos: 60000 * 1, // 1 minute 100 updateVideos: 60000 // 1 minute
103} 101}
104 102
105// --------------------------------------------------------------------------- 103// ---------------------------------------------------------------------------
diff --git a/server/lib/schedulers/update-videos-scheduler.ts b/server/lib/schedulers/update-videos-scheduler.ts
index d123c3ceb..a964648fd 100644
--- a/server/lib/schedulers/update-videos-scheduler.ts
+++ b/server/lib/schedulers/update-videos-scheduler.ts
@@ -33,7 +33,9 @@ export class UpdateVideosScheduler extends AbstractScheduler {
33 } 33 }
34 } 34 }
35 35
36 private updateVideos () { 36 private async updateVideos () {
37 if (!await ScheduleVideoUpdateModel.areVideosToUpdate()) return undefined
38
37 return sequelizeTypescript.transaction(async t => { 39 return sequelizeTypescript.transaction(async t => {
38 const schedules = await ScheduleVideoUpdateModel.listVideosToUpdate(t) 40 const schedules = await ScheduleVideoUpdateModel.listVideosToUpdate(t)
39 41
diff --git a/server/middlewares/validators/videos.ts b/server/middlewares/validators/videos.ts
index 9fe5a253b..da17b4a68 100644
--- a/server/middlewares/validators/videos.ts
+++ b/server/middlewares/validators/videos.ts
@@ -223,7 +223,7 @@ const videosUpdateValidator = [
223 223
224 if (video.privacy !== VideoPrivacy.PRIVATE && req.body.privacy === VideoPrivacy.PRIVATE) { 224 if (video.privacy !== VideoPrivacy.PRIVATE && req.body.privacy === VideoPrivacy.PRIVATE) {
225 return res.status(409) 225 return res.status(409)
226 .json({ error: 'Cannot set "private" a video that was not private anymore.' }) 226 .json({ error: 'Cannot set "private" a video that was not private.' })
227 .end() 227 .end()
228 } 228 }
229 229
diff --git a/server/models/video/schedule-video-update.ts b/server/models/video/schedule-video-update.ts
index d4e37beb5..3cf5f6c99 100644
--- a/server/models/video/schedule-video-update.ts
+++ b/server/models/video/schedule-video-update.ts
@@ -25,7 +25,7 @@ export class ScheduleVideoUpdateModel extends Model<ScheduleVideoUpdateModel> {
25 @AllowNull(true) 25 @AllowNull(true)
26 @Default(null) 26 @Default(null)
27 @Column 27 @Column
28 privacy: VideoPrivacy 28 privacy: VideoPrivacy.PUBLIC | VideoPrivacy.UNLISTED
29 29
30 @CreatedAt 30 @CreatedAt
31 createdAt: Date 31 createdAt: Date
@@ -45,6 +45,21 @@ export class ScheduleVideoUpdateModel extends Model<ScheduleVideoUpdateModel> {
45 }) 45 })
46 Video: VideoModel 46 Video: VideoModel
47 47
48 static areVideosToUpdate () {
49 const query = {
50 logging: false,
51 attributes: [ 'id' ],
52 where: {
53 updateAt: {
54 [Sequelize.Op.lte]: new Date()
55 }
56 }
57 }
58
59 return ScheduleVideoUpdateModel.findOne(query)
60 .then(res => !!res)
61 }
62
48 static listVideosToUpdate (t: Transaction) { 63 static listVideosToUpdate (t: Transaction) {
49 const query = { 64 const query = {
50 where: { 65 where: {
@@ -68,4 +83,10 @@ export class ScheduleVideoUpdateModel extends Model<ScheduleVideoUpdateModel> {
68 return ScheduleVideoUpdateModel.findAll(query) 83 return ScheduleVideoUpdateModel.findAll(query)
69 } 84 }
70 85
86 toFormattedJSON () {
87 return {
88 updateAt: this.updateAt,
89 privacy: this.privacy || undefined
90 }
91 }
71} 92}
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index 440f4d171..0041e4d38 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -97,7 +97,8 @@ export enum ScopeNames {
97 AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST', 97 AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST',
98 WITH_ACCOUNT_DETAILS = 'WITH_ACCOUNT_DETAILS', 98 WITH_ACCOUNT_DETAILS = 'WITH_ACCOUNT_DETAILS',
99 WITH_TAGS = 'WITH_TAGS', 99 WITH_TAGS = 'WITH_TAGS',
100 WITH_FILES = 'WITH_FILES' 100 WITH_FILES = 'WITH_FILES',
101 WITH_SCHEDULED_UPDATE = 'WITH_SCHEDULED_UPDATE'
101} 102}
102 103
103@Scopes({ 104@Scopes({
@@ -286,6 +287,14 @@ export enum ScopeNames {
286 required: true 287 required: true
287 } 288 }
288 ] 289 ]
290 },
291 [ScopeNames.WITH_SCHEDULED_UPDATE]: {
292 include: [
293 {
294 model: () => ScheduleVideoUpdateModel.unscoped(),
295 required: false
296 }
297 ]
289 } 298 }
290}) 299})
291@Table({ 300@Table({
@@ -843,7 +852,7 @@ export class VideoModel extends Model<VideoModel> {
843 } 852 }
844 853
845 return VideoModel 854 return VideoModel
846 .scope([ ScopeNames.WITH_TAGS, ScopeNames.WITH_FILES, ScopeNames.WITH_ACCOUNT_DETAILS ]) 855 .scope([ ScopeNames.WITH_TAGS, ScopeNames.WITH_FILES, ScopeNames.WITH_ACCOUNT_DETAILS, ScopeNames.WITH_SCHEDULED_UPDATE ])
847 .findById(id, options) 856 .findById(id, options)
848 } 857 }
849 858
@@ -869,7 +878,7 @@ export class VideoModel extends Model<VideoModel> {
869 } 878 }
870 879
871 return VideoModel 880 return VideoModel
872 .scope([ ScopeNames.WITH_TAGS, ScopeNames.WITH_FILES, ScopeNames.WITH_ACCOUNT_DETAILS ]) 881 .scope([ ScopeNames.WITH_TAGS, ScopeNames.WITH_FILES, ScopeNames.WITH_ACCOUNT_DETAILS, ScopeNames.WITH_SCHEDULED_UPDATE ])
873 .findOne(options) 882 .findOne(options)
874 } 883 }
875 884
@@ -1022,9 +1031,9 @@ export class VideoModel extends Model<VideoModel> {
1022 1031
1023 toFormattedJSON (options?: { 1032 toFormattedJSON (options?: {
1024 additionalAttributes: { 1033 additionalAttributes: {
1025 state: boolean, 1034 state?: boolean,
1026 waitTranscoding: boolean, 1035 waitTranscoding?: boolean,
1027 scheduledUpdate: boolean 1036 scheduledUpdate?: boolean
1028 } 1037 }
1029 }): Video { 1038 }): Video {
1030 const formattedAccount = this.VideoChannel.Account.toFormattedJSON() 1039 const formattedAccount = this.VideoChannel.Account.toFormattedJSON()
@@ -1084,18 +1093,18 @@ export class VideoModel extends Model<VideoModel> {
1084 } 1093 }
1085 1094
1086 if (options) { 1095 if (options) {
1087 if (options.additionalAttributes.state) { 1096 if (options.additionalAttributes.state === true) {
1088 videoObject.state = { 1097 videoObject.state = {
1089 id: this.state, 1098 id: this.state,
1090 label: VideoModel.getStateLabel(this.state) 1099 label: VideoModel.getStateLabel(this.state)
1091 } 1100 }
1092 } 1101 }
1093 1102
1094 if (options.additionalAttributes.waitTranscoding) { 1103 if (options.additionalAttributes.waitTranscoding === true) {
1095 videoObject.waitTranscoding = this.waitTranscoding 1104 videoObject.waitTranscoding = this.waitTranscoding
1096 } 1105 }
1097 1106
1098 if (options.additionalAttributes.scheduledUpdate && this.ScheduleVideoUpdate) { 1107 if (options.additionalAttributes.scheduledUpdate === true && this.ScheduleVideoUpdate) {
1099 videoObject.scheduledUpdate = { 1108 videoObject.scheduledUpdate = {
1100 updateAt: this.ScheduleVideoUpdate.updateAt, 1109 updateAt: this.ScheduleVideoUpdate.updateAt,
1101 privacy: this.ScheduleVideoUpdate.privacy || undefined 1110 privacy: this.ScheduleVideoUpdate.privacy || undefined
@@ -1107,7 +1116,11 @@ export class VideoModel extends Model<VideoModel> {
1107 } 1116 }
1108 1117
1109 toFormattedDetailsJSON (): VideoDetails { 1118 toFormattedDetailsJSON (): VideoDetails {
1110 const formattedJson = this.toFormattedJSON() 1119 const formattedJson = this.toFormattedJSON({
1120 additionalAttributes: {
1121 scheduledUpdate: true
1122 }
1123 })
1111 1124
1112 const detailsJson = { 1125 const detailsJson = {
1113 support: this.support, 1126 support: this.support,
diff --git a/server/tests/api/check-params/videos.ts b/server/tests/api/check-params/videos.ts
index 04bed3b44..abbea6ba3 100644
--- a/server/tests/api/check-params/videos.ts
+++ b/server/tests/api/check-params/videos.ts
@@ -291,6 +291,23 @@ describe('Test videos API validator', function () {
291 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) 291 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
292 }) 292 })
293 293
294 it('Should fail with a bad schedule update (miss updateAt)', async function () {
295 const fields = immutableAssign(baseCorrectParams, { 'scheduleUpdate[privacy]': VideoPrivacy.PUBLIC })
296 const attaches = baseCorrectAttaches
297
298 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
299 })
300
301 it('Should fail with a bad schedule update (wrong updateAt)', async function () {
302 const fields = immutableAssign(baseCorrectParams, {
303 'scheduleUpdate[privacy]': VideoPrivacy.PUBLIC,
304 'scheduleUpdate[updateAt]': 'toto'
305 })
306 const attaches = baseCorrectAttaches
307
308 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
309 })
310
294 it('Should fail without an input file', async function () { 311 it('Should fail without an input file', async function () {
295 const fields = baseCorrectParams 312 const fields = baseCorrectParams
296 const attaches = {} 313 const attaches = {}
@@ -494,6 +511,18 @@ describe('Test videos API validator', function () {
494 await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields }) 511 await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields })
495 }) 512 })
496 513
514 it('Should fail with a bad schedule update (miss updateAt)', async function () {
515 const fields = immutableAssign(baseCorrectParams, { scheduleUpdate: { privacy: VideoPrivacy.PUBLIC } })
516
517 await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields })
518 })
519
520 it('Should fail with a bad schedule update (wrong updateAt)', async function () {
521 const fields = immutableAssign(baseCorrectParams, { scheduleUpdate: { updateAt: 'toto', privacy: VideoPrivacy.PUBLIC } })
522
523 await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields })
524 })
525
497 it('Should fail with an incorrect thumbnail file', async function () { 526 it('Should fail with an incorrect thumbnail file', async function () {
498 const fields = baseCorrectParams 527 const fields = baseCorrectParams
499 const attaches = { 528 const attaches = {
diff --git a/server/tests/api/videos/video-schedule-update.ts b/server/tests/api/videos/video-schedule-update.ts
index 8b87ea855..a260fa4da 100644
--- a/server/tests/api/videos/video-schedule-update.ts
+++ b/server/tests/api/videos/video-schedule-update.ts
@@ -5,11 +5,14 @@ import 'mocha'
5import { VideoPrivacy } from '../../../../shared/models/videos' 5import { VideoPrivacy } from '../../../../shared/models/videos'
6import { 6import {
7 doubleFollow, 7 doubleFollow,
8 flushAndRunMultipleServers, getMyVideos, 8 flushAndRunMultipleServers,
9 getMyVideos,
9 getVideosList, 10 getVideosList,
11 getVideoWithToken,
10 killallServers, 12 killallServers,
11 ServerInfo, 13 ServerInfo,
12 setAccessTokensToServers, updateVideo, 14 setAccessTokensToServers,
15 updateVideo,
13 uploadVideo, 16 uploadVideo,
14 wait 17 wait
15} from '../../utils' 18} from '../../utils'
@@ -69,17 +72,22 @@ describe('Test video update scheduler', function () {
69 const res = await getMyVideos(servers[0].url, servers[0].accessToken, 0, 5) 72 const res = await getMyVideos(servers[0].url, servers[0].accessToken, 0, 5)
70 expect(res.body.total).to.equal(1) 73 expect(res.body.total).to.equal(1)
71 74
72 const video = res.body.data[0] 75 const videoFromList = res.body.data[0]
73 expect(video.name).to.equal('video 1') 76 const res2 = await getVideoWithToken(servers[0].url, servers[0].accessToken, videoFromList.uuid)
74 expect(video.privacy.id).to.equal(VideoPrivacy.PRIVATE) 77 const videoFromGet = res2.body
75 expect(new Date(video.scheduledUpdate.updateAt)).to.be.above(new Date()) 78
76 expect(video.scheduledUpdate.privacy).to.equal(VideoPrivacy.PUBLIC) 79 for (const video of [ videoFromList, videoFromGet ]) {
80 expect(video.name).to.equal('video 1')
81 expect(video.privacy.id).to.equal(VideoPrivacy.PRIVATE)
82 expect(new Date(video.scheduledUpdate.updateAt)).to.be.above(new Date())
83 expect(video.scheduledUpdate.privacy).to.equal(VideoPrivacy.PUBLIC)
84 }
77 }) 85 })
78 86
79 it('Should wait some seconds and have the video in public privacy', async function () { 87 it('Should wait some seconds and have the video in public privacy', async function () {
80 this.timeout(20000) 88 this.timeout(20000)
81 89
82 await wait(10000) 90 await wait(15000)
83 await waitJobs(servers) 91 await waitJobs(servers)
84 92
85 for (const server of servers) { 93 for (const server of servers) {
@@ -144,7 +152,7 @@ describe('Test video update scheduler', function () {
144 it('Should wait some seconds and have the updated video in public privacy', async function () { 152 it('Should wait some seconds and have the updated video in public privacy', async function () {
145 this.timeout(20000) 153 this.timeout(20000)
146 154
147 await wait(10000) 155 await wait(15000)
148 await waitJobs(servers) 156 await waitJobs(servers)
149 157
150 for (const server of servers) { 158 for (const server of servers) {