aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2018-01-25 15:05:18 +0100
committerChocobozzz <me@florianbigard.com>2018-01-25 18:41:17 +0100
commit94a5ff8a4a75d75bb9df542a39ce8769e7a7e6a4 (patch)
tree32a9148e0e4567f0c4ffae0412cbed20b84e8873
parentd765fafc3faf0db9818eb1a07161df1cb1bc0efa (diff)
downloadPeerTube-94a5ff8a4a75d75bb9df542a39ce8769e7a7e6a4.tar.gz
PeerTube-94a5ff8a4a75d75bb9df542a39ce8769e7a7e6a4.tar.zst
PeerTube-94a5ff8a4a75d75bb9df542a39ce8769e7a7e6a4.zip
Move job queue to redis
We'll use it as cache in the future. /!\ You'll loose your old jobs (pending jobs too) so upgrade only when you don't have pending job anymore.
-rw-r--r--README.md1
-rw-r--r--client/src/app/+admin/jobs/jobs-list/jobs-list.component.html17
-rw-r--r--client/src/app/+admin/jobs/jobs-list/jobs-list.component.scss7
-rw-r--r--client/src/app/+admin/jobs/jobs-list/jobs-list.component.ts11
-rw-r--r--client/src/app/+admin/jobs/shared/job.service.ts9
-rw-r--r--config/default.yaml5
-rw-r--r--config/production.yaml.example5
-rw-r--r--package.json2
-rwxr-xr-xscripts/clean/server/test.sh1
-rwxr-xr-xscripts/parse-log.ts22
-rw-r--r--server.ts8
-rw-r--r--server/controllers/api/jobs.ts38
-rw-r--r--server/controllers/api/server/follows.ts2
-rw-r--r--server/controllers/api/videos/index.ts24
-rw-r--r--server/helpers/custom-validators/jobs.ts14
-rw-r--r--server/helpers/database-utils.ts1
-rw-r--r--server/initializers/constants.ts49
-rw-r--r--server/initializers/database.ts2
-rw-r--r--server/initializers/migrations/0100-activitypub.ts5
-rw-r--r--server/initializers/migrations/0180-job-table-delete.ts18
-rw-r--r--server/lib/activitypub/actor.ts59
-rw-r--r--server/lib/activitypub/fetch.ts9
-rw-r--r--server/lib/activitypub/process/process-accept.ts2
-rw-r--r--server/lib/activitypub/process/process-follow.ts2
-rw-r--r--server/lib/activitypub/send/misc.ts26
-rw-r--r--server/lib/activitypub/send/send-accept.ts5
-rw-r--r--server/lib/activitypub/send/send-announce.ts2
-rw-r--r--server/lib/activitypub/send/send-create.ts22
-rw-r--r--server/lib/activitypub/send/send-follow.ts5
-rw-r--r--server/lib/activitypub/send/send-like.ts2
-rw-r--r--server/lib/activitypub/send/send-undo.ts14
-rw-r--r--server/lib/job-queue/handlers/activitypub-http-broadcast.ts (renamed from server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-broadcast-handler.ts)32
-rw-r--r--server/lib/job-queue/handlers/activitypub-http-fetcher.ts (renamed from server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-fetcher-handler.ts)27
-rw-r--r--server/lib/job-queue/handlers/activitypub-http-unicast.ts43
-rw-r--r--server/lib/job-queue/handlers/utils/activitypub-http-utils.ts39
-rw-r--r--server/lib/job-queue/handlers/video-file.ts (renamed from server/lib/jobs/transcoding-job-scheduler/video-file-optimizer-handler.ts)60
-rw-r--r--server/lib/job-queue/index.ts1
-rw-r--r--server/lib/job-queue/job-queue.ts124
-rw-r--r--server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler.ts94
-rw-r--r--server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-unicast-handler.ts50
-rw-r--r--server/lib/jobs/activitypub-http-job-scheduler/index.ts1
-rw-r--r--server/lib/jobs/index.ts2
-rw-r--r--server/lib/jobs/job-scheduler.ts144
-rw-r--r--server/lib/jobs/transcoding-job-scheduler/index.ts1
-rw-r--r--server/lib/jobs/transcoding-job-scheduler/transcoding-job-scheduler.ts23
-rw-r--r--server/lib/jobs/transcoding-job-scheduler/video-file-transcoder-handler.ts48
-rw-r--r--server/lib/schedulers/remove-old-jobs-scheduler.ts19
-rw-r--r--server/middlewares/validators/jobs.ts23
-rw-r--r--server/models/job/job.ts80
-rw-r--r--server/tests/api/check-params/jobs.ts11
-rw-r--r--server/tests/api/server/handle-down.ts10
-rw-r--r--server/tests/api/server/jobs.ts10
-rw-r--r--server/tests/api/videos/multiple-servers.ts21
-rw-r--r--server/tests/real-world/real-world.ts21
-rw-r--r--server/tests/utils/server/jobs.ts9
-rw-r--r--shared/models/job.model.ts14
-rw-r--r--support/doc/dependencies.md4
-rw-r--r--support/doc/development/server/code.md1
-rw-r--r--tsconfig.json8
-rw-r--r--yarn.lock372
60 files changed, 985 insertions, 696 deletions
diff --git a/README.md b/README.md
index 491b09a36..956403552 100644
--- a/README.md
+++ b/README.md
@@ -131,6 +131,7 @@ BitTorrent) inside the web browser, as of today.
131 131
132 * nginx 132 * nginx
133 * PostgreSQL 133 * PostgreSQL
134 * Redis
134 * **NodeJS >= 8.x** 135 * **NodeJS >= 8.x**
135 * yarn 136 * yarn
136 * OpenSSL (cli) 137 * OpenSSL (cli)
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
index 809155338..f1b14e5e3 100644
--- a/client/src/app/+admin/jobs/jobs-list/jobs-list.component.html
+++ b/client/src/app/+admin/jobs/jobs-list/jobs-list.component.html
@@ -1,20 +1,27 @@
1<div class="admin-sub-header"> 1<div class="admin-sub-header">
2 <div class="admin-sub-title">Jobs list</div> 2 <div class="admin-sub-title">Jobs list</div>
3
4 <div class="peertube-select-container">
5 <select [(ngModel)]="jobState" (ngModelChange)="onJobStateChanged()">
6 <option *ngFor="let state of jobStates" [value]="state">{{ state }}</option>
7 </select>
8 </div>
3</div> 9</div>
4 10
11
12
5<p-dataTable 13<p-dataTable
6 [value]="jobs" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage" 14 [value]="jobs" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
7 sortField="createdAt" (onLazyLoad)="loadLazy($event)" [scrollable]="true" [virtualScroll]="true" [scrollHeight]="scrollHeight" 15 sortField="createdAt" (onLazyLoad)="loadLazy($event)" [scrollable]="true" [virtualScroll]="true" [scrollHeight]="scrollHeight"
8> 16>
9 <p-column field="id" header="ID" [style]="{ width: '60px' }"></p-column> 17 <p-column field="id" header="ID" [style]="{ width: '60px' }"></p-column>
10 <p-column field="category" header="Category" [style]="{ width: '130px' }"></p-column> 18 <p-column field="type" header="Type" [style]="{ width: '210px' }"></p-column>
11 <p-column field="handlerName" header="Handler name" [style]="{ width: '210px' }"></p-column> 19 <p-column field="state" header="State" [style]="{ width: '130px' }"></p-column>
12 <p-column header="Input data"> 20 <p-column header="Payload">
13 <ng-template pTemplate="body" let-job="rowData"> 21 <ng-template pTemplate="body" let-job="rowData">
14 <pre>{{ job.handlerInputData }}</pre> 22 <pre>{{ job.data }}</pre>
15 </ng-template> 23 </ng-template>
16 </p-column> 24 </p-column>
17 <p-column field="state" header="State" [style]="{ width: '100px' }"></p-column>
18 <p-column field="createdAt" header="Created date" [sortable]="true" [style]="{ width: '250px' }"></p-column> 25 <p-column field="createdAt" header="Created date" [sortable]="true" [style]="{ width: '250px' }"></p-column>
19 <p-column field="updatedAt" header="Updated date" [style]="{ width: '250px' }"></p-column> 26 <p-column field="updatedAt" header="Updated date" [style]="{ width: '250px' }"></p-column>
20</p-dataTable> 27</p-dataTable>
diff --git a/client/src/app/+admin/jobs/jobs-list/jobs-list.component.scss b/client/src/app/+admin/jobs/jobs-list/jobs-list.component.scss
index 47c01f64a..5c2ad21f2 100644
--- a/client/src/app/+admin/jobs/jobs-list/jobs-list.component.scss
+++ b/client/src/app/+admin/jobs/jobs-list/jobs-list.component.scss
@@ -1,3 +1,10 @@
1@import '_variables';
2@import '_mixins';
3
4.peertube-select-container {
5 @include peertube-select-container(auto);
6}
7
1pre { 8pre {
2 font-size: 11px; 9 font-size: 11px;
3} 10}
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
index f93847f29..7de6f70d2 100644
--- a/client/src/app/+admin/jobs/jobs-list/jobs-list.component.ts
+++ b/client/src/app/+admin/jobs/jobs-list/jobs-list.component.ts
@@ -2,6 +2,7 @@ import { Component, OnInit } from '@angular/core'
2import { NotificationsService } from 'angular2-notifications' 2import { NotificationsService } from 'angular2-notifications'
3import { SortMeta } from 'primeng/primeng' 3import { SortMeta } from 'primeng/primeng'
4import { Job } from '../../../../../../shared/index' 4import { Job } from '../../../../../../shared/index'
5import { JobState } from '../../../../../../shared/models'
5import { RestPagination, RestTable } from '../../../shared' 6import { RestPagination, RestTable } from '../../../shared'
6import { viewportHeight } from '../../../shared/misc/utils' 7import { viewportHeight } from '../../../shared/misc/utils'
7import { JobService } from '../shared' 8import { JobService } from '../shared'
@@ -13,10 +14,12 @@ import { RestExtractor } from '../../../shared/rest/rest-extractor.service'
13 styleUrls: [ './jobs-list.component.scss' ] 14 styleUrls: [ './jobs-list.component.scss' ]
14}) 15})
15export class JobsListComponent extends RestTable implements OnInit { 16export class JobsListComponent extends RestTable implements OnInit {
17 jobState: JobState = 'inactive'
18 jobStates: JobState[] = [ 'active', 'complete', 'failed', 'inactive', 'delayed' ]
16 jobs: Job[] = [] 19 jobs: Job[] = []
17 totalRecords = 0 20 totalRecords = 0
18 rowsPerPage = 20 21 rowsPerPage = 20
19 sort: SortMeta = { field: 'createdAt', order: 1 } 22 sort: SortMeta = { field: 'createdAt', order: -1 }
20 pagination: RestPagination = { count: this.rowsPerPage, start: 0 } 23 pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
21 scrollHeight = '' 24 scrollHeight = ''
22 25
@@ -33,9 +36,13 @@ export class JobsListComponent extends RestTable implements OnInit {
33 this.scrollHeight = (viewportHeight() - 380) + 'px' 36 this.scrollHeight = (viewportHeight() - 380) + 'px'
34 } 37 }
35 38
39 onJobStateChanged () {
40 this.loadData()
41 }
42
36 protected loadData () { 43 protected loadData () {
37 this.jobsService 44 this.jobsService
38 .getJobs(this.pagination, this.sort) 45 .getJobs(this.jobState, this.pagination, this.sort)
39 .subscribe( 46 .subscribe(
40 resultList => { 47 resultList => {
41 this.jobs = resultList.data 48 this.jobs = resultList.data
diff --git a/client/src/app/+admin/jobs/shared/job.service.ts b/client/src/app/+admin/jobs/shared/job.service.ts
index 61ee16077..a19d278c9 100644
--- a/client/src/app/+admin/jobs/shared/job.service.ts
+++ b/client/src/app/+admin/jobs/shared/job.service.ts
@@ -5,6 +5,7 @@ import 'rxjs/add/operator/catch'
5import 'rxjs/add/operator/map' 5import 'rxjs/add/operator/map'
6import { Observable } from 'rxjs/Observable' 6import { Observable } from 'rxjs/Observable'
7import { ResultList } from '../../../../../../shared' 7import { ResultList } from '../../../../../../shared'
8import { JobState } from '../../../../../../shared/models'
8import { Job } from '../../../../../../shared/models/job.model' 9import { Job } from '../../../../../../shared/models/job.model'
9import { environment } from '../../../../environments/environment' 10import { environment } from '../../../../environments/environment'
10import { RestExtractor, RestPagination, RestService } from '../../../shared' 11import { RestExtractor, RestPagination, RestService } from '../../../shared'
@@ -19,19 +20,19 @@ export class JobService {
19 private restExtractor: RestExtractor 20 private restExtractor: RestExtractor
20 ) {} 21 ) {}
21 22
22 getJobs (pagination: RestPagination, sort: SortMeta): Observable<ResultList<Job>> { 23 getJobs (state: JobState, pagination: RestPagination, sort: SortMeta): Observable<ResultList<Job>> {
23 let params = new HttpParams() 24 let params = new HttpParams()
24 params = this.restService.addRestGetParams(params, pagination, sort) 25 params = this.restService.addRestGetParams(params, pagination, sort)
25 26
26 return this.authHttp.get<ResultList<Job>>(JobService.BASE_JOB_URL, { params }) 27 return this.authHttp.get<ResultList<Job>>(JobService.BASE_JOB_URL + '/' + state, { params })
27 .map(res => this.restExtractor.convertResultListDateToHuman(res)) 28 .map(res => this.restExtractor.convertResultListDateToHuman(res))
28 .map(res => this.restExtractor.applyToResultListData(res, this.prettyPrintData)) 29 .map(res => this.restExtractor.applyToResultListData(res, this.prettyPrintData))
29 .catch(err => this.restExtractor.handleError(err)) 30 .catch(err => this.restExtractor.handleError(err))
30 } 31 }
31 32
32 private prettyPrintData (obj: Job) { 33 private prettyPrintData (obj: Job) {
33 const handlerInputData = JSON.stringify(obj.handlerInputData, null, 2) 34 const data = JSON.stringify(obj.data, null, 2)
34 35
35 return Object.assign(obj, { handlerInputData }) 36 return Object.assign(obj, { data })
36 } 37 }
37} 38}
diff --git a/config/default.yaml b/config/default.yaml
index 3d0732069..fd04b5ce6 100644
--- a/config/default.yaml
+++ b/config/default.yaml
@@ -14,6 +14,11 @@ database:
14 username: 'peertube' 14 username: 'peertube'
15 password: 'peertube' 15 password: 'peertube'
16 16
17redis:
18 hostname: 'localhost'
19 port: 6379
20 auth: null
21
17# From the project root directory 22# From the project root directory
18storage: 23storage:
19 avatars: 'storage/avatars/' 24 avatars: 'storage/avatars/'
diff --git a/config/production.yaml.example b/config/production.yaml.example
index 1288eeaa2..a2b332983 100644
--- a/config/production.yaml.example
+++ b/config/production.yaml.example
@@ -15,6 +15,11 @@ database:
15 username: 'peertube' 15 username: 'peertube'
16 password: 'peertube' 16 password: 'peertube'
17 17
18redis:
19 hostname: 'localhost'
20 port: 6379
21 auth: null
22
18# From the project root directory 23# From the project root directory
19storage: 24storage:
20 avatars: '/var/www/peertube/storage/avatars/' 25 avatars: '/var/www/peertube/storage/avatars/'
diff --git a/package.json b/package.json
index 45651a1e5..db1bfe5d8 100644
--- a/package.json
+++ b/package.json
@@ -70,6 +70,7 @@
70 "js-yaml": "^3.5.4", 70 "js-yaml": "^3.5.4",
71 "jsonld": "^0.5.12", 71 "jsonld": "^0.5.12",
72 "jsonld-signatures": "https://github.com/Chocobozzz/jsonld-signatures#rsa2017", 72 "jsonld-signatures": "https://github.com/Chocobozzz/jsonld-signatures#rsa2017",
73 "kue": "^0.11.6",
73 "lodash": "^4.11.1", 74 "lodash": "^4.11.1",
74 "magnet-uri": "^5.1.4", 75 "magnet-uri": "^5.1.4",
75 "mkdirp": "^0.5.1", 76 "mkdirp": "^0.5.1",
@@ -103,6 +104,7 @@
103 "@types/chai": "^4.0.4", 104 "@types/chai": "^4.0.4",
104 "@types/config": "^0.0.33", 105 "@types/config": "^0.0.33",
105 "@types/express": "^4.0.35", 106 "@types/express": "^4.0.35",
107 "@types/kue": "^0.11.8",
106 "@types/lodash": "^4.14.64", 108 "@types/lodash": "^4.14.64",
107 "@types/magnet-uri": "^5.1.1", 109 "@types/magnet-uri": "^5.1.1",
108 "@types/mkdirp": "^0.5.1", 110 "@types/mkdirp": "^0.5.1",
diff --git a/scripts/clean/server/test.sh b/scripts/clean/server/test.sh
index 2ceb71244..b56021874 100755
--- a/scripts/clean/server/test.sh
+++ b/scripts/clean/server/test.sh
@@ -6,4 +6,5 @@ for i in $(seq 1 6); do
6 rm -f "./config/local-test.json" 6 rm -f "./config/local-test.json"
7 rm -f "./config/local-test-$i.json" 7 rm -f "./config/local-test-$i.json"
8 createdb "peertube_test$i" 8 createdb "peertube_test$i"
9 redis-cli KEYS "q-localhost:900$i*" | grep -v empty | xargs --no-run-if-empty redis-cli DEL
9done 10done
diff --git a/scripts/parse-log.ts b/scripts/parse-log.ts
index 7e804b3f9..9429512b7 100755
--- a/scripts/parse-log.ts
+++ b/scripts/parse-log.ts
@@ -2,16 +2,34 @@ import { createReadStream } from 'fs'
2import { join } from 'path' 2import { join } from 'path'
3import { createInterface } from 'readline' 3import { createInterface } from 'readline'
4import * as winston from 'winston' 4import * as winston from 'winston'
5import { labelFormatter, loggerFormat, timestampFormatter } from '../server/helpers/logger' 5import { labelFormatter } from '../server/helpers/logger'
6import { CONFIG } from '../server/initializers/constants' 6import { CONFIG } from '../server/initializers/constants'
7 7
8const excludedKeys = {
9 level: true,
10 message: true,
11 splat: true,
12 timestamp: true,
13 label: true
14}
15function keysExcluder (key, value) {
16 return excludedKeys[key] === true ? undefined : value
17}
18
19const loggerFormat = winston.format.printf((info) => {
20 let additionalInfos = JSON.stringify(info, keysExcluder, 2)
21 if (additionalInfos === '{}') additionalInfos = ''
22 else additionalInfos = ' ' + additionalInfos
23
24 return `[${info.label}] ${new Date(info.timestamp).toISOString()} ${info.level}: ${info.message}${additionalInfos}`
25})
26
8const logger = new winston.createLogger({ 27const logger = new winston.createLogger({
9 transports: [ 28 transports: [
10 new winston.transports.Console({ 29 new winston.transports.Console({
11 level: 'debug', 30 level: 'debug',
12 stderrLevels: [], 31 stderrLevels: [],
13 format: winston.format.combine( 32 format: winston.format.combine(
14 timestampFormatter,
15 winston.format.splat(), 33 winston.format.splat(),
16 labelFormatter, 34 labelFormatter,
17 winston.format.colorize(), 35 winston.format.colorize(),
diff --git a/server.ts b/server.ts
index a138b6359..d0b351c62 100644
--- a/server.ts
+++ b/server.ts
@@ -53,10 +53,11 @@ migrate()
53 53
54// ----------- PeerTube modules ----------- 54// ----------- PeerTube modules -----------
55import { installApplication } from './server/initializers' 55import { installApplication } from './server/initializers'
56import { activitypubHttpJobScheduler, transcodingJobScheduler } from './server/lib/jobs' 56import { JobQueue } from './server/lib/job-queue'
57import { VideosPreviewCache } from './server/lib/cache' 57import { VideosPreviewCache } from './server/lib/cache'
58import { apiRouter, clientsRouter, staticRouter, servicesRouter, webfingerRouter, activityPubRouter } from './server/controllers' 58import { apiRouter, clientsRouter, staticRouter, servicesRouter, webfingerRouter, activityPubRouter } from './server/controllers'
59import { BadActorFollowScheduler } from './server/lib/schedulers/bad-actor-follow-scheduler' 59import { BadActorFollowScheduler } from './server/lib/schedulers/bad-actor-follow-scheduler'
60import { RemoveOldJobsScheduler } from './server/lib/schedulers/remove-old-jobs-scheduler'
60 61
61// ----------- Command line ----------- 62// ----------- Command line -----------
62 63
@@ -170,9 +171,8 @@ function onDatabaseInitDone () {
170 server.listen(port, () => { 171 server.listen(port, () => {
171 VideosPreviewCache.Instance.init(CONFIG.CACHE.PREVIEWS.SIZE) 172 VideosPreviewCache.Instance.init(CONFIG.CACHE.PREVIEWS.SIZE)
172 BadActorFollowScheduler.Instance.enable() 173 BadActorFollowScheduler.Instance.enable()
173 174 RemoveOldJobsScheduler.Instance.enable()
174 activitypubHttpJobScheduler.activate() 175 JobQueue.Instance.init()
175 transcodingJobScheduler.activate()
176 176
177 logger.info('Server listening on port %d', port) 177 logger.info('Server listening on port %d', port)
178 logger.info('Web server: %s', CONFIG.WEBSERVER.URL) 178 logger.info('Web server: %s', CONFIG.WEBSERVER.URL)
diff --git a/server/controllers/api/jobs.ts b/server/controllers/api/jobs.ts
index de37dea39..132d110ad 100644
--- a/server/controllers/api/jobs.ts
+++ b/server/controllers/api/jobs.ts
@@ -1,22 +1,29 @@
1import * as express from 'express' 1import * as express from 'express'
2import { ResultList } from '../../../shared'
3import { Job, JobType, JobState } from '../../../shared/models'
2import { UserRight } from '../../../shared/models/users' 4import { UserRight } from '../../../shared/models/users'
3import { getFormattedObjects } from '../../helpers/utils' 5import { JobQueue } from '../../lib/job-queue'
4import { 6import {
5 asyncMiddleware, authenticate, ensureUserHasRight, jobsSortValidator, setDefaultPagination, 7 asyncMiddleware,
8 authenticate,
9 ensureUserHasRight,
10 jobsSortValidator,
11 setDefaultPagination,
6 setDefaultSort 12 setDefaultSort
7} from '../../middlewares' 13} from '../../middlewares'
8import { paginationValidator } from '../../middlewares/validators' 14import { paginationValidator } from '../../middlewares/validators'
9import { JobModel } from '../../models/job/job' 15import { listJobsValidator } from '../../middlewares/validators/jobs'
10 16
11const jobsRouter = express.Router() 17const jobsRouter = express.Router()
12 18
13jobsRouter.get('/', 19jobsRouter.get('/:state',
14 authenticate, 20 authenticate,
15 ensureUserHasRight(UserRight.MANAGE_JOBS), 21 ensureUserHasRight(UserRight.MANAGE_JOBS),
16 paginationValidator, 22 paginationValidator,
17 jobsSortValidator, 23 jobsSortValidator,
18 setDefaultSort, 24 setDefaultSort,
19 setDefaultPagination, 25 setDefaultPagination,
26 asyncMiddleware(listJobsValidator),
20 asyncMiddleware(listJobs) 27 asyncMiddleware(listJobs)
21) 28)
22 29
@@ -29,7 +36,26 @@ export {
29// --------------------------------------------------------------------------- 36// ---------------------------------------------------------------------------
30 37
31async function listJobs (req: express.Request, res: express.Response, next: express.NextFunction) { 38async function listJobs (req: express.Request, res: express.Response, next: express.NextFunction) {
32 const resultList = await JobModel.listForApi(req.query.start, req.query.count, req.query.sort) 39 const sort = req.query.sort === 'createdAt' ? 'asc' : 'desc'
40
41 const jobs = await JobQueue.Instance.listForApi(req.params.state, req.query.start, req.query.count, sort)
42 const total = await JobQueue.Instance.count(req.params.state)
43
44 const result: ResultList<any> = {
45 total,
46 data: jobs.map(j => formatJob(j.toJSON()))
47 }
48 return res.json(result)
49}
33 50
34 return res.json(getFormattedObjects(resultList.data, resultList.total)) 51function formatJob (job: any): Job {
52 return {
53 id: job.id,
54 state: job.state as JobState,
55 type: job.type as JobType,
56 data: job.data,
57 error: job.error,
58 createdAt: new Date(parseInt(job.created_at, 10)),
59 updatedAt: new Date(parseInt(job.updated_at, 10))
60 }
35} 61}
diff --git a/server/controllers/api/server/follows.ts b/server/controllers/api/server/follows.ts
index 506b9668e..bb8713e7a 100644
--- a/server/controllers/api/server/follows.ts
+++ b/server/controllers/api/server/follows.ts
@@ -123,7 +123,7 @@ function follow (fromActor: ActorModel, targetActor: ActorModel) {
123 actorFollow.ActorFollower = fromActor 123 actorFollow.ActorFollower = fromActor
124 124
125 // Send a notification to remote server 125 // Send a notification to remote server
126 await sendFollow(actorFollow, t) 126 await sendFollow(actorFollow)
127 }) 127 })
128} 128}
129 129
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index c2fdb4f95..459795141 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -12,7 +12,7 @@ import {
12} from '../../../initializers' 12} from '../../../initializers'
13import { fetchRemoteVideoDescription, getVideoActivityPubUrl, shareVideoByServerAndChannel } from '../../../lib/activitypub' 13import { fetchRemoteVideoDescription, getVideoActivityPubUrl, shareVideoByServerAndChannel } from '../../../lib/activitypub'
14import { sendCreateVideo, sendCreateViewToOrigin, sendCreateViewToVideoFollowers, sendUpdateVideo } from '../../../lib/activitypub/send' 14import { sendCreateVideo, sendCreateViewToOrigin, sendCreateViewToVideoFollowers, sendUpdateVideo } from '../../../lib/activitypub/send'
15import { transcodingJobScheduler } from '../../../lib/jobs/transcoding-job-scheduler' 15import { JobQueue } from '../../../lib/job-queue'
16import { 16import {
17 asyncMiddleware, authenticate, paginationValidator, setDefaultSort, setDefaultPagination, videosAddValidator, videosGetValidator, 17 asyncMiddleware, authenticate, paginationValidator, setDefaultSort, setDefaultPagination, videosAddValidator, videosGetValidator,
18 videosRemoveValidator, videosSearchValidator, videosSortValidator, videosUpdateValidator 18 videosRemoveValidator, videosSearchValidator, videosSortValidator, videosUpdateValidator
@@ -176,18 +176,9 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi
176 ) 176 )
177 await Promise.all(tasks) 177 await Promise.all(tasks)
178 178
179 return sequelizeTypescript.transaction(async t => { 179 const videoCreated = await sequelizeTypescript.transaction(async t => {
180 const sequelizeOptions = { transaction: t } 180 const sequelizeOptions = { transaction: t }
181 181
182 if (CONFIG.TRANSCODING.ENABLED === true) {
183 // Put uuid because we don't have id auto incremented for now
184 const dataInput = {
185 videoUUID: video.uuid
186 }
187
188 await transcodingJobScheduler.createJob(t, 'videoFileOptimizer', dataInput)
189 }
190
191 const videoCreated = await video.save(sequelizeOptions) 182 const videoCreated = await video.save(sequelizeOptions)
192 // Do not forget to add video channel information to the created video 183 // Do not forget to add video channel information to the created video
193 videoCreated.VideoChannel = res.locals.videoChannel 184 videoCreated.VideoChannel = res.locals.videoChannel
@@ -216,6 +207,17 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi
216 207
217 return videoCreated 208 return videoCreated
218 }) 209 })
210
211 if (CONFIG.TRANSCODING.ENABLED === true) {
212 // Put uuid because we don't have id auto incremented for now
213 const dataInput = {
214 videoUUID: videoCreated.uuid
215 }
216
217 await JobQueue.Instance.createJob({ type: 'video-file', payload: dataInput })
218 }
219
220 return videoCreated
219} 221}
220 222
221async function updateVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { 223async function updateVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
diff --git a/server/helpers/custom-validators/jobs.ts b/server/helpers/custom-validators/jobs.ts
new file mode 100644
index 000000000..9700fbd12
--- /dev/null
+++ b/server/helpers/custom-validators/jobs.ts
@@ -0,0 +1,14 @@
1import { JobState } from '../../../shared/models'
2import { exists } from './misc'
3
4const jobStates: JobState[] = [ 'active', 'complete', 'failed', 'inactive', 'delayed' ]
5
6function isValidJobState (value: JobState) {
7 return exists(value) && jobStates.indexOf(value) !== -1
8}
9
10// ---------------------------------------------------------------------------
11
12export {
13 isValidJobState
14}
diff --git a/server/helpers/database-utils.ts b/server/helpers/database-utils.ts
index 78ca768b9..b4adaf9cc 100644
--- a/server/helpers/database-utils.ts
+++ b/server/helpers/database-utils.ts
@@ -16,6 +16,7 @@ function retryTransactionWrapper <T> (
16 .catch(err => callback(err)) 16 .catch(err => callback(err))
17 }) 17 })
18 .catch(err => { 18 .catch(err => {
19 console.error(err)
19 logger.error(options.errorMessage, err) 20 logger.error(options.errorMessage, err)
20 throw err 21 throw err
21 }) 22 })
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index cb043251a..329d0ffe8 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -1,6 +1,6 @@
1import { IConfig } from 'config' 1import { IConfig } from 'config'
2import { dirname, join } from 'path' 2import { dirname, join } from 'path'
3import { JobCategory, JobState, VideoRateType } from '../../shared/models' 3import { JobType, VideoRateType } from '../../shared/models'
4import { ActivityPubActorType } from '../../shared/models/activitypub' 4import { ActivityPubActorType } from '../../shared/models/activitypub'
5import { FollowState } from '../../shared/models/actors' 5import { FollowState } from '../../shared/models/actors'
6import { VideoPrivacy } from '../../shared/models/videos' 6import { VideoPrivacy } from '../../shared/models/videos'
@@ -12,7 +12,7 @@ let config: IConfig = require('config')
12 12
13// --------------------------------------------------------------------------- 13// ---------------------------------------------------------------------------
14 14
15const LAST_MIGRATION_VERSION = 175 15const LAST_MIGRATION_VERSION = 180
16 16
17// --------------------------------------------------------------------------- 17// ---------------------------------------------------------------------------
18 18
@@ -26,7 +26,7 @@ const PAGINATION_COUNT_DEFAULT = 15
26const SORTABLE_COLUMNS = { 26const SORTABLE_COLUMNS = {
27 USERS: [ 'id', 'username', 'createdAt' ], 27 USERS: [ 'id', 'username', 'createdAt' ],
28 ACCOUNTS: [ 'createdAt' ], 28 ACCOUNTS: [ 'createdAt' ],
29 JOBS: [ 'id', 'createdAt' ], 29 JOBS: [ 'createdAt' ],
30 VIDEO_ABUSES: [ 'id', 'createdAt' ], 30 VIDEO_ABUSES: [ 'id', 'createdAt' ],
31 VIDEO_CHANNELS: [ 'id', 'name', 'updatedAt', 'createdAt' ], 31 VIDEO_CHANNELS: [ 'id', 'name', 'updatedAt', 'createdAt' ],
32 VIDEOS: [ 'name', 'duration', 'createdAt', 'views', 'likes' ], 32 VIDEOS: [ 'name', 'duration', 'createdAt', 'views', 'likes' ],
@@ -61,23 +61,20 @@ const REMOTE_SCHEME = {
61 WS: 'wss' 61 WS: 'wss'
62} 62}
63 63
64const JOB_STATES: { [ id: string ]: JobState } = { 64const JOB_ATTEMPTS: { [ id in JobType ]: number } = {
65 PENDING: 'pending', 65 'activitypub-http-broadcast': 5,
66 PROCESSING: 'processing', 66 'activitypub-http-unicast': 5,
67 ERROR: 'error', 67 'activitypub-http-fetcher': 5,
68 SUCCESS: 'success' 68 'video-file': 1
69}
70const JOB_CATEGORIES: { [ id: string ]: JobCategory } = {
71 TRANSCODING: 'transcoding',
72 ACTIVITYPUB_HTTP: 'activitypub-http'
73} 69}
74// How many maximum jobs we fetch from the database per cycle 70const JOB_CONCURRENCY: { [ id in JobType ]: number } = {
75const JOBS_FETCH_LIMIT_PER_CYCLE = { 71 'activitypub-http-broadcast': 1,
76 transcoding: 10, 72 'activitypub-http-unicast': 5,
77 httpRequest: 20 73 'activitypub-http-fetcher': 1,
74 'video-file': 1
78} 75}
79// 1 minutes 76// 2 days
80let JOBS_FETCHING_INTERVAL = 60000 77const JOB_COMPLETED_LIFETIME = 60000 * 60 * 24 * 2
81 78
82// 1 hour 79// 1 hour
83let SCHEDULER_INTERVAL = 60000 * 60 80let SCHEDULER_INTERVAL = 60000 * 60
@@ -96,6 +93,11 @@ const CONFIG = {
96 USERNAME: config.get<string>('database.username'), 93 USERNAME: config.get<string>('database.username'),
97 PASSWORD: config.get<string>('database.password') 94 PASSWORD: config.get<string>('database.password')
98 }, 95 },
96 REDIS: {
97 HOSTNAME: config.get<string>('redis.hostname'),
98 PORT: config.get<string>('redis.port'),
99 AUTH: config.get<string>('redis.auth')
100 },
99 STORAGE: { 101 STORAGE: {
100 AVATARS_DIR: buildPath(config.get<string>('storage.avatars')), 102 AVATARS_DIR: buildPath(config.get<string>('storage.avatars')),
101 LOG_DIR: buildPath(config.get<string>('storage.logs')), 103 LOG_DIR: buildPath(config.get<string>('storage.logs')),
@@ -284,7 +286,6 @@ const ACTIVITY_PUB = {
284 PUBLIC: 'https://www.w3.org/ns/activitystreams#Public', 286 PUBLIC: 'https://www.w3.org/ns/activitystreams#Public',
285 COLLECTION_ITEMS_PER_PAGE: 10, 287 COLLECTION_ITEMS_PER_PAGE: 10,
286 FETCH_PAGE_LIMIT: 100, 288 FETCH_PAGE_LIMIT: 100,
287 MAX_HTTP_ATTEMPT: 5,
288 URL_MIME_TYPES: { 289 URL_MIME_TYPES: {
289 VIDEO: Object.keys(VIDEO_MIMETYPE_EXT), 290 VIDEO: Object.keys(VIDEO_MIMETYPE_EXT),
290 TORRENT: [ 'application/x-bittorrent' ], 291 TORRENT: [ 'application/x-bittorrent' ],
@@ -358,7 +359,6 @@ const OPENGRAPH_AND_OEMBED_COMMENT = '<!-- open graph and oembed tags -->'
358// Special constants for a test instance 359// Special constants for a test instance
359if (isTestInstance() === true) { 360if (isTestInstance() === true) {
360 ACTOR_FOLLOW_SCORE.BASE = 20 361 ACTOR_FOLLOW_SCORE.BASE = 20
361 JOBS_FETCHING_INTERVAL = 1000
362 REMOTE_SCHEME.HTTP = 'http' 362 REMOTE_SCHEME.HTTP = 'http'
363 REMOTE_SCHEME.WS = 'ws' 363 REMOTE_SCHEME.WS = 'ws'
364 STATIC_MAX_AGE = '0' 364 STATIC_MAX_AGE = '0'
@@ -381,10 +381,8 @@ export {
381 CONFIG, 381 CONFIG,
382 CONSTRAINTS_FIELDS, 382 CONSTRAINTS_FIELDS,
383 EMBED_SIZE, 383 EMBED_SIZE,
384 JOB_STATES, 384 JOB_CONCURRENCY,
385 JOBS_FETCH_LIMIT_PER_CYCLE, 385 JOB_ATTEMPTS,
386 JOBS_FETCHING_INTERVAL,
387 JOB_CATEGORIES,
388 LAST_MIGRATION_VERSION, 386 LAST_MIGRATION_VERSION,
389 OAUTH_LIFETIME, 387 OAUTH_LIFETIME,
390 OPENGRAPH_AND_OEMBED_COMMENT, 388 OPENGRAPH_AND_OEMBED_COMMENT,
@@ -408,7 +406,8 @@ export {
408 VIDEO_RATE_TYPES, 406 VIDEO_RATE_TYPES,
409 VIDEO_MIMETYPE_EXT, 407 VIDEO_MIMETYPE_EXT,
410 AVATAR_MIMETYPE_EXT, 408 AVATAR_MIMETYPE_EXT,
411 SCHEDULER_INTERVAL 409 SCHEDULER_INTERVAL,
410 JOB_COMPLETED_LIFETIME
412} 411}
413 412
414// --------------------------------------------------------------------------- 413// ---------------------------------------------------------------------------
diff --git a/server/initializers/database.ts b/server/initializers/database.ts
index 852db68a0..b537ee59a 100644
--- a/server/initializers/database.ts
+++ b/server/initializers/database.ts
@@ -9,7 +9,6 @@ import { ActorModel } from '../models/activitypub/actor'
9import { ActorFollowModel } from '../models/activitypub/actor-follow' 9import { ActorFollowModel } from '../models/activitypub/actor-follow'
10import { ApplicationModel } from '../models/application/application' 10import { ApplicationModel } from '../models/application/application'
11import { AvatarModel } from '../models/avatar/avatar' 11import { AvatarModel } from '../models/avatar/avatar'
12import { JobModel } from '../models/job/job'
13import { OAuthClientModel } from '../models/oauth/oauth-client' 12import { OAuthClientModel } from '../models/oauth/oauth-client'
14import { OAuthTokenModel } from '../models/oauth/oauth-token' 13import { OAuthTokenModel } from '../models/oauth/oauth-token'
15import { ServerModel } from '../models/server/server' 14import { ServerModel } from '../models/server/server'
@@ -61,7 +60,6 @@ async function initDatabaseModels (silent: boolean) {
61 ActorFollowModel, 60 ActorFollowModel,
62 AvatarModel, 61 AvatarModel,
63 AccountModel, 62 AccountModel,
64 JobModel,
65 OAuthClientModel, 63 OAuthClientModel,
66 OAuthTokenModel, 64 OAuthTokenModel,
67 ServerModel, 65 ServerModel,
diff --git a/server/initializers/migrations/0100-activitypub.ts b/server/initializers/migrations/0100-activitypub.ts
index 8c5198f85..a7ebd804c 100644
--- a/server/initializers/migrations/0100-activitypub.ts
+++ b/server/initializers/migrations/0100-activitypub.ts
@@ -1,11 +1,10 @@
1import { values } from 'lodash'
2import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
3import { createPrivateAndPublicKeys } from '../../helpers/peertube-crypto' 2import { createPrivateAndPublicKeys } from '../../helpers/peertube-crypto'
4import { shareVideoByServerAndChannel } from '../../lib/activitypub/share' 3import { shareVideoByServerAndChannel } from '../../lib/activitypub/share'
5import { getVideoActivityPubUrl, getVideoChannelActivityPubUrl } from '../../lib/activitypub/url' 4import { getVideoActivityPubUrl, getVideoChannelActivityPubUrl } from '../../lib/activitypub/url'
6import { createLocalAccountWithoutKeys } from '../../lib/user' 5import { createLocalAccountWithoutKeys } from '../../lib/user'
7import { ApplicationModel } from '../../models/application/application' 6import { ApplicationModel } from '../../models/application/application'
8import { JOB_CATEGORIES, SERVER_ACTOR_NAME } from '../constants' 7import { SERVER_ACTOR_NAME } from '../constants'
9 8
10async function up (utils: { 9async function up (utils: {
11 transaction: Sequelize.Transaction, 10 transaction: Sequelize.Transaction,
@@ -161,7 +160,7 @@ async function up (utils: {
161 160
162 { 161 {
163 const data = { 162 const data = {
164 type: Sequelize.ENUM(values(JOB_CATEGORIES)), 163 type: Sequelize.ENUM('transcoding', 'activitypub-http'),
165 defaultValue: 'transcoding', 164 defaultValue: 'transcoding',
166 allowNull: false 165 allowNull: false
167 } 166 }
diff --git a/server/initializers/migrations/0180-job-table-delete.ts b/server/initializers/migrations/0180-job-table-delete.ts
new file mode 100644
index 000000000..df29145d0
--- /dev/null
+++ b/server/initializers/migrations/0180-job-table-delete.ts
@@ -0,0 +1,18 @@
1import * as Sequelize from 'sequelize'
2
3async function up (utils: {
4 transaction: Sequelize.Transaction,
5 queryInterface: Sequelize.QueryInterface,
6 sequelize: Sequelize.Sequelize
7}): Promise<void> {
8 await utils.queryInterface.dropTable('job')
9}
10
11function down (options) {
12 throw new Error('Not implemented.')
13}
14
15export {
16 up,
17 down
18}
diff --git a/server/lib/activitypub/actor.ts b/server/lib/activitypub/actor.ts
index c708b38ba..712de7d0d 100644
--- a/server/lib/activitypub/actor.ts
+++ b/server/lib/activitypub/actor.ts
@@ -64,7 +64,11 @@ async function getOrCreateActorAndServerAndModel (actorUrl: string, recurseIfNee
64 actor = await retryTransactionWrapper(saveActorAndServerAndModelIfNotExist, options) 64 actor = await retryTransactionWrapper(saveActorAndServerAndModelIfNotExist, options)
65 } 65 }
66 66
67 return refreshActorIfNeeded(actor) 67 const options = {
68 arguments: [ actor ],
69 errorMessage: 'Cannot refresh actor if needed with many retries.'
70 }
71 return retryTransactionWrapper(refreshActorIfNeeded, options)
68} 72}
69 73
70function buildActorInstance (type: ActivityPubActorType, url: string, preferredUsername: string, uuid?: string) { 74function buildActorInstance (type: ActivityPubActorType, url: string, preferredUsername: string, uuid?: string) {
@@ -325,38 +329,43 @@ async function saveVideoChannel (actor: ActorModel, result: FetchRemoteActorResu
325async function refreshActorIfNeeded (actor: ActorModel) { 329async function refreshActorIfNeeded (actor: ActorModel) {
326 if (!actor.isOutdated()) return actor 330 if (!actor.isOutdated()) return actor
327 331
328 const actorUrl = await getUrlFromWebfinger(actor.preferredUsername, actor.getHost()) 332 try {
329 const result = await fetchRemoteActor(actorUrl) 333 const actorUrl = await getUrlFromWebfinger(actor.preferredUsername, actor.getHost())
330 if (result === undefined) { 334 const result = await fetchRemoteActor(actorUrl)
331 logger.warn('Cannot fetch remote actor in refresh actor.') 335 if (result === undefined) {
332 return actor 336 logger.warn('Cannot fetch remote actor in refresh actor.')
333 } 337 return actor
334
335 return sequelizeTypescript.transaction(async t => {
336 updateInstanceWithAnother(actor, result.actor)
337
338 if (result.avatarName !== undefined) {
339 await updateActorAvatarInstance(actor, result.avatarName, t)
340 } 338 }
341 339
342 // Force update 340 return sequelizeTypescript.transaction(async t => {
343 actor.setDataValue('updatedAt', new Date()) 341 updateInstanceWithAnother(actor, result.actor)
344 await actor.save({ transaction: t })
345 342
346 if (actor.Account) { 343 if (result.avatarName !== undefined) {
347 await actor.save({ transaction: t }) 344 await updateActorAvatarInstance(actor, result.avatarName, t)
345 }
348 346
349 actor.Account.set('name', result.name) 347 // Force update
350 await actor.Account.save({ transaction: t }) 348 actor.setDataValue('updatedAt', new Date())
351 } else if (actor.VideoChannel) {
352 await actor.save({ transaction: t }) 349 await actor.save({ transaction: t })
353 350
354 actor.VideoChannel.set('name', result.name) 351 if (actor.Account) {
355 await actor.VideoChannel.save({ transaction: t }) 352 await actor.save({ transaction: t })
356 } 353
354 actor.Account.set('name', result.name)
355 await actor.Account.save({ transaction: t })
356 } else if (actor.VideoChannel) {
357 await actor.save({ transaction: t })
358
359 actor.VideoChannel.set('name', result.name)
360 await actor.VideoChannel.save({ transaction: t })
361 }
357 362
363 return actor
364 })
365 } catch (err) {
366 logger.warn('Cannot refresh actor.', err)
358 return actor 367 return actor
359 }) 368 }
360} 369}
361 370
362function normalizeActor (actor: any) { 371function normalizeActor (actor: any) {
diff --git a/server/lib/activitypub/fetch.ts b/server/lib/activitypub/fetch.ts
index 4fc97cc38..b1b370a1a 100644
--- a/server/lib/activitypub/fetch.ts
+++ b/server/lib/activitypub/fetch.ts
@@ -1,13 +1,12 @@
1import { Transaction } from 'sequelize'
2import { ActorModel } from '../../models/activitypub/actor' 1import { ActorModel } from '../../models/activitypub/actor'
3import { activitypubHttpJobScheduler, ActivityPubHttpPayload } from '../jobs/activitypub-http-job-scheduler' 2import { JobQueue } from '../job-queue'
4 3
5async function addFetchOutboxJob (actor: ActorModel, t: Transaction) { 4async function addFetchOutboxJob (actor: ActorModel) {
6 const jobPayload: ActivityPubHttpPayload = { 5 const payload = {
7 uris: [ actor.outboxUrl ] 6 uris: [ actor.outboxUrl ]
8 } 7 }
9 8
10 return activitypubHttpJobScheduler.createJob(t, 'activitypubHttpFetcherHandler', jobPayload) 9 return JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload })
11} 10}
12 11
13export { 12export {
diff --git a/server/lib/activitypub/process/process-accept.ts b/server/lib/activitypub/process/process-accept.ts
index 551f09ea7..7db2f8ff0 100644
--- a/server/lib/activitypub/process/process-accept.ts
+++ b/server/lib/activitypub/process/process-accept.ts
@@ -26,6 +26,6 @@ async function processAccept (actor: ActorModel, targetActor: ActorModel) {
26 if (follow.state !== 'accepted') { 26 if (follow.state !== 'accepted') {
27 follow.set('state', 'accepted') 27 follow.set('state', 'accepted')
28 await follow.save() 28 await follow.save()
29 await addFetchOutboxJob(targetActor, undefined) 29 await addFetchOutboxJob(targetActor)
30 } 30 }
31} 31}
diff --git a/server/lib/activitypub/process/process-follow.ts b/server/lib/activitypub/process/process-follow.ts
index 69f5c51b5..dc1d542b5 100644
--- a/server/lib/activitypub/process/process-follow.ts
+++ b/server/lib/activitypub/process/process-follow.ts
@@ -63,7 +63,7 @@ async function follow (actor: ActorModel, targetActorURL: string) {
63 actorFollow.ActorFollowing = targetActor 63 actorFollow.ActorFollowing = targetActor
64 64
65 // Target sends to actor he accepted the follow request 65 // Target sends to actor he accepted the follow request
66 return sendAccept(actorFollow, t) 66 return sendAccept(actorFollow)
67 }) 67 })
68 68
69 logger.info('Actor %s is followed by actor %s.', targetActorURL, actor.url) 69 logger.info('Actor %s is followed by actor %s.', targetActorURL, actor.url)
diff --git a/server/lib/activitypub/send/misc.ts b/server/lib/activitypub/send/misc.ts
index dc0d3de57..7a21f0c94 100644
--- a/server/lib/activitypub/send/misc.ts
+++ b/server/lib/activitypub/send/misc.ts
@@ -7,7 +7,7 @@ import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
7import { VideoModel } from '../../../models/video/video' 7import { VideoModel } from '../../../models/video/video'
8import { VideoCommentModel } from '../../../models/video/video-comment' 8import { VideoCommentModel } from '../../../models/video/video-comment'
9import { VideoShareModel } from '../../../models/video/video-share' 9import { VideoShareModel } from '../../../models/video/video-share'
10import { activitypubHttpJobScheduler, ActivityPubHttpPayload } from '../../jobs/activitypub-http-job-scheduler' 10import { JobQueue } from '../../job-queue'
11 11
12async function forwardActivity ( 12async function forwardActivity (
13 activity: Activity, 13 activity: Activity,
@@ -35,12 +35,11 @@ async function forwardActivity (
35 35
36 logger.debug('Creating forwarding job.', { uris }) 36 logger.debug('Creating forwarding job.', { uris })
37 37
38 const jobPayload: ActivityPubHttpPayload = { 38 const payload = {
39 uris, 39 uris,
40 body: activity 40 body: activity
41 } 41 }
42 42 return JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload })
43 return activitypubHttpJobScheduler.createJob(t, 'activitypubHttpBroadcastHandler', jobPayload)
44} 43}
45 44
46async function broadcastToFollowers ( 45async function broadcastToFollowers (
@@ -51,44 +50,43 @@ async function broadcastToFollowers (
51 actorsException: ActorModel[] = [] 50 actorsException: ActorModel[] = []
52) { 51) {
53 const uris = await computeFollowerUris(toActorFollowers, actorsException, t) 52 const uris = await computeFollowerUris(toActorFollowers, actorsException, t)
54 return broadcastTo(uris, data, byActor, t) 53 return broadcastTo(uris, data, byActor)
55} 54}
56 55
57async function broadcastToActors ( 56async function broadcastToActors (
58 data: any, 57 data: any,
59 byActor: ActorModel, 58 byActor: ActorModel,
60 toActors: ActorModel[], 59 toActors: ActorModel[],
61 t: Transaction,
62 actorsException: ActorModel[] = [] 60 actorsException: ActorModel[] = []
63) { 61) {
64 const uris = await computeUris(toActors, actorsException) 62 const uris = await computeUris(toActors, actorsException)
65 return broadcastTo(uris, data, byActor, t) 63 return broadcastTo(uris, data, byActor)
66} 64}
67 65
68async function broadcastTo (uris: string[], data: any, byActor: ActorModel, t: Transaction) { 66async function broadcastTo (uris: string[], data: any, byActor: ActorModel) {
69 if (uris.length === 0) return undefined 67 if (uris.length === 0) return undefined
70 68
71 logger.debug('Creating broadcast job.', { uris }) 69 logger.debug('Creating broadcast job.', { uris })
72 70
73 const jobPayload: ActivityPubHttpPayload = { 71 const payload = {
74 uris, 72 uris,
75 signatureActorId: byActor.id, 73 signatureActorId: byActor.id,
76 body: data 74 body: data
77 } 75 }
78 76
79 return activitypubHttpJobScheduler.createJob(t, 'activitypubHttpBroadcastHandler', jobPayload) 77 return JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload })
80} 78}
81 79
82async function unicastTo (data: any, byActor: ActorModel, toActorUrl: string, t: Transaction) { 80async function unicastTo (data: any, byActor: ActorModel, toActorUrl: string) {
83 logger.debug('Creating unicast job.', { uri: toActorUrl }) 81 logger.debug('Creating unicast job.', { uri: toActorUrl })
84 82
85 const jobPayload: ActivityPubHttpPayload = { 83 const payload = {
86 uris: [ toActorUrl ], 84 uri: toActorUrl,
87 signatureActorId: byActor.id, 85 signatureActorId: byActor.id,
88 body: data 86 body: data
89 } 87 }
90 88
91 return activitypubHttpJobScheduler.createJob(t, 'activitypubHttpUnicastHandler', jobPayload) 89 return JobQueue.Instance.createJob({ type: 'activitypub-http-unicast', payload })
92} 90}
93 91
94function getOriginVideoAudience (video: VideoModel, actorsInvolvedInVideo: ActorModel[]) { 92function getOriginVideoAudience (video: VideoModel, actorsInvolvedInVideo: ActorModel[]) {
diff --git a/server/lib/activitypub/send/send-accept.ts b/server/lib/activitypub/send/send-accept.ts
index 4eaa329d9..064fd88d2 100644
--- a/server/lib/activitypub/send/send-accept.ts
+++ b/server/lib/activitypub/send/send-accept.ts
@@ -1,4 +1,3 @@
1import { Transaction } from 'sequelize'
2import { ActivityAccept, ActivityFollow } from '../../../../shared/models/activitypub' 1import { ActivityAccept, ActivityFollow } from '../../../../shared/models/activitypub'
3import { ActorModel } from '../../../models/activitypub/actor' 2import { ActorModel } from '../../../models/activitypub/actor'
4import { ActorFollowModel } from '../../../models/activitypub/actor-follow' 3import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
@@ -6,7 +5,7 @@ import { getActorFollowAcceptActivityPubUrl, getActorFollowActivityPubUrl } from
6import { unicastTo } from './misc' 5import { unicastTo } from './misc'
7import { followActivityData } from './send-follow' 6import { followActivityData } from './send-follow'
8 7
9async function sendAccept (actorFollow: ActorFollowModel, t: Transaction) { 8async function sendAccept (actorFollow: ActorFollowModel) {
10 const follower = actorFollow.ActorFollower 9 const follower = actorFollow.ActorFollower
11 const me = actorFollow.ActorFollowing 10 const me = actorFollow.ActorFollowing
12 11
@@ -16,7 +15,7 @@ async function sendAccept (actorFollow: ActorFollowModel, t: Transaction) {
16 const url = getActorFollowAcceptActivityPubUrl(actorFollow) 15 const url = getActorFollowAcceptActivityPubUrl(actorFollow)
17 const data = acceptActivityData(url, me, followData) 16 const data = acceptActivityData(url, me, followData)
18 17
19 return unicastTo(data, me, follower.inboxUrl, t) 18 return unicastTo(data, me, follower.inboxUrl)
20} 19}
21 20
22// --------------------------------------------------------------------------- 21// ---------------------------------------------------------------------------
diff --git a/server/lib/activitypub/send/send-announce.ts b/server/lib/activitypub/send/send-announce.ts
index 578fbc630..93b5668d2 100644
--- a/server/lib/activitypub/send/send-announce.ts
+++ b/server/lib/activitypub/send/send-announce.ts
@@ -42,7 +42,7 @@ async function sendVideoAnnounceToOrigin (byActor: ActorModel, video: VideoModel
42 const audience = getOriginVideoAudience(video, actorsInvolvedInVideo) 42 const audience = getOriginVideoAudience(video, actorsInvolvedInVideo)
43 const data = await createActivityData(url, byActor, announcedActivity, t, audience) 43 const data = await createActivityData(url, byActor, announcedActivity, t, audience)
44 44
45 return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl, t) 45 return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)
46} 46}
47 47
48async function announceActivityData ( 48async function announceActivityData (
diff --git a/server/lib/activitypub/send/send-create.ts b/server/lib/activitypub/send/send-create.ts
index 9db663be1..b92615e9b 100644
--- a/server/lib/activitypub/send/send-create.ts
+++ b/server/lib/activitypub/send/send-create.ts
@@ -8,8 +8,14 @@ import { VideoAbuseModel } from '../../../models/video/video-abuse'
8import { VideoCommentModel } from '../../../models/video/video-comment' 8import { VideoCommentModel } from '../../../models/video/video-comment'
9import { getVideoAbuseActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoViewActivityPubUrl } from '../url' 9import { getVideoAbuseActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoViewActivityPubUrl } from '../url'
10import { 10import {
11 audiencify, broadcastToActors, broadcastToFollowers, getActorsInvolvedInVideo, getAudience, getObjectFollowersAudience, 11 audiencify,
12 getOriginVideoAudience, getOriginVideoCommentAudience, 12 broadcastToActors,
13 broadcastToFollowers,
14 getActorsInvolvedInVideo,
15 getAudience,
16 getObjectFollowersAudience,
17 getOriginVideoAudience,
18 getOriginVideoCommentAudience,
13 unicastTo 19 unicastTo
14} from './misc' 20} from './misc'
15 21
@@ -31,7 +37,7 @@ async function sendVideoAbuse (byActor: ActorModel, videoAbuse: VideoAbuseModel,
31 const audience = { to: [ video.VideoChannel.Account.Actor.url ], cc: [] } 37 const audience = { to: [ video.VideoChannel.Account.Actor.url ], cc: [] }
32 const data = await createActivityData(url, byActor, videoAbuse.toActivityPubObject(), t, audience) 38 const data = await createActivityData(url, byActor, videoAbuse.toActivityPubObject(), t, audience)
33 39
34 return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl, t) 40 return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)
35} 41}
36 42
37async function sendCreateVideoCommentToOrigin (comment: VideoCommentModel, t: Transaction) { 43async function sendCreateVideoCommentToOrigin (comment: VideoCommentModel, t: Transaction) {
@@ -47,13 +53,13 @@ async function sendCreateVideoCommentToOrigin (comment: VideoCommentModel, t: Tr
47 53
48 // This was a reply, send it to the parent actors 54 // This was a reply, send it to the parent actors
49 const actorsException = [ byActor ] 55 const actorsException = [ byActor ]
50 await broadcastToActors(data, byActor, threadParentComments.map(c => c.Account.Actor), t, actorsException) 56 await broadcastToActors(data, byActor, threadParentComments.map(c => c.Account.Actor), actorsException)
51 57
52 // Broadcast to our followers 58 // Broadcast to our followers
53 await broadcastToFollowers(data, byActor, [ byActor ], t) 59 await broadcastToFollowers(data, byActor, [ byActor ], t)
54 60
55 // Send to origin 61 // Send to origin
56 return unicastTo(data, byActor, comment.Video.VideoChannel.Account.Actor.sharedInboxUrl, t) 62 return unicastTo(data, byActor, comment.Video.VideoChannel.Account.Actor.sharedInboxUrl)
57} 63}
58 64
59async function sendCreateVideoCommentToVideoFollowers (comment: VideoCommentModel, t: Transaction) { 65async function sendCreateVideoCommentToVideoFollowers (comment: VideoCommentModel, t: Transaction) {
@@ -69,7 +75,7 @@ async function sendCreateVideoCommentToVideoFollowers (comment: VideoCommentMode
69 75
70 // This was a reply, send it to the parent actors 76 // This was a reply, send it to the parent actors
71 const actorsException = [ byActor ] 77 const actorsException = [ byActor ]
72 await broadcastToActors(data, byActor, threadParentComments.map(c => c.Account.Actor), t, actorsException) 78 await broadcastToActors(data, byActor, threadParentComments.map(c => c.Account.Actor), actorsException)
73 79
74 // Broadcast to our followers 80 // Broadcast to our followers
75 await broadcastToFollowers(data, byActor, [ byActor ], t) 81 await broadcastToFollowers(data, byActor, [ byActor ], t)
@@ -86,7 +92,7 @@ async function sendCreateViewToOrigin (byActor: ActorModel, video: VideoModel, t
86 const audience = getOriginVideoAudience(video, actorsInvolvedInVideo) 92 const audience = getOriginVideoAudience(video, actorsInvolvedInVideo)
87 const data = await createActivityData(url, byActor, viewActivityData, t, audience) 93 const data = await createActivityData(url, byActor, viewActivityData, t, audience)
88 94
89 return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl, t) 95 return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)
90} 96}
91 97
92async function sendCreateViewToVideoFollowers (byActor: ActorModel, video: VideoModel, t: Transaction) { 98async function sendCreateViewToVideoFollowers (byActor: ActorModel, video: VideoModel, t: Transaction) {
@@ -111,7 +117,7 @@ async function sendCreateDislikeToOrigin (byActor: ActorModel, video: VideoModel
111 const audience = getOriginVideoAudience(video, actorsInvolvedInVideo) 117 const audience = getOriginVideoAudience(video, actorsInvolvedInVideo)
112 const data = await createActivityData(url, byActor, dislikeActivityData, t, audience) 118 const data = await createActivityData(url, byActor, dislikeActivityData, t, audience)
113 119
114 return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl, t) 120 return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)
115} 121}
116 122
117async function sendCreateDislikeToVideoFollowers (byActor: ActorModel, video: VideoModel, t: Transaction) { 123async function sendCreateDislikeToVideoFollowers (byActor: ActorModel, video: VideoModel, t: Transaction) {
diff --git a/server/lib/activitypub/send/send-follow.ts b/server/lib/activitypub/send/send-follow.ts
index eac60e94f..4e9865af4 100644
--- a/server/lib/activitypub/send/send-follow.ts
+++ b/server/lib/activitypub/send/send-follow.ts
@@ -1,18 +1,17 @@
1import { Transaction } from 'sequelize'
2import { ActivityFollow } from '../../../../shared/models/activitypub' 1import { ActivityFollow } from '../../../../shared/models/activitypub'
3import { ActorModel } from '../../../models/activitypub/actor' 2import { ActorModel } from '../../../models/activitypub/actor'
4import { ActorFollowModel } from '../../../models/activitypub/actor-follow' 3import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
5import { getActorFollowActivityPubUrl } from '../url' 4import { getActorFollowActivityPubUrl } from '../url'
6import { unicastTo } from './misc' 5import { unicastTo } from './misc'
7 6
8function sendFollow (actorFollow: ActorFollowModel, t: Transaction) { 7function sendFollow (actorFollow: ActorFollowModel) {
9 const me = actorFollow.ActorFollower 8 const me = actorFollow.ActorFollower
10 const following = actorFollow.ActorFollowing 9 const following = actorFollow.ActorFollowing
11 10
12 const url = getActorFollowActivityPubUrl(actorFollow) 11 const url = getActorFollowActivityPubUrl(actorFollow)
13 const data = followActivityData(url, me, following) 12 const data = followActivityData(url, me, following)
14 13
15 return unicastTo(data, me, following.inboxUrl, t) 14 return unicastTo(data, me, following.inboxUrl)
16} 15}
17 16
18function followActivityData (url: string, byActor: ActorModel, targetActor: ActorModel): ActivityFollow { 17function followActivityData (url: string, byActor: ActorModel, targetActor: ActorModel): ActivityFollow {
diff --git a/server/lib/activitypub/send/send-like.ts b/server/lib/activitypub/send/send-like.ts
index 743646455..78ed1aaf2 100644
--- a/server/lib/activitypub/send/send-like.ts
+++ b/server/lib/activitypub/send/send-like.ts
@@ -20,7 +20,7 @@ async function sendLikeToOrigin (byActor: ActorModel, video: VideoModel, t: Tran
20 const audience = getOriginVideoAudience(video, accountsInvolvedInVideo) 20 const audience = getOriginVideoAudience(video, accountsInvolvedInVideo)
21 const data = await likeActivityData(url, byActor, video, t, audience) 21 const data = await likeActivityData(url, byActor, video, t, audience)
22 22
23 return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl, t) 23 return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)
24} 24}
25 25
26async function sendLikeToVideoFollowers (byActor: ActorModel, video: VideoModel, t: Transaction) { 26async function sendLikeToVideoFollowers (byActor: ActorModel, video: VideoModel, t: Transaction) {
diff --git a/server/lib/activitypub/send/send-undo.ts b/server/lib/activitypub/send/send-undo.ts
index 3a0597fba..4a08b5ca1 100644
--- a/server/lib/activitypub/send/send-undo.ts
+++ b/server/lib/activitypub/send/send-undo.ts
@@ -1,11 +1,5 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { 2import { ActivityAudience, ActivityCreate, ActivityFollow, ActivityLike, ActivityUndo } from '../../../../shared/models/activitypub'
3 ActivityAudience,
4 ActivityCreate,
5 ActivityFollow,
6 ActivityLike,
7 ActivityUndo
8} from '../../../../shared/models/activitypub'
9import { ActorModel } from '../../../models/activitypub/actor' 3import { ActorModel } from '../../../models/activitypub/actor'
10import { ActorFollowModel } from '../../../models/activitypub/actor-follow' 4import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
11import { VideoModel } from '../../../models/video/video' 5import { VideoModel } from '../../../models/video/video'
@@ -33,7 +27,7 @@ async function sendUndoFollow (actorFollow: ActorFollowModel, t: Transaction) {
33 const object = followActivityData(followUrl, me, following) 27 const object = followActivityData(followUrl, me, following)
34 const data = await undoActivityData(undoUrl, me, object, t) 28 const data = await undoActivityData(undoUrl, me, object, t)
35 29
36 return unicastTo(data, me, following.inboxUrl, t) 30 return unicastTo(data, me, following.inboxUrl)
37} 31}
38 32
39async function sendUndoLikeToOrigin (byActor: ActorModel, video: VideoModel, t: Transaction) { 33async function sendUndoLikeToOrigin (byActor: ActorModel, video: VideoModel, t: Transaction) {
@@ -45,7 +39,7 @@ async function sendUndoLikeToOrigin (byActor: ActorModel, video: VideoModel, t:
45 const object = await likeActivityData(likeUrl, byActor, video, t) 39 const object = await likeActivityData(likeUrl, byActor, video, t)
46 const data = await undoActivityData(undoUrl, byActor, object, t, audience) 40 const data = await undoActivityData(undoUrl, byActor, object, t, audience)
47 41
48 return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl, t) 42 return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)
49} 43}
50 44
51async function sendUndoLikeToVideoFollowers (byActor: ActorModel, video: VideoModel, t: Transaction) { 45async function sendUndoLikeToVideoFollowers (byActor: ActorModel, video: VideoModel, t: Transaction) {
@@ -72,7 +66,7 @@ async function sendUndoDislikeToOrigin (byActor: ActorModel, video: VideoModel,
72 66
73 const data = await undoActivityData(undoUrl, byActor, object, t, audience) 67 const data = await undoActivityData(undoUrl, byActor, object, t, audience)
74 68
75 return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl, t) 69 return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)
76} 70}
77 71
78async function sendUndoDislikeToVideoFollowers (byActor: ActorModel, video: VideoModel, t: Transaction) { 72async function sendUndoDislikeToVideoFollowers (byActor: ActorModel, video: VideoModel, t: Transaction) {
diff --git a/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-broadcast-handler.ts b/server/lib/job-queue/handlers/activitypub-http-broadcast.ts
index 3f780e319..159856cda 100644
--- a/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-broadcast-handler.ts
+++ b/server/lib/job-queue/handlers/activitypub-http-broadcast.ts
@@ -1,10 +1,19 @@
1import * as kue from 'kue'
1import { logger } from '../../../helpers/logger' 2import { logger } from '../../../helpers/logger'
2import { doRequest } from '../../../helpers/requests' 3import { doRequest } from '../../../helpers/requests'
3import { ActorFollowModel } from '../../../models/activitypub/actor-follow' 4import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
4import { ActivityPubHttpPayload, buildSignedRequestOptions, computeBody, maybeRetryRequestLater } from './activitypub-http-job-scheduler' 5import { buildSignedRequestOptions, computeBody } from './utils/activitypub-http-utils'
5 6
6async function process (payload: ActivityPubHttpPayload, jobId: number) { 7export type ActivitypubHttpBroadcastPayload = {
7 logger.info('Processing ActivityPub broadcast in job %d.', jobId) 8 uris: string[]
9 signatureActorId?: number
10 body: any
11}
12
13async function processActivityPubHttpBroadcast (job: kue.Job) {
14 logger.info('Processing ActivityPub broadcast in job %d.', job.id)
15
16 const payload = job.data as ActivitypubHttpBroadcastPayload
8 17
9 const body = await computeBody(payload) 18 const body = await computeBody(payload)
10 const httpSignatureOptions = await buildSignedRequestOptions(payload) 19 const httpSignatureOptions = await buildSignedRequestOptions(payload)
@@ -26,28 +35,15 @@ async function process (payload: ActivityPubHttpPayload, jobId: number) {
26 await doRequest(options) 35 await doRequest(options)
27 goodUrls.push(uri) 36 goodUrls.push(uri)
28 } catch (err) { 37 } catch (err) {
29 const isRetryingLater = await maybeRetryRequestLater(err, payload, uri) 38 badUrls.push(uri)
30 if (isRetryingLater === false) badUrls.push(uri)
31 } 39 }
32 } 40 }
33 41
34 return ActorFollowModel.updateActorFollowsScoreAndRemoveBadOnes(goodUrls, badUrls, undefined) 42 return ActorFollowModel.updateActorFollowsScoreAndRemoveBadOnes(goodUrls, badUrls, undefined)
35} 43}
36 44
37function onError (err: Error, jobId: number) {
38 logger.error('Error when broadcasting ActivityPub request in job %d.', jobId, err)
39 return Promise.resolve()
40}
41
42function onSuccess (jobId: number) {
43 logger.info('Job %d is a success.', jobId)
44 return Promise.resolve()
45}
46
47// --------------------------------------------------------------------------- 45// ---------------------------------------------------------------------------
48 46
49export { 47export {
50 process, 48 processActivityPubHttpBroadcast
51 onError,
52 onSuccess
53} 49}
diff --git a/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-fetcher-handler.ts b/server/lib/job-queue/handlers/activitypub-http-fetcher.ts
index a7b5aabd0..062211c85 100644
--- a/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-fetcher-handler.ts
+++ b/server/lib/job-queue/handlers/activitypub-http-fetcher.ts
@@ -1,11 +1,18 @@
1import * as kue from 'kue'
1import { logger } from '../../../helpers/logger' 2import { logger } from '../../../helpers/logger'
2import { doRequest } from '../../../helpers/requests' 3import { doRequest } from '../../../helpers/requests'
3import { ACTIVITY_PUB } from '../../../initializers' 4import { ACTIVITY_PUB } from '../../../initializers'
4import { processActivities } from '../../activitypub/process' 5import { processActivities } from '../../activitypub/process'
5import { ActivityPubHttpPayload } from './activitypub-http-job-scheduler' 6import { ActivitypubHttpBroadcastPayload } from './activitypub-http-broadcast'
6 7
7async function process (payload: ActivityPubHttpPayload, jobId: number) { 8export type ActivitypubHttpFetcherPayload = {
8 logger.info('Processing ActivityPub fetcher in job %d.', jobId) 9 uris: string[]
10}
11
12async function processActivityPubHttpFetcher (job: kue.Job) {
13 logger.info('Processing ActivityPub fetcher in job %d.', job.id)
14
15 const payload = job.data as ActivitypubHttpBroadcastPayload
9 16
10 const options = { 17 const options = {
11 method: 'GET', 18 method: 'GET',
@@ -49,20 +56,8 @@ async function process (payload: ActivityPubHttpPayload, jobId: number) {
49 } 56 }
50} 57}
51 58
52function onError (err: Error, jobId: number) {
53 logger.error('Error when fetcher ActivityPub request in job %d.', jobId, err)
54 return Promise.resolve()
55}
56
57function onSuccess (jobId: number) {
58 logger.info('Job %d is a success.', jobId)
59 return Promise.resolve()
60}
61
62// --------------------------------------------------------------------------- 59// ---------------------------------------------------------------------------
63 60
64export { 61export {
65 process, 62 processActivityPubHttpFetcher
66 onError,
67 onSuccess
68} 63}
diff --git a/server/lib/job-queue/handlers/activitypub-http-unicast.ts b/server/lib/job-queue/handlers/activitypub-http-unicast.ts
new file mode 100644
index 000000000..9b4188c50
--- /dev/null
+++ b/server/lib/job-queue/handlers/activitypub-http-unicast.ts
@@ -0,0 +1,43 @@
1import * as kue from 'kue'
2import { logger } from '../../../helpers/logger'
3import { doRequest } from '../../../helpers/requests'
4import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
5import { buildSignedRequestOptions, computeBody } from './utils/activitypub-http-utils'
6
7export type ActivitypubHttpUnicastPayload = {
8 uri: string
9 signatureActorId?: number
10 body: any
11}
12
13async function processActivityPubHttpUnicast (job: kue.Job) {
14 logger.info('Processing ActivityPub unicast in job %d.', job.id)
15
16 const payload = job.data as ActivitypubHttpUnicastPayload
17 const uri = payload.uri
18
19 const body = await computeBody(payload)
20 const httpSignatureOptions = await buildSignedRequestOptions(payload)
21
22 const options = {
23 method: 'POST',
24 uri,
25 json: body,
26 httpSignature: httpSignatureOptions
27 }
28
29 try {
30 await doRequest(options)
31 ActorFollowModel.updateActorFollowsScoreAndRemoveBadOnes([ uri ], [], undefined)
32 } catch (err) {
33 ActorFollowModel.updateActorFollowsScoreAndRemoveBadOnes([], [ uri ], undefined)
34
35 throw err
36 }
37}
38
39// ---------------------------------------------------------------------------
40
41export {
42 processActivityPubHttpUnicast
43}
diff --git a/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts b/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts
new file mode 100644
index 000000000..c087371c6
--- /dev/null
+++ b/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts
@@ -0,0 +1,39 @@
1import { buildSignedActivity } from '../../../../helpers/activitypub'
2import { getServerActor } from '../../../../helpers/utils'
3import { ActorModel } from '../../../../models/activitypub/actor'
4
5async function computeBody (payload: { body: any, signatureActorId?: number }) {
6 let body = payload.body
7
8 if (payload.signatureActorId) {
9 const actorSignature = await ActorModel.load(payload.signatureActorId)
10 if (!actorSignature) throw new Error('Unknown signature actor id.')
11 body = await buildSignedActivity(actorSignature, payload.body)
12 }
13
14 return body
15}
16
17async function buildSignedRequestOptions (payload: { signatureActorId?: number }) {
18 let actor: ActorModel
19 if (payload.signatureActorId) {
20 actor = await ActorModel.load(payload.signatureActorId)
21 if (!actor) throw new Error('Unknown signature actor id.')
22 } else {
23 // We need to sign the request, so use the server
24 actor = await getServerActor()
25 }
26
27 const keyId = actor.getWebfingerUrl()
28 return {
29 algorithm: 'rsa-sha256',
30 authorizationHeaderName: 'Signature',
31 keyId,
32 key: actor.privateKey
33 }
34}
35
36export {
37 computeBody,
38 buildSignedRequestOptions
39}
diff --git a/server/lib/jobs/transcoding-job-scheduler/video-file-optimizer-handler.ts b/server/lib/job-queue/handlers/video-file.ts
index f224a31b4..5294483bd 100644
--- a/server/lib/jobs/transcoding-job-scheduler/video-file-optimizer-handler.ts
+++ b/server/lib/job-queue/handlers/video-file.ts
@@ -1,38 +1,60 @@
1import * as Bluebird from 'bluebird' 1import * as kue from 'kue'
2import { VideoResolution } from '../../../../shared'
2import { VideoPrivacy } from '../../../../shared/models/videos' 3import { VideoPrivacy } from '../../../../shared/models/videos'
3import { logger } from '../../../helpers/logger' 4import { logger } from '../../../helpers/logger'
4import { computeResolutionsToTranscode } from '../../../helpers/utils' 5import { computeResolutionsToTranscode } from '../../../helpers/utils'
5import { sequelizeTypescript } from '../../../initializers' 6import { sequelizeTypescript } from '../../../initializers'
6import { JobModel } from '../../../models/job/job'
7import { VideoModel } from '../../../models/video/video' 7import { VideoModel } from '../../../models/video/video'
8import { shareVideoByServerAndChannel } from '../../activitypub' 8import { shareVideoByServerAndChannel } from '../../activitypub'
9import { sendCreateVideo } from '../../activitypub/send' 9import { sendCreateVideo, sendUpdateVideo } from '../../activitypub/send'
10import { JobScheduler } from '../job-scheduler' 10import { JobQueue } from '../job-queue'
11import { TranscodingJobPayload } from './transcoding-job-scheduler'
12 11
13async function process (data: TranscodingJobPayload, jobId: number) { 12export type VideoFilePayload = {
14 const video = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(data.videoUUID) 13 videoUUID: string
14 resolution?: VideoResolution
15}
16
17async function processVideoFile (job: kue.Job) {
18 const payload = job.data as VideoFilePayload
19 logger.info('Processing video file in job %d.', job.id)
20
21 const video = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(payload.videoUUID)
15 // No video, maybe deleted? 22 // No video, maybe deleted?
16 if (!video) { 23 if (!video) {
17 logger.info('Do not process job %d, video does not exist.', jobId, { videoUUID: video.uuid }) 24 logger.info('Do not process job %d, video does not exist.', job.id, { videoUUID: video.uuid })
18 return undefined 25 return undefined
19 } 26 }
20 27
21 await video.optimizeOriginalVideofile() 28 // Transcoding in other resolution
29 if (payload.resolution) {
30 await video.transcodeOriginalVideofile(payload.resolution)
31 await onVideoFileTranscoderSuccess(video)
32 } else {
33 await video.optimizeOriginalVideofile()
34 await onVideoFileOptimizerSuccess(video)
35 }
22 36
23 return video 37 return video
24} 38}
25 39
26function onError (err: Error, jobId: number) { 40async function onVideoFileTranscoderSuccess (video: VideoModel) {
27 logger.error('Error when optimized video file in job %d.', jobId, err) 41 if (video === undefined) return undefined
28 return Promise.resolve() 42
43 // Maybe the video changed in database, refresh it
44 const videoDatabase = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(video.uuid)
45 // Video does not exist anymore
46 if (!videoDatabase) return undefined
47
48 if (video.privacy !== VideoPrivacy.PRIVATE) {
49 await sendUpdateVideo(video, undefined)
50 }
51
52 return undefined
29} 53}
30 54
31async function onSuccess (jobId: number, video: VideoModel, jobScheduler: JobScheduler<TranscodingJobPayload, VideoModel>) { 55async function onVideoFileOptimizerSuccess (video: VideoModel) {
32 if (video === undefined) return undefined 56 if (video === undefined) return undefined
33 57
34 logger.info('Job %d is a success.', jobId)
35
36 // Maybe the video changed in database, refresh it 58 // Maybe the video changed in database, refresh it
37 const videoDatabase = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(video.uuid) 59 const videoDatabase = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(video.uuid)
38 // Video does not exist anymore 60 // Video does not exist anymore
@@ -56,7 +78,7 @@ async function onSuccess (jobId: number, video: VideoModel, jobScheduler: JobSch
56 if (resolutionsEnabled.length !== 0) { 78 if (resolutionsEnabled.length !== 0) {
57 try { 79 try {
58 await sequelizeTypescript.transaction(async t => { 80 await sequelizeTypescript.transaction(async t => {
59 const tasks: Bluebird<JobModel>[] = [] 81 const tasks: Promise<any>[] = []
60 82
61 for (const resolution of resolutionsEnabled) { 83 for (const resolution of resolutionsEnabled) {
62 const dataInput = { 84 const dataInput = {
@@ -64,7 +86,7 @@ async function onSuccess (jobId: number, video: VideoModel, jobScheduler: JobSch
64 resolution 86 resolution
65 } 87 }
66 88
67 const p = jobScheduler.createJob(t, 'videoFileTranscoder', dataInput) 89 const p = JobQueue.Instance.createJob({ type: 'video-file', payload: dataInput })
68 tasks.push(p) 90 tasks.push(p)
69 } 91 }
70 92
@@ -84,7 +106,5 @@ async function onSuccess (jobId: number, video: VideoModel, jobScheduler: JobSch
84// --------------------------------------------------------------------------- 106// ---------------------------------------------------------------------------
85 107
86export { 108export {
87 process, 109 processVideoFile
88 onError,
89 onSuccess
90} 110}
diff --git a/server/lib/job-queue/index.ts b/server/lib/job-queue/index.ts
new file mode 100644
index 000000000..57231e649
--- /dev/null
+++ b/server/lib/job-queue/index.ts
@@ -0,0 +1 @@
export * from './job-queue'
diff --git a/server/lib/job-queue/job-queue.ts b/server/lib/job-queue/job-queue.ts
new file mode 100644
index 000000000..7a2b6c78d
--- /dev/null
+++ b/server/lib/job-queue/job-queue.ts
@@ -0,0 +1,124 @@
1import * as kue from 'kue'
2import { JobType, JobState } from '../../../shared/models'
3import { logger } from '../../helpers/logger'
4import { CONFIG, JOB_ATTEMPTS, JOB_COMPLETED_LIFETIME, JOB_CONCURRENCY } from '../../initializers'
5import { ActivitypubHttpBroadcastPayload, processActivityPubHttpBroadcast } from './handlers/activitypub-http-broadcast'
6import { ActivitypubHttpFetcherPayload, processActivityPubHttpFetcher } from './handlers/activitypub-http-fetcher'
7import { ActivitypubHttpUnicastPayload, processActivityPubHttpUnicast } from './handlers/activitypub-http-unicast'
8import { processVideoFile, VideoFilePayload } from './handlers/video-file'
9
10type CreateJobArgument =
11 { type: 'activitypub-http-broadcast', payload: ActivitypubHttpBroadcastPayload } |
12 { type: 'activitypub-http-unicast', payload: ActivitypubHttpUnicastPayload } |
13 { type: 'activitypub-http-fetcher', payload: ActivitypubHttpFetcherPayload } |
14 { type: 'video-file', payload: VideoFilePayload }
15
16const handlers: { [ id in JobType ]: (job: kue.Job) => Promise<any>} = {
17 'activitypub-http-broadcast': processActivityPubHttpBroadcast,
18 'activitypub-http-unicast': processActivityPubHttpUnicast,
19 'activitypub-http-fetcher': processActivityPubHttpFetcher,
20 'video-file': processVideoFile
21}
22
23class JobQueue {
24
25 private static instance: JobQueue
26
27 private jobQueue: kue.Queue
28 private initialized = false
29
30 private constructor () {}
31
32 init () {
33 // Already initialized
34 if (this.initialized === true) return
35 this.initialized = true
36
37 this.jobQueue = kue.createQueue({
38 prefix: 'q-' + CONFIG.WEBSERVER.HOST,
39 redis: {
40 host: CONFIG.REDIS.HOSTNAME,
41 port: CONFIG.REDIS.PORT,
42 auth: CONFIG.REDIS.AUTH
43 }
44 })
45
46 this.jobQueue.on('error', err => {
47 logger.error('Error in job queue.', err)
48 process.exit(-1)
49 })
50 this.jobQueue.watchStuckJobs(5000)
51
52 for (const handlerName of Object.keys(handlers)) {
53 this.jobQueue.process(handlerName, JOB_CONCURRENCY[handlerName], async (job, done) => {
54 try {
55 const res = await handlers[ handlerName ](job)
56 return done(null, res)
57 } catch (err) {
58 return done(err)
59 }
60 })
61 }
62 }
63
64 createJob (obj: CreateJobArgument, priority = 'normal') {
65 return new Promise((res, rej) => {
66 this.jobQueue
67 .create(obj.type, obj.payload)
68 .priority(priority)
69 .attempts(JOB_ATTEMPTS[obj.type])
70 .backoff({ type: 'exponential' })
71 .save(err => {
72 if (err) return rej(err)
73
74 return res()
75 })
76 })
77 }
78
79 listForApi (state: JobState, start: number, count: number, sort: string) {
80 return new Promise<kue.Job[]>((res, rej) => {
81 kue.Job.rangeByState(state, start, count, sort, (err, jobs) => {
82 if (err) return rej(err)
83
84 return res(jobs)
85 })
86 })
87 }
88
89 count (state: JobState) {
90 return new Promise<number>((res, rej) => {
91 this.jobQueue[state + 'Count']((err, total) => {
92 if (err) return rej(err)
93
94 return res(total)
95 })
96 })
97 }
98
99 removeOldJobs () {
100 const now = new Date().getTime()
101 kue.Job.rangeByState('complete', 0, -1, 'asc', (err, jobs) => {
102 if (err) {
103 logger.error('Cannot get jobs when removing old jobs.', err)
104 return
105 }
106
107 for (const job of jobs) {
108 if (now - job.created_at > JOB_COMPLETED_LIFETIME) {
109 job.remove()
110 }
111 }
112 })
113 }
114
115 static get Instance () {
116 return this.instance || (this.instance = new this())
117 }
118}
119
120// ---------------------------------------------------------------------------
121
122export {
123 JobQueue
124}
diff --git a/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler.ts b/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler.ts
deleted file mode 100644
index 4459152db..000000000
--- a/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler.ts
+++ /dev/null
@@ -1,94 +0,0 @@
1import { JobCategory } from '../../../../shared'
2import { buildSignedActivity } from '../../../helpers/activitypub'
3import { logger } from '../../../helpers/logger'
4import { getServerActor } from '../../../helpers/utils'
5import { ACTIVITY_PUB } from '../../../initializers'
6import { ActorModel } from '../../../models/activitypub/actor'
7import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
8import { JobHandler, JobScheduler } from '../job-scheduler'
9
10import * as activitypubHttpBroadcastHandler from './activitypub-http-broadcast-handler'
11import * as activitypubHttpFetcherHandler from './activitypub-http-fetcher-handler'
12import * as activitypubHttpUnicastHandler from './activitypub-http-unicast-handler'
13
14type ActivityPubHttpPayload = {
15 uris: string[]
16 signatureActorId?: number
17 body?: any
18 attemptNumber?: number
19}
20
21const jobHandlers: { [ handlerName: string ]: JobHandler<ActivityPubHttpPayload, void> } = {
22 activitypubHttpBroadcastHandler,
23 activitypubHttpUnicastHandler,
24 activitypubHttpFetcherHandler
25}
26const jobCategory: JobCategory = 'activitypub-http'
27
28const activitypubHttpJobScheduler = new JobScheduler(jobCategory, jobHandlers)
29
30async function maybeRetryRequestLater (err: Error, payload: ActivityPubHttpPayload, uri: string) {
31 logger.warn('Cannot make request to %s.', uri, err)
32
33 let attemptNumber = payload.attemptNumber || 1
34 attemptNumber += 1
35
36 if (attemptNumber < ACTIVITY_PUB.MAX_HTTP_ATTEMPT) {
37 logger.debug('Retrying request to %s (attempt %d/%d).', uri, attemptNumber, ACTIVITY_PUB.MAX_HTTP_ATTEMPT, err)
38
39 const actor = await ActorFollowModel.loadByFollowerInbox(uri, undefined)
40 if (!actor) {
41 logger.debug('Actor %s is not a follower, do not retry the request.', uri)
42 return false
43 }
44
45 const newPayload = Object.assign(payload, {
46 uris: [ uri ],
47 attemptNumber
48 })
49 await activitypubHttpJobScheduler.createJob(undefined, 'activitypubHttpUnicastHandler', newPayload)
50
51 return true
52 }
53
54 return false
55}
56
57async function computeBody (payload: ActivityPubHttpPayload) {
58 let body = payload.body
59
60 if (payload.signatureActorId) {
61 const actorSignature = await ActorModel.load(payload.signatureActorId)
62 if (!actorSignature) throw new Error('Unknown signature actor id.')
63 body = await buildSignedActivity(actorSignature, payload.body)
64 }
65
66 return body
67}
68
69async function buildSignedRequestOptions (payload: ActivityPubHttpPayload) {
70 let actor: ActorModel
71 if (payload.signatureActorId) {
72 actor = await ActorModel.load(payload.signatureActorId)
73 if (!actor) throw new Error('Unknown signature actor id.')
74 } else {
75 // We need to sign the request, so use the server
76 actor = await getServerActor()
77 }
78
79 const keyId = actor.getWebfingerUrl()
80 return {
81 algorithm: 'rsa-sha256',
82 authorizationHeaderName: 'Signature',
83 keyId,
84 key: actor.privateKey
85 }
86}
87
88export {
89 ActivityPubHttpPayload,
90 activitypubHttpJobScheduler,
91 maybeRetryRequestLater,
92 computeBody,
93 buildSignedRequestOptions
94}
diff --git a/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-unicast-handler.ts b/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-unicast-handler.ts
deleted file mode 100644
index 54a7504e8..000000000
--- a/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-unicast-handler.ts
+++ /dev/null
@@ -1,50 +0,0 @@
1import { logger } from '../../../helpers/logger'
2import { doRequest } from '../../../helpers/requests'
3import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
4import { ActivityPubHttpPayload, buildSignedRequestOptions, computeBody, maybeRetryRequestLater } from './activitypub-http-job-scheduler'
5
6async function process (payload: ActivityPubHttpPayload, jobId: number) {
7 logger.info('Processing ActivityPub unicast in job %d.', jobId)
8
9 const uri = payload.uris[0]
10
11 const body = await computeBody(payload)
12 const httpSignatureOptions = await buildSignedRequestOptions(payload)
13
14 const options = {
15 method: 'POST',
16 uri,
17 json: body,
18 httpSignature: httpSignatureOptions
19 }
20
21 try {
22 await doRequest(options)
23 ActorFollowModel.updateActorFollowsScoreAndRemoveBadOnes([ uri ], [], undefined)
24 } catch (err) {
25 const isRetryingLater = await maybeRetryRequestLater(err, payload, uri)
26 if (isRetryingLater === false) {
27 ActorFollowModel.updateActorFollowsScoreAndRemoveBadOnes([], [ uri ], undefined)
28 }
29
30 throw err
31 }
32}
33
34function onError (err: Error, jobId: number) {
35 logger.error('Error when sending ActivityPub request in job %d.', jobId, err)
36 return Promise.resolve()
37}
38
39function onSuccess (jobId: number) {
40 logger.info('Job %d is a success.', jobId)
41 return Promise.resolve()
42}
43
44// ---------------------------------------------------------------------------
45
46export {
47 process,
48 onError,
49 onSuccess
50}
diff --git a/server/lib/jobs/activitypub-http-job-scheduler/index.ts b/server/lib/jobs/activitypub-http-job-scheduler/index.ts
deleted file mode 100644
index ad8f527b4..000000000
--- a/server/lib/jobs/activitypub-http-job-scheduler/index.ts
+++ /dev/null
@@ -1 +0,0 @@
1export * from './activitypub-http-job-scheduler'
diff --git a/server/lib/jobs/index.ts b/server/lib/jobs/index.ts
deleted file mode 100644
index 394264ec1..000000000
--- a/server/lib/jobs/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
1export * from './activitypub-http-job-scheduler'
2export * from './transcoding-job-scheduler'
diff --git a/server/lib/jobs/job-scheduler.ts b/server/lib/jobs/job-scheduler.ts
deleted file mode 100644
index 9d55880e6..000000000
--- a/server/lib/jobs/job-scheduler.ts
+++ /dev/null
@@ -1,144 +0,0 @@
1import { AsyncQueue, forever, queue } from 'async'
2import * as Sequelize from 'sequelize'
3import { JobCategory } from '../../../shared'
4import { logger } from '../../helpers/logger'
5import { JOB_STATES, JOBS_FETCH_LIMIT_PER_CYCLE, JOBS_FETCHING_INTERVAL } from '../../initializers'
6import { JobModel } from '../../models/job/job'
7
8export interface JobHandler<P, T> {
9 process (data: object, jobId: number): Promise<T>
10 onError (err: Error, jobId: number)
11 onSuccess (jobId: number, jobResult: T, jobScheduler: JobScheduler<P, T>): Promise<any>
12}
13type JobQueueCallback = (err: Error) => void
14
15class JobScheduler<P, T> {
16
17 constructor (
18 private jobCategory: JobCategory,
19 private jobHandlers: { [ id: string ]: JobHandler<P, T> }
20 ) {}
21
22 async activate () {
23 const limit = JOBS_FETCH_LIMIT_PER_CYCLE[this.jobCategory]
24
25 logger.info('Jobs scheduler %s activated.', this.jobCategory)
26
27 const jobsQueue = queue<JobModel, JobQueueCallback>(this.processJob.bind(this))
28
29 // Finish processing jobs from a previous start
30 const state = JOB_STATES.PROCESSING
31 try {
32 const jobs = await JobModel.listWithLimitByCategory(limit, state, this.jobCategory)
33
34 this.enqueueJobs(jobsQueue, jobs)
35 } catch (err) {
36 logger.error('Cannot list pending jobs.', err)
37 }
38
39 forever(
40 async next => {
41 if (jobsQueue.length() !== 0) {
42 // Finish processing the queue first
43 return setTimeout(next, JOBS_FETCHING_INTERVAL)
44 }
45
46 const state = JOB_STATES.PENDING
47 try {
48 const jobs = await JobModel.listWithLimitByCategory(limit, state, this.jobCategory)
49
50 this.enqueueJobs(jobsQueue, jobs)
51 } catch (err) {
52 logger.error('Cannot list pending jobs.', err)
53 }
54
55 // Optimization: we could use "drain" from queue object
56 return setTimeout(next, JOBS_FETCHING_INTERVAL)
57 },
58
59 err => logger.error('Error in job scheduler queue.', err)
60 )
61 }
62
63 createJob (transaction: Sequelize.Transaction, handlerName: string, handlerInputData: P) {
64 const createQuery = {
65 state: JOB_STATES.PENDING,
66 category: this.jobCategory,
67 handlerName,
68 handlerInputData
69 }
70
71 const options = { transaction }
72
73 return JobModel.create(createQuery, options)
74 }
75
76 private enqueueJobs (jobsQueue: AsyncQueue<JobModel>, jobs: JobModel[]) {
77 jobs.forEach(job => jobsQueue.push(job))
78 }
79
80 private async processJob (job: JobModel, callback: (err: Error) => void) {
81 const jobHandler = this.jobHandlers[job.handlerName]
82 if (jobHandler === undefined) {
83 const errorString = 'Unknown job handler ' + job.handlerName + ' for job ' + job.id
84 logger.error(errorString)
85
86 const error = new Error(errorString)
87 await this.onJobError(jobHandler, job, error)
88 return callback(error)
89 }
90
91 logger.info('Processing job %d with handler %s.', job.id, job.handlerName)
92
93 job.state = JOB_STATES.PROCESSING
94 await job.save()
95
96 try {
97 const result: T = await jobHandler.process(job.handlerInputData, job.id)
98 await this.onJobSuccess(jobHandler, job, result)
99 } catch (err) {
100 logger.error('Error in job handler %s.', job.handlerName, err)
101
102 try {
103 await this.onJobError(jobHandler, job, err)
104 } catch (innerErr) {
105 this.cannotSaveJobError(innerErr)
106 return callback(innerErr)
107 }
108 }
109
110 return callback(null)
111 }
112
113 private async onJobError (jobHandler: JobHandler<P, T>, job: JobModel, err: Error) {
114 job.state = JOB_STATES.ERROR
115
116 try {
117 await job.save()
118 if (jobHandler) await jobHandler.onError(err, job.id)
119 } catch (err) {
120 this.cannotSaveJobError(err)
121 }
122 }
123
124 private async onJobSuccess (jobHandler: JobHandler<P, T>, job: JobModel, jobResult: T) {
125 job.state = JOB_STATES.SUCCESS
126
127 try {
128 await job.save()
129 await jobHandler.onSuccess(job.id, jobResult, this)
130 } catch (err) {
131 this.cannotSaveJobError(err)
132 }
133 }
134
135 private cannotSaveJobError (err: Error) {
136 logger.error('Cannot save new job state.', err)
137 }
138}
139
140// ---------------------------------------------------------------------------
141
142export {
143 JobScheduler
144}
diff --git a/server/lib/jobs/transcoding-job-scheduler/index.ts b/server/lib/jobs/transcoding-job-scheduler/index.ts
deleted file mode 100644
index 73152a1be..000000000
--- a/server/lib/jobs/transcoding-job-scheduler/index.ts
+++ /dev/null
@@ -1 +0,0 @@
1export * from './transcoding-job-scheduler'
diff --git a/server/lib/jobs/transcoding-job-scheduler/transcoding-job-scheduler.ts b/server/lib/jobs/transcoding-job-scheduler/transcoding-job-scheduler.ts
deleted file mode 100644
index e5530a73c..000000000
--- a/server/lib/jobs/transcoding-job-scheduler/transcoding-job-scheduler.ts
+++ /dev/null
@@ -1,23 +0,0 @@
1import { JobCategory } from '../../../../shared'
2import { VideoModel } from '../../../models/video/video'
3import { JobHandler, JobScheduler } from '../job-scheduler'
4
5import * as videoFileOptimizer from './video-file-optimizer-handler'
6import * as videoFileTranscoder from './video-file-transcoder-handler'
7
8type TranscodingJobPayload = {
9 videoUUID: string
10 resolution?: number
11}
12const jobHandlers: { [ handlerName: string ]: JobHandler<TranscodingJobPayload, VideoModel> } = {
13 videoFileOptimizer,
14 videoFileTranscoder
15}
16const jobCategory: JobCategory = 'transcoding'
17
18const transcodingJobScheduler = new JobScheduler(jobCategory, jobHandlers)
19
20export {
21 TranscodingJobPayload,
22 transcodingJobScheduler
23}
diff --git a/server/lib/jobs/transcoding-job-scheduler/video-file-transcoder-handler.ts b/server/lib/jobs/transcoding-job-scheduler/video-file-transcoder-handler.ts
deleted file mode 100644
index 883d3eba8..000000000
--- a/server/lib/jobs/transcoding-job-scheduler/video-file-transcoder-handler.ts
+++ /dev/null
@@ -1,48 +0,0 @@
1import { VideoResolution } from '../../../../shared'
2import { VideoPrivacy } from '../../../../shared/models/videos'
3import { logger } from '../../../helpers/logger'
4import { VideoModel } from '../../../models/video/video'
5import { sendUpdateVideo } from '../../activitypub/send'
6
7async function process (data: { videoUUID: string, resolution: VideoResolution }, jobId: number) {
8 const video = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(data.videoUUID)
9 // No video, maybe deleted?
10 if (!video) {
11 logger.info('Do not process job %d, video does not exist.', jobId, { videoUUID: video.uuid })
12 return undefined
13 }
14
15 await video.transcodeOriginalVideofile(data.resolution)
16
17 return video
18}
19
20function onError (err: Error, jobId: number) {
21 logger.error('Error when transcoding video file in job %d.', jobId, err)
22 return Promise.resolve()
23}
24
25async function onSuccess (jobId: number, video: VideoModel) {
26 if (video === undefined) return undefined
27
28 logger.info('Job %d is a success.', jobId)
29
30 // Maybe the video changed in database, refresh it
31 const videoDatabase = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(video.uuid)
32 // Video does not exist anymore
33 if (!videoDatabase) return undefined
34
35 if (video.privacy !== VideoPrivacy.PRIVATE) {
36 await sendUpdateVideo(video, undefined)
37 }
38
39 return undefined
40}
41
42// ---------------------------------------------------------------------------
43
44export {
45 process,
46 onError,
47 onSuccess
48}
diff --git a/server/lib/schedulers/remove-old-jobs-scheduler.ts b/server/lib/schedulers/remove-old-jobs-scheduler.ts
new file mode 100644
index 000000000..add5677ac
--- /dev/null
+++ b/server/lib/schedulers/remove-old-jobs-scheduler.ts
@@ -0,0 +1,19 @@
1import { JobQueue } from '../job-queue'
2import { AbstractScheduler } from './abstract-scheduler'
3
4export class RemoveOldJobsScheduler extends AbstractScheduler {
5
6 private static instance: AbstractScheduler
7
8 private constructor () {
9 super()
10 }
11
12 async execute () {
13 JobQueue.Instance.removeOldJobs()
14 }
15
16 static get Instance () {
17 return this.instance || (this.instance = new this())
18 }
19}
diff --git a/server/middlewares/validators/jobs.ts b/server/middlewares/validators/jobs.ts
new file mode 100644
index 000000000..2f8b1738c
--- /dev/null
+++ b/server/middlewares/validators/jobs.ts
@@ -0,0 +1,23 @@
1import * as express from 'express'
2import { param } from 'express-validator/check'
3import { isValidJobState } from '../../helpers/custom-validators/jobs'
4import { logger } from '../../helpers/logger'
5import { areValidationErrors } from './utils'
6
7const listJobsValidator = [
8 param('state').custom(isValidJobState).not().isEmpty().withMessage('Should have a valid job state'),
9
10 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
11 logger.debug('Checking listJobsValidator parameters.', { parameters: req.params })
12
13 if (areValidationErrors(req, res)) return
14
15 return next()
16 }
17]
18
19// ---------------------------------------------------------------------------
20
21export {
22 listJobsValidator
23}
diff --git a/server/models/job/job.ts b/server/models/job/job.ts
deleted file mode 100644
index ba1c6737e..000000000
--- a/server/models/job/job.ts
+++ /dev/null
@@ -1,80 +0,0 @@
1import { values } from 'lodash'
2import { AllowNull, Column, CreatedAt, DataType, Model, Table, UpdatedAt } from 'sequelize-typescript'
3import { JobCategory, JobState } from '../../../shared/models'
4import { JOB_CATEGORIES, JOB_STATES } from '../../initializers'
5import { getSort } from '../utils'
6
7@Table({
8 tableName: 'job',
9 indexes: [
10 {
11 fields: [ 'state', 'category' ]
12 }
13 ]
14})
15export class JobModel extends Model<JobModel> {
16 @AllowNull(false)
17 @Column(DataType.ENUM(values(JOB_STATES)))
18 state: JobState
19
20 @AllowNull(false)
21 @Column(DataType.ENUM(values(JOB_CATEGORIES)))
22 category: JobCategory
23
24 @AllowNull(false)
25 @Column
26 handlerName: string
27
28 @AllowNull(true)
29 @Column(DataType.JSON)
30 handlerInputData: any
31
32 @CreatedAt
33 createdAt: Date
34
35 @UpdatedAt
36 updatedAt: Date
37
38 static listWithLimitByCategory (limit: number, state: JobState, jobCategory: JobCategory) {
39 const query = {
40 order: [
41 [ 'id', 'ASC' ]
42 ],
43 limit: limit,
44 where: {
45 state,
46 category: jobCategory
47 },
48 logging: false
49 }
50
51 return JobModel.findAll(query)
52 }
53
54 static listForApi (start: number, count: number, sort: string) {
55 const query = {
56 offset: start,
57 limit: count,
58 order: [ getSort(sort) ]
59 }
60
61 return JobModel.findAndCountAll(query).then(({ rows, count }) => {
62 return {
63 data: rows,
64 total: count
65 }
66 })
67 }
68
69 toFormattedJSON () {
70 return {
71 id: this.id,
72 state: this.state,
73 category: this.category,
74 handlerName: this.handlerName,
75 handlerInputData: this.handlerInputData,
76 createdAt: this.createdAt,
77 updatedAt: this.updatedAt
78 }
79 }
80}
diff --git a/server/tests/api/check-params/jobs.ts b/server/tests/api/check-params/jobs.ts
index b12818bb1..ce3ac8809 100644
--- a/server/tests/api/check-params/jobs.ts
+++ b/server/tests/api/check-params/jobs.ts
@@ -7,7 +7,7 @@ import { checkBadCountPagination, checkBadSortPagination, checkBadStartPaginatio
7import { makeGetRequest } from '../../utils/requests/requests' 7import { makeGetRequest } from '../../utils/requests/requests'
8 8
9describe('Test jobs API validators', function () { 9describe('Test jobs API validators', function () {
10 const path = '/api/v1/jobs/' 10 const path = '/api/v1/jobs/failed'
11 let server: ServerInfo 11 let server: ServerInfo
12 let userAccessToken = '' 12 let userAccessToken = ''
13 13
@@ -31,6 +31,15 @@ describe('Test jobs API validators', function () {
31 }) 31 })
32 32
33 describe('When listing jobs', function () { 33 describe('When listing jobs', function () {
34
35 it('Should fail with a bad state', async function () {
36 await makeGetRequest({
37 url: server.url,
38 token: server.accessToken,
39 path: path + 'ade'
40 })
41 })
42
34 it('Should fail with a bad start pagination', async function () { 43 it('Should fail with a bad start pagination', async function () {
35 await checkBadStartPagination(server.url, path, server.accessToken) 44 await checkBadStartPagination(server.url, path, server.accessToken)
36 }) 45 })
diff --git a/server/tests/api/server/handle-down.ts b/server/tests/api/server/handle-down.ts
index de4e77b2f..4cedeb89e 100644
--- a/server/tests/api/server/handle-down.ts
+++ b/server/tests/api/server/handle-down.ts
@@ -2,6 +2,7 @@
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { JobState } from '../../../../shared/models'
5import { VideoPrivacy } from '../../../../shared/models/videos' 6import { VideoPrivacy } from '../../../../shared/models/videos'
6import { VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model' 7import { VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model'
7import { completeVideoCheck, getVideo, immutableAssign, reRunServer, viewVideo } from '../../utils' 8import { completeVideoCheck, getVideo, immutableAssign, reRunServer, viewVideo } from '../../utils'
@@ -139,12 +140,11 @@ describe('Test handle downs', function () {
139 }) 140 })
140 141
141 it('Should not have pending/processing jobs anymore', async function () { 142 it('Should not have pending/processing jobs anymore', async function () {
142 const res = await getJobsListPaginationAndSort(servers[0].url, servers[0].accessToken, 0, 50, '-createdAt') 143 const states: JobState[] = [ 'inactive', 'active' ]
143 const jobs = res.body.data
144 144
145 for (const job of jobs) { 145 for (const state of states) {
146 expect(job.state).not.to.equal('pending') 146 const res = await getJobsListPaginationAndSort(servers[ 0 ].url, servers[ 0 ].accessToken, state,0, 50, '-createdAt')
147 expect(job.state).not.to.equal('processing') 147 expect(res.body.data).to.have.length(0)
148 } 148 }
149 }) 149 })
150 150
diff --git a/server/tests/api/server/jobs.ts b/server/tests/api/server/jobs.ts
index 2e17e71a4..671498769 100644
--- a/server/tests/api/server/jobs.ts
+++ b/server/tests/api/server/jobs.ts
@@ -35,20 +35,20 @@ describe('Test jobs', function () {
35 }) 35 })
36 36
37 it('Should list jobs', async function () { 37 it('Should list jobs', async function () {
38 const res = await getJobsList(servers[1].url, servers[1].accessToken) 38 const res = await getJobsList(servers[1].url, servers[1].accessToken, 'complete')
39 expect(res.body.total).to.be.above(2) 39 expect(res.body.total).to.be.above(2)
40 expect(res.body.data).to.have.length.above(2) 40 expect(res.body.data).to.have.length.above(2)
41 }) 41 })
42 42
43 it('Should list jobs with sort and pagination', async function () { 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') 44 const res = await getJobsListPaginationAndSort(servers[1].url, servers[1].accessToken, 'complete', 1, 1, 'createdAt')
45 expect(res.body.total).to.be.above(2) 45 expect(res.body.total).to.be.above(2)
46 expect(res.body.data).to.have.lengthOf(1) 46 expect(res.body.data).to.have.lengthOf(1)
47 47
48 const job = res.body.data[0] 48 const job = res.body.data[0]
49 expect(job.state).to.equal('success') 49
50 expect(job.category).to.equal('transcoding') 50 expect(job.state).to.equal('complete')
51 expect(job.handlerName).to.have.length.above(3) 51 expect(job.type).to.equal('activitypub-http-unicast')
52 expect(dateIsValid(job.createdAt)).to.be.true 52 expect(dateIsValid(job.createdAt)).to.be.true
53 expect(dateIsValid(job.updatedAt)).to.be.true 53 expect(dateIsValid(job.updatedAt)).to.be.true
54 }) 54 })
diff --git a/server/tests/api/videos/multiple-servers.ts b/server/tests/api/videos/multiple-servers.ts
index 4c4b5123d..0215b3011 100644
--- a/server/tests/api/videos/multiple-servers.ts
+++ b/server/tests/api/videos/multiple-servers.ts
@@ -475,16 +475,17 @@ describe('Test multiple servers', function () {
475 it('Should like and dislikes videos on different services', async function () { 475 it('Should like and dislikes videos on different services', async function () {
476 this.timeout(20000) 476 this.timeout(20000)
477 477
478 const tasks: Promise<any>[] = [] 478 await rateVideo(servers[0].url, servers[0].accessToken, remoteVideosServer1[0], 'like')
479 tasks.push(rateVideo(servers[0].url, servers[0].accessToken, remoteVideosServer1[0], 'like')) 479 await wait(200)
480 tasks.push(rateVideo(servers[0].url, servers[0].accessToken, remoteVideosServer1[0], 'dislike')) 480 await rateVideo(servers[0].url, servers[0].accessToken, remoteVideosServer1[0], 'dislike')
481 tasks.push(rateVideo(servers[0].url, servers[0].accessToken, remoteVideosServer1[0], 'like')) 481 await wait(200)
482 tasks.push(rateVideo(servers[2].url, servers[2].accessToken, localVideosServer3[1], 'like')) 482 await rateVideo(servers[0].url, servers[0].accessToken, remoteVideosServer1[0], 'like')
483 tasks.push(rateVideo(servers[2].url, servers[2].accessToken, localVideosServer3[1], 'dislike')) 483 await rateVideo(servers[2].url, servers[2].accessToken, localVideosServer3[1], 'like')
484 tasks.push(rateVideo(servers[2].url, servers[2].accessToken, remoteVideosServer3[1], 'dislike')) 484 await wait(200)
485 tasks.push(rateVideo(servers[2].url, servers[2].accessToken, remoteVideosServer3[0], 'like')) 485 await rateVideo(servers[2].url, servers[2].accessToken, localVideosServer3[1], 'dislike')
486 486 await rateVideo(servers[2].url, servers[2].accessToken, remoteVideosServer3[1], 'dislike')
487 await Promise.all(tasks) 487 await wait(200)
488 await rateVideo(servers[2].url, servers[2].accessToken, remoteVideosServer3[0], 'like')
488 489
489 await wait(10000) 490 await wait(10000)
490 491
diff --git a/server/tests/real-world/real-world.ts b/server/tests/real-world/real-world.ts
index e41203351..7f67525ed 100644
--- a/server/tests/real-world/real-world.ts
+++ b/server/tests/real-world/real-world.ts
@@ -3,6 +3,7 @@ process.env.NODE_ENV = 'test'
3 3
4import * as program from 'commander' 4import * as program from 'commander'
5import { Video, VideoFile, VideoRateType } from '../../../shared' 5import { Video, VideoFile, VideoRateType } from '../../../shared'
6import { JobState } from '../../../shared/models'
6import { 7import {
7 flushAndRunMultipleServers, 8 flushAndRunMultipleServers,
8 flushTests, follow, 9 flushTests, follow,
@@ -346,23 +347,19 @@ function goodbye () {
346} 347}
347 348
348async function isTherePendingRequests (servers: ServerInfo[]) { 349async function isTherePendingRequests (servers: ServerInfo[]) {
350 const states: JobState[] = [ 'inactive', 'active' ]
349 const tasks: Promise<any>[] = [] 351 const tasks: Promise<any>[] = []
350 let pendingRequests = false 352 let pendingRequests = false
351 353
352 // Check if each server has pending request 354 // Check if each server has pending request
353 for (const server of servers) { 355 for (const server of servers) {
354 const p = getJobsListPaginationAndSort(server.url, server.accessToken, 0, 10, '-createdAt') 356 for (const state of states) {
355 .then(res => { 357 const p = getJobsListPaginationAndSort(server.url, server.accessToken, state, 0, 10, '-createdAt')
356 const jobs = res.body.data 358 .then(res => {
357 359 if (res.body.total > 0) pendingRequests = true
358 for (const job of jobs) { 360 })
359 if (job.state === 'pending' || job.state === 'processing') { 361 tasks.push(p)
360 pendingRequests = true 362 }
361 }
362 }
363 })
364
365 tasks.push(p)
366 } 363 }
367 364
368 await Promise.all(tasks) 365 await Promise.all(tasks)
diff --git a/server/tests/utils/server/jobs.ts b/server/tests/utils/server/jobs.ts
index 0a8c51575..4053dd40b 100644
--- a/server/tests/utils/server/jobs.ts
+++ b/server/tests/utils/server/jobs.ts
@@ -1,7 +1,8 @@
1import * as request from 'supertest' 1import * as request from 'supertest'
2import { JobState } from '../../../../shared/models'
2 3
3function getJobsList (url: string, accessToken: string) { 4function getJobsList (url: string, accessToken: string, state: JobState) {
4 const path = '/api/v1/jobs' 5 const path = '/api/v1/jobs/' + state
5 6
6 return request(url) 7 return request(url)
7 .get(path) 8 .get(path)
@@ -11,8 +12,8 @@ function getJobsList (url: string, accessToken: string) {
11 .expect('Content-Type', /json/) 12 .expect('Content-Type', /json/)
12} 13}
13 14
14function getJobsListPaginationAndSort (url: string, accessToken: string, start: number, count: number, sort: string) { 15function getJobsListPaginationAndSort (url: string, accessToken: string, state: JobState, start: number, count: number, sort: string) {
15 const path = '/api/v1/jobs' 16 const path = '/api/v1/jobs/' + state
16 17
17 return request(url) 18 return request(url)
18 .get(path) 19 .get(path)
diff --git a/shared/models/job.model.ts b/shared/models/job.model.ts
index 1c46a7900..1a25600f3 100644
--- a/shared/models/job.model.ts
+++ b/shared/models/job.model.ts
@@ -1,12 +1,16 @@
1export type JobState = 'pending' | 'processing' | 'error' | 'success' 1export type JobState = 'active' | 'complete' | 'failed' | 'inactive' | 'delayed'
2export type JobCategory = 'transcoding' | 'activitypub-http' 2
3export type JobType = 'activitypub-http-unicast' |
4 'activitypub-http-broadcast' |
5 'activitypub-http-fetcher' |
6 'video-file'
3 7
4export interface Job { 8export interface Job {
5 id: number 9 id: number
6 state: JobState 10 state: JobState
7 category: JobCategory 11 type: JobType
8 handlerName: string 12 data: any,
9 handlerInputData: any 13 error: any,
10 createdAt: Date 14 createdAt: Date
11 updatedAt: Date 15 updatedAt: Date
12} 16}
diff --git a/support/doc/dependencies.md b/support/doc/dependencies.md
index c950b357f..7017976e5 100644
--- a/support/doc/dependencies.md
+++ b/support/doc/dependencies.md
@@ -10,7 +10,7 @@
10 10
11``` 11```
12$ sudo apt update 12$ sudo apt update
13$ sudo apt install nginx ffmpeg postgresql openssl g++ make 13$ sudo apt install nginx ffmpeg postgresql openssl g++ make redis-server
14``` 14```
15 15
16## Arch Linux 16## Arch Linux
@@ -18,7 +18,7 @@ $ sudo apt install nginx ffmpeg postgresql openssl g++ make
18 1. Run: 18 1. Run:
19 19
20``` 20```
21$ sudo pacman -S nodejs yarn ffmpeg postgresql openssl 21$ sudo pacman -S nodejs yarn ffmpeg postgresql openssl redis
22``` 22```
23 23
24## Other distributions 24## Other distributions
diff --git a/support/doc/development/server/code.md b/support/doc/development/server/code.md
index 953ccdbfe..e9ab7373c 100644
--- a/support/doc/development/server/code.md
+++ b/support/doc/development/server/code.md
@@ -7,6 +7,7 @@ The server is a web server developed with [TypeScript](https://www.typescriptlan
7 7
8 * [TypeScript](https://www.typescriptlang.org/) -> Language 8 * [TypeScript](https://www.typescriptlang.org/) -> Language
9 * [PostgreSQL](https://www.postgresql.org/) -> Database 9 * [PostgreSQL](https://www.postgresql.org/) -> Database
10 * [Redis](https://redis.io/) -> Job queue/cache
10 * [Express](http://expressjs.com) -> Web server framework 11 * [Express](http://expressjs.com) -> Web server framework
11 * [Sequelize](http://docs.sequelizejs.com/en/v3/) -> SQL ORM 12 * [Sequelize](http://docs.sequelizejs.com/en/v3/) -> SQL ORM
12 * [WebTorrent](https://webtorrent.io/) -> BitTorrent tracker and torrent creation 13 * [WebTorrent](https://webtorrent.io/) -> BitTorrent tracker and torrent creation
diff --git a/tsconfig.json b/tsconfig.json
index 71674e165..4e6816430 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -19,6 +19,12 @@
19 }, 19 },
20 "exclude": [ 20 "exclude": [
21 "node_modules", 21 "node_modules",
22 "client" 22 "client",
23 "text1",
24 "text2",
25 "text3",
26 "text4",
27 "text5",
28 "text6"
23 ] 29 ]
24} 30}
diff --git a/yarn.lock b/yarn.lock
index a0f6da289..a3f6fce8a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -82,6 +82,14 @@
82 version "1.0.6" 82 version "1.0.6"
83 resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-1.0.6.tgz#3e02972728c69248c2af08d60a48cbb8680fffdf" 83 resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-1.0.6.tgz#3e02972728c69248c2af08d60a48cbb8680fffdf"
84 84
85"@types/kue@^0.11.8":
86 version "0.11.8"
87 resolved "https://registry.yarnpkg.com/@types/kue/-/kue-0.11.8.tgz#820f5e3db6025f0411e0942cd3ccab461a060c90"
88 dependencies:
89 "@types/express" "*"
90 "@types/node" "*"
91 "@types/redis" "*"
92
85"@types/lodash@*", "@types/lodash@^4.14.64": 93"@types/lodash@*", "@types/lodash@^4.14.64":
86 version "4.14.95" 94 version "4.14.95"
87 resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.95.tgz#02c170690719bbaca8293d9c8cdcccb565728081" 95 resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.95.tgz#02c170690719bbaca8293d9c8cdcccb565728081"
@@ -144,6 +152,13 @@
144 version "1.9.3" 152 version "1.9.3"
145 resolved "https://registry.yarnpkg.com/@types/pem/-/pem-1.9.3.tgz#0c864c8b79e43fef6367db895f60fd1edd10e86c" 153 resolved "https://registry.yarnpkg.com/@types/pem/-/pem-1.9.3.tgz#0c864c8b79e43fef6367db895f60fd1edd10e86c"
146 154
155"@types/redis@*":
156 version "2.8.5"
157 resolved "https://registry.yarnpkg.com/@types/redis/-/redis-2.8.5.tgz#c4a31a63e95434202eb84908290528ad8510b149"
158 dependencies:
159 "@types/events" "*"
160 "@types/node" "*"
161
147"@types/reflect-metadata@0.0.4": 162"@types/reflect-metadata@0.0.4":
148 version "0.0.4" 163 version "0.0.4"
149 resolved "https://registry.yarnpkg.com/@types/reflect-metadata/-/reflect-metadata-0.0.4.tgz#b6477ca9a97e5265f2ac67f9ea704eae5e0eaf4d" 164 resolved "https://registry.yarnpkg.com/@types/reflect-metadata/-/reflect-metadata-0.0.4.tgz#b6477ca9a97e5265f2ac67f9ea704eae5e0eaf4d"
@@ -240,16 +255,26 @@ accepts@~1.3.4:
240 mime-types "~2.1.16" 255 mime-types "~2.1.16"
241 negotiator "0.6.1" 256 negotiator "0.6.1"
242 257
258acorn-globals@^3.0.0:
259 version "3.1.0"
260 resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-3.1.0.tgz#fd8270f71fbb4996b004fa880ee5d46573a731bf"
261 dependencies:
262 acorn "^4.0.4"
263
243acorn-jsx@^3.0.0: 264acorn-jsx@^3.0.0:
244 version "3.0.1" 265 version "3.0.1"
245 resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b" 266 resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b"
246 dependencies: 267 dependencies:
247 acorn "^3.0.4" 268 acorn "^3.0.4"
248 269
249acorn@^3.0.4: 270acorn@^3.0.4, acorn@^3.1.0, acorn@~3.3.0:
250 version "3.3.0" 271 version "3.3.0"
251 resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" 272 resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a"
252 273
274acorn@^4.0.4, acorn@~4.0.2:
275 version "4.0.13"
276 resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787"
277
253acorn@^5.2.1: 278acorn@^5.2.1:
254 version "5.3.0" 279 version "5.3.0"
255 resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.3.0.tgz#7446d39459c54fb49a80e6ee6478149b940ec822" 280 resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.3.0.tgz#7446d39459c54fb49a80e6ee6478149b940ec822"
@@ -340,6 +365,12 @@ anymatch@^2.0.0:
340 micromatch "^3.1.4" 365 micromatch "^3.1.4"
341 normalize-path "^2.1.1" 366 normalize-path "^2.1.1"
342 367
368"apparatus@>= 0.0.9":
369 version "0.0.9"
370 resolved "https://registry.yarnpkg.com/apparatus/-/apparatus-0.0.9.tgz#37dcd25834ad0b651076596291db823eeb1908bd"
371 dependencies:
372 sylvester ">= 0.0.8"
373
343append-field@^0.1.0: 374append-field@^0.1.0:
344 version "0.1.0" 375 version "0.1.0"
345 resolved "https://registry.yarnpkg.com/append-field/-/append-field-0.1.0.tgz#6ddc58fa083c7bc545d3c5995b2830cc2366d44a" 376 resolved "https://registry.yarnpkg.com/append-field/-/append-field-0.1.0.tgz#6ddc58fa083c7bc545d3c5995b2830cc2366d44a"
@@ -717,7 +748,7 @@ bn.js@^4.4.0:
717 version "4.11.8" 748 version "4.11.8"
718 resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" 749 resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f"
719 750
720body-parser@1.18.2, body-parser@^1.12.4: 751body-parser@1.18.2, body-parser@^1.12.2, body-parser@^1.12.4:
721 version "1.18.2" 752 version "1.18.2"
722 resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.2.tgz#87678a19d84b47d859b83199bd59bce222b10454" 753 resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.2.tgz#87678a19d84b47d859b83199bd59bce222b10454"
723 dependencies: 754 dependencies:
@@ -959,6 +990,12 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.0:
959 escape-string-regexp "^1.0.5" 990 escape-string-regexp "^1.0.5"
960 supports-color "^4.0.0" 991 supports-color "^4.0.0"
961 992
993character-parser@^2.1.1:
994 version "2.2.0"
995 resolved "https://registry.yarnpkg.com/character-parser/-/character-parser-2.2.0.tgz#c7ce28f36d4bcd9744e5ffc2c5fcde1c73261fc0"
996 dependencies:
997 is-regex "^1.0.3"
998
962charenc@~0.0.1: 999charenc@~0.0.1:
963 version "0.0.2" 1000 version "0.0.2"
964 resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" 1001 resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
@@ -1025,7 +1062,7 @@ class-utils@^0.3.5:
1025 isobject "^3.0.0" 1062 isobject "^3.0.0"
1026 static-extend "^0.1.1" 1063 static-extend "^0.1.1"
1027 1064
1028clean-css@~3.4.2: 1065clean-css@^3.3.0, clean-css@~3.4.2:
1029 version "3.4.28" 1066 version "3.4.28"
1030 resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-3.4.28.tgz#bf1945e82fc808f55695e6ddeaec01400efd03ff" 1067 resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-3.4.28.tgz#bf1945e82fc808f55695e6ddeaec01400efd03ff"
1031 dependencies: 1068 dependencies:
@@ -1268,6 +1305,13 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0:
1268 version "1.1.0" 1305 version "1.1.0"
1269 resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" 1306 resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
1270 1307
1308constantinople@^3.0.1:
1309 version "3.1.0"
1310 resolved "https://registry.yarnpkg.com/constantinople/-/constantinople-3.1.0.tgz#7569caa8aa3f8d5935d62e1fa96f9f702cd81c79"
1311 dependencies:
1312 acorn "^3.1.0"
1313 is-expression "^2.0.1"
1314
1271contains-path@^0.1.0: 1315contains-path@^0.1.0:
1272 version "0.1.0" 1316 version "0.1.0"
1273 resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" 1317 resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a"
@@ -1367,6 +1411,10 @@ crypto-random-string@^1.0.0:
1367 version "1.0.0" 1411 version "1.0.0"
1368 resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" 1412 resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e"
1369 1413
1414css-parse@1.7.x:
1415 version "1.7.0"
1416 resolved "https://registry.yarnpkg.com/css-parse/-/css-parse-1.7.0.tgz#321f6cf73782a6ff751111390fc05e2c657d8c9b"
1417
1370css-select@~1.0.0: 1418css-select@~1.0.0:
1371 version "1.0.0" 1419 version "1.0.0"
1372 resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.0.0.tgz#b1121ca51848dd264e2244d058cee254deeb44b0" 1420 resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.0.0.tgz#b1121ca51848dd264e2244d058cee254deeb44b0"
@@ -1410,15 +1458,15 @@ debug-log@^1.0.0:
1410 version "1.0.1" 1458 version "1.0.1"
1411 resolved "https://registry.yarnpkg.com/debug-log/-/debug-log-1.0.1.tgz#2307632d4c04382b8df8a32f70b895046d52745f" 1459 resolved "https://registry.yarnpkg.com/debug-log/-/debug-log-1.0.1.tgz#2307632d4c04382b8df8a32f70b895046d52745f"
1412 1460
1413debug@2.6.9, debug@^2.0.0, debug@^2.1.0, debug@^2.1.1, debug@^2.2.0, debug@^2.3.3, debug@^2.5.2, debug@^2.6.8, debug@^2.6.9: 1461debug@*, debug@3.1.0, debug@^3.0.0, debug@^3.1.0:
1414 version "2.6.9" 1462 version "3.1.0"
1415 resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 1463 resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
1416 dependencies: 1464 dependencies:
1417 ms "2.0.0" 1465 ms "2.0.0"
1418 1466
1419debug@3.1.0, debug@^3.0.0, debug@^3.1.0: 1467debug@2.6.9, debug@^2.0.0, debug@^2.1.0, debug@^2.1.1, debug@^2.2.0, debug@^2.3.3, debug@^2.5.2, debug@^2.6.8, debug@^2.6.9:
1420 version "3.1.0" 1468 version "2.6.9"
1421 resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" 1469 resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
1422 dependencies: 1470 dependencies:
1423 ms "2.0.0" 1471 ms "2.0.0"
1424 1472
@@ -1567,6 +1615,10 @@ doctrine@^2.0.0:
1567 dependencies: 1615 dependencies:
1568 esutils "^2.0.2" 1616 esutils "^2.0.2"
1569 1617
1618doctypes@^1.1.0:
1619 version "1.1.0"
1620 resolved "https://registry.yarnpkg.com/doctypes/-/doctypes-1.1.0.tgz#ea80b106a87538774e8a3a4a5afe293de489e0a9"
1621
1570dom-serializer@0, dom-serializer@~0.1.0: 1622dom-serializer@0, dom-serializer@~0.1.0:
1571 version "0.1.0" 1623 version "0.1.0"
1572 resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" 1624 resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82"
@@ -1611,6 +1663,10 @@ dottie@^2.0.0:
1611 version "2.0.0" 1663 version "2.0.0"
1612 resolved "https://registry.yarnpkg.com/dottie/-/dottie-2.0.0.tgz#da191981c8b8d713ca0115d5898cf397c2f0ddd0" 1664 resolved "https://registry.yarnpkg.com/dottie/-/dottie-2.0.0.tgz#da191981c8b8d713ca0115d5898cf397c2f0ddd0"
1613 1665
1666double-ended-queue@^2.1.0-0:
1667 version "2.1.0-0"
1668 resolved "https://registry.yarnpkg.com/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz#103d3527fd31528f40188130c841efdd78264e5c"
1669
1614duplexer3@^0.1.4: 1670duplexer3@^0.1.4:
1615 version "0.1.4" 1671 version "0.1.4"
1616 resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" 1672 resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
@@ -2010,7 +2066,7 @@ express-validator@^4.1.1:
2010 lodash "^4.16.0" 2066 lodash "^4.16.0"
2011 validator "~8.2.0" 2067 validator "~8.2.0"
2012 2068
2013express@^4.12.4, express@^4.13.3: 2069express@^4.12.2, express@^4.12.4, express@^4.13.3:
2014 version "4.16.2" 2070 version "4.16.2"
2015 resolved "https://registry.yarnpkg.com/express/-/express-4.16.2.tgz#e35c6dfe2d64b7dca0a5cd4f21781be3299e076c" 2071 resolved "https://registry.yarnpkg.com/express/-/express-4.16.2.tgz#e35c6dfe2d64b7dca0a5cd4f21781be3299e076c"
2016 dependencies: 2072 dependencies:
@@ -2058,6 +2114,10 @@ extend-shallow@^3.0.0:
2058 assign-symbols "^1.0.0" 2114 assign-symbols "^1.0.0"
2059 is-extendable "^1.0.1" 2115 is-extendable "^1.0.1"
2060 2116
2117extend@^1.2.1:
2118 version "1.3.0"
2119 resolved "https://registry.yarnpkg.com/extend/-/extend-1.3.0.tgz#d1516fb0ff5624d2ebf9123ea1dac5a1994004f8"
2120
2061extend@^3.0.0, extend@~3.0.0, extend@~3.0.1: 2121extend@^3.0.0, extend@~3.0.0, extend@~3.0.1:
2062 version "3.0.1" 2122 version "3.0.1"
2063 resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" 2123 resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444"
@@ -2409,6 +2469,17 @@ glob-parent@^3.1.0:
2409 is-glob "^3.1.0" 2469 is-glob "^3.1.0"
2410 path-dirname "^1.0.0" 2470 path-dirname "^1.0.0"
2411 2471
2472glob@7.0.x:
2473 version "7.0.6"
2474 resolved "https://registry.yarnpkg.com/glob/-/glob-7.0.6.tgz#211bafaf49e525b8cd93260d14ab136152b3f57a"
2475 dependencies:
2476 fs.realpath "^1.0.0"
2477 inflight "^1.0.4"
2478 inherits "2"
2479 minimatch "^3.0.2"
2480 once "^1.3.0"
2481 path-is-absolute "^1.0.0"
2482
2412glob@7.1.2, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@~7.1.1: 2483glob@7.1.2, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@~7.1.1:
2413 version "7.1.2" 2484 version "7.1.2"
2414 resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" 2485 resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
@@ -3094,6 +3165,20 @@ is-descriptor@^1.0.0:
3094 is-data-descriptor "^1.0.0" 3165 is-data-descriptor "^1.0.0"
3095 kind-of "^6.0.2" 3166 kind-of "^6.0.2"
3096 3167
3168is-expression@^2.0.1:
3169 version "2.1.0"
3170 resolved "https://registry.yarnpkg.com/is-expression/-/is-expression-2.1.0.tgz#91be9d47debcfef077977e9722be6dcfb4465ef0"
3171 dependencies:
3172 acorn "~3.3.0"
3173 object-assign "^4.0.1"
3174
3175is-expression@^3.0.0:
3176 version "3.0.0"
3177 resolved "https://registry.yarnpkg.com/is-expression/-/is-expression-3.0.0.tgz#39acaa6be7fd1f3471dc42c7416e61c24317ac9f"
3178 dependencies:
3179 acorn "~4.0.2"
3180 object-assign "^4.0.1"
3181
3097is-extendable@^0.1.0, is-extendable@^0.1.1: 3182is-extendable@^0.1.0, is-extendable@^0.1.1:
3098 version "0.1.1" 3183 version "0.1.1"
3099 resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" 3184 resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
@@ -3202,6 +3287,10 @@ is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4:
3202 dependencies: 3287 dependencies:
3203 isobject "^3.0.1" 3288 isobject "^3.0.1"
3204 3289
3290is-promise@^2.0.0:
3291 version "2.1.0"
3292 resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa"
3293
3205is-property@^1.0.0: 3294is-property@^1.0.0:
3206 version "1.0.2" 3295 version "1.0.2"
3207 resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" 3296 resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84"
@@ -3210,7 +3299,7 @@ is-redirect@^1.0.0:
3210 version "1.0.0" 3299 version "1.0.0"
3211 resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" 3300 resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24"
3212 3301
3213is-regex@^1.0.4: 3302is-regex@^1.0.3, is-regex@^1.0.4:
3214 version "1.0.4" 3303 version "1.0.4"
3215 resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" 3304 resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491"
3216 dependencies: 3305 dependencies:
@@ -3286,6 +3375,10 @@ js-string-escape@1.0.1:
3286 version "1.0.1" 3375 version "1.0.1"
3287 resolved "https://registry.yarnpkg.com/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef" 3376 resolved "https://registry.yarnpkg.com/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef"
3288 3377
3378js-stringify@^1.0.1:
3379 version "1.0.2"
3380 resolved "https://registry.yarnpkg.com/js-stringify/-/js-stringify-1.0.2.tgz#1736fddfd9724f28a3682adc6230ae7e4e9679db"
3381
3289js-tokens@^3.0.2: 3382js-tokens@^3.0.2:
3290 version "3.0.2" 3383 version "3.0.2"
3291 resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" 3384 resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
@@ -3382,6 +3475,13 @@ jsprim@^1.2.2:
3382 json-schema "0.2.3" 3475 json-schema "0.2.3"
3383 verror "1.10.0" 3476 verror "1.10.0"
3384 3477
3478jstransformer@1.0.0:
3479 version "1.0.0"
3480 resolved "https://registry.yarnpkg.com/jstransformer/-/jstransformer-1.0.0.tgz#ed8bf0921e2f3f1ed4d5c1a44f68709ed24722c3"
3481 dependencies:
3482 is-promise "^2.0.0"
3483 promise "^7.0.1"
3484
3385jsx-ast-utils@^1.3.4: 3485jsx-ast-utils@^1.3.4:
3386 version "1.4.1" 3486 version "1.4.1"
3387 resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-1.4.1.tgz#3867213e8dd79bf1e8f2300c0cfc1efb182c0df1" 3487 resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-1.4.1.tgz#3867213e8dd79bf1e8f2300c0cfc1efb182c0df1"
@@ -3453,6 +3553,22 @@ kind-of@^6.0.0, kind-of@^6.0.2:
3453 version "6.0.2" 3553 version "6.0.2"
3454 resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" 3554 resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051"
3455 3555
3556kue@^0.11.6:
3557 version "0.11.6"
3558 resolved "https://registry.yarnpkg.com/kue/-/kue-0.11.6.tgz#5b76916bcedd56636a107861471c63c94611860a"
3559 dependencies:
3560 body-parser "^1.12.2"
3561 express "^4.12.2"
3562 lodash "^4.0.0"
3563 nib "~1.1.2"
3564 node-redis-warlock "~0.2.0"
3565 pug "^2.0.0-beta3"
3566 redis "~2.6.0-2"
3567 stylus "~0.54.5"
3568 yargs "^4.0.0"
3569 optionalDependencies:
3570 reds "^0.2.5"
3571
3456kuler@0.0.x: 3572kuler@0.0.x:
3457 version "0.0.0" 3573 version "0.0.0"
3458 resolved "https://registry.yarnpkg.com/kuler/-/kuler-0.0.0.tgz#b66bb46b934e550f59d818848e0abba4f7f5553c" 3574 resolved "https://registry.yarnpkg.com/kuler/-/kuler-0.0.0.tgz#b66bb46b934e550f59d818848e0abba4f7f5553c"
@@ -3564,7 +3680,7 @@ lodash._isiterateecall@^3.0.0:
3564 version "3.0.9" 3680 version "3.0.9"
3565 resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c" 3681 resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c"
3566 3682
3567lodash.assign@^4.2.0: 3683lodash.assign@^4.0.3, lodash.assign@^4.0.6, lodash.assign@^4.2.0:
3568 version "4.2.0" 3684 version "4.2.0"
3569 resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" 3685 resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7"
3570 3686
@@ -3692,7 +3808,7 @@ lowercase-keys@^1.0.0:
3692 version "1.0.0" 3808 version "1.0.0"
3693 resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" 3809 resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306"
3694 3810
3695lru-cache@2: 3811lru-cache@2, lru-cache@^2.5.0:
3696 version "2.7.3" 3812 version "2.7.3"
3697 resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" 3813 resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952"
3698 3814
@@ -3916,7 +4032,7 @@ mixin-deep@^1.2.0:
3916 for-in "^1.0.2" 4032 for-in "^1.0.2"
3917 is-extendable "^1.0.1" 4033 is-extendable "^1.0.1"
3918 4034
3919mkdirp@0.5.1, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0: 4035mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0:
3920 version "0.5.1" 4036 version "0.5.1"
3921 resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" 4037 resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
3922 dependencies: 4038 dependencies:
@@ -4036,6 +4152,14 @@ natural-compare@^1.4.0:
4036 version "1.4.0" 4152 version "1.4.0"
4037 resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" 4153 resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
4038 4154
4155natural@^0.2.0:
4156 version "0.2.1"
4157 resolved "https://registry.yarnpkg.com/natural/-/natural-0.2.1.tgz#1eb5156a9d90b4591949e20e94ebc77bb2339eda"
4158 dependencies:
4159 apparatus ">= 0.0.9"
4160 sylvester ">= 0.0.12"
4161 underscore ">=1.3.1"
4162
4039negotiator@0.6.1: 4163negotiator@0.6.1:
4040 version "0.6.1" 4164 version "0.6.1"
4041 resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" 4165 resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
@@ -4048,6 +4172,12 @@ next-event@^1.0.0:
4048 version "1.0.0" 4172 version "1.0.0"
4049 resolved "https://registry.yarnpkg.com/next-event/-/next-event-1.0.0.tgz#e7778acde2e55802e0ad1879c39cf6f75eda61d8" 4173 resolved "https://registry.yarnpkg.com/next-event/-/next-event-1.0.0.tgz#e7778acde2e55802e0ad1879c39cf6f75eda61d8"
4050 4174
4175nib@~1.1.2:
4176 version "1.1.2"
4177 resolved "https://registry.yarnpkg.com/nib/-/nib-1.1.2.tgz#6a69ede4081b95c0def8be024a4c8ae0c2cbb6c7"
4178 dependencies:
4179 stylus "0.54.5"
4180
4051node-abi@^2.1.1: 4181node-abi@^2.1.1:
4052 version "2.1.2" 4182 version "2.1.2"
4053 resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.1.2.tgz#4da6caceb6685fcd31e7dd1994ef6bb7d0a9c0b2" 4183 resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.1.2.tgz#4da6caceb6685fcd31e7dd1994ef6bb7d0a9c0b2"
@@ -4106,6 +4236,20 @@ node-pre-gyp@^0.6.39:
4106 tar "^2.2.1" 4236 tar "^2.2.1"
4107 tar-pack "^3.4.0" 4237 tar-pack "^3.4.0"
4108 4238
4239node-redis-scripty@0.0.5:
4240 version "0.0.5"
4241 resolved "https://registry.yarnpkg.com/node-redis-scripty/-/node-redis-scripty-0.0.5.tgz#4bf2d365ab6dab202cc08b7ac63f8f55aadc9625"
4242 dependencies:
4243 extend "^1.2.1"
4244 lru-cache "^2.5.0"
4245
4246node-redis-warlock@~0.2.0:
4247 version "0.2.0"
4248 resolved "https://registry.yarnpkg.com/node-redis-warlock/-/node-redis-warlock-0.2.0.tgz#56395b994c828e8e32f6aae53b93b6edfcd97990"
4249 dependencies:
4250 node-redis-scripty "0.0.5"
4251 uuid "^2.0.1"
4252
4109node-sass@^4.0.0: 4253node-sass@^4.0.0:
4110 version "4.7.2" 4254 version "4.7.2"
4111 resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.7.2.tgz#9366778ba1469eb01438a9e8592f4262bcb6794e" 4255 resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.7.2.tgz#9366778ba1469eb01438a9e8592f4262bcb6794e"
@@ -4703,7 +4847,7 @@ progress@^1.1.8:
4703 version "1.1.8" 4847 version "1.1.8"
4704 resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" 4848 resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be"
4705 4849
4706promise@^7.1.1: 4850promise@^7.0.1, promise@^7.1.1:
4707 version "7.3.1" 4851 version "7.3.1"
4708 resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" 4852 resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
4709 dependencies: 4853 dependencies:
@@ -4744,6 +4888,99 @@ pstree.remy@^1.1.0:
4744 dependencies: 4888 dependencies:
4745 ps-tree "^1.1.0" 4889 ps-tree "^1.1.0"
4746 4890
4891pug-attrs@^2.0.2:
4892 version "2.0.2"
4893 resolved "https://registry.yarnpkg.com/pug-attrs/-/pug-attrs-2.0.2.tgz#8be2b2225568ffa75d1b866982bff9f4111affcb"
4894 dependencies:
4895 constantinople "^3.0.1"
4896 js-stringify "^1.0.1"
4897 pug-runtime "^2.0.3"
4898
4899pug-code-gen@^2.0.0:
4900 version "2.0.0"
4901 resolved "https://registry.yarnpkg.com/pug-code-gen/-/pug-code-gen-2.0.0.tgz#96aea39a9e62f1ec5d2b6a5b42a29d528c70b43d"
4902 dependencies:
4903 constantinople "^3.0.1"
4904 doctypes "^1.1.0"
4905 js-stringify "^1.0.1"
4906 pug-attrs "^2.0.2"
4907 pug-error "^1.3.2"
4908 pug-runtime "^2.0.3"
4909 void-elements "^2.0.1"
4910 with "^5.0.0"
4911
4912pug-error@^1.3.2:
4913 version "1.3.2"
4914 resolved "https://registry.yarnpkg.com/pug-error/-/pug-error-1.3.2.tgz#53ae7d9d29bb03cf564493a026109f54c47f5f26"
4915
4916pug-filters@^2.1.5:
4917 version "2.1.5"
4918 resolved "https://registry.yarnpkg.com/pug-filters/-/pug-filters-2.1.5.tgz#66bf6e80d97fbef829bab0aa35eddff33fc964f3"
4919 dependencies:
4920 clean-css "^3.3.0"
4921 constantinople "^3.0.1"
4922 jstransformer "1.0.0"
4923 pug-error "^1.3.2"
4924 pug-walk "^1.1.5"
4925 resolve "^1.1.6"
4926 uglify-js "^2.6.1"
4927
4928pug-lexer@^3.1.0:
4929 version "3.1.0"
4930 resolved "https://registry.yarnpkg.com/pug-lexer/-/pug-lexer-3.1.0.tgz#fd087376d4a675b4f59f8fef422883434e9581a2"
4931 dependencies:
4932 character-parser "^2.1.1"
4933 is-expression "^3.0.0"
4934 pug-error "^1.3.2"
4935
4936pug-linker@^3.0.3:
4937 version "3.0.3"
4938 resolved "https://registry.yarnpkg.com/pug-linker/-/pug-linker-3.0.3.tgz#25f59eb750237f0368e59c3379764229c0189c41"
4939 dependencies:
4940 pug-error "^1.3.2"
4941 pug-walk "^1.1.5"
4942
4943pug-load@^2.0.9:
4944 version "2.0.9"
4945 resolved "https://registry.yarnpkg.com/pug-load/-/pug-load-2.0.9.tgz#ee217c914cc1d9324d44b86c32d1df241d36de7a"
4946 dependencies:
4947 object-assign "^4.1.0"
4948 pug-walk "^1.1.5"
4949
4950pug-parser@^4.0.0:
4951 version "4.0.0"
4952 resolved "https://registry.yarnpkg.com/pug-parser/-/pug-parser-4.0.0.tgz#c9f52322e4eabe4bf5beeba64ed18373bb627801"
4953 dependencies:
4954 pug-error "^1.3.2"
4955 token-stream "0.0.1"
4956
4957pug-runtime@^2.0.3:
4958 version "2.0.3"
4959 resolved "https://registry.yarnpkg.com/pug-runtime/-/pug-runtime-2.0.3.tgz#98162607b0fce9e254d427f33987a5aee7168bda"
4960
4961pug-strip-comments@^1.0.2:
4962 version "1.0.2"
4963 resolved "https://registry.yarnpkg.com/pug-strip-comments/-/pug-strip-comments-1.0.2.tgz#d313afa01bcc374980e1399e23ebf2eb9bdc8513"
4964 dependencies:
4965 pug-error "^1.3.2"
4966
4967pug-walk@^1.1.5:
4968 version "1.1.5"
4969 resolved "https://registry.yarnpkg.com/pug-walk/-/pug-walk-1.1.5.tgz#90e943acbcf7021e6454cf1b32245891cba6f851"
4970
4971pug@^2.0.0-beta3:
4972 version "2.0.0-rc.4"
4973 resolved "https://registry.yarnpkg.com/pug/-/pug-2.0.0-rc.4.tgz#b7b08f6599bd5302568042b7436984fb28c80a13"
4974 dependencies:
4975 pug-code-gen "^2.0.0"
4976 pug-filters "^2.1.5"
4977 pug-lexer "^3.1.0"
4978 pug-linker "^3.0.3"
4979 pug-load "^2.0.9"
4980 pug-parser "^4.0.0"
4981 pug-runtime "^2.0.3"
4982 pug-strip-comments "^1.0.2"
4983
4747pump@^1.0.0, pump@^1.0.1: 4984pump@^1.0.0, pump@^1.0.1:
4748 version "1.0.3" 4985 version "1.0.3"
4749 resolved "https://registry.yarnpkg.com/pump/-/pump-1.0.3.tgz#5dfe8311c33bbf6fc18261f9f34702c47c08a954" 4986 resolved "https://registry.yarnpkg.com/pump/-/pump-1.0.3.tgz#5dfe8311c33bbf6fc18261f9f34702c47c08a954"
@@ -4908,6 +5145,33 @@ redent@^1.0.0:
4908 indent-string "^2.1.0" 5145 indent-string "^2.1.0"
4909 strip-indent "^1.0.1" 5146 strip-indent "^1.0.1"
4910 5147
5148redis-commands@^1.2.0:
5149 version "1.3.1"
5150 resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.3.1.tgz#81d826f45fa9c8b2011f4cd7a0fe597d241d442b"
5151
5152redis-parser@^2.0.0:
5153 version "2.6.0"
5154 resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-2.6.0.tgz#52ed09dacac108f1a631c07e9b69941e7a19504b"
5155
5156redis@^0.12.1:
5157 version "0.12.1"
5158 resolved "https://registry.yarnpkg.com/redis/-/redis-0.12.1.tgz#64df76ad0fc8acebaebd2a0645e8a48fac49185e"
5159
5160redis@~2.6.0-2:
5161 version "2.6.5"
5162 resolved "https://registry.yarnpkg.com/redis/-/redis-2.6.5.tgz#87c1eff4a489f94b70871f3d08b6988f23a95687"
5163 dependencies:
5164 double-ended-queue "^2.1.0-0"
5165 redis-commands "^1.2.0"
5166 redis-parser "^2.0.0"
5167
5168reds@^0.2.5:
5169 version "0.2.5"
5170 resolved "https://registry.yarnpkg.com/reds/-/reds-0.2.5.tgz#38a767f7663cd749036848697d82c74fd29bc01f"
5171 dependencies:
5172 natural "^0.2.0"
5173 redis "^0.12.1"
5174
4911reflect-metadata@^0.1.10: 5175reflect-metadata@^0.1.10:
4912 version "0.1.12" 5176 version "0.1.12"
4913 resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.12.tgz#311bf0c6b63cd782f228a81abe146a2bfa9c56f2" 5177 resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.12.tgz#311bf0c6b63cd782f228a81abe146a2bfa9c56f2"
@@ -5140,6 +5404,10 @@ sass-graph@^2.2.4:
5140 scss-tokenizer "^0.2.3" 5404 scss-tokenizer "^0.2.3"
5141 yargs "^7.0.0" 5405 yargs "^7.0.0"
5142 5406
5407sax@0.5.x:
5408 version "0.5.8"
5409 resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.8.tgz#d472db228eb331c2506b0e8c15524adb939d12c1"
5410
5143scripty@^1.5.0: 5411scripty@^1.5.0:
5144 version "1.7.2" 5412 version "1.7.2"
5145 resolved "https://registry.yarnpkg.com/scripty/-/scripty-1.7.2.tgz#92367b724cb77b086729691f7b01aa57f3ddd356" 5413 resolved "https://registry.yarnpkg.com/scripty/-/scripty-1.7.2.tgz#92367b724cb77b086729691f7b01aa57f3ddd356"
@@ -5453,6 +5721,12 @@ source-map-url@^0.4.0:
5453 version "0.4.0" 5721 version "0.4.0"
5454 resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" 5722 resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
5455 5723
5724source-map@0.1.x:
5725 version "0.1.43"
5726 resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.43.tgz#c24bc146ca517c1471f5dacbe2571b2b7f9e3346"
5727 dependencies:
5728 amdefine ">=0.0.4"
5729
5456source-map@0.4.x, source-map@^0.4.2, source-map@^0.4.4: 5730source-map@0.4.x, source-map@^0.4.2, source-map@^0.4.4:
5457 version "0.4.4" 5731 version "0.4.4"
5458 resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" 5732 resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b"
@@ -5721,6 +5995,17 @@ strip-json-comments@^2.0.0, strip-json-comments@~2.0.1:
5721 version "2.0.1" 5995 version "2.0.1"
5722 resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" 5996 resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
5723 5997
5998stylus@0.54.5, stylus@~0.54.5:
5999 version "0.54.5"
6000 resolved "https://registry.yarnpkg.com/stylus/-/stylus-0.54.5.tgz#42b9560931ca7090ce8515a798ba9e6aa3d6dc79"
6001 dependencies:
6002 css-parse "1.7.x"
6003 debug "*"
6004 glob "7.0.x"
6005 mkdirp "0.5.x"
6006 sax "0.5.x"
6007 source-map "0.1.x"
6008
5724superagent@^3.0.0, superagent@^3.6.3: 6009superagent@^3.0.0, superagent@^3.6.3:
5725 version "3.8.2" 6010 version "3.8.2"
5726 resolved "https://registry.yarnpkg.com/superagent/-/superagent-3.8.2.tgz#e4a11b9d047f7d3efeb3bbe536d9ec0021d16403" 6011 resolved "https://registry.yarnpkg.com/superagent/-/superagent-3.8.2.tgz#e4a11b9d047f7d3efeb3bbe536d9ec0021d16403"
@@ -5769,6 +6054,10 @@ supports-color@^4.0.0:
5769 dependencies: 6054 dependencies:
5770 has-flag "^2.0.0" 6055 has-flag "^2.0.0"
5771 6056
6057"sylvester@>= 0.0.12", "sylvester@>= 0.0.8":
6058 version "0.0.21"
6059 resolved "https://registry.yarnpkg.com/sylvester/-/sylvester-0.0.21.tgz#2987b1ce2bd2f38b0dce2a34388884bfa4400ea7"
6060
5772sync-request@^4.1.0: 6061sync-request@^4.1.0:
5773 version "4.1.0" 6062 version "4.1.0"
5774 resolved "https://registry.yarnpkg.com/sync-request/-/sync-request-4.1.0.tgz#324b4e506fb994d2afd2a0021a455f800725f07a" 6063 resolved "https://registry.yarnpkg.com/sync-request/-/sync-request-4.1.0.tgz#324b4e506fb994d2afd2a0021a455f800725f07a"
@@ -5933,6 +6222,10 @@ to-regex@^3.0.1:
5933 extend-shallow "^2.0.1" 6222 extend-shallow "^2.0.1"
5934 regex-not "^1.0.0" 6223 regex-not "^1.0.0"
5935 6224
6225token-stream@0.0.1:
6226 version "0.0.1"
6227 resolved "https://registry.yarnpkg.com/token-stream/-/token-stream-0.0.1.tgz#ceeefc717a76c4316f126d0b9dbaa55d7e7df01a"
6228
5936toposort-class@^1.0.1: 6229toposort-class@^1.0.1:
5937 version "1.0.1" 6230 version "1.0.1"
5938 resolved "https://registry.yarnpkg.com/toposort-class/-/toposort-class-1.0.1.tgz#7ffd1f78c8be28c3ba45cd4e1a3f5ee193bd9988" 6231 resolved "https://registry.yarnpkg.com/toposort-class/-/toposort-class-1.0.1.tgz#7ffd1f78c8be28c3ba45cd4e1a3f5ee193bd9988"
@@ -6100,7 +6393,7 @@ typescript@^2.5.2:
6100 version "2.6.2" 6393 version "2.6.2"
6101 resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" 6394 resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4"
6102 6395
6103uglify-js@^2.6: 6396uglify-js@^2.6, uglify-js@^2.6.1:
6104 version "2.8.29" 6397 version "2.8.29"
6105 resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" 6398 resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd"
6106 dependencies: 6399 dependencies:
@@ -6152,7 +6445,7 @@ underscore.string@~2.4.0:
6152 version "2.4.0" 6445 version "2.4.0"
6153 resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-2.4.0.tgz#8cdd8fbac4e2d2ea1e7e2e8097c42f442280f85b" 6446 resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-2.4.0.tgz#8cdd8fbac4e2d2ea1e7e2e8097c42f442280f85b"
6154 6447
6155underscore@^1.7.0: 6448underscore@>=1.3.1, underscore@^1.7.0:
6156 version "1.8.3" 6449 version "1.8.3"
6157 resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022" 6450 resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022"
6158 6451
@@ -6278,6 +6571,10 @@ utils-merge@1.0.1:
6278 version "1.0.1" 6571 version "1.0.1"
6279 resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" 6572 resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
6280 6573
6574uuid@^2.0.1:
6575 version "2.0.3"
6576 resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a"
6577
6281uuid@^3.0.0, uuid@^3.1.0, uuid@^3.2.1: 6578uuid@^3.0.0, uuid@^3.1.0, uuid@^3.2.1:
6282 version "3.2.1" 6579 version "3.2.1"
6283 resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14" 6580 resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14"
@@ -6328,6 +6625,10 @@ videostream@^2.3.0:
6328 pump "^1.0.1" 6625 pump "^1.0.1"
6329 range-slice-stream "^1.2.0" 6626 range-slice-stream "^1.2.0"
6330 6627
6628void-elements@^2.0.1:
6629 version "2.0.1"
6630 resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"
6631
6331webfinger.js@^2.6.6: 6632webfinger.js@^2.6.6:
6332 version "2.6.6" 6633 version "2.6.6"
6333 resolved "https://registry.yarnpkg.com/webfinger.js/-/webfinger.js-2.6.6.tgz#52ebdc85da8c8fb6beb690e8e32594c99d2ff4ae" 6634 resolved "https://registry.yarnpkg.com/webfinger.js/-/webfinger.js-2.6.6.tgz#52ebdc85da8c8fb6beb690e8e32594c99d2ff4ae"
@@ -6415,6 +6716,10 @@ window-size@0.1.0:
6415 version "0.1.0" 6716 version "0.1.0"
6416 resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" 6717 resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d"
6417 6718
6719window-size@^0.2.0:
6720 version "0.2.0"
6721 resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075"
6722
6418winston-transport@^3.0.1: 6723winston-transport@^3.0.1:
6419 version "3.0.1" 6724 version "3.0.1"
6420 resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-3.0.1.tgz#8008b15eef5660c4fb3fa094d58ccbd08528c58d" 6725 resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-3.0.1.tgz#8008b15eef5660c4fb3fa094d58ccbd08528c58d"
@@ -6432,6 +6737,13 @@ winston@3.0.0-rc1:
6432 triple-beam "^1.0.1" 6737 triple-beam "^1.0.1"
6433 winston-transport "^3.0.1" 6738 winston-transport "^3.0.1"
6434 6739
6740with@^5.0.0:
6741 version "5.1.1"
6742 resolved "https://registry.yarnpkg.com/with/-/with-5.1.1.tgz#fa4daa92daf32c4ea94ed453c81f04686b575dfe"
6743 dependencies:
6744 acorn "^3.1.0"
6745 acorn-globals "^3.0.0"
6746
6435wkx@^0.4.1: 6747wkx@^0.4.1:
6436 version "0.4.2" 6748 version "0.4.2"
6437 resolved "https://registry.yarnpkg.com/wkx/-/wkx-0.4.2.tgz#776d35a634a5c22e656e4744bdeb54f83fd2ce8d" 6749 resolved "https://registry.yarnpkg.com/wkx/-/wkx-0.4.2.tgz#776d35a634a5c22e656e4744bdeb54f83fd2ce8d"
@@ -6519,6 +6831,13 @@ yallist@^3.0.0, yallist@^3.0.2:
6519 version "3.0.2" 6831 version "3.0.2"
6520 resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9" 6832 resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9"
6521 6833
6834yargs-parser@^2.4.1:
6835 version "2.4.1"
6836 resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-2.4.1.tgz#85568de3cf150ff49fa51825f03a8c880ddcc5c4"
6837 dependencies:
6838 camelcase "^3.0.0"
6839 lodash.assign "^4.0.6"
6840
6522yargs-parser@^5.0.0: 6841yargs-parser@^5.0.0:
6523 version "5.0.0" 6842 version "5.0.0"
6524 resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a" 6843 resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a"
@@ -6531,6 +6850,25 @@ yargs-parser@^8.0.0:
6531 dependencies: 6850 dependencies:
6532 camelcase "^4.1.0" 6851 camelcase "^4.1.0"
6533 6852
6853yargs@^4.0.0:
6854 version "4.8.1"
6855 resolved "https://registry.yarnpkg.com/yargs/-/yargs-4.8.1.tgz#c0c42924ca4aaa6b0e6da1739dfb216439f9ddc0"
6856 dependencies:
6857 cliui "^3.2.0"
6858 decamelize "^1.1.1"
6859 get-caller-file "^1.0.1"
6860 lodash.assign "^4.0.3"
6861 os-locale "^1.4.0"
6862 read-pkg-up "^1.0.1"
6863 require-directory "^2.1.1"
6864 require-main-filename "^1.0.1"
6865 set-blocking "^2.0.0"
6866 string-width "^1.0.1"
6867 which-module "^1.0.0"
6868 window-size "^0.2.0"
6869 y18n "^3.2.1"
6870 yargs-parser "^2.4.1"
6871
6534yargs@^7.0.0: 6872yargs@^7.0.0:
6535 version "7.1.0" 6873 version "7.1.0"
6536 resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8" 6874 resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8"