diff --git a/server/initializers/migrations/0120-video-null.ts b/server/initializers/migrations/0120-video-null.ts new file mode 100644 index 000000000..3506a5046 --- /dev/null +++ b/server/initializers/migrations/0120-video-null.ts @@ -0,0 +1,46 @@ +import * as Sequelize from 'sequelize' +import { PeerTubeDatabase } from '../database' + +async function up (utils: { + transaction: Sequelize.Transaction, + queryInterface: Sequelize.QueryInterface, + sequelize: Sequelize.Sequelize, + db: PeerTubeDatabase +}): Promise { + + { + const data = { + type: Sequelize.INTEGER, + allowNull: true, + defaultValue: null + } + await utils.queryInterface.changeColumn('Videos', 'licence', data) + } + + { + const data = { + type: Sequelize.INTEGER, + allowNull: true, + defaultValue: null + } + await utils.queryInterface.changeColumn('Videos', 'category', data) + } + + { + const data = { + type: Sequelize.INTEGER, + allowNull: true, + defaultValue: null + } + await utils.queryInterface.changeColumn('Videos', 'description', data) + } +} + +function down (options) { + throw new Error('Not implemented.') +} + +export { + up, + down +} diff --git a/server/middlewares/validators/videos.ts b/server/middlewares/validators/videos.ts index ee2ac50c8..10625e41d 100644 --- a/server/middlewares/validators/videos.ts +++ b/server/middlewares/validators/videos.ts @@ -31,11 +31,11 @@ const videosAddValidator = [ + CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ') ), body('name').custom(isVideoNameValid).withMessage('Should have a valid name'), - body('category').custom(isVideoCategoryValid).withMessage('Should have a valid category'), - body('licence').custom(isVideoLicenceValid).withMessage('Should have a valid licence'), + body('category').optional().custom(isVideoCategoryValid).withMessage('Should have a valid category'), + body('licence').optional().custom(isVideoLicenceValid).withMessage('Should have a valid licence'), body('language').optional().custom(isVideoLanguageValid).withMessage('Should have a valid language'), body('nsfw').custom(isVideoNSFWValid).withMessage('Should have a valid NSFW attribute'), - body('description').custom(isVideoDescriptionValid).withMessage('Should have a valid description'), + body('description').optional().custom(isVideoDescriptionValid).withMessage('Should have a valid description'), body('channelId').custom(isIdValid).withMessage('Should have correct video channel id'), body('privacy').custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'), body('tags').optional().custom(isVideoTagsValid).withMessage('Should have correct tags'), diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 60023bc8c..8b1eb1f96 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts @@ -104,7 +104,8 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da }, category: { type: DataTypes.INTEGER, - allowNull: false, + allowNull: true, + defaultValue: null, validate: { categoryValid: value => { const res = isVideoCategoryValid(value) @@ -114,7 +115,7 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da }, licence: { type: DataTypes.INTEGER, - allowNull: false, + allowNull: true, defaultValue: null, validate: { licenceValid: value => { @@ -126,6 +127,7 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da language: { type: DataTypes.INTEGER, allowNull: true, + defaultValue: null, validate: { languageValid: value => { const res = isVideoLanguageValid(value) @@ -155,7 +157,8 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da }, description: { type: DataTypes.STRING(CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.max), - allowNull: false, + allowNull: true, + defaultValue: null, validate: { descriptionValid: value => { const res = isVideoDescriptionValid(value) @@ -664,6 +667,8 @@ toActivityPubObject = function (this: VideoInstance) { } getTruncatedDescription = function (this: VideoInstance) { + if (!this.description) return null + const options = { length: CONSTRAINTS_FIELDS.VIDEOS.TRUNCATED_DESCRIPTION.max } @@ -754,8 +759,6 @@ getDescriptionPath = function (this: VideoInstance) { getCategoryLabel = function (this: VideoInstance) { let categoryLabel = VIDEO_CATEGORIES[this.category] - - // Maybe our server is not up to date and there are new categories since our version if (!categoryLabel) categoryLabel = 'Misc' return categoryLabel @@ -763,15 +766,12 @@ getCategoryLabel = function (this: VideoInstance) { getLicenceLabel = function (this: VideoInstance) { let licenceLabel = VIDEO_LICENCES[this.licence] - - // Maybe our server is not up to date and there are new licences since our version if (!licenceLabel) licenceLabel = 'Unknown' return licenceLabel } getLanguageLabel = function (this: VideoInstance) { - // Language is an optional attribute let languageLabel = VIDEO_LANGUAGES[this.language] if (!languageLabel) languageLabel = 'Unknown' diff --git a/server/tests/api/check-params/videos.ts b/server/tests/api/check-params/videos.ts index 2962f5640..00a209665 100644 --- a/server/tests/api/check-params/videos.ts +++ b/server/tests/api/check-params/videos.ts @@ -189,14 +189,6 @@ describe('Test videos API validator', function () { await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) }) - it('Should fail without a category', async function () { - const fields = getCompleteVideoUploadAttributes() - delete fields.category - - const attaches = getVideoUploadAttaches - await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) - }) - it('Should fail with a bad category', async function () { const fields = getCompleteVideoUploadAttributes() fields.category = 125 @@ -205,14 +197,6 @@ describe('Test videos API validator', function () { await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) }) - it('Should fail without a licence', async function () { - const fields = getCompleteVideoUploadAttributes() - delete fields.licence - - const attaches = getVideoUploadAttaches() - await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) - }) - it('Should fail with a bad licence', async function () { const fields = getCompleteVideoUploadAttributes() fields.licence = 125 @@ -245,14 +229,6 @@ describe('Test videos API validator', function () { await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) }) - it('Should fail without description', async function () { - const fields = getCompleteVideoUploadAttributes() - delete fields.description - - const attaches = getVideoUploadAttaches() - await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) - }) - it('Should fail with a long description', async function () { const fields = getCompleteVideoUploadAttributes() fields.description = 'my super description which is very very very very very very very very very very very very long'.repeat(35) diff --git a/server/tests/api/single-server.ts b/server/tests/api/single-server.ts index d7e9ad41f..fbb2dd1fb 100644 --- a/server/tests/api/single-server.ts +++ b/server/tests/api/single-server.ts @@ -1,40 +1,41 @@ /* tslint:disable:no-unused-expression */ -import { keyBy } from 'lodash' -import { join } from 'path' -import 'mocha' import * as chai from 'chai' -const expect = chai.expect - +import { keyBy } from 'lodash' +import 'mocha' +import { join } from 'path' +import * as request from 'supertest' import { - ServerInfo, - flushTests, - runServer, - uploadVideo, - getVideosList, - rateVideo, - removeVideo, - wait, - setAccessTokensToServers, - searchVideo, - killallServers, dateIsValid, - getVideoCategories, - getVideoLicences, - getVideoLanguages, - getVideoPrivacies, - testVideoImage, - webtorrentAdd, + flushTests, getVideo, - readdirPromise, + getVideoCategories, + getVideoLanguages, + getVideoLicences, + getVideoPrivacies, + getVideosList, getVideosListPagination, - searchVideoWithPagination, getVideosListSort, + killallServers, + rateVideo, + readdirPromise, + removeVideo, + runServer, + searchVideo, + searchVideoWithPagination, searchVideoWithSort, - updateVideo + ServerInfo, + setAccessTokensToServers, + testVideoImage, + updateVideo, + uploadVideo, + wait, + webtorrentAdd } from '../utils' import { viewVideo } from '../utils/videos' +const expect = chai.expect + describe('Test a single server', function () { let server: ServerInfo = null let videoId = -1 @@ -693,6 +694,43 @@ describe('Test a single server', function () { expect(video.dislikes).to.equal(1) }) + it('Should upload a video with minimum parameters', async function () { + const path = '/api/v1/videos/upload' + + const req = request(server.url) + .post(path) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + server.accessToken) + .field('name', 'minimum parameters') + .field('privacy', '1') + .field('nsfw', 'false') + .field('channelId', '1') + + const filePath = join(__dirname, '..', 'api', 'fixtures', 'video_short.webm') + + await req.attach('videofile', filePath) + .expect(204) + + const res = await getVideosList(server.url) + const video = res.body.data.find(v => v.name === 'minimum parameters') + + expect(video.name).to.equal('minimum parameters') + expect(video.category).to.equal(null) + expect(video.categoryLabel).to.equal('Misc') + expect(video.licence).to.equal(null) + expect(video.licenceLabel).to.equal('Unknown') + expect(video.language).to.equal(null) + expect(video.languageLabel).to.equal('Unknown') + expect(video.nsfw).to.not.be.ok + expect(video.description).to.equal(null) + expect(video.serverHost).to.equal('localhost:9001') + expect(video.accountName).to.equal('root') + expect(video.isLocal).to.be.true + expect(video.tags).to.deep.equal([ ]) + expect(dateIsValid(video.createdAt)).to.be.true + expect(dateIsValid(video.updatedAt)).to.be.true + }) + after(async function () { killallServers([ server ])