From 5cd80545422bba855cc9a730a2e13cc9d982c34b Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 30 Nov 2017 10:51:13 +0100 Subject: [PATCH] Add ability to list jobs --- client/src/app/+admin/admin-routing.module.ts | 4 +- client/src/app/+admin/admin.module.ts | 11 ++- client/src/app/+admin/jobs/index.ts | 1 + client/src/app/+admin/jobs/job.component.ts | 6 ++ client/src/app/+admin/jobs/job.routes.ts | 35 ++++++++ client/src/app/+admin/jobs/jobs-list/index.ts | 1 + .../jobs/jobs-list/jobs-list.component.html | 18 ++++ .../jobs/jobs-list/jobs-list.component.ts | 50 +++++++++++ client/src/app/+admin/jobs/shared/index.ts | 1 + .../src/app/+admin/jobs/shared/job.service.ts | 30 +++++++ .../app/core/menu/menu-admin.component.html | 5 ++ .../src/app/core/menu/menu-admin.component.ts | 4 + server/controllers/api/index.ts | 2 + server/controllers/api/jobs.ts | 34 ++++++++ server/initializers/constants.ts | 1 + server/middlewares/sort.ts | 9 +- server/middlewares/validators/sort.ts | 5 +- server/models/account/user-interface.ts | 8 +- server/models/job/job-interface.ts | 15 +++- server/models/job/job.ts | 52 +++++++++--- server/tests/api/check-params/index.ts | 1 + server/tests/api/check-params/jobs.ts | 84 +++++++++++++++++++ server/tests/api/index-slow.ts | 1 + server/tests/api/jobs.ts | 64 ++++++++++++++ server/tests/utils/follows.ts | 2 +- server/tests/utils/jobs.ts | 33 ++++++++ shared/models/job.model.ts | 10 +++ shared/models/users/user-right.enum.ts | 1 + shared/models/users/user-role.ts | 1 + 29 files changed, 462 insertions(+), 27 deletions(-) create mode 100644 client/src/app/+admin/jobs/index.ts create mode 100644 client/src/app/+admin/jobs/job.component.ts create mode 100644 client/src/app/+admin/jobs/job.routes.ts create mode 100644 client/src/app/+admin/jobs/jobs-list/index.ts create mode 100644 client/src/app/+admin/jobs/jobs-list/jobs-list.component.html create mode 100644 client/src/app/+admin/jobs/jobs-list/jobs-list.component.ts create mode 100644 client/src/app/+admin/jobs/shared/index.ts create mode 100644 client/src/app/+admin/jobs/shared/job.service.ts create mode 100644 server/controllers/api/jobs.ts create mode 100644 server/tests/api/check-params/jobs.ts create mode 100644 server/tests/api/jobs.ts create mode 100644 server/tests/utils/jobs.ts 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' import { UsersRoutes } from './users' import { VideoAbusesRoutes } from './video-abuses' import { VideoBlacklistRoutes } from './video-blacklist' +import { JobsRoutes } from './jobs/job.routes' const adminRoutes: Routes = [ { @@ -24,7 +25,8 @@ const adminRoutes: Routes = [ ...FollowsRoutes, ...UsersRoutes, ...VideoAbusesRoutes, - ...VideoBlacklistRoutes + ...VideoBlacklistRoutes, + ...JobsRoutes ] } ] 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' import { AdminComponent } from './admin.component' import { FollowersListComponent, FollowingAddComponent, FollowsComponent, FollowService } from './follows' import { FollowingListComponent } from './follows/following-list/following-list.component' +import { JobsComponent } from './jobs/job.component' +import { JobsListComponent } from './jobs/jobs-list/jobs-list.component' +import { JobService } from './jobs/shared/job.service' import { UserAddComponent, UserListComponent, UsersComponent, UserService, UserUpdateComponent } from './users' import { VideoAbuseListComponent, VideoAbusesComponent } from './video-abuses' import { VideoBlacklistComponent, VideoBlacklistListComponent } from './video-blacklist' @@ -33,7 +36,10 @@ import { VideoBlacklistComponent, VideoBlacklistListComponent } from './video-bl VideoBlacklistListComponent, VideoAbusesComponent, - VideoAbuseListComponent + VideoAbuseListComponent, + + JobsComponent, + JobsListComponent ], exports: [ @@ -42,7 +48,8 @@ import { VideoBlacklistComponent, VideoBlacklistListComponent } from './video-bl providers: [ FollowService, - UserService + UserService, + JobService ] }) 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 @@ +import { Component } from '@angular/core' + +@Component({ + template: '' +}) +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 @@ +import { Routes } from '@angular/router' + +import { UserRightGuard } from '../../core' +import { FollowingAddComponent } from './following-add' +import { UserRight } from '../../../../../shared' +import { FollowingListComponent } from './following-list/following-list.component' +import { JobsComponent } from './job.component' +import { JobsListComponent } from './jobs-list/jobs-list.component' + +export const JobsRoutes: Routes = [ + { + path: 'jobs', + component: JobsComponent, + canActivate: [ UserRightGuard ], + data: { + userRight: UserRight.MANAGE_JOBS + }, + children: [ + { + path: '', + redirectTo: 'list', + pathMatch: 'full' + }, + { + path: 'list', + component: JobsListComponent, + data: { + meta: { + title: 'Jobs list' + } + } + } + ] + } +] 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 @@ +
+
+

