aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/controllers/api/videos/index.ts13
-rw-r--r--server/controllers/api/videos/upload.ts7
-rw-r--r--server/initializers/constants.ts2
-rw-r--r--server/initializers/database.ts2
-rw-r--r--server/initializers/migrations/0715-video-source.ts34
-rw-r--r--server/middlewares/validators/videos/index.ts1
-rw-r--r--server/middlewares/validators/videos/video-source.ts37
-rw-r--r--server/middlewares/validators/videos/videos.ts2
-rw-r--r--server/models/video/video-source.ts55
-rw-r--r--server/models/video/video.ts10
-rw-r--r--server/tests/api/check-params/index.ts11
-rw-r--r--server/tests/api/check-params/video-source.ts44
-rw-r--r--server/tests/api/videos/index.ts1
-rw-r--r--server/tests/api/videos/video-source.ts39
-rw-r--r--server/types/express.d.ts6
-rw-r--r--server/types/models/video/video-source.ts3
16 files changed, 259 insertions, 8 deletions
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index be233722c..d4e08293e 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -26,6 +26,7 @@ import {
26 setDefaultVideosSort, 26 setDefaultVideosSort,
27 videosCustomGetValidator, 27 videosCustomGetValidator,
28 videosGetValidator, 28 videosGetValidator,
29 videoSourceGetValidator,
29 videosRemoveValidator, 30 videosRemoveValidator,
30 videosSortValidator 31 videosSortValidator
31} from '../../../middlewares' 32} from '../../../middlewares'
@@ -96,6 +97,14 @@ videosRouter.get('/:id/description',
96 asyncMiddleware(videosGetValidator), 97 asyncMiddleware(videosGetValidator),
97 asyncMiddleware(getVideoDescription) 98 asyncMiddleware(getVideoDescription)
98) 99)
100
101videosRouter.get('/:id/source',
102 openapiOperationDoc({ operationId: 'getVideoSource' }),
103 authenticate,
104 asyncMiddleware(videoSourceGetValidator),
105 getVideoSource
106)
107
99videosRouter.get('/:id', 108videosRouter.get('/:id',
100 openapiOperationDoc({ operationId: 'getVideo' }), 109 openapiOperationDoc({ operationId: 'getVideo' }),
101 optionalAuthenticate, 110 optionalAuthenticate,
@@ -155,6 +164,10 @@ async function getVideoDescription (req: express.Request, res: express.Response)
155 return res.json({ description }) 164 return res.json({ description })
156} 165}
157 166
167function getVideoSource (req: express.Request, res: express.Response) {
168 return res.json(res.locals.videoSource.toFormattedJSON())
169}
170
158async function listVideos (req: express.Request, res: express.Response) { 171async function listVideos (req: express.Request, res: express.Response) {
159 const serverActor = await getServerActor() 172 const serverActor = await getServerActor()
160 173
diff --git a/server/controllers/api/videos/upload.ts b/server/controllers/api/videos/upload.ts
index 3afbedbb2..c5890691e 100644
--- a/server/controllers/api/videos/upload.ts
+++ b/server/controllers/api/videos/upload.ts
@@ -44,6 +44,7 @@ import {
44import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update' 44import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update'
45import { VideoModel } from '../../../models/video/video' 45import { VideoModel } from '../../../models/video/video'
46import { VideoFileModel } from '../../../models/video/video-file' 46import { VideoFileModel } from '../../../models/video/video-file'
47import { VideoSourceModel } from '@server/models/video/video-source'
47 48
48const lTags = loggerTagsFactory('api', 'video') 49const lTags = loggerTagsFactory('api', 'video')
49const auditLogger = auditLoggerFactory('videos') 50const auditLogger = auditLoggerFactory('videos')
@@ -151,6 +152,7 @@ async function addVideo (options: {
151 video.url = getLocalVideoActivityPubUrl(video) // We use the UUID, so set the URL after building the object 152 video.url = getLocalVideoActivityPubUrl(video) // We use the UUID, so set the URL after building the object
152 153
153 const videoFile = await buildNewFile(videoPhysicalFile) 154 const videoFile = await buildNewFile(videoPhysicalFile)
155 const originalFilename = videoPhysicalFile.originalname
154 156
155 // Move physical file 157 // Move physical file
156 const destination = VideoPathManager.Instance.getFSVideoFileOutputPath(video, videoFile) 158 const destination = VideoPathManager.Instance.getFSVideoFileOutputPath(video, videoFile)
@@ -181,6 +183,11 @@ async function addVideo (options: {
181 183
182 video.VideoFiles = [ videoFile ] 184 video.VideoFiles = [ videoFile ]
183 185
186 await VideoSourceModel.create({
187 filename: originalFilename,
188 videoId: video.id
189 }, { transaction: t })
190
184 await setVideoTags({ video, tags: videoInfo.tags, transaction: t }) 191 await setVideoTags({ video, tags: videoInfo.tags, transaction: t })
185 192
186 // Schedule an update in the future? 193 // Schedule an update in the future?
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index f54ce9506..0d7e7077d 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -24,7 +24,7 @@ import { CONFIG, registerConfigChangedHandler } from './config'
24 24
25// --------------------------------------------------------------------------- 25// ---------------------------------------------------------------------------
26 26
27const LAST_MIGRATION_VERSION = 710 27const LAST_MIGRATION_VERSION = 715
28 28
29// --------------------------------------------------------------------------- 29// ---------------------------------------------------------------------------
30 30
diff --git a/server/initializers/database.ts b/server/initializers/database.ts
index 3576f444c..09786a91f 100644
--- a/server/initializers/database.ts
+++ b/server/initializers/database.ts
@@ -49,6 +49,7 @@ import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-pla
49import { VideoTagModel } from '../models/video/video-tag' 49import { VideoTagModel } from '../models/video/video-tag'
50import { VideoViewModel } from '../models/view/video-view' 50import { VideoViewModel } from '../models/view/video-view'
51import { CONFIG } from './config' 51import { CONFIG } from './config'
52import { VideoSourceModel } from '@server/models/video/video-source'
52 53
53require('pg').defaults.parseInt8 = true // Avoid BIGINT to be converted to string 54require('pg').defaults.parseInt8 = true // Avoid BIGINT to be converted to string
54 55
@@ -126,6 +127,7 @@ async function initDatabaseModels (silent: boolean) {
126 VideoChannelModel, 127 VideoChannelModel,
127 VideoShareModel, 128 VideoShareModel,
128 VideoFileModel, 129 VideoFileModel,
130 VideoSourceModel,
129 VideoCaptionModel, 131 VideoCaptionModel,
130 VideoBlacklistModel, 132 VideoBlacklistModel,
131 VideoTagModel, 133 VideoTagModel,
diff --git a/server/initializers/migrations/0715-video-source.ts b/server/initializers/migrations/0715-video-source.ts
new file mode 100644
index 000000000..efcf77ebd
--- /dev/null
+++ b/server/initializers/migrations/0715-video-source.ts
@@ -0,0 +1,34 @@
1import * as Sequelize from 'sequelize'
2
3async function up (utils: {
4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize
7 db: any
8}): Promise<void> {
9 {
10 const query = `
11 CREATE TABLE IF NOT EXISTS "videoSource" (
12 "id" SERIAL ,
13 "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
14 "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
15 "filename" VARCHAR(255) DEFAULT NULL,
16 "videoId" INTEGER
17 REFERENCES "video" ("id")
18 ON DELETE CASCADE
19 ON UPDATE CASCADE,
20 PRIMARY KEY ("id")
21 );
22 `
23 await utils.sequelize.query(query)
24 }
25}
26
27function down (options) {
28 throw new Error('Not implemented.')
29}
30
31export {
32 up,
33 down
34}
diff --git a/server/middlewares/validators/videos/index.ts b/server/middlewares/validators/videos/index.ts
index bd2590bc5..1dd7b5d2e 100644
--- a/server/middlewares/validators/videos/index.ts
+++ b/server/middlewares/validators/videos/index.ts
@@ -9,6 +9,7 @@ export * from './video-ownership-changes'
9export * from './video-view' 9export * from './video-view'
10export * from './video-rates' 10export * from './video-rates'
11export * from './video-shares' 11export * from './video-shares'
12export * from './video-source'
12export * from './video-stats' 13export * from './video-stats'
13export * from './video-studio' 14export * from './video-studio'
14export * from './video-transcoding' 15export * from './video-transcoding'
diff --git a/server/middlewares/validators/videos/video-source.ts b/server/middlewares/validators/videos/video-source.ts
new file mode 100644
index 000000000..31a2f16b3
--- /dev/null
+++ b/server/middlewares/validators/videos/video-source.ts
@@ -0,0 +1,37 @@
1import express from 'express'
2import { getVideoWithAttributes } from '@server/helpers/video'
3import { VideoSourceModel } from '@server/models/video/video-source'
4import { MVideoFullLight } from '@server/types/models'
5import { HttpStatusCode, UserRight } from '@shared/models'
6import { logger } from '../../../helpers/logger'
7import { areValidationErrors, checkUserCanManageVideo, doesVideoExist, isValidVideoIdParam } from '../shared'
8
9const videoSourceGetValidator = [
10 isValidVideoIdParam('id'),
11
12 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
13 logger.debug('Checking videoSourceGet parameters', { parameters: req.params })
14
15 if (areValidationErrors(req, res)) return
16 if (!await doesVideoExist(req.params.id, res, 'for-api')) return
17
18 const video = getVideoWithAttributes(res) as MVideoFullLight
19
20 res.locals.videoSource = await VideoSourceModel.loadByVideoId(video.id)
21 if (!res.locals.videoSource) {
22 return res.fail({
23 status: HttpStatusCode.NOT_FOUND_404,
24 message: 'Video source not found'
25 })
26 }
27
28 const user = res.locals.oauth.token.User
29 if (!checkUserCanManageVideo(user, video, UserRight.UPDATE_ANY_VIDEO, res)) return
30
31 return next()
32 }
33]
34
35export {
36 videoSourceGetValidator
37}
diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts
index 0b6b8bfe5..c75c3640b 100644
--- a/server/middlewares/validators/videos/videos.ts
+++ b/server/middlewares/validators/videos/videos.ts
@@ -152,7 +152,7 @@ const videosAddResumableValidator = [
152 152
153 if (!await isVideoAccepted(req, res, file)) return cleanup() 153 if (!await isVideoAccepted(req, res, file)) return cleanup()
154 154
155 res.locals.videoFileResumable = file 155 res.locals.videoFileResumable = { ...file, originalname: file.filename }
156 156
157 return next() 157 return next()
158 } 158 }
diff --git a/server/models/video/video-source.ts b/server/models/video/video-source.ts
new file mode 100644
index 000000000..e306b160d
--- /dev/null
+++ b/server/models/video/video-source.ts
@@ -0,0 +1,55 @@
1import { Op } from 'sequelize'
2import {
3 AllowNull,
4 BelongsTo,
5 Column,
6 CreatedAt,
7 ForeignKey,
8 Model,
9 Table,
10 UpdatedAt
11} from 'sequelize-typescript'
12import { AttributesOnly } from '@shared/typescript-utils'
13import { VideoModel } from './video'
14
15@Table({
16 tableName: 'videoSource',
17 indexes: [
18 {
19 fields: [ 'videoId' ],
20 where: {
21 videoId: {
22 [Op.ne]: null
23 }
24 }
25 }
26 ]
27})
28export class VideoSourceModel extends Model<Partial<AttributesOnly<VideoSourceModel>>> {
29 @CreatedAt
30 createdAt: Date
31
32 @UpdatedAt
33 updatedAt: Date
34
35 @AllowNull(false)
36 @Column
37 filename: string
38
39 @ForeignKey(() => VideoModel)
40 @Column
41 videoId: number
42
43 @BelongsTo(() => VideoModel)
44 Video: VideoModel
45
46 static loadByVideoId (videoId) {
47 return VideoSourceModel.findOne({ where: { videoId } })
48 }
49
50 toFormattedJSON () {
51 return {
52 filename: this.filename
53 }
54 }
55}
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index e6a8d3f95..08adbced6 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -136,6 +136,7 @@ import { VideoPlaylistElementModel } from './video-playlist-element'
136import { VideoShareModel } from './video-share' 136import { VideoShareModel } from './video-share'
137import { VideoStreamingPlaylistModel } from './video-streaming-playlist' 137import { VideoStreamingPlaylistModel } from './video-streaming-playlist'
138import { VideoTagModel } from './video-tag' 138import { VideoTagModel } from './video-tag'
139import { VideoSourceModel } from './video-source'
139 140
140export enum ScopeNames { 141export enum ScopeNames {
141 FOR_API = 'FOR_API', 142 FOR_API = 'FOR_API',
@@ -597,6 +598,15 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
597 }) 598 })
598 VideoPlaylistElements: VideoPlaylistElementModel[] 599 VideoPlaylistElements: VideoPlaylistElementModel[]
599 600
601 @HasOne(() => VideoSourceModel, {
602 foreignKey: {
603 name: 'videoId',
604 allowNull: true
605 },
606 onDelete: 'CASCADE'
607 })
608 VideoSource: VideoSourceModel
609
600 @HasMany(() => VideoAbuseModel, { 610 @HasMany(() => VideoAbuseModel, {
601 foreignKey: { 611 foreignKey: {
602 name: 'videoId', 612 name: 'videoId',
diff --git a/server/tests/api/check-params/index.ts b/server/tests/api/check-params/index.ts
index 259d7e783..a27bc8509 100644
--- a/server/tests/api/check-params/index.ts
+++ b/server/tests/api/check-params/index.ts
@@ -3,14 +3,14 @@ import './accounts'
3import './blocklist' 3import './blocklist'
4import './bulk' 4import './bulk'
5import './config' 5import './config'
6import './custom-pages'
7import './contact-form' 6import './contact-form'
7import './custom-pages'
8import './debug' 8import './debug'
9import './follows' 9import './follows'
10import './jobs' 10import './jobs'
11import './live'
11import './logs' 12import './logs'
12import './my-user' 13import './my-user'
13import './live'
14import './plugins' 14import './plugins'
15import './redundancy' 15import './redundancy'
16import './search' 16import './search'
@@ -25,12 +25,13 @@ import './video-blacklist'
25import './video-captions' 25import './video-captions'
26import './video-channels' 26import './video-channels'
27import './video-comments' 27import './video-comments'
28import './video-studio' 28import './video-files'
29import './video-imports' 29import './video-imports'
30import './video-playlists' 30import './video-playlists'
31import './videos' 31import './video-source'
32import './video-studio'
32import './videos-common-filters' 33import './videos-common-filters'
33import './video-files'
34import './videos-history' 34import './videos-history'
35import './videos-overviews' 35import './videos-overviews'
36import './videos'
36import './views' 37import './views'
diff --git a/server/tests/api/check-params/video-source.ts b/server/tests/api/check-params/video-source.ts
new file mode 100644
index 000000000..ca324bb9d
--- /dev/null
+++ b/server/tests/api/check-params/video-source.ts
@@ -0,0 +1,44 @@
1import { HttpStatusCode } from '@shared/models'
2import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
3
4describe('Test video sources API validator', function () {
5 let server: PeerTubeServer = null
6 let uuid: string
7 let userToken: string
8
9 before(async function () {
10 this.timeout(30000)
11
12 server = await createSingleServer(1)
13 await setAccessTokensToServers([ server ])
14
15 const created = await server.videos.quickUpload({ name: 'video' })
16 uuid = created.uuid
17
18 userToken = await server.users.generateUserAndToken('user')
19 })
20
21 it('Should fail without a valid uuid', async function () {
22 await server.videos.getSource({ id: '4da6fde3-88f7-4d16-b119-108df563d0b0', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
23 })
24
25 it('Should receive 404 when passing a non existing video id', async function () {
26 await server.videos.getSource({ id: '4da6fde3-88f7-4d16-b119-108df5630b06', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
27 })
28
29 it('Should not get the source as unauthenticated', async function () {
30 await server.videos.getSource({ id: uuid, expectedStatus: HttpStatusCode.UNAUTHORIZED_401, token: null })
31 })
32
33 it('Should not get the source with another user', async function () {
34 await server.videos.getSource({ id: uuid, expectedStatus: HttpStatusCode.FORBIDDEN_403, token: userToken })
35 })
36
37 it('Should succeed with the correct parameters get the source as another user', async function () {
38 await server.videos.getSource({ id: uuid })
39 })
40
41 after(async function () {
42 await cleanupTests([ server ])
43 })
44})
diff --git a/server/tests/api/videos/index.ts b/server/tests/api/videos/index.ts
index 27b119f30..a0b6b01cf 100644
--- a/server/tests/api/videos/index.ts
+++ b/server/tests/api/videos/index.ts
@@ -16,3 +16,4 @@ import './video-schedule-update'
16import './videos-common-filters' 16import './videos-common-filters'
17import './videos-history' 17import './videos-history'
18import './videos-overview' 18import './videos-overview'
19import './video-source'
diff --git a/server/tests/api/videos/video-source.ts b/server/tests/api/videos/video-source.ts
new file mode 100644
index 000000000..e34642300
--- /dev/null
+++ b/server/tests/api/videos/video-source.ts
@@ -0,0 +1,39 @@
1import 'mocha'
2import * as chai from 'chai'
3import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
4
5const expect = chai.expect
6
7describe('Test video source', () => {
8 let server: PeerTubeServer = null
9 const fixture = 'video_short.webm'
10
11 before(async function () {
12 this.timeout(30000)
13
14 server = await createSingleServer(1)
15 await setAccessTokensToServers([ server ])
16 })
17
18 it('Should get the source filename with legacy upload', async function () {
19 this.timeout(30000)
20
21 const { uuid } = await server.videos.upload({ attributes: { name: 'my video', fixture }, mode: 'legacy' })
22
23 const source = await server.videos.getSource({ id: uuid })
24 expect(source.filename).to.equal(fixture)
25 })
26
27 it('Should get the source filename with resumable upload', async function () {
28 this.timeout(30000)
29
30 const { uuid } = await server.videos.upload({ attributes: { name: 'my video', fixture }, mode: 'resumable' })
31
32 const source = await server.videos.getSource({ id: uuid })
33 expect(source.filename).to.equal(fixture)
34 })
35
36 after(async function () {
37 await cleanupTests([ server ])
38 })
39})
diff --git a/server/types/express.d.ts b/server/types/express.d.ts
index 7cc13f21d..27e532c31 100644
--- a/server/types/express.d.ts
+++ b/server/types/express.d.ts
@@ -42,6 +42,7 @@ import {
42 MVideoThumbnail 42 MVideoThumbnail
43} from './models' 43} from './models'
44import { Writable } from 'stream' 44import { Writable } from 'stream'
45import { MVideoSource } from './models/video/video-source'
45 46
46declare module 'express' { 47declare module 'express' {
47 export interface Request { 48 export interface Request {
@@ -68,7 +69,7 @@ declare module 'express' {
68 } | UploadFileForCheck[] 69 } | UploadFileForCheck[]
69 70
70 // Upload file with a duration added by our middleware 71 // Upload file with a duration added by our middleware
71 export type VideoUploadFile = Pick<Express.Multer.File, 'path' | 'filename' | 'size'> & { 72 export type VideoUploadFile = Pick<Express.Multer.File, 'path' | 'filename' | 'size', 'originalname'> & {
72 duration: number 73 duration: number
73 } 74 }
74 75
@@ -85,6 +86,7 @@ declare module 'express' {
85 duration: number 86 duration: number
86 path: string 87 path: string
87 filename: string 88 filename: string
89 originalname: string
88 } 90 }
89 91
90 // Extends Response with added functions and potential variables passed by middlewares 92 // Extends Response with added functions and potential variables passed by middlewares
@@ -123,6 +125,8 @@ declare module 'express' {
123 125
124 videoShare?: MVideoShareActor 126 videoShare?: MVideoShareActor
125 127
128 videoSource?: MVideoSource
129
126 videoFile?: MVideoFile 130 videoFile?: MVideoFile
127 131
128 videoFileResumable?: EnhancedUploadXFile 132 videoFileResumable?: EnhancedUploadXFile
diff --git a/server/types/models/video/video-source.ts b/server/types/models/video/video-source.ts
new file mode 100644
index 000000000..0948f3b2e
--- /dev/null
+++ b/server/types/models/video/video-source.ts
@@ -0,0 +1,3 @@
1import { VideoSourceModel } from '@server/models/video/video-source'
2
3export type MVideoSource = Omit<VideoSourceModel, 'Video'>