diff options
-rw-r--r-- | client/src/app/shared/misc/utils.ts | 5 | ||||
-rw-r--r-- | client/src/app/videos/+video-edit/shared/video-edit.component.ts | 2 | ||||
-rw-r--r-- | client/src/assets/player/peertube-chunk-store.ts | 59 | ||||
-rw-r--r-- | server/controllers/api/videos/index.ts | 4 | ||||
-rw-r--r-- | server/helpers/custom-validators/misc.ts | 4 | ||||
-rw-r--r-- | server/helpers/custom-validators/videos.ts | 8 | ||||
-rw-r--r-- | server/middlewares/validators/videos.ts | 16 | ||||
-rw-r--r-- | server/models/video/tag.ts | 2 |
8 files changed, 61 insertions, 39 deletions
diff --git a/client/src/app/shared/misc/utils.ts b/client/src/app/shared/misc/utils.ts index b5bf99be2..b9aa223cf 100644 --- a/client/src/app/shared/misc/utils.ts +++ b/client/src/app/shared/misc/utils.ts | |||
@@ -66,6 +66,11 @@ function objectToFormData (obj: any, form?: FormData, namespace?: string) { | |||
66 | 66 | ||
67 | if (obj[key] === undefined) continue | 67 | if (obj[key] === undefined) continue |
68 | 68 | ||
69 | if (Array.isArray(obj[key]) && obj[key].length === 0) { | ||
70 | fd.append(key, null) | ||
71 | continue | ||
72 | } | ||
73 | |||
69 | if (obj[key] !== null && typeof obj[ key ] === 'object' && !(obj[ key ] instanceof File)) { | 74 | if (obj[key] !== null && typeof obj[ key ] === 'object' && !(obj[ key ] instanceof File)) { |
70 | objectToFormData(obj[ key ], fd, key) | 75 | objectToFormData(obj[ key ], fd, key) |
71 | } else { | 76 | } else { |
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.ts b/client/src/app/videos/+video-edit/shared/video-edit.component.ts index af4438bd2..d4567e26c 100644 --- a/client/src/app/videos/+video-edit/shared/video-edit.component.ts +++ b/client/src/app/videos/+video-edit/shared/video-edit.component.ts | |||
@@ -80,7 +80,7 @@ export class VideoEditComponent implements OnInit { | |||
80 | this.form.addControl('licence', new FormControl('', VIDEO_LICENCE.VALIDATORS)) | 80 | this.form.addControl('licence', new FormControl('', VIDEO_LICENCE.VALIDATORS)) |
81 | this.form.addControl('language', new FormControl('', VIDEO_LANGUAGE.VALIDATORS)) | 81 | this.form.addControl('language', new FormControl('', VIDEO_LANGUAGE.VALIDATORS)) |
82 | this.form.addControl('description', new FormControl('', VIDEO_DESCRIPTION.VALIDATORS)) | 82 | this.form.addControl('description', new FormControl('', VIDEO_DESCRIPTION.VALIDATORS)) |
83 | this.form.addControl('tags', new FormControl('')) | 83 | this.form.addControl('tags', new FormControl([])) |
84 | this.form.addControl('thumbnailfile', new FormControl('')) | 84 | this.form.addControl('thumbnailfile', new FormControl('')) |
85 | this.form.addControl('previewfile', new FormControl('')) | 85 | this.form.addControl('previewfile', new FormControl('')) |
86 | this.form.addControl('support', new FormControl('', VIDEO_SUPPORT.VALIDATORS)) | 86 | this.form.addControl('support', new FormControl('', VIDEO_SUPPORT.VALIDATORS)) |
diff --git a/client/src/assets/player/peertube-chunk-store.ts b/client/src/assets/player/peertube-chunk-store.ts index 84fbaf146..e14e31c04 100644 --- a/client/src/assets/player/peertube-chunk-store.ts +++ b/client/src/assets/player/peertube-chunk-store.ts | |||
@@ -155,17 +155,17 @@ export class PeertubeChunkStore extends EventEmitter { | |||
155 | this.cleanerInterval = null | 155 | this.cleanerInterval = null |
156 | } | 156 | } |
157 | 157 | ||
158 | if (this.db) { | ||
159 | await this.db.close() | ||
160 | |||
161 | await this.dropDatabase(this.databaseName) | ||
162 | } | ||
163 | |||
158 | if (this.expirationDB) { | 164 | if (this.expirationDB) { |
159 | await this.expirationDB.close() | 165 | await this.expirationDB.close() |
160 | this.expirationDB = null | 166 | this.expirationDB = null |
161 | } | 167 | } |
162 | 168 | ||
163 | if (this.db) { | ||
164 | console.log('Destroying IndexDB database %s.', this.databaseName) | ||
165 | await this.db.close() | ||
166 | await this.db.delete() | ||
167 | } | ||
168 | |||
169 | return cb() | 169 | return cb() |
170 | } catch (err) { | 170 | } catch (err) { |
171 | console.error('Cannot destroy peertube chunk store.', err) | 171 | console.error('Cannot destroy peertube chunk store.', err) |
@@ -181,31 +181,42 @@ export class PeertubeChunkStore extends EventEmitter { | |||
181 | }, PeertubeChunkStore.CLEANER_INTERVAL_MS) | 181 | }, PeertubeChunkStore.CLEANER_INTERVAL_MS) |
182 | } | 182 | } |
183 | 183 | ||
184 | private checkExpiration () { | 184 | private async checkExpiration () { |
185 | this.expirationDB.transaction('rw', this.expirationDB.databases, async () => { | 185 | let databasesToDeleteInfo: { name: string }[] = [] |
186 | // Update our database expiration since we are alive | ||
187 | await this.expirationDB.databases.put({ | ||
188 | name: this.databaseName, | ||
189 | expiration: new Date().getTime() + PeertubeChunkStore.CLEANER_EXPIRATION_MS | ||
190 | }) | ||
191 | 186 | ||
192 | const now = new Date().getTime() | 187 | try { |
193 | const databasesToDeleteInfo = await this.expirationDB.databases.where('expiration').below(now).toArray() | 188 | await this.expirationDB.transaction('rw', this.expirationDB.databases, async () => { |
189 | // Update our database expiration since we are alive | ||
190 | await this.expirationDB.databases.put({ | ||
191 | name: this.databaseName, | ||
192 | expiration: new Date().getTime() + PeertubeChunkStore.CLEANER_EXPIRATION_MS | ||
193 | }) | ||
194 | 194 | ||
195 | for (const databaseToDeleteInfo of databasesToDeleteInfo) { | 195 | const now = new Date().getTime() |
196 | await this.dropDatabase(databaseToDeleteInfo.name) | 196 | databasesToDeleteInfo = await this.expirationDB.databases.where('expiration').below(now).toArray() |
197 | }) | ||
198 | } catch (err) { | ||
199 | console.error('Cannot update expiration of fetch expired databases.', err) | ||
200 | } | ||
197 | 201 | ||
198 | await this.expirationDB.databases.where({ name: databaseToDeleteInfo.name }).delete() | 202 | for (const databaseToDeleteInfo of databasesToDeleteInfo) { |
199 | } | 203 | await this.dropDatabase(databaseToDeleteInfo.name) |
200 | }).catch(err => console.error('Cannot check expiration.', err)) | 204 | } |
201 | } | 205 | } |
202 | 206 | ||
203 | private dropDatabase (databaseName: string) { | 207 | private async dropDatabase (databaseName: string) { |
204 | const dbToDelete = new ChunkDatabase(databaseName) | 208 | const dbToDelete = new ChunkDatabase(databaseName) |
209 | console.log('Destroying IndexDB database %s.', databaseName) | ||
205 | 210 | ||
206 | console.log('Deleting %s.', databaseName) | 211 | try { |
207 | return dbToDelete.delete() | 212 | await dbToDelete.delete() |
208 | .catch(err => console.error('Cannot delete %s.', databaseName)) | 213 | |
214 | await this.expirationDB.transaction('rw', this.expirationDB.databases, () => { | ||
215 | return this.expirationDB.databases.where({ name: databaseName }).delete() | ||
216 | }) | ||
217 | } catch (err) { | ||
218 | console.error('Cannot delete %s.', databaseName, err) | ||
219 | } | ||
209 | } | 220 | } |
210 | 221 | ||
211 | private nextTick (cb, err, val?) { | 222 | private nextTick (cb, err, val?) { |
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index c07430e6c..bcf1eaee6 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts | |||
@@ -244,7 +244,7 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi | |||
244 | 244 | ||
245 | video.VideoFiles = [ videoFile ] | 245 | video.VideoFiles = [ videoFile ] |
246 | 246 | ||
247 | if (videoInfo.tags) { | 247 | if (videoInfo.tags !== undefined) { |
248 | const tagInstances = await TagModel.findOrCreateTags(videoInfo.tags, t) | 248 | const tagInstances = await TagModel.findOrCreateTags(videoInfo.tags, t) |
249 | 249 | ||
250 | await video.$set('Tags', tagInstances, sequelizeOptions) | 250 | await video.$set('Tags', tagInstances, sequelizeOptions) |
@@ -332,7 +332,7 @@ async function updateVideo (req: express.Request, res: express.Response) { | |||
332 | const videoInstanceUpdated = await videoInstance.save(sequelizeOptions) | 332 | const videoInstanceUpdated = await videoInstance.save(sequelizeOptions) |
333 | 333 | ||
334 | // Video tags update? | 334 | // Video tags update? |
335 | if (videoInfoToUpdate.tags) { | 335 | if (videoInfoToUpdate.tags !== undefined) { |
336 | const tagInstances = await TagModel.findOrCreateTags(videoInfoToUpdate.tags, t) | 336 | const tagInstances = await TagModel.findOrCreateTags(videoInfoToUpdate.tags, t) |
337 | 337 | ||
338 | await videoInstanceUpdated.$set('Tags', tagInstances, sequelizeOptions) | 338 | await videoInstanceUpdated.$set('Tags', tagInstances, sequelizeOptions) |
diff --git a/server/helpers/custom-validators/misc.ts b/server/helpers/custom-validators/misc.ts index 275482fa1..254b4db6c 100644 --- a/server/helpers/custom-validators/misc.ts +++ b/server/helpers/custom-validators/misc.ts | |||
@@ -35,7 +35,7 @@ function toIntOrNull (value: string) { | |||
35 | return validator.toInt(value) | 35 | return validator.toInt(value) |
36 | } | 36 | } |
37 | 37 | ||
38 | function toStringOrNull (value: string) { | 38 | function toValueOrNull (value: string) { |
39 | if (value === 'null') return null | 39 | if (value === 'null') return null |
40 | 40 | ||
41 | return value | 41 | return value |
@@ -73,7 +73,7 @@ export { | |||
73 | isUUIDValid, | 73 | isUUIDValid, |
74 | isIdOrUUIDValid, | 74 | isIdOrUUIDValid, |
75 | isDateValid, | 75 | isDateValid, |
76 | toStringOrNull, | 76 | toValueOrNull, |
77 | isBooleanValid, | 77 | isBooleanValid, |
78 | toIntOrNull, | 78 | toIntOrNull, |
79 | isFileValid | 79 | isFileValid |
diff --git a/server/helpers/custom-validators/videos.ts b/server/helpers/custom-validators/videos.ts index c35db49ac..002324fe0 100644 --- a/server/helpers/custom-validators/videos.ts +++ b/server/helpers/custom-validators/videos.ts | |||
@@ -57,9 +57,11 @@ function isVideoTagValid (tag: string) { | |||
57 | } | 57 | } |
58 | 58 | ||
59 | function isVideoTagsValid (tags: string[]) { | 59 | function isVideoTagsValid (tags: string[]) { |
60 | return isArray(tags) && | 60 | return tags === null || ( |
61 | validator.isInt(tags.length.toString(), VIDEOS_CONSTRAINTS_FIELDS.TAGS) && | 61 | isArray(tags) && |
62 | tags.every(tag => isVideoTagValid(tag)) | 62 | validator.isInt(tags.length.toString(), VIDEOS_CONSTRAINTS_FIELDS.TAGS) && |
63 | tags.every(tag => isVideoTagValid(tag)) | ||
64 | ) | ||
63 | } | 65 | } |
64 | 66 | ||
65 | function isVideoAbuseReasonValid (value: string) { | 67 | function isVideoAbuseReasonValid (value: string) { |
diff --git a/server/middlewares/validators/videos.ts b/server/middlewares/validators/videos.ts index aa2afb068..dd0246a63 100644 --- a/server/middlewares/validators/videos.ts +++ b/server/middlewares/validators/videos.ts | |||
@@ -2,7 +2,7 @@ import * as express from 'express' | |||
2 | import 'express-validator' | 2 | import 'express-validator' |
3 | import { body, param, query } from 'express-validator/check' | 3 | import { body, param, query } from 'express-validator/check' |
4 | import { UserRight, VideoPrivacy } from '../../../shared' | 4 | import { UserRight, VideoPrivacy } from '../../../shared' |
5 | import { isBooleanValid, isIdOrUUIDValid, isIdValid, isUUIDValid, toIntOrNull, toStringOrNull } from '../../helpers/custom-validators/misc' | 5 | import { isBooleanValid, isIdOrUUIDValid, isIdValid, isUUIDValid, toIntOrNull, toValueOrNull } from '../../helpers/custom-validators/misc' |
6 | import { | 6 | import { |
7 | isVideoAbuseReasonValid, | 7 | isVideoAbuseReasonValid, |
8 | isVideoCategoryValid, | 8 | isVideoCategoryValid, |
@@ -52,21 +52,22 @@ const videosAddValidator = [ | |||
52 | .custom(isVideoLicenceValid).withMessage('Should have a valid licence'), | 52 | .custom(isVideoLicenceValid).withMessage('Should have a valid licence'), |
53 | body('language') | 53 | body('language') |
54 | .optional() | 54 | .optional() |
55 | .customSanitizer(toStringOrNull) | 55 | .customSanitizer(toValueOrNull) |
56 | .custom(isVideoLanguageValid).withMessage('Should have a valid language'), | 56 | .custom(isVideoLanguageValid).withMessage('Should have a valid language'), |
57 | body('nsfw') | 57 | body('nsfw') |
58 | .toBoolean() | 58 | .toBoolean() |
59 | .custom(isBooleanValid).withMessage('Should have a valid NSFW attribute'), | 59 | .custom(isBooleanValid).withMessage('Should have a valid NSFW attribute'), |
60 | body('description') | 60 | body('description') |
61 | .optional() | 61 | .optional() |
62 | .customSanitizer(toStringOrNull) | 62 | .customSanitizer(toValueOrNull) |
63 | .custom(isVideoDescriptionValid).withMessage('Should have a valid description'), | 63 | .custom(isVideoDescriptionValid).withMessage('Should have a valid description'), |
64 | body('support') | 64 | body('support') |
65 | .optional() | 65 | .optional() |
66 | .customSanitizer(toStringOrNull) | 66 | .customSanitizer(toValueOrNull) |
67 | .custom(isVideoSupportValid).withMessage('Should have a valid support text'), | 67 | .custom(isVideoSupportValid).withMessage('Should have a valid support text'), |
68 | body('tags') | 68 | body('tags') |
69 | .optional() | 69 | .optional() |
70 | .customSanitizer(toValueOrNull) | ||
70 | .custom(isVideoTagsValid).withMessage('Should have correct tags'), | 71 | .custom(isVideoTagsValid).withMessage('Should have correct tags'), |
71 | body('commentsEnabled') | 72 | body('commentsEnabled') |
72 | .toBoolean() | 73 | .toBoolean() |
@@ -142,7 +143,7 @@ const videosUpdateValidator = [ | |||
142 | .custom(isVideoLicenceValid).withMessage('Should have a valid licence'), | 143 | .custom(isVideoLicenceValid).withMessage('Should have a valid licence'), |
143 | body('language') | 144 | body('language') |
144 | .optional() | 145 | .optional() |
145 | .customSanitizer(toStringOrNull) | 146 | .customSanitizer(toValueOrNull) |
146 | .custom(isVideoLanguageValid).withMessage('Should have a valid language'), | 147 | .custom(isVideoLanguageValid).withMessage('Should have a valid language'), |
147 | body('nsfw') | 148 | body('nsfw') |
148 | .optional() | 149 | .optional() |
@@ -154,14 +155,15 @@ const videosUpdateValidator = [ | |||
154 | .custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'), | 155 | .custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'), |
155 | body('description') | 156 | body('description') |
156 | .optional() | 157 | .optional() |
157 | .customSanitizer(toStringOrNull) | 158 | .customSanitizer(toValueOrNull) |
158 | .custom(isVideoDescriptionValid).withMessage('Should have a valid description'), | 159 | .custom(isVideoDescriptionValid).withMessage('Should have a valid description'), |
159 | body('support') | 160 | body('support') |
160 | .optional() | 161 | .optional() |
161 | .customSanitizer(toStringOrNull) | 162 | .customSanitizer(toValueOrNull) |
162 | .custom(isVideoSupportValid).withMessage('Should have a valid support text'), | 163 | .custom(isVideoSupportValid).withMessage('Should have a valid support text'), |
163 | body('tags') | 164 | body('tags') |
164 | .optional() | 165 | .optional() |
166 | .customSanitizer(toValueOrNull) | ||
165 | .custom(isVideoTagsValid).withMessage('Should have correct tags'), | 167 | .custom(isVideoTagsValid).withMessage('Should have correct tags'), |
166 | body('commentsEnabled') | 168 | body('commentsEnabled') |
167 | .optional() | 169 | .optional() |
diff --git a/server/models/video/tag.ts b/server/models/video/tag.ts index 0ae74d808..6d79a5575 100644 --- a/server/models/video/tag.ts +++ b/server/models/video/tag.ts | |||
@@ -37,6 +37,8 @@ export class TagModel extends Model<TagModel> { | |||
37 | Videos: VideoModel[] | 37 | Videos: VideoModel[] |
38 | 38 | ||
39 | static findOrCreateTags (tags: string[], transaction: Transaction) { | 39 | static findOrCreateTags (tags: string[], transaction: Transaction) { |
40 | if (tags === null) return [] | ||
41 | |||
40 | const tasks: Bluebird<TagModel>[] = [] | 42 | const tasks: Bluebird<TagModel>[] = [] |
41 | tags.forEach(tag => { | 43 | tags.forEach(tag => { |
42 | const query = { | 44 | const query = { |