Jobs list

+ + + + + + + + + + +
+
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 @@ +import { Component } from '@angular/core' +import { NotificationsService } from 'angular2-notifications' +import { SortMeta } from 'primeng/primeng' +import { Job } from '../../../../../../shared/index' +import { RestPagination, RestTable } from '../../../shared' +import { JobService } from '../shared' +import { RestExtractor } from '../../../shared/rest/rest-extractor.service' + +@Component({ + selector: 'my-jobs-list', + templateUrl: './jobs-list.component.html', + styleUrls: [ ] +}) +export class JobsListComponent extends RestTable { + jobs: Job[] = [] + totalRecords = 0 + rowsPerPage = 10 + sort: SortMeta = { field: 'createdAt', order: 1 } + pagination: RestPagination = { count: this.rowsPerPage, start: 0 } + + constructor ( + private notificationsService: NotificationsService, + private restExtractor: RestExtractor, + private jobsService: JobService + ) { + super() + } + + protected loadData () { + this.jobsService + .getJobs(this.pagination, this.sort) + .map(res => this.restExtractor.applyToResultListData(res, this.formatJob.bind(this))) + .subscribe( + resultList => { + this.jobs = resultList.data + this.totalRecords = resultList.total + }, + + err => this.notificationsService.error('Error', err.message) + ) + } + + private formatJob (job: Job) { + const handlerInputData = JSON.stringify(job.handlerInputData) + + return Object.assign(job, { + handlerInputData + }) + } +} 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 @@ +import { HttpClient, HttpParams } from '@angular/common/http' +import { Injectable } from '@angular/core' +import { SortMeta } from 'primeng/primeng' +import 'rxjs/add/operator/catch' +import 'rxjs/add/operator/map' +import { Observable } from 'rxjs/Observable' +import { ResultList } from '../../../../../../shared' +import { Job } from '../../../../../../shared/models/job.model' + +import { RestExtractor, RestPagination, RestService } from '../../../shared' + +@Injectable() +export class JobService { + private static BASE_JOB_URL = API_URL + '/api/v1/jobs' + + constructor ( + private authHttp: HttpClient, + private restService: RestService, + private restExtractor: RestExtractor + ) {} + + getJobs (pagination: RestPagination, sort: SortMeta): Observable> { + let params = new HttpParams() + params = this.restService.addRestGetParams(params, pagination, sort) + + return this.authHttp.get>(JobService.BASE_JOB_URL, { params }) + .map(res => this.restExtractor.convertResultListDateToHuman(res)) + .catch(err => this.restExtractor.handleError(err)) + } +} 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 @@ Video blacklist + + + + Jobs +
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 { hasVideoBlacklistRight () { return this.auth.getUser().hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) } + + hasJobsRight () { + return this.auth.getUser().hasRight(UserRight.MANAGE_JOBS) + } } 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' import { serverRouter } from './server' import { usersRouter } from './users' import { videosRouter } from './videos' +import { jobsRouter } from './jobs' const apiRouter = express.Router() @@ -15,6 +16,7 @@ apiRouter.use('/oauth-clients', oauthClientsRouter) apiRouter.use('/config', configRouter) apiRouter.use('/users', usersRouter) apiRouter.use('/videos', videosRouter) +apiRouter.use('/jobs', jobsRouter) apiRouter.use('/ping', pong) apiRouter.use('/*', badRequest) 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 @@ +import * as express from 'express' +import { asyncMiddleware, jobsSortValidator, setJobsSort, setPagination } from '../../middlewares' +import { paginationValidator } from '../../middlewares/validators/pagination' +import { database as db } from '../../initializers' +import { getFormattedObjects } from '../../helpers/utils' +import { authenticate } from '../../middlewares/oauth' +import { ensureUserHasRight } from '../../middlewares/user-right' +import { UserRight } from '../../../shared/models/users/user-right.enum' + +const jobsRouter = express.Router() + +jobsRouter.get('/', + authenticate, + ensureUserHasRight(UserRight.MANAGE_JOBS), + paginationValidator, + jobsSortValidator, + setJobsSort, + setPagination, + asyncMiddleware(listJobs) +) + +// --------------------------------------------------------------------------- + +export { + jobsRouter +} + +// --------------------------------------------------------------------------- + +async function listJobs (req: express.Request, res: express.Response, next: express.NextFunction) { + const resultList = await db.Job.listForApi(req.query.start, req.query.count, req.query.sort) + + return res.json(getFormattedObjects(resultList.data, resultList.total)) +} 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 = { // Sortable columns per schema const SORTABLE_COLUMNS = { USERS: [ 'id', 'username', 'createdAt' ], + JOBS: [ 'id', 'createdAt' ], VIDEO_ABUSES: [ 'id', 'createdAt' ], VIDEO_CHANNELS: [ 'id', 'name', 'updatedAt', 'createdAt' ], 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 return next() } +function setJobsSort (req: express.Request, res: express.Response, next: express.NextFunction) { + if (!req.query.sort) req.query.sort = '-createdAt' + + return next() +} + function setVideoAbusesSort (req: express.Request, res: express.Response, next: express.NextFunction) { if (!req.query.sort) req.query.sort = '-createdAt' @@ -70,5 +76,6 @@ export { setVideosSort, setBlacklistSort, setFollowersSort, - setFollowingSort + setFollowingSort, + setJobsSort } 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' // Initialize constants here for better performances const SORTABLE_USERS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.USERS) +const SORTABLE_JOBS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.JOBS) const SORTABLE_VIDEO_ABUSES_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_ABUSES) const SORTABLE_VIDEOS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEOS) const SORTABLE_BLACKLISTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.BLACKLISTS) @@ -15,6 +16,7 @@ const SORTABLE_FOLLOWERS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.FOLLOW const SORTABLE_FOLLOWING_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.FOLLOWING) const usersSortValidator = checkSort(SORTABLE_USERS_COLUMNS) +const jobsSortValidator = checkSort(SORTABLE_JOBS_COLUMNS) const videoAbusesSortValidator = checkSort(SORTABLE_VIDEO_ABUSES_COLUMNS) const videosSortValidator = checkSort(SORTABLE_VIDEOS_COLUMNS) const blacklistSortValidator = checkSort(SORTABLE_BLACKLISTS_COLUMNS) @@ -31,7 +33,8 @@ export { videosSortValidator, blacklistSortValidator, followersSortValidator, - followingSortValidator + followingSortValidator, + jobsSortValidator } // --------------------------------------------------------------------------- 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 @@ -import * as Sequelize from 'sequelize' import * as Bluebird from 'bluebird' - -// Don't use barrel, import just what we need -import { AccountInstance } from './account-interface' -import { User as FormattedUser } from '../../../shared/models/users/user.model' +import * as Sequelize from 'sequelize' import { ResultList } from '../../../shared/models/result-list.model' import { UserRight } from '../../../shared/models/users/user-right.enum' import { UserRole } from '../../../shared/models/users/user-role' +import { User as FormattedUser } from '../../../shared/models/users/user.model' +import { AccountInstance } from './account-interface' export namespace UserMethods { 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 @@ +import * as Bluebird from 'bluebird' import * as Sequelize from 'sequelize' -import * as Promise from 'bluebird' - -import { JobCategory, JobState } from '../../../shared/models/job.model' +import { Job as FormattedJob, JobCategory, JobState } from '../../../shared/models/job.model' +import { ResultList } from '../../../shared/models/result-list.model' export namespace JobMethods { - export type ListWithLimitByCategory = (limit: number, state: JobState, category: JobCategory) => Promise + export type ListWithLimitByCategory = (limit: number, state: JobState, category: JobCategory) => Bluebird + export type ListForApi = (start: number, count: number, sort: string) => Bluebird< ResultList > + + export type ToFormattedJSON = (this: JobInstance) => FormattedJob } export interface JobClass { listWithLimitByCategory: JobMethods.ListWithLimitByCategory + listForApi: JobMethods.ListForApi, } export interface JobAttributes { state: JobState + category: JobCategory handlerName: string handlerInputData: any } @@ -21,6 +26,8 @@ export interface JobInstance extends JobClass, JobAttributes, Sequelize.Instance id: number createdAt: Date updatedAt: Date + + toFormattedJSON: JobMethods.ToFormattedJSON } export interface JobModel extends JobClass, Sequelize.Model {} 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 @@ import { values } from 'lodash' import * as Sequelize from 'sequelize' - -import { JOB_STATES, JOB_CATEGORIES } from '../../initializers' - -import { addMethodsToModel } from '../utils' -import { - JobInstance, - JobAttributes, - - JobMethods -} from './job-interface' import { JobCategory, JobState } from '../../../shared/models/job.model' +import { JOB_CATEGORIES, JOB_STATES } from '../../initializers' +import { addMethodsToModel, getSort } from '../utils' +import { JobAttributes, JobInstance, JobMethods } from './job-interface' let Job: Sequelize.Model let listWithLimitByCategory: JobMethods.ListWithLimitByCategory +let listForApi: JobMethods.ListForApi +let toFormattedJSON: JobMethods.ToFormattedJSON export default function defineJob (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { Job = sequelize.define('Job', @@ -44,12 +39,30 @@ export default function defineJob (sequelize: Sequelize.Sequelize, DataTypes: Se } ) - const classMethods = [ listWithLimitByCategory ] - addMethodsToModel(Job, classMethods) + const classMethods = [ + listWithLimitByCategory, + listForApi + ] + const instanceMethods = [ + toFormattedJSON + ] + addMethodsToModel(Job, classMethods, instanceMethods) return Job } +toFormattedJSON = function (this: JobInstance) { + return { + id: this.id, + state: this.state, + category: this.category, + handlerName: this.handlerName, + handlerInputData: this.handlerInputData, + createdAt: this.createdAt, + updatedAt: this.updatedAt + } +} + // --------------------------------------------------------------------------- listWithLimitByCategory = function (limit: number, state: JobState, jobCategory: JobCategory) { @@ -66,3 +79,18 @@ listWithLimitByCategory = function (limit: number, state: JobState, jobCategory: return Job.findAll(query) } + +listForApi = function (start: number, count: number, sort: string) { + const query = { + offset: start, + limit: count, + order: [ getSort(sort) ] + } + + return Job.findAndCountAll(query).then(({ rows, count }) => { + return { + data: rows, + total: count + } + }) +} 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 @@ // Order of the tests we want to execute import './follows' +import './jobs' import './users' import './services' 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 @@ +/* tslint:disable:no-unused-expression */ + +import 'mocha' +import * as request from 'supertest' + +import { createUser, flushTests, getUserAccessToken, killallServers, runServer, ServerInfo, setAccessTokensToServers } from '../../utils' + +describe('Test jobs API validators', function () { + const path = '/api/v1/jobs/' + let server: ServerInfo + let userAccessToken = '' + + // --------------------------------------------------------------- + + before(async function () { + this.timeout(120000) + + await flushTests() + + server = await runServer(1) + + await setAccessTokensToServers([ server ]) + + const user = { + username: 'user1', + password: 'my super password' + } + await createUser(server.url, server.accessToken, user.username, user.password) + userAccessToken = await getUserAccessToken(server, user) + }) + + describe('When listing jobs', function () { + it('Should fail with a bad start pagination', async function () { + await request(server.url) + .get(path) + .query({ start: 'hello' }) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + server.accessToken) + .expect(400) + }) + + it('Should fail with a bad count pagination', async function () { + await request(server.url) + .get(path) + .query({ count: 'hello' }) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + server.accessToken) + .expect(400) + }) + + it('Should fail with an incorrect sort', async function () { + await request(server.url) + .get(path) + .query({ sort: 'hello' }) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + server.accessToken) + .expect(400) + }) + + it('Should fail with a non authenticated user', async function () { + await request(server.url) + .get(path) + .set('Accept', 'application/json') + .expect(401) + }) + + it('Should fail with a non admin user', async function () { + await request(server.url) + .get(path) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + userAccessToken) + .expect(403) + }) + }) + + after(async function () { + killallServers([ server ]) + + // Keep the logs if the test failed + if (this['ok']) { + await flushTests() + } + }) +}) 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 @@ import './video-transcoder' import './multiple-servers' import './follows' +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 @@ +/* tslint:disable:no-unused-expression */ + +import * as chai from 'chai' +import 'mocha' +import { flushTests, killallServers, ServerInfo, setAccessTokensToServers, wait } from '../utils' +import { doubleFollow } from '../utils/follows' +import { getJobsList, getJobsListPaginationAndSort } from '../utils/jobs' +import { flushAndRunMultipleServers } from '../utils/servers' +import { uploadVideo } from '../utils/videos' +import { dateIsValid } from '../utils/miscs' + +const expect = chai.expect + +describe('Test jobs', function () { + let servers: ServerInfo[] + + before(async function () { + this.timeout(30000) + + servers = await flushAndRunMultipleServers(2) + + await setAccessTokensToServers(servers) + + // Server 1 and server 2 follow each other + await doubleFollow(servers[0], servers[1]) + }) + + it('Should create some jobs', async function () { + this.timeout(30000) + + await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video1' }) + await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video2' }) + + await wait(15000) + }) + + it('Should list jobs', async function () { + const res = await getJobsList(servers[1].url, servers[1].accessToken) + expect(res.body.total).to.be.above(2) + expect(res.body.data).to.have.length.above(2) + }) + + it('Should list jobs with sort and pagination', async function () { + const res = await getJobsListPaginationAndSort(servers[1].url, servers[1].accessToken, 4, 1, 'createdAt') + expect(res.body.total).to.be.above(2) + expect(res.body.data).to.have.lengthOf(1) + + const job = res.body.data[0] + expect(job.state).to.equal('success') + expect(job.category).to.equal('transcoding') + expect(job.handlerName).to.have.length.above(3) + expect(dateIsValid(job.createdAt)).to.be.true + expect(dateIsValid(job.updatedAt)).to.be.true + }) + + after(async function () { + killallServers(servers) + + // Keep the logs if the test failed + if (this['ok']) { + await flushTests() + } + }) +}) 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) { ]) // Wait request propagation - await wait(20000) + await wait(10000) return true } 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 @@ +import * as request from 'supertest' + +function getJobsList (url: string, accessToken: string) { + const path = '/api/v1/jobs' + + return request(url) + .get(path) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + accessToken) + .expect(200) + .expect('Content-Type', /json/) +} + +function getJobsListPaginationAndSort (url: string, accessToken: string, start: number, count: number, sort: string) { + const path = '/api/v1/jobs' + + return request(url) + .get(path) + .query({ start }) + .query({ count }) + .query({ sort }) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + accessToken) + .expect(200) + .expect('Content-Type', /json/) +} + +// --------------------------------------------------------------------------- + +export { + getJobsList, + getJobsListPaginationAndSort +} 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 @@ export type JobState = 'pending' | 'processing' | 'error' | 'success' export type JobCategory = 'transcoding' | 'activitypub-http' + +export interface Job { + id: number + state: JobState + category: JobCategory + handlerName: string + handlerInputData: any + createdAt: Date + updatedAt: Date +} 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 { MANAGE_SERVER_FOLLOW, MANAGE_VIDEO_ABUSES, MANAGE_VIDEO_BLACKLIST, + MANAGE_JOBS, REMOVE_ANY_VIDEO, REMOVE_ANY_VIDEO_CHANNEL } 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 @@ import { UserRight } from './user-right.enum' +import user from '../../../server/models/account/user' // Keep the order export enum UserRole { -- 2.41.0