diff options
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' | |||
8 | import { UsersRoutes } from './users' | 8 | import { UsersRoutes } from './users' |
9 | import { VideoAbusesRoutes } from './video-abuses' | 9 | import { VideoAbusesRoutes } from './video-abuses' |
10 | import { VideoBlacklistRoutes } from './video-blacklist' | 10 | import { VideoBlacklistRoutes } from './video-blacklist' |
11 | import { JobsRoutes } from './jobs/job.routes' | ||
11 | 12 | ||
12 | const adminRoutes: Routes = [ | 13 | const 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' | |||
5 | import { AdminComponent } from './admin.component' | 5 | import { AdminComponent } from './admin.component' |
6 | import { FollowersListComponent, FollowingAddComponent, FollowsComponent, FollowService } from './follows' | 6 | import { FollowersListComponent, FollowingAddComponent, FollowsComponent, FollowService } from './follows' |
7 | import { FollowingListComponent } from './follows/following-list/following-list.component' | 7 | import { FollowingListComponent } from './follows/following-list/following-list.component' |
8 | import { JobsComponent } from './jobs/job.component' | ||
9 | import { JobsListComponent } from './jobs/jobs-list/jobs-list.component' | ||
10 | import { JobService } from './jobs/shared/job.service' | ||
8 | import { UserAddComponent, UserListComponent, UsersComponent, UserService, UserUpdateComponent } from './users' | 11 | import { UserAddComponent, UserListComponent, UsersComponent, UserService, UserUpdateComponent } from './users' |
9 | import { VideoAbuseListComponent, VideoAbusesComponent } from './video-abuses' | 12 | import { VideoAbuseListComponent, VideoAbusesComponent } from './video-abuses' |
10 | import { VideoBlacklistComponent, VideoBlacklistListComponent } from './video-blacklist' | 13 | import { 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 | }) |
48 | export class AdminModule { } | 55 | export 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 @@ | |||
1 | import { Component } from '@angular/core' | ||
2 | |||
3 | @Component({ | ||
4 | template: '<router-outlet></router-outlet>' | ||
5 | }) | ||
6 | export 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 @@ | |||
1 | import { Routes } from '@angular/router' | ||
2 | |||
3 | import { UserRightGuard } from '../../core' | ||
4 | import { FollowingAddComponent } from './following-add' | ||
5 | import { UserRight } from '../../../../../shared' | ||
6 | import { FollowingListComponent } from './following-list/following-list.component' | ||
7 | import { JobsComponent } from './job.component' | ||
8 | import { JobsListComponent } from './jobs-list/jobs-list.component' | ||
9 | |||
10 | export 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 @@ | |||
1 | import { Component } from '@angular/core' | ||
2 | import { NotificationsService } from 'angular2-notifications' | ||
3 | import { SortMeta } from 'primeng/primeng' | ||
4 | import { Job } from '../../../../../../shared/index' | ||
5 | import { RestPagination, RestTable } from '../../../shared' | ||
6 | import { JobService } from '../shared' | ||
7 | import { RestExtractor } from '../../../shared/rest/rest-extractor.service' | ||
8 | |||
9 | @Component({ | ||
10 | selector: 'my-jobs-list', | ||
11 | templateUrl: './jobs-list.component.html', | ||
12 | styleUrls: [ ] | ||
13 | }) | ||
14 | export 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 @@ | |||
1 | import { HttpClient, HttpParams } from '@angular/common/http' | ||
2 | import { Injectable } from '@angular/core' | ||
3 | import { SortMeta } from 'primeng/primeng' | ||
4 | import 'rxjs/add/operator/catch' | ||
5 | import 'rxjs/add/operator/map' | ||
6 | import { Observable } from 'rxjs/Observable' | ||
7 | import { ResultList } from '../../../../../../shared' | ||
8 | import { Job } from '../../../../../../shared/models/job.model' | ||
9 | |||
10 | import { RestExtractor, RestPagination, RestService } from '../../../shared' | ||
11 | |||
12 | @Injectable() | ||
13 | export 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' | |||
7 | import { serverRouter } from './server' | 7 | import { serverRouter } from './server' |
8 | import { usersRouter } from './users' | 8 | import { usersRouter } from './users' |
9 | import { videosRouter } from './videos' | 9 | import { videosRouter } from './videos' |
10 | import { jobsRouter } from './jobs' | ||
10 | 11 | ||
11 | const apiRouter = express.Router() | 12 | const apiRouter = express.Router() |
12 | 13 | ||
@@ -15,6 +16,7 @@ apiRouter.use('/oauth-clients', oauthClientsRouter) | |||
15 | apiRouter.use('/config', configRouter) | 16 | apiRouter.use('/config', configRouter) |
16 | apiRouter.use('/users', usersRouter) | 17 | apiRouter.use('/users', usersRouter) |
17 | apiRouter.use('/videos', videosRouter) | 18 | apiRouter.use('/videos', videosRouter) |
19 | apiRouter.use('/jobs', jobsRouter) | ||
18 | apiRouter.use('/ping', pong) | 20 | apiRouter.use('/ping', pong) |
19 | apiRouter.use('/*', badRequest) | 21 | apiRouter.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 @@ | |||
1 | import * as express from 'express' | ||
2 | import { asyncMiddleware, jobsSortValidator, setJobsSort, setPagination } from '../../middlewares' | ||
3 | import { paginationValidator } from '../../middlewares/validators/pagination' | ||
4 | import { database as db } from '../../initializers' | ||
5 | import { getFormattedObjects } from '../../helpers/utils' | ||
6 | import { authenticate } from '../../middlewares/oauth' | ||
7 | import { ensureUserHasRight } from '../../middlewares/user-right' | ||
8 | import { UserRight } from '../../../shared/models/users/user-right.enum' | ||
9 | |||
10 | const jobsRouter = express.Router() | ||
11 | |||
12 | jobsRouter.get('/', | ||
13 | authenticate, | ||
14 | ensureUserHasRight(UserRight.MANAGE_JOBS), | ||
15 | paginationValidator, | ||
16 | jobsSortValidator, | ||
17 | setJobsSort, | ||
18 | setPagination, | ||
19 | asyncMiddleware(listJobs) | ||
20 | ) | ||
21 | |||
22 | // --------------------------------------------------------------------------- | ||
23 | |||
24 | export { | ||
25 | jobsRouter | ||
26 | } | ||
27 | |||
28 | // --------------------------------------------------------------------------- | ||
29 | |||
30 | async 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 |
33 | const SORTABLE_COLUMNS = { | 33 | const 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 | ||
13 | function 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 | |||
13 | function setVideoAbusesSort (req: express.Request, res: express.Response, next: express.NextFunction) { | 19 | function 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 |
9 | const SORTABLE_USERS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.USERS) | 9 | const SORTABLE_USERS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.USERS) |
10 | const SORTABLE_JOBS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.JOBS) | ||
10 | const SORTABLE_VIDEO_ABUSES_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_ABUSES) | 11 | const SORTABLE_VIDEO_ABUSES_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_ABUSES) |
11 | const SORTABLE_VIDEOS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEOS) | 12 | const SORTABLE_VIDEOS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEOS) |
12 | const SORTABLE_BLACKLISTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.BLACKLISTS) | 13 | const SORTABLE_BLACKLISTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.BLACKLISTS) |
@@ -15,6 +16,7 @@ const SORTABLE_FOLLOWERS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.FOLLOW | |||
15 | const SORTABLE_FOLLOWING_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.FOLLOWING) | 16 | const SORTABLE_FOLLOWING_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.FOLLOWING) |
16 | 17 | ||
17 | const usersSortValidator = checkSort(SORTABLE_USERS_COLUMNS) | 18 | const usersSortValidator = checkSort(SORTABLE_USERS_COLUMNS) |
19 | const jobsSortValidator = checkSort(SORTABLE_JOBS_COLUMNS) | ||
18 | const videoAbusesSortValidator = checkSort(SORTABLE_VIDEO_ABUSES_COLUMNS) | 20 | const videoAbusesSortValidator = checkSort(SORTABLE_VIDEO_ABUSES_COLUMNS) |
19 | const videosSortValidator = checkSort(SORTABLE_VIDEOS_COLUMNS) | 21 | const videosSortValidator = checkSort(SORTABLE_VIDEOS_COLUMNS) |
20 | const blacklistSortValidator = checkSort(SORTABLE_BLACKLISTS_COLUMNS) | 22 | const 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | import * as Bluebird from 'bluebird' | 1 | import * as Bluebird from 'bluebird' |
3 | 2 | import * as Sequelize from 'sequelize' | |
4 | // Don't use barrel, import just what we need | ||
5 | import { AccountInstance } from './account-interface' | ||
6 | import { User as FormattedUser } from '../../../shared/models/users/user.model' | ||
7 | import { ResultList } from '../../../shared/models/result-list.model' | 3 | import { ResultList } from '../../../shared/models/result-list.model' |
8 | import { UserRight } from '../../../shared/models/users/user-right.enum' | 4 | import { UserRight } from '../../../shared/models/users/user-right.enum' |
9 | import { UserRole } from '../../../shared/models/users/user-role' | 5 | import { UserRole } from '../../../shared/models/users/user-role' |
6 | import { User as FormattedUser } from '../../../shared/models/users/user.model' | ||
7 | import { AccountInstance } from './account-interface' | ||
10 | 8 | ||
11 | export namespace UserMethods { | 9 | export 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 @@ | |||
1 | import * as Bluebird from 'bluebird' | ||
1 | import * as Sequelize from 'sequelize' | 2 | import * as Sequelize from 'sequelize' |
2 | import * as Promise from 'bluebird' | 3 | import { Job as FormattedJob, JobCategory, JobState } from '../../../shared/models/job.model' |
3 | 4 | import { ResultList } from '../../../shared/models/result-list.model' | |
4 | import { JobCategory, JobState } from '../../../shared/models/job.model' | ||
5 | 5 | ||
6 | export namespace JobMethods { | 6 | export 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 | ||
10 | export interface JobClass { | 13 | export interface JobClass { |
11 | listWithLimitByCategory: JobMethods.ListWithLimitByCategory | 14 | listWithLimitByCategory: JobMethods.ListWithLimitByCategory |
15 | listForApi: JobMethods.ListForApi, | ||
12 | } | 16 | } |
13 | 17 | ||
14 | export interface JobAttributes { | 18 | export 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 | ||
26 | export interface JobModel extends JobClass, Sequelize.Model<JobInstance, JobAttributes> {} | 33 | export 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 @@ | |||
1 | import { values } from 'lodash' | 1 | import { values } from 'lodash' |
2 | import * as Sequelize from 'sequelize' | 2 | import * as Sequelize from 'sequelize' |
3 | |||
4 | import { JOB_STATES, JOB_CATEGORIES } from '../../initializers' | ||
5 | |||
6 | import { addMethodsToModel } from '../utils' | ||
7 | import { | ||
8 | JobInstance, | ||
9 | JobAttributes, | ||
10 | |||
11 | JobMethods | ||
12 | } from './job-interface' | ||
13 | import { JobCategory, JobState } from '../../../shared/models/job.model' | 3 | import { JobCategory, JobState } from '../../../shared/models/job.model' |
4 | import { JOB_CATEGORIES, JOB_STATES } from '../../initializers' | ||
5 | import { addMethodsToModel, getSort } from '../utils' | ||
6 | import { JobAttributes, JobInstance, JobMethods } from './job-interface' | ||
14 | 7 | ||
15 | let Job: Sequelize.Model<JobInstance, JobAttributes> | 8 | let Job: Sequelize.Model<JobInstance, JobAttributes> |
16 | let listWithLimitByCategory: JobMethods.ListWithLimitByCategory | 9 | let listWithLimitByCategory: JobMethods.ListWithLimitByCategory |
10 | let listForApi: JobMethods.ListForApi | ||
11 | let toFormattedJSON: JobMethods.ToFormattedJSON | ||
17 | 12 | ||
18 | export default function defineJob (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { | 13 | export 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 | ||
54 | toFormattedJSON = 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 | ||
55 | listWithLimitByCategory = function (limit: number, state: JobState, jobCategory: JobCategory) { | 68 | listWithLimitByCategory = 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 | |||
83 | listForApi = 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 |
2 | import './follows' | 2 | import './follows' |
3 | import './jobs' | ||
3 | import './users' | 4 | import './users' |
4 | import './services' | 5 | import './services' |
5 | import './videos' | 6 | import './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 | |||
3 | import 'mocha' | ||
4 | import * as request from 'supertest' | ||
5 | |||
6 | import { createUser, flushTests, getUserAccessToken, killallServers, runServer, ServerInfo, setAccessTokensToServers } from '../../utils' | ||
7 | |||
8 | describe('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 @@ | |||
3 | import './video-transcoder' | 3 | import './video-transcoder' |
4 | import './multiple-servers' | 4 | import './multiple-servers' |
5 | import './follows' | 5 | import './follows' |
6 | import './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 | |||
3 | import * as chai from 'chai' | ||
4 | import 'mocha' | ||
5 | import { flushTests, killallServers, ServerInfo, setAccessTokensToServers, wait } from '../utils' | ||
6 | import { doubleFollow } from '../utils/follows' | ||
7 | import { getJobsList, getJobsListPaginationAndSort } from '../utils/jobs' | ||
8 | import { flushAndRunMultipleServers } from '../utils/servers' | ||
9 | import { uploadVideo } from '../utils/videos' | ||
10 | import { dateIsValid } from '../utils/miscs' | ||
11 | |||
12 | const expect = chai.expect | ||
13 | |||
14 | describe('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 @@ | |||
1 | import * as request from 'supertest' | ||
2 | |||
3 | function 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 | |||
14 | function 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 | |||
30 | export { | ||
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 @@ | |||
1 | export type JobState = 'pending' | 'processing' | 'error' | 'success' | 1 | export type JobState = 'pending' | 'processing' | 'error' | 'success' |
2 | export type JobCategory = 'transcoding' | 'activitypub-http' | 2 | export type JobCategory = 'transcoding' | 'activitypub-http' |
3 | |||
4 | export 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 @@ | |||
1 | import { UserRight } from './user-right.enum' | 1 | import { UserRight } from './user-right.enum' |
2 | import user from '../../../server/models/account/user' | ||
2 | 3 | ||
3 | // Keep the order | 4 | // Keep the order |
4 | export enum UserRole { | 5 | export enum UserRole { |