aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/controllers/api/index.ts2
-rw-r--r--server/controllers/api/jobs.ts34
-rw-r--r--server/initializers/constants.ts1
-rw-r--r--server/middlewares/sort.ts9
-rw-r--r--server/middlewares/validators/sort.ts5
-rw-r--r--server/models/account/user-interface.ts8
-rw-r--r--server/models/job/job-interface.ts15
-rw-r--r--server/models/job/job.ts52
-rw-r--r--server/tests/api/check-params/index.ts1
-rw-r--r--server/tests/api/check-params/jobs.ts84
-rw-r--r--server/tests/api/index-slow.ts1
-rw-r--r--server/tests/api/jobs.ts64
-rw-r--r--server/tests/utils/follows.ts2
-rw-r--r--server/tests/utils/jobs.ts33
14 files changed, 287 insertions, 24 deletions
diff --git a/server/controllers/api/index.ts b/server/controllers/api/index.ts
index b00fb7467..737ea4602 100644
--- a/server/controllers/api/index.ts
+++ b/server/controllers/api/index.ts
@@ -7,6 +7,7 @@ import { configRouter } from './config'
7import { serverRouter } from './server' 7import { serverRouter } from './server'
8import { usersRouter } from './users' 8import { usersRouter } from './users'
9import { videosRouter } from './videos' 9import { videosRouter } from './videos'
10import { jobsRouter } from './jobs'
10 11
11const apiRouter = express.Router() 12const apiRouter = express.Router()
12 13
@@ -15,6 +16,7 @@ apiRouter.use('/oauth-clients', oauthClientsRouter)
15apiRouter.use('/config', configRouter) 16apiRouter.use('/config', configRouter)
16apiRouter.use('/users', usersRouter) 17apiRouter.use('/users', usersRouter)
17apiRouter.use('/videos', videosRouter) 18apiRouter.use('/videos', videosRouter)
19apiRouter.use('/jobs', jobsRouter)
18apiRouter.use('/ping', pong) 20apiRouter.use('/ping', pong)
19apiRouter.use('/*', badRequest) 21apiRouter.use('/*', badRequest)
20 22
diff --git a/server/controllers/api/jobs.ts b/server/controllers/api/jobs.ts
new file mode 100644
index 000000000..f6fbff369
--- /dev/null
+++ b/server/controllers/api/jobs.ts
@@ -0,0 +1,34 @@
1import * as express from 'express'
2import { asyncMiddleware, jobsSortValidator, setJobsSort, setPagination } from '../../middlewares'
3import { paginationValidator } from '../../middlewares/validators/pagination'
4import { database as db } from '../../initializers'
5import { getFormattedObjects } from '../../helpers/utils'
6import { authenticate } from '../../middlewares/oauth'
7import { ensureUserHasRight } from '../../middlewares/user-right'
8import { UserRight } from '../../../shared/models/users/user-right.enum'
9
10const jobsRouter = express.Router()
11
12jobsRouter.get('/',
13 authenticate,
14 ensureUserHasRight(UserRight.MANAGE_JOBS),
15 paginationValidator,
16 jobsSortValidator,
17 setJobsSort,
18 setPagination,
19 asyncMiddleware(listJobs)
20)
21
22// ---------------------------------------------------------------------------
23
24export {
25 jobsRouter
26}
27
28// ---------------------------------------------------------------------------
29
30async function listJobs (req: express.Request, res: express.Response, next: express.NextFunction) {
31 const resultList = await db.Job.listForApi(req.query.start, req.query.count, req.query.sort)
32
33 return res.json(getFormattedObjects(resultList.data, resultList.total))
34}
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index 6d8aa7332..8f278fb0b 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -32,6 +32,7 @@ const SEARCHABLE_COLUMNS = {
32// Sortable columns per schema 32// Sortable columns per schema
33const SORTABLE_COLUMNS = { 33const SORTABLE_COLUMNS = {
34 USERS: [ 'id', 'username', 'createdAt' ], 34 USERS: [ 'id', 'username', 'createdAt' ],
35 JOBS: [ 'id', 'createdAt' ],
35 VIDEO_ABUSES: [ 'id', 'createdAt' ], 36 VIDEO_ABUSES: [ 'id', 'createdAt' ],
36 VIDEO_CHANNELS: [ 'id', 'name', 'updatedAt', 'createdAt' ], 37 VIDEO_CHANNELS: [ 'id', 'name', 'updatedAt', 'createdAt' ],
37 VIDEOS: [ 'name', 'duration', 'createdAt', 'views', 'likes' ], 38 VIDEOS: [ 'name', 'duration', 'createdAt', 'views', 'likes' ],
diff --git a/server/middlewares/sort.ts b/server/middlewares/sort.ts
index 279b29e65..7b60920de 100644
--- a/server/middlewares/sort.ts
+++ b/server/middlewares/sort.ts
@@ -10,6 +10,12 @@ function setUsersSort (req: express.Request, res: express.Response, next: expres
10 return next() 10 return next()
11} 11}
12 12
13function setJobsSort (req: express.Request, res: express.Response, next: express.NextFunction) {
14 if (!req.query.sort) req.query.sort = '-createdAt'
15
16 return next()
17}
18
13function setVideoAbusesSort (req: express.Request, res: express.Response, next: express.NextFunction) { 19function setVideoAbusesSort (req: express.Request, res: express.Response, next: express.NextFunction) {
14 if (!req.query.sort) req.query.sort = '-createdAt' 20 if (!req.query.sort) req.query.sort = '-createdAt'
15 21
@@ -70,5 +76,6 @@ export {
70 setVideosSort, 76 setVideosSort,
71 setBlacklistSort, 77 setBlacklistSort,
72 setFollowersSort, 78 setFollowersSort,
73 setFollowingSort 79 setFollowingSort,
80 setJobsSort
74} 81}
diff --git a/server/middlewares/validators/sort.ts b/server/middlewares/validators/sort.ts
index 636f68885..d5822ac81 100644
--- a/server/middlewares/validators/sort.ts
+++ b/server/middlewares/validators/sort.ts
@@ -7,6 +7,7 @@ import { areValidationErrors } from './utils'
7 7
8// Initialize constants here for better performances 8// Initialize constants here for better performances
9const SORTABLE_USERS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.USERS) 9const SORTABLE_USERS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.USERS)
10const SORTABLE_JOBS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.JOBS)
10const SORTABLE_VIDEO_ABUSES_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_ABUSES) 11const SORTABLE_VIDEO_ABUSES_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_ABUSES)
11const SORTABLE_VIDEOS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEOS) 12const SORTABLE_VIDEOS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEOS)
12const SORTABLE_BLACKLISTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.BLACKLISTS) 13const SORTABLE_BLACKLISTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.BLACKLISTS)
@@ -15,6 +16,7 @@ const SORTABLE_FOLLOWERS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.FOLLOW
15const SORTABLE_FOLLOWING_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.FOLLOWING) 16const SORTABLE_FOLLOWING_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.FOLLOWING)
16 17
17const usersSortValidator = checkSort(SORTABLE_USERS_COLUMNS) 18const usersSortValidator = checkSort(SORTABLE_USERS_COLUMNS)
19const jobsSortValidator = checkSort(SORTABLE_JOBS_COLUMNS)
18const videoAbusesSortValidator = checkSort(SORTABLE_VIDEO_ABUSES_COLUMNS) 20const videoAbusesSortValidator = checkSort(SORTABLE_VIDEO_ABUSES_COLUMNS)
19const videosSortValidator = checkSort(SORTABLE_VIDEOS_COLUMNS) 21const videosSortValidator = checkSort(SORTABLE_VIDEOS_COLUMNS)
20const blacklistSortValidator = checkSort(SORTABLE_BLACKLISTS_COLUMNS) 22const blacklistSortValidator = checkSort(SORTABLE_BLACKLISTS_COLUMNS)
@@ -31,7 +33,8 @@ export {
31 videosSortValidator, 33 videosSortValidator,
32 blacklistSortValidator, 34 blacklistSortValidator,
33 followersSortValidator, 35 followersSortValidator,
34 followingSortValidator 36 followingSortValidator,
37 jobsSortValidator
35} 38}
36 39
37// --------------------------------------------------------------------------- 40// ---------------------------------------------------------------------------
diff --git a/server/models/account/user-interface.ts b/server/models/account/user-interface.ts
index 1a04fb750..0f0b72063 100644
--- a/server/models/account/user-interface.ts
+++ b/server/models/account/user-interface.ts
@@ -1,12 +1,10 @@
1import * as Sequelize from 'sequelize'
2import * as Bluebird from 'bluebird' 1import * as Bluebird from 'bluebird'
3 2import * as Sequelize from 'sequelize'
4// Don't use barrel, import just what we need
5import { AccountInstance } from './account-interface'
6import { User as FormattedUser } from '../../../shared/models/users/user.model'
7import { ResultList } from '../../../shared/models/result-list.model' 3import { ResultList } from '../../../shared/models/result-list.model'
8import { UserRight } from '../../../shared/models/users/user-right.enum' 4import { UserRight } from '../../../shared/models/users/user-right.enum'
9import { UserRole } from '../../../shared/models/users/user-role' 5import { UserRole } from '../../../shared/models/users/user-role'
6import { User as FormattedUser } from '../../../shared/models/users/user.model'
7import { AccountInstance } from './account-interface'
10 8
11export namespace UserMethods { 9export namespace UserMethods {
12 export type HasRight = (this: UserInstance, right: UserRight) => boolean 10 export type HasRight = (this: UserInstance, right: UserRight) => boolean
diff --git a/server/models/job/job-interface.ts b/server/models/job/job-interface.ts
index 411a05029..3cfc0fbed 100644
--- a/server/models/job/job-interface.ts
+++ b/server/models/job/job-interface.ts
@@ -1,18 +1,23 @@
1import * as Bluebird from 'bluebird'
1import * as Sequelize from 'sequelize' 2import * as Sequelize from 'sequelize'
2import * as Promise from 'bluebird' 3import { Job as FormattedJob, JobCategory, JobState } from '../../../shared/models/job.model'
3 4import { ResultList } from '../../../shared/models/result-list.model'
4import { JobCategory, JobState } from '../../../shared/models/job.model'
5 5
6export namespace JobMethods { 6export namespace JobMethods {
7 export type ListWithLimitByCategory = (limit: number, state: JobState, category: JobCategory) => Promise<JobInstance[]> 7 export type ListWithLimitByCategory = (limit: number, state: JobState, category: JobCategory) => Bluebird<JobInstance[]>
8 export type ListForApi = (start: number, count: number, sort: string) => Bluebird< ResultList<JobInstance> >
9
10 export type ToFormattedJSON = (this: JobInstance) => FormattedJob
8} 11}
9 12
10export interface JobClass { 13export interface JobClass {
11 listWithLimitByCategory: JobMethods.ListWithLimitByCategory 14 listWithLimitByCategory: JobMethods.ListWithLimitByCategory
15 listForApi: JobMethods.ListForApi,
12} 16}
13 17
14export interface JobAttributes { 18export interface JobAttributes {
15 state: JobState 19 state: JobState
20 category: JobCategory
16 handlerName: string 21 handlerName: string
17 handlerInputData: any 22 handlerInputData: any
18} 23}
@@ -21,6 +26,8 @@ export interface JobInstance extends JobClass, JobAttributes, Sequelize.Instance
21 id: number 26 id: number
22 createdAt: Date 27 createdAt: Date
23 updatedAt: Date 28 updatedAt: Date
29
30 toFormattedJSON: JobMethods.ToFormattedJSON
24} 31}
25 32
26export interface JobModel extends JobClass, Sequelize.Model<JobInstance, JobAttributes> {} 33export interface JobModel extends JobClass, Sequelize.Model<JobInstance, JobAttributes> {}
diff --git a/server/models/job/job.ts b/server/models/job/job.ts
index c2d088090..f428e26db 100644
--- a/server/models/job/job.ts
+++ b/server/models/job/job.ts
@@ -1,19 +1,14 @@
1import { values } from 'lodash' 1import { values } from 'lodash'
2import * as Sequelize from 'sequelize' 2import * as Sequelize from 'sequelize'
3
4import { JOB_STATES, JOB_CATEGORIES } from '../../initializers'
5
6import { addMethodsToModel } from '../utils'
7import {
8 JobInstance,
9 JobAttributes,
10
11 JobMethods
12} from './job-interface'
13import { JobCategory, JobState } from '../../../shared/models/job.model' 3import { JobCategory, JobState } from '../../../shared/models/job.model'
4import { JOB_CATEGORIES, JOB_STATES } from '../../initializers'
5import { addMethodsToModel, getSort } from '../utils'
6import { JobAttributes, JobInstance, JobMethods } from './job-interface'
14 7
15let Job: Sequelize.Model<JobInstance, JobAttributes> 8let Job: Sequelize.Model<JobInstance, JobAttributes>
16let listWithLimitByCategory: JobMethods.ListWithLimitByCategory 9let listWithLimitByCategory: JobMethods.ListWithLimitByCategory
10let listForApi: JobMethods.ListForApi
11let toFormattedJSON: JobMethods.ToFormattedJSON
17 12
18export default function defineJob (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { 13export default function defineJob (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
19 Job = sequelize.define<JobInstance, JobAttributes>('Job', 14 Job = sequelize.define<JobInstance, JobAttributes>('Job',
@@ -44,12 +39,30 @@ export default function defineJob (sequelize: Sequelize.Sequelize, DataTypes: Se
44 } 39 }
45 ) 40 )
46 41
47 const classMethods = [ listWithLimitByCategory ] 42 const classMethods = [
48 addMethodsToModel(Job, classMethods) 43 listWithLimitByCategory,
44 listForApi
45 ]
46 const instanceMethods = [
47 toFormattedJSON
48 ]
49 addMethodsToModel(Job, classMethods, instanceMethods)
49 50
50 return Job 51 return Job
51} 52}
52 53
54toFormattedJSON = function (this: JobInstance) {
55 return {
56 id: this.id,
57 state: this.state,
58 category: this.category,
59 handlerName: this.handlerName,
60 handlerInputData: this.handlerInputData,
61 createdAt: this.createdAt,
62 updatedAt: this.updatedAt
63 }
64}
65
53// --------------------------------------------------------------------------- 66// ---------------------------------------------------------------------------
54 67
55listWithLimitByCategory = function (limit: number, state: JobState, jobCategory: JobCategory) { 68listWithLimitByCategory = function (limit: number, state: JobState, jobCategory: JobCategory) {
@@ -66,3 +79,18 @@ listWithLimitByCategory = function (limit: number, state: JobState, jobCategory:
66 79
67 return Job.findAll(query) 80 return Job.findAll(query)
68} 81}
82
83listForApi = function (start: number, count: number, sort: string) {
84 const query = {
85 offset: start,
86 limit: count,
87 order: [ getSort(sort) ]
88 }
89
90 return Job.findAndCountAll(query).then(({ rows, count }) => {
91 return {
92 data: rows,
93 total: count
94 }
95 })
96}
diff --git a/server/tests/api/check-params/index.ts b/server/tests/api/check-params/index.ts
index 287480808..b22bf054a 100644
--- a/server/tests/api/check-params/index.ts
+++ b/server/tests/api/check-params/index.ts
@@ -1,5 +1,6 @@
1// Order of the tests we want to execute 1// Order of the tests we want to execute
2import './follows' 2import './follows'
3import './jobs'
3import './users' 4import './users'
4import './services' 5import './services'
5import './videos' 6import './videos'
diff --git a/server/tests/api/check-params/jobs.ts b/server/tests/api/check-params/jobs.ts
new file mode 100644
index 000000000..7a0dd6e8c
--- /dev/null
+++ b/server/tests/api/check-params/jobs.ts
@@ -0,0 +1,84 @@
1/* tslint:disable:no-unused-expression */
2
3import 'mocha'
4import * as request from 'supertest'
5
6import { createUser, flushTests, getUserAccessToken, killallServers, runServer, ServerInfo, setAccessTokensToServers } from '../../utils'
7
8describe('Test jobs API validators', function () {
9 const path = '/api/v1/jobs/'
10 let server: ServerInfo
11 let userAccessToken = ''
12
13 // ---------------------------------------------------------------
14
15 before(async function () {
16 this.timeout(120000)
17
18 await flushTests()
19
20 server = await runServer(1)
21
22 await setAccessTokensToServers([ server ])
23
24 const user = {
25 username: 'user1',
26 password: 'my super password'
27 }
28 await createUser(server.url, server.accessToken, user.username, user.password)
29 userAccessToken = await getUserAccessToken(server, user)
30 })
31
32 describe('When listing jobs', function () {
33 it('Should fail with a bad start pagination', async function () {
34 await request(server.url)
35 .get(path)
36 .query({ start: 'hello' })
37 .set('Accept', 'application/json')
38 .set('Authorization', 'Bearer ' + server.accessToken)
39 .expect(400)
40 })
41
42 it('Should fail with a bad count pagination', async function () {
43 await request(server.url)
44 .get(path)
45 .query({ count: 'hello' })
46 .set('Accept', 'application/json')
47 .set('Authorization', 'Bearer ' + server.accessToken)
48 .expect(400)
49 })
50
51 it('Should fail with an incorrect sort', async function () {
52 await request(server.url)
53 .get(path)
54 .query({ sort: 'hello' })
55 .set('Accept', 'application/json')
56 .set('Authorization', 'Bearer ' + server.accessToken)
57 .expect(400)
58 })
59
60 it('Should fail with a non authenticated user', async function () {
61 await request(server.url)
62 .get(path)
63 .set('Accept', 'application/json')
64 .expect(401)
65 })
66
67 it('Should fail with a non admin user', async function () {
68 await request(server.url)
69 .get(path)
70 .set('Accept', 'application/json')
71 .set('Authorization', 'Bearer ' + userAccessToken)
72 .expect(403)
73 })
74 })
75
76 after(async function () {
77 killallServers([ server ])
78
79 // Keep the logs if the test failed
80 if (this['ok']) {
81 await flushTests()
82 }
83 })
84})
diff --git a/server/tests/api/index-slow.ts b/server/tests/api/index-slow.ts
index 2448147d8..4cd5b09a2 100644
--- a/server/tests/api/index-slow.ts
+++ b/server/tests/api/index-slow.ts
@@ -3,3 +3,4 @@
3import './video-transcoder' 3import './video-transcoder'
4import './multiple-servers' 4import './multiple-servers'
5import './follows' 5import './follows'
6import './jobs'
diff --git a/server/tests/api/jobs.ts b/server/tests/api/jobs.ts
new file mode 100644
index 000000000..4d9b61392
--- /dev/null
+++ b/server/tests/api/jobs.ts
@@ -0,0 +1,64 @@
1/* tslint:disable:no-unused-expression */
2
3import * as chai from 'chai'
4import 'mocha'
5import { flushTests, killallServers, ServerInfo, setAccessTokensToServers, wait } from '../utils'
6import { doubleFollow } from '../utils/follows'
7import { getJobsList, getJobsListPaginationAndSort } from '../utils/jobs'
8import { flushAndRunMultipleServers } from '../utils/servers'
9import { uploadVideo } from '../utils/videos'
10import { dateIsValid } from '../utils/miscs'
11
12const expect = chai.expect
13
14describe('Test jobs', function () {
15 let servers: ServerInfo[]
16
17 before(async function () {
18 this.timeout(30000)
19
20 servers = await flushAndRunMultipleServers(2)
21
22 await setAccessTokensToServers(servers)
23
24 // Server 1 and server 2 follow each other
25 await doubleFollow(servers[0], servers[1])
26 })
27
28 it('Should create some jobs', async function () {
29 this.timeout(30000)
30
31 await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video1' })
32 await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video2' })
33
34 await wait(15000)
35 })
36
37 it('Should list jobs', async function () {
38 const res = await getJobsList(servers[1].url, servers[1].accessToken)
39 expect(res.body.total).to.be.above(2)
40 expect(res.body.data).to.have.length.above(2)
41 })
42
43 it('Should list jobs with sort and pagination', async function () {
44 const res = await getJobsListPaginationAndSort(servers[1].url, servers[1].accessToken, 4, 1, 'createdAt')
45 expect(res.body.total).to.be.above(2)
46 expect(res.body.data).to.have.lengthOf(1)
47
48 const job = res.body.data[0]
49 expect(job.state).to.equal('success')
50 expect(job.category).to.equal('transcoding')
51 expect(job.handlerName).to.have.length.above(3)
52 expect(dateIsValid(job.createdAt)).to.be.true
53 expect(dateIsValid(job.updatedAt)).to.be.true
54 })
55
56 after(async function () {
57 killallServers(servers)
58
59 // Keep the logs if the test failed
60 if (this['ok']) {
61 await flushTests()
62 }
63 })
64})
diff --git a/server/tests/utils/follows.ts b/server/tests/utils/follows.ts
index b88776011..033c6a719 100644
--- a/server/tests/utils/follows.ts
+++ b/server/tests/utils/follows.ts
@@ -61,7 +61,7 @@ async function doubleFollow (server1: ServerInfo, server2: ServerInfo) {
61 ]) 61 ])
62 62
63 // Wait request propagation 63 // Wait request propagation
64 await wait(20000) 64 await wait(10000)
65 65
66 return true 66 return true
67} 67}
diff --git a/server/tests/utils/jobs.ts b/server/tests/utils/jobs.ts
new file mode 100644
index 000000000..0a8c51575
--- /dev/null
+++ b/server/tests/utils/jobs.ts
@@ -0,0 +1,33 @@
1import * as request from 'supertest'
2
3function getJobsList (url: string, accessToken: string) {
4 const path = '/api/v1/jobs'
5
6 return request(url)
7 .get(path)
8 .set('Accept', 'application/json')
9 .set('Authorization', 'Bearer ' + accessToken)
10 .expect(200)
11 .expect('Content-Type', /json/)
12}
13
14function getJobsListPaginationAndSort (url: string, accessToken: string, start: number, count: number, sort: string) {
15 const path = '/api/v1/jobs'
16
17 return request(url)
18 .get(path)
19 .query({ start })
20 .query({ count })
21 .query({ sort })
22 .set('Accept', 'application/json')
23 .set('Authorization', 'Bearer ' + accessToken)
24 .expect(200)
25 .expect('Content-Type', /json/)
26}
27
28// ---------------------------------------------------------------------------
29
30export {
31 getJobsList,
32 getJobsListPaginationAndSort
33}