aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--client/src/app/+admin/admin-routing.module.ts4
-rw-r--r--client/src/app/+admin/admin.module.ts11
-rw-r--r--client/src/app/+admin/jobs/index.ts1
-rw-r--r--client/src/app/+admin/jobs/job.component.ts6
-rw-r--r--client/src/app/+admin/jobs/job.routes.ts35
-rw-r--r--client/src/app/+admin/jobs/jobs-list/index.ts1
-rw-r--r--client/src/app/+admin/jobs/jobs-list/jobs-list.component.html18
-rw-r--r--client/src/app/+admin/jobs/jobs-list/jobs-list.component.ts50
-rw-r--r--client/src/app/+admin/jobs/shared/index.ts1
-rw-r--r--client/src/app/+admin/jobs/shared/job.service.ts30
-rw-r--r--client/src/app/core/menu/menu-admin.component.html5
-rw-r--r--client/src/app/core/menu/menu-admin.component.ts4
-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
-rw-r--r--shared/models/job.model.ts10
-rw-r--r--shared/models/users/user-right.enum.ts1
-rw-r--r--shared/models/users/user-role.ts1
29 files changed, 462 insertions, 27 deletions
diff --git a/client/src/app/+admin/admin-routing.module.ts b/client/src/app/+admin/admin-routing.module.ts
index cd8b9bdef..7ef5c6105 100644
--- a/client/src/app/+admin/admin-routing.module.ts
+++ b/client/src/app/+admin/admin-routing.module.ts
@@ -8,6 +8,7 @@ import { FollowsRoutes } from './follows'
8import { UsersRoutes } from './users' 8import { UsersRoutes } from './users'
9import { VideoAbusesRoutes } from './video-abuses' 9import { VideoAbusesRoutes } from './video-abuses'
10import { VideoBlacklistRoutes } from './video-blacklist' 10import { VideoBlacklistRoutes } from './video-blacklist'
11import { JobsRoutes } from './jobs/job.routes'
11 12
12const adminRoutes: Routes = [ 13const adminRoutes: Routes = [
13 { 14 {
@@ -24,7 +25,8 @@ const adminRoutes: Routes = [
24 ...FollowsRoutes, 25 ...FollowsRoutes,
25 ...UsersRoutes, 26 ...UsersRoutes,
26 ...VideoAbusesRoutes, 27 ...VideoAbusesRoutes,
27 ...VideoBlacklistRoutes 28 ...VideoBlacklistRoutes,
29 ...JobsRoutes
28 ] 30 ]
29 } 31 }
30] 32]
diff --git a/client/src/app/+admin/admin.module.ts b/client/src/app/+admin/admin.module.ts
index 3c6b7a793..c0b006e73 100644
--- a/client/src/app/+admin/admin.module.ts
+++ b/client/src/app/+admin/admin.module.ts
@@ -5,6 +5,9 @@ import { AdminRoutingModule } from './admin-routing.module'
5import { AdminComponent } from './admin.component' 5import { AdminComponent } from './admin.component'
6import { FollowersListComponent, FollowingAddComponent, FollowsComponent, FollowService } from './follows' 6import { FollowersListComponent, FollowingAddComponent, FollowsComponent, FollowService } from './follows'
7import { FollowingListComponent } from './follows/following-list/following-list.component' 7import { FollowingListComponent } from './follows/following-list/following-list.component'
8import { JobsComponent } from './jobs/job.component'
9import { JobsListComponent } from './jobs/jobs-list/jobs-list.component'
10import { JobService } from './jobs/shared/job.service'
8import { UserAddComponent, UserListComponent, UsersComponent, UserService, UserUpdateComponent } from './users' 11import { UserAddComponent, UserListComponent, UsersComponent, UserService, UserUpdateComponent } from './users'
9import { VideoAbuseListComponent, VideoAbusesComponent } from './video-abuses' 12import { VideoAbuseListComponent, VideoAbusesComponent } from './video-abuses'
10import { VideoBlacklistComponent, VideoBlacklistListComponent } from './video-blacklist' 13import { VideoBlacklistComponent, VideoBlacklistListComponent } from './video-blacklist'
@@ -33,7 +36,10 @@ import { VideoBlacklistComponent, VideoBlacklistListComponent } from './video-bl
33 VideoBlacklistListComponent, 36 VideoBlacklistListComponent,
34 37
35 VideoAbusesComponent, 38 VideoAbusesComponent,
36 VideoAbuseListComponent 39 VideoAbuseListComponent,
40
41 JobsComponent,
42 JobsListComponent
37 ], 43 ],
38 44
39 exports: [ 45 exports: [
@@ -42,7 +48,8 @@ import { VideoBlacklistComponent, VideoBlacklistListComponent } from './video-bl
42 48
43 providers: [ 49 providers: [
44 FollowService, 50 FollowService,
45 UserService 51 UserService,
52 JobService
46 ] 53 ]
47}) 54})
48export class AdminModule { } 55export class AdminModule { }
diff --git a/client/src/app/+admin/jobs/index.ts b/client/src/app/+admin/jobs/index.ts
new file mode 100644
index 000000000..7b5271956
--- /dev/null
+++ b/client/src/app/+admin/jobs/index.ts
@@ -0,0 +1 @@
export * from './'
diff --git a/client/src/app/+admin/jobs/job.component.ts b/client/src/app/+admin/jobs/job.component.ts
new file mode 100644
index 000000000..bc80c9a6a
--- /dev/null
+++ b/client/src/app/+admin/jobs/job.component.ts
@@ -0,0 +1,6 @@
1import { Component } from '@angular/core'
2
3@Component({
4 template: '<router-outlet></router-outlet>'
5})
6export class JobsComponent {}
diff --git a/client/src/app/+admin/jobs/job.routes.ts b/client/src/app/+admin/jobs/job.routes.ts
new file mode 100644
index 000000000..a7bf2b221
--- /dev/null
+++ b/client/src/app/+admin/jobs/job.routes.ts
@@ -0,0 +1,35 @@
1import { Routes } from '@angular/router'
2
3import { UserRightGuard } from '../../core'
4import { FollowingAddComponent } from './following-add'
5import { UserRight } from '../../../../../shared'
6import { FollowingListComponent } from './following-list/following-list.component'
7import { JobsComponent } from './job.component'
8import { JobsListComponent } from './jobs-list/jobs-list.component'
9
10export const JobsRoutes: Routes = [
11 {
12 path: 'jobs',
13 component: JobsComponent,
14 canActivate: [ UserRightGuard ],
15 data: {
16 userRight: UserRight.MANAGE_JOBS
17 },
18 children: [
19 {
20 path: '',
21 redirectTo: 'list',
22 pathMatch: 'full'
23 },
24 {
25 path: 'list',
26 component: JobsListComponent,
27 data: {
28 meta: {
29 title: 'Jobs list'
30 }
31 }
32 }
33 ]
34 }
35]
diff --git a/client/src/app/+admin/jobs/jobs-list/index.ts b/client/src/app/+admin/jobs/jobs-list/index.ts
new file mode 100644
index 000000000..cf590a6f8
--- /dev/null
+++ b/client/src/app/+admin/jobs/jobs-list/index.ts
@@ -0,0 +1 @@
export * from './jobs-list.component'
diff --git a/client/src/app/+admin/jobs/jobs-list/jobs-list.component.html b/client/src/app/+admin/jobs/jobs-list/jobs-list.component.html
new file mode 100644
index 000000000..a90267172
--- /dev/null
+++ b/client/src/app/+admin/jobs/jobs-list/jobs-list.component.html
@@ -0,0 +1,18 @@
1<div class="row">
2 <div class="content-padding">
3 <h3>Jobs list</h3>
4
5 <p-dataTable
6 [value]="jobs" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
7 sortField="createdAt" (onLazyLoad)="loadLazy($event)"
8 >
9 <p-column field="id" header="ID"></p-column>
10 <p-column field="category" header="Category"></p-column>
11 <p-column field="handlerName" header="Handler name"></p-column>
12 <p-column field="handlerInputData" header="Input data"></p-column>
13 <p-column field="state" header="State"></p-column>
14 <p-column field="createdAt" header="Created date" [sortable]="true"></p-column>
15 <p-column field="updatedAt" header="Updated date"></p-column>
16 </p-dataTable>
17 </div>
18</div>
diff --git a/client/src/app/+admin/jobs/jobs-list/jobs-list.component.ts b/client/src/app/+admin/jobs/jobs-list/jobs-list.component.ts
new file mode 100644
index 000000000..88fe259fb
--- /dev/null
+++ b/client/src/app/+admin/jobs/jobs-list/jobs-list.component.ts
@@ -0,0 +1,50 @@
1import { Component } from '@angular/core'
2import { NotificationsService } from 'angular2-notifications'
3import { SortMeta } from 'primeng/primeng'
4import { Job } from '../../../../../../shared/index'
5import { RestPagination, RestTable } from '../../../shared'
6import { JobService } from '../shared'
7import { RestExtractor } from '../../../shared/rest/rest-extractor.service'
8
9@Component({
10 selector: 'my-jobs-list',
11 templateUrl: './jobs-list.component.html',
12 styleUrls: [ ]
13})
14export class JobsListComponent extends RestTable {
15 jobs: Job[] = []
16 totalRecords = 0
17 rowsPerPage = 10
18 sort: SortMeta = { field: 'createdAt', order: 1 }
19 pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
20
21 constructor (
22 private notificationsService: NotificationsService,
23 private restExtractor: RestExtractor,
24 private jobsService: JobService
25 ) {
26 super()
27 }
28
29 protected loadData () {
30 this.jobsService
31 .getJobs(this.pagination, this.sort)
32 .map(res => this.restExtractor.applyToResultListData(res, this.formatJob.bind(this)))
33 .subscribe(
34 resultList => {
35 this.jobs = resultList.data
36 this.totalRecords = resultList.total
37 },
38
39 err => this.notificationsService.error('Error', err.message)
40 )
41 }
42
43 private formatJob (job: Job) {
44 const handlerInputData = JSON.stringify(job.handlerInputData)
45
46 return Object.assign(job, {
47 handlerInputData
48 })
49 }
50}
diff --git a/client/src/app/+admin/jobs/shared/index.ts b/client/src/app/+admin/jobs/shared/index.ts
new file mode 100644
index 000000000..609439e5c
--- /dev/null
+++ b/client/src/app/+admin/jobs/shared/index.ts
@@ -0,0 +1 @@
export * from './job.service'
diff --git a/client/src/app/+admin/jobs/shared/job.service.ts b/client/src/app/+admin/jobs/shared/job.service.ts
new file mode 100644
index 000000000..49f1ab6f5
--- /dev/null
+++ b/client/src/app/+admin/jobs/shared/job.service.ts
@@ -0,0 +1,30 @@
1import { HttpClient, HttpParams } from '@angular/common/http'
2import { Injectable } from '@angular/core'
3import { SortMeta } from 'primeng/primeng'
4import 'rxjs/add/operator/catch'
5import 'rxjs/add/operator/map'
6import { Observable } from 'rxjs/Observable'
7import { ResultList } from '../../../../../../shared'
8import { Job } from '../../../../../../shared/models/job.model'
9
10import { RestExtractor, RestPagination, RestService } from '../../../shared'
11
12@Injectable()
13export class JobService {
14 private static BASE_JOB_URL = API_URL + '/api/v1/jobs'
15
16 constructor (
17 private authHttp: HttpClient,
18 private restService: RestService,
19 private restExtractor: RestExtractor
20 ) {}
21
22 getJobs (pagination: RestPagination, sort: SortMeta): Observable<ResultList<Job>> {
23 let params = new HttpParams()
24 params = this.restService.addRestGetParams(params, pagination, sort)
25
26 return this.authHttp.get<ResultList<Job>>(JobService.BASE_JOB_URL, { params })
27 .map(res => this.restExtractor.convertResultListDateToHuman(res))
28 .catch(err => this.restExtractor.handleError(err))
29 }
30}
diff --git a/client/src/app/core/menu/menu-admin.component.html b/client/src/app/core/menu/menu-admin.component.html
index eb2d0d69c..9857b2e3e 100644
--- a/client/src/app/core/menu/menu-admin.component.html
+++ b/client/src/app/core/menu/menu-admin.component.html
@@ -19,6 +19,11 @@
19 <span class="hidden-xs glyphicon glyphicon-eye-close"></span> 19 <span class="hidden-xs glyphicon glyphicon-eye-close"></span>
20 Video blacklist 20 Video blacklist
21 </a> 21 </a>
22
23 <a *ngIf="hasJobsRight()" routerLink="/admin/jobs" routerLinkActive="active">
24 <span class="hidden-xs glyphicon glyphicon-tasks"></span>
25 Jobs
26 </a>
22 </div> 27 </div>
23 28
24 <div class="panel-block"> 29 <div class="panel-block">
diff --git a/client/src/app/core/menu/menu-admin.component.ts b/client/src/app/core/menu/menu-admin.component.ts
index 466da1aee..ea8d5f57c 100644
--- a/client/src/app/core/menu/menu-admin.component.ts
+++ b/client/src/app/core/menu/menu-admin.component.ts
@@ -26,4 +26,8 @@ export class MenuAdminComponent {
26 hasVideoBlacklistRight () { 26 hasVideoBlacklistRight () {
27 return this.auth.getUser().hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) 27 return this.auth.getUser().hasRight(UserRight.MANAGE_VIDEO_BLACKLIST)
28 } 28 }
29
30 hasJobsRight () {
31 return this.auth.getUser().hasRight(UserRight.MANAGE_JOBS)
32 }
29} 33}
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}
diff --git a/shared/models/job.model.ts b/shared/models/job.model.ts
index 10696e3f8..1c46a7900 100644
--- a/shared/models/job.model.ts
+++ b/shared/models/job.model.ts
@@ -1,2 +1,12 @@
1export type JobState = 'pending' | 'processing' | 'error' | 'success' 1export type JobState = 'pending' | 'processing' | 'error' | 'success'
2export type JobCategory = 'transcoding' | 'activitypub-http' 2export type JobCategory = 'transcoding' | 'activitypub-http'
3
4export interface Job {
5 id: number
6 state: JobState
7 category: JobCategory
8 handlerName: string
9 handlerInputData: any
10 createdAt: Date
11 updatedAt: Date
12}
diff --git a/shared/models/users/user-right.enum.ts b/shared/models/users/user-right.enum.ts
index 9460b668e..238e38a36 100644
--- a/shared/models/users/user-right.enum.ts
+++ b/shared/models/users/user-right.enum.ts
@@ -4,6 +4,7 @@ export enum UserRight {
4 MANAGE_SERVER_FOLLOW, 4 MANAGE_SERVER_FOLLOW,
5 MANAGE_VIDEO_ABUSES, 5 MANAGE_VIDEO_ABUSES,
6 MANAGE_VIDEO_BLACKLIST, 6 MANAGE_VIDEO_BLACKLIST,
7 MANAGE_JOBS,
7 REMOVE_ANY_VIDEO, 8 REMOVE_ANY_VIDEO,
8 REMOVE_ANY_VIDEO_CHANNEL 9 REMOVE_ANY_VIDEO_CHANNEL
9} 10}
diff --git a/shared/models/users/user-role.ts b/shared/models/users/user-role.ts
index cc32c768d..954fa426e 100644
--- a/shared/models/users/user-role.ts
+++ b/shared/models/users/user-role.ts
@@ -1,4 +1,5 @@
1import { UserRight } from './user-right.enum' 1import { UserRight } from './user-right.enum'
2import user from '../../../server/models/account/user'
2 3
3// Keep the order 4// Keep the order
4export enum UserRole { 5export enum UserRole {