diff options
60 files changed, 985 insertions, 696 deletions
@@ -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 | |||
1 | pre { | 8 | pre { |
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' | |||
2 | import { NotificationsService } from 'angular2-notifications' | 2 | import { NotificationsService } from 'angular2-notifications' |
3 | import { SortMeta } from 'primeng/primeng' | 3 | import { SortMeta } from 'primeng/primeng' |
4 | import { Job } from '../../../../../../shared/index' | 4 | import { Job } from '../../../../../../shared/index' |
5 | import { JobState } from '../../../../../../shared/models' | ||
5 | import { RestPagination, RestTable } from '../../../shared' | 6 | import { RestPagination, RestTable } from '../../../shared' |
6 | import { viewportHeight } from '../../../shared/misc/utils' | 7 | import { viewportHeight } from '../../../shared/misc/utils' |
7 | import { JobService } from '../shared' | 8 | import { 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 | }) |
15 | export class JobsListComponent extends RestTable implements OnInit { | 16 | export 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' | |||
5 | import 'rxjs/add/operator/map' | 5 | import 'rxjs/add/operator/map' |
6 | import { Observable } from 'rxjs/Observable' | 6 | import { Observable } from 'rxjs/Observable' |
7 | import { ResultList } from '../../../../../../shared' | 7 | import { ResultList } from '../../../../../../shared' |
8 | import { JobState } from '../../../../../../shared/models' | ||
8 | import { Job } from '../../../../../../shared/models/job.model' | 9 | import { Job } from '../../../../../../shared/models/job.model' |
9 | import { environment } from '../../../../environments/environment' | 10 | import { environment } from '../../../../environments/environment' |
10 | import { RestExtractor, RestPagination, RestService } from '../../../shared' | 11 | import { 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 | ||
17 | redis: | ||
18 | hostname: 'localhost' | ||
19 | port: 6379 | ||
20 | auth: null | ||
21 | |||
17 | # From the project root directory | 22 | # From the project root directory |
18 | storage: | 23 | storage: |
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 | ||
18 | redis: | ||
19 | hostname: 'localhost' | ||
20 | port: 6379 | ||
21 | auth: null | ||
22 | |||
18 | # From the project root directory | 23 | # From the project root directory |
19 | storage: | 24 | storage: |
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 | ||
9 | done | 10 | done |
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' | |||
2 | import { join } from 'path' | 2 | import { join } from 'path' |
3 | import { createInterface } from 'readline' | 3 | import { createInterface } from 'readline' |
4 | import * as winston from 'winston' | 4 | import * as winston from 'winston' |
5 | import { labelFormatter, loggerFormat, timestampFormatter } from '../server/helpers/logger' | 5 | import { labelFormatter } from '../server/helpers/logger' |
6 | import { CONFIG } from '../server/initializers/constants' | 6 | import { CONFIG } from '../server/initializers/constants' |
7 | 7 | ||
8 | const excludedKeys = { | ||
9 | level: true, | ||
10 | message: true, | ||
11 | splat: true, | ||
12 | timestamp: true, | ||
13 | label: true | ||
14 | } | ||
15 | function keysExcluder (key, value) { | ||
16 | return excludedKeys[key] === true ? undefined : value | ||
17 | } | ||
18 | |||
19 | const 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 | |||
8 | const logger = new winston.createLogger({ | 27 | const 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(), |
@@ -53,10 +53,11 @@ migrate() | |||
53 | 53 | ||
54 | // ----------- PeerTube modules ----------- | 54 | // ----------- PeerTube modules ----------- |
55 | import { installApplication } from './server/initializers' | 55 | import { installApplication } from './server/initializers' |
56 | import { activitypubHttpJobScheduler, transcodingJobScheduler } from './server/lib/jobs' | 56 | import { JobQueue } from './server/lib/job-queue' |
57 | import { VideosPreviewCache } from './server/lib/cache' | 57 | import { VideosPreviewCache } from './server/lib/cache' |
58 | import { apiRouter, clientsRouter, staticRouter, servicesRouter, webfingerRouter, activityPubRouter } from './server/controllers' | 58 | import { apiRouter, clientsRouter, staticRouter, servicesRouter, webfingerRouter, activityPubRouter } from './server/controllers' |
59 | import { BadActorFollowScheduler } from './server/lib/schedulers/bad-actor-follow-scheduler' | 59 | import { BadActorFollowScheduler } from './server/lib/schedulers/bad-actor-follow-scheduler' |
60 | import { 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { ResultList } from '../../../shared' | ||
3 | import { Job, JobType, JobState } from '../../../shared/models' | ||
2 | import { UserRight } from '../../../shared/models/users' | 4 | import { UserRight } from '../../../shared/models/users' |
3 | import { getFormattedObjects } from '../../helpers/utils' | 5 | import { JobQueue } from '../../lib/job-queue' |
4 | import { | 6 | import { |
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' |
8 | import { paginationValidator } from '../../middlewares/validators' | 14 | import { paginationValidator } from '../../middlewares/validators' |
9 | import { JobModel } from '../../models/job/job' | 15 | import { listJobsValidator } from '../../middlewares/validators/jobs' |
10 | 16 | ||
11 | const jobsRouter = express.Router() | 17 | const jobsRouter = express.Router() |
12 | 18 | ||
13 | jobsRouter.get('/', | 19 | jobsRouter.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 | ||
31 | async function listJobs (req: express.Request, res: express.Response, next: express.NextFunction) { | 38 | async 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)) | 51 | function 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' |
13 | import { fetchRemoteVideoDescription, getVideoActivityPubUrl, shareVideoByServerAndChannel } from '../../../lib/activitypub' | 13 | import { fetchRemoteVideoDescription, getVideoActivityPubUrl, shareVideoByServerAndChannel } from '../../../lib/activitypub' |
14 | import { sendCreateVideo, sendCreateViewToOrigin, sendCreateViewToVideoFollowers, sendUpdateVideo } from '../../../lib/activitypub/send' | 14 | import { sendCreateVideo, sendCreateViewToOrigin, sendCreateViewToVideoFollowers, sendUpdateVideo } from '../../../lib/activitypub/send' |
15 | import { transcodingJobScheduler } from '../../../lib/jobs/transcoding-job-scheduler' | 15 | import { JobQueue } from '../../../lib/job-queue' |
16 | import { | 16 | import { |
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 | ||
221 | async function updateVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { | 223 | async 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 @@ | |||
1 | import { JobState } from '../../../shared/models' | ||
2 | import { exists } from './misc' | ||
3 | |||
4 | const jobStates: JobState[] = [ 'active', 'complete', 'failed', 'inactive', 'delayed' ] | ||
5 | |||
6 | function isValidJobState (value: JobState) { | ||
7 | return exists(value) && jobStates.indexOf(value) !== -1 | ||
8 | } | ||
9 | |||
10 | // --------------------------------------------------------------------------- | ||
11 | |||
12 | export { | ||
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 @@ | |||
1 | import { IConfig } from 'config' | 1 | import { IConfig } from 'config' |
2 | import { dirname, join } from 'path' | 2 | import { dirname, join } from 'path' |
3 | import { JobCategory, JobState, VideoRateType } from '../../shared/models' | 3 | import { JobType, VideoRateType } from '../../shared/models' |
4 | import { ActivityPubActorType } from '../../shared/models/activitypub' | 4 | import { ActivityPubActorType } from '../../shared/models/activitypub' |
5 | import { FollowState } from '../../shared/models/actors' | 5 | import { FollowState } from '../../shared/models/actors' |
6 | import { VideoPrivacy } from '../../shared/models/videos' | 6 | import { VideoPrivacy } from '../../shared/models/videos' |
@@ -12,7 +12,7 @@ let config: IConfig = require('config') | |||
12 | 12 | ||
13 | // --------------------------------------------------------------------------- | 13 | // --------------------------------------------------------------------------- |
14 | 14 | ||
15 | const LAST_MIGRATION_VERSION = 175 | 15 | const LAST_MIGRATION_VERSION = 180 |
16 | 16 | ||
17 | // --------------------------------------------------------------------------- | 17 | // --------------------------------------------------------------------------- |
18 | 18 | ||
@@ -26,7 +26,7 @@ const PAGINATION_COUNT_DEFAULT = 15 | |||
26 | const SORTABLE_COLUMNS = { | 26 | const 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 | ||
64 | const JOB_STATES: { [ id: string ]: JobState } = { | 64 | const 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 | } | ||
70 | const 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 | 70 | const JOB_CONCURRENCY: { [ id in JobType ]: number } = { |
75 | const 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 |
80 | let JOBS_FETCHING_INTERVAL = 60000 | 77 | const JOB_COMPLETED_LIFETIME = 60000 * 60 * 24 * 2 |
81 | 78 | ||
82 | // 1 hour | 79 | // 1 hour |
83 | let SCHEDULER_INTERVAL = 60000 * 60 | 80 | let 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 |
359 | if (isTestInstance() === true) { | 360 | if (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' | |||
9 | import { ActorFollowModel } from '../models/activitypub/actor-follow' | 9 | import { ActorFollowModel } from '../models/activitypub/actor-follow' |
10 | import { ApplicationModel } from '../models/application/application' | 10 | import { ApplicationModel } from '../models/application/application' |
11 | import { AvatarModel } from '../models/avatar/avatar' | 11 | import { AvatarModel } from '../models/avatar/avatar' |
12 | import { JobModel } from '../models/job/job' | ||
13 | import { OAuthClientModel } from '../models/oauth/oauth-client' | 12 | import { OAuthClientModel } from '../models/oauth/oauth-client' |
14 | import { OAuthTokenModel } from '../models/oauth/oauth-token' | 13 | import { OAuthTokenModel } from '../models/oauth/oauth-token' |
15 | import { ServerModel } from '../models/server/server' | 14 | import { 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 @@ | |||
1 | import { values } from 'lodash' | ||
2 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
3 | import { createPrivateAndPublicKeys } from '../../helpers/peertube-crypto' | 2 | import { createPrivateAndPublicKeys } from '../../helpers/peertube-crypto' |
4 | import { shareVideoByServerAndChannel } from '../../lib/activitypub/share' | 3 | import { shareVideoByServerAndChannel } from '../../lib/activitypub/share' |
5 | import { getVideoActivityPubUrl, getVideoChannelActivityPubUrl } from '../../lib/activitypub/url' | 4 | import { getVideoActivityPubUrl, getVideoChannelActivityPubUrl } from '../../lib/activitypub/url' |
6 | import { createLocalAccountWithoutKeys } from '../../lib/user' | 5 | import { createLocalAccountWithoutKeys } from '../../lib/user' |
7 | import { ApplicationModel } from '../../models/application/application' | 6 | import { ApplicationModel } from '../../models/application/application' |
8 | import { JOB_CATEGORIES, SERVER_ACTOR_NAME } from '../constants' | 7 | import { SERVER_ACTOR_NAME } from '../constants' |
9 | 8 | ||
10 | async function up (utils: { | 9 | async 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | async 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 | |||
11 | function down (options) { | ||
12 | throw new Error('Not implemented.') | ||
13 | } | ||
14 | |||
15 | export { | ||
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 | ||
70 | function buildActorInstance (type: ActivityPubActorType, url: string, preferredUsername: string, uuid?: string) { | 74 | function buildActorInstance (type: ActivityPubActorType, url: string, preferredUsername: string, uuid?: string) { |
@@ -325,38 +329,43 @@ async function saveVideoChannel (actor: ActorModel, result: FetchRemoteActorResu | |||
325 | async function refreshActorIfNeeded (actor: ActorModel) { | 329 | async 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 | ||
362 | function normalizeActor (actor: any) { | 371 | function 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 @@ | |||
1 | import { Transaction } from 'sequelize' | ||
2 | import { ActorModel } from '../../models/activitypub/actor' | 1 | import { ActorModel } from '../../models/activitypub/actor' |
3 | import { activitypubHttpJobScheduler, ActivityPubHttpPayload } from '../jobs/activitypub-http-job-scheduler' | 2 | import { JobQueue } from '../job-queue' |
4 | 3 | ||
5 | async function addFetchOutboxJob (actor: ActorModel, t: Transaction) { | 4 | async 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 | ||
13 | export { | 12 | export { |
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' | |||
7 | import { VideoModel } from '../../../models/video/video' | 7 | import { VideoModel } from '../../../models/video/video' |
8 | import { VideoCommentModel } from '../../../models/video/video-comment' | 8 | import { VideoCommentModel } from '../../../models/video/video-comment' |
9 | import { VideoShareModel } from '../../../models/video/video-share' | 9 | import { VideoShareModel } from '../../../models/video/video-share' |
10 | import { activitypubHttpJobScheduler, ActivityPubHttpPayload } from '../../jobs/activitypub-http-job-scheduler' | 10 | import { JobQueue } from '../../job-queue' |
11 | 11 | ||
12 | async function forwardActivity ( | 12 | async 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 | ||
46 | async function broadcastToFollowers ( | 45 | async 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 | ||
57 | async function broadcastToActors ( | 56 | async 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 | ||
68 | async function broadcastTo (uris: string[], data: any, byActor: ActorModel, t: Transaction) { | 66 | async 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 | ||
82 | async function unicastTo (data: any, byActor: ActorModel, toActorUrl: string, t: Transaction) { | 80 | async 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 | ||
94 | function getOriginVideoAudience (video: VideoModel, actorsInvolvedInVideo: ActorModel[]) { | 92 | function 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 @@ | |||
1 | import { Transaction } from 'sequelize' | ||
2 | import { ActivityAccept, ActivityFollow } from '../../../../shared/models/activitypub' | 1 | import { ActivityAccept, ActivityFollow } from '../../../../shared/models/activitypub' |
3 | import { ActorModel } from '../../../models/activitypub/actor' | 2 | import { ActorModel } from '../../../models/activitypub/actor' |
4 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | 3 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' |
@@ -6,7 +5,7 @@ import { getActorFollowAcceptActivityPubUrl, getActorFollowActivityPubUrl } from | |||
6 | import { unicastTo } from './misc' | 5 | import { unicastTo } from './misc' |
7 | import { followActivityData } from './send-follow' | 6 | import { followActivityData } from './send-follow' |
8 | 7 | ||
9 | async function sendAccept (actorFollow: ActorFollowModel, t: Transaction) { | 8 | async 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 | ||
48 | async function announceActivityData ( | 48 | async 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' | |||
8 | import { VideoCommentModel } from '../../../models/video/video-comment' | 8 | import { VideoCommentModel } from '../../../models/video/video-comment' |
9 | import { getVideoAbuseActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoViewActivityPubUrl } from '../url' | 9 | import { getVideoAbuseActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoViewActivityPubUrl } from '../url' |
10 | import { | 10 | import { |
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 | ||
37 | async function sendCreateVideoCommentToOrigin (comment: VideoCommentModel, t: Transaction) { | 43 | async 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 | ||
59 | async function sendCreateVideoCommentToVideoFollowers (comment: VideoCommentModel, t: Transaction) { | 65 | async 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 | ||
92 | async function sendCreateViewToVideoFollowers (byActor: ActorModel, video: VideoModel, t: Transaction) { | 98 | async 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 | ||
117 | async function sendCreateDislikeToVideoFollowers (byActor: ActorModel, video: VideoModel, t: Transaction) { | 123 | async 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 @@ | |||
1 | import { Transaction } from 'sequelize' | ||
2 | import { ActivityFollow } from '../../../../shared/models/activitypub' | 1 | import { ActivityFollow } from '../../../../shared/models/activitypub' |
3 | import { ActorModel } from '../../../models/activitypub/actor' | 2 | import { ActorModel } from '../../../models/activitypub/actor' |
4 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | 3 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' |
5 | import { getActorFollowActivityPubUrl } from '../url' | 4 | import { getActorFollowActivityPubUrl } from '../url' |
6 | import { unicastTo } from './misc' | 5 | import { unicastTo } from './misc' |
7 | 6 | ||
8 | function sendFollow (actorFollow: ActorFollowModel, t: Transaction) { | 7 | function 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 | ||
18 | function followActivityData (url: string, byActor: ActorModel, targetActor: ActorModel): ActivityFollow { | 17 | function 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 | ||
26 | async function sendLikeToVideoFollowers (byActor: ActorModel, video: VideoModel, t: Transaction) { | 26 | async 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 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { | 2 | import { ActivityAudience, ActivityCreate, ActivityFollow, ActivityLike, ActivityUndo } from '../../../../shared/models/activitypub' |
3 | ActivityAudience, | ||
4 | ActivityCreate, | ||
5 | ActivityFollow, | ||
6 | ActivityLike, | ||
7 | ActivityUndo | ||
8 | } from '../../../../shared/models/activitypub' | ||
9 | import { ActorModel } from '../../../models/activitypub/actor' | 3 | import { ActorModel } from '../../../models/activitypub/actor' |
10 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | 4 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' |
11 | import { VideoModel } from '../../../models/video/video' | 5 | import { 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 | ||
39 | async function sendUndoLikeToOrigin (byActor: ActorModel, video: VideoModel, t: Transaction) { | 33 | async 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 | ||
51 | async function sendUndoLikeToVideoFollowers (byActor: ActorModel, video: VideoModel, t: Transaction) { | 45 | async 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 | ||
78 | async function sendUndoDislikeToVideoFollowers (byActor: ActorModel, video: VideoModel, t: Transaction) { | 72 | async 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 @@ | |||
1 | import * as kue from 'kue' | ||
1 | import { logger } from '../../../helpers/logger' | 2 | import { logger } from '../../../helpers/logger' |
2 | import { doRequest } from '../../../helpers/requests' | 3 | import { doRequest } from '../../../helpers/requests' |
3 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | 4 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' |
4 | import { ActivityPubHttpPayload, buildSignedRequestOptions, computeBody, maybeRetryRequestLater } from './activitypub-http-job-scheduler' | 5 | import { buildSignedRequestOptions, computeBody } from './utils/activitypub-http-utils' |
5 | 6 | ||
6 | async function process (payload: ActivityPubHttpPayload, jobId: number) { | 7 | export type ActivitypubHttpBroadcastPayload = { |
7 | logger.info('Processing ActivityPub broadcast in job %d.', jobId) | 8 | uris: string[] |
9 | signatureActorId?: number | ||
10 | body: any | ||
11 | } | ||
12 | |||
13 | async 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 | ||
37 | function onError (err: Error, jobId: number) { | ||
38 | logger.error('Error when broadcasting ActivityPub request in job %d.', jobId, err) | ||
39 | return Promise.resolve() | ||
40 | } | ||
41 | |||
42 | function onSuccess (jobId: number) { | ||
43 | logger.info('Job %d is a success.', jobId) | ||
44 | return Promise.resolve() | ||
45 | } | ||
46 | |||
47 | // --------------------------------------------------------------------------- | 45 | // --------------------------------------------------------------------------- |
48 | 46 | ||
49 | export { | 47 | export { |
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 @@ | |||
1 | import * as kue from 'kue' | ||
1 | import { logger } from '../../../helpers/logger' | 2 | import { logger } from '../../../helpers/logger' |
2 | import { doRequest } from '../../../helpers/requests' | 3 | import { doRequest } from '../../../helpers/requests' |
3 | import { ACTIVITY_PUB } from '../../../initializers' | 4 | import { ACTIVITY_PUB } from '../../../initializers' |
4 | import { processActivities } from '../../activitypub/process' | 5 | import { processActivities } from '../../activitypub/process' |
5 | import { ActivityPubHttpPayload } from './activitypub-http-job-scheduler' | 6 | import { ActivitypubHttpBroadcastPayload } from './activitypub-http-broadcast' |
6 | 7 | ||
7 | async function process (payload: ActivityPubHttpPayload, jobId: number) { | 8 | export type ActivitypubHttpFetcherPayload = { |
8 | logger.info('Processing ActivityPub fetcher in job %d.', jobId) | 9 | uris: string[] |
10 | } | ||
11 | |||
12 | async 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 | ||
52 | function onError (err: Error, jobId: number) { | ||
53 | logger.error('Error when fetcher ActivityPub request in job %d.', jobId, err) | ||
54 | return Promise.resolve() | ||
55 | } | ||
56 | |||
57 | function onSuccess (jobId: number) { | ||
58 | logger.info('Job %d is a success.', jobId) | ||
59 | return Promise.resolve() | ||
60 | } | ||
61 | |||
62 | // --------------------------------------------------------------------------- | 59 | // --------------------------------------------------------------------------- |
63 | 60 | ||
64 | export { | 61 | export { |
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 @@ | |||
1 | import * as kue from 'kue' | ||
2 | import { logger } from '../../../helpers/logger' | ||
3 | import { doRequest } from '../../../helpers/requests' | ||
4 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | ||
5 | import { buildSignedRequestOptions, computeBody } from './utils/activitypub-http-utils' | ||
6 | |||
7 | export type ActivitypubHttpUnicastPayload = { | ||
8 | uri: string | ||
9 | signatureActorId?: number | ||
10 | body: any | ||
11 | } | ||
12 | |||
13 | async 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 | |||
41 | export { | ||
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 @@ | |||
1 | import { buildSignedActivity } from '../../../../helpers/activitypub' | ||
2 | import { getServerActor } from '../../../../helpers/utils' | ||
3 | import { ActorModel } from '../../../../models/activitypub/actor' | ||
4 | |||
5 | async 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 | |||
17 | async 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 | |||
36 | export { | ||
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 @@ | |||
1 | import * as Bluebird from 'bluebird' | 1 | import * as kue from 'kue' |
2 | import { VideoResolution } from '../../../../shared' | ||
2 | import { VideoPrivacy } from '../../../../shared/models/videos' | 3 | import { VideoPrivacy } from '../../../../shared/models/videos' |
3 | import { logger } from '../../../helpers/logger' | 4 | import { logger } from '../../../helpers/logger' |
4 | import { computeResolutionsToTranscode } from '../../../helpers/utils' | 5 | import { computeResolutionsToTranscode } from '../../../helpers/utils' |
5 | import { sequelizeTypescript } from '../../../initializers' | 6 | import { sequelizeTypescript } from '../../../initializers' |
6 | import { JobModel } from '../../../models/job/job' | ||
7 | import { VideoModel } from '../../../models/video/video' | 7 | import { VideoModel } from '../../../models/video/video' |
8 | import { shareVideoByServerAndChannel } from '../../activitypub' | 8 | import { shareVideoByServerAndChannel } from '../../activitypub' |
9 | import { sendCreateVideo } from '../../activitypub/send' | 9 | import { sendCreateVideo, sendUpdateVideo } from '../../activitypub/send' |
10 | import { JobScheduler } from '../job-scheduler' | 10 | import { JobQueue } from '../job-queue' |
11 | import { TranscodingJobPayload } from './transcoding-job-scheduler' | ||
12 | 11 | ||
13 | async function process (data: TranscodingJobPayload, jobId: number) { | 12 | export type VideoFilePayload = { |
14 | const video = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(data.videoUUID) | 13 | videoUUID: string |
14 | resolution?: VideoResolution | ||
15 | } | ||
16 | |||
17 | async 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 | ||
26 | function onError (err: Error, jobId: number) { | 40 | async 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 | ||
31 | async function onSuccess (jobId: number, video: VideoModel, jobScheduler: JobScheduler<TranscodingJobPayload, VideoModel>) { | 55 | async 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 | ||
86 | export { | 108 | export { |
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 @@ | |||
1 | import * as kue from 'kue' | ||
2 | import { JobType, JobState } from '../../../shared/models' | ||
3 | import { logger } from '../../helpers/logger' | ||
4 | import { CONFIG, JOB_ATTEMPTS, JOB_COMPLETED_LIFETIME, JOB_CONCURRENCY } from '../../initializers' | ||
5 | import { ActivitypubHttpBroadcastPayload, processActivityPubHttpBroadcast } from './handlers/activitypub-http-broadcast' | ||
6 | import { ActivitypubHttpFetcherPayload, processActivityPubHttpFetcher } from './handlers/activitypub-http-fetcher' | ||
7 | import { ActivitypubHttpUnicastPayload, processActivityPubHttpUnicast } from './handlers/activitypub-http-unicast' | ||
8 | import { processVideoFile, VideoFilePayload } from './handlers/video-file' | ||
9 | |||
10 | type 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 | |||
16 | const 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 | |||
23 | class 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 | |||
122 | export { | ||
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 @@ | |||
1 | import { JobCategory } from '../../../../shared' | ||
2 | import { buildSignedActivity } from '../../../helpers/activitypub' | ||
3 | import { logger } from '../../../helpers/logger' | ||
4 | import { getServerActor } from '../../../helpers/utils' | ||
5 | import { ACTIVITY_PUB } from '../../../initializers' | ||
6 | import { ActorModel } from '../../../models/activitypub/actor' | ||
7 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | ||
8 | import { JobHandler, JobScheduler } from '../job-scheduler' | ||
9 | |||
10 | import * as activitypubHttpBroadcastHandler from './activitypub-http-broadcast-handler' | ||
11 | import * as activitypubHttpFetcherHandler from './activitypub-http-fetcher-handler' | ||
12 | import * as activitypubHttpUnicastHandler from './activitypub-http-unicast-handler' | ||
13 | |||
14 | type ActivityPubHttpPayload = { | ||
15 | uris: string[] | ||
16 | signatureActorId?: number | ||
17 | body?: any | ||
18 | attemptNumber?: number | ||
19 | } | ||
20 | |||
21 | const jobHandlers: { [ handlerName: string ]: JobHandler<ActivityPubHttpPayload, void> } = { | ||
22 | activitypubHttpBroadcastHandler, | ||
23 | activitypubHttpUnicastHandler, | ||
24 | activitypubHttpFetcherHandler | ||
25 | } | ||
26 | const jobCategory: JobCategory = 'activitypub-http' | ||
27 | |||
28 | const activitypubHttpJobScheduler = new JobScheduler(jobCategory, jobHandlers) | ||
29 | |||
30 | async 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 | |||
57 | async 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 | |||
69 | async 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 | |||
88 | export { | ||
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 @@ | |||
1 | import { logger } from '../../../helpers/logger' | ||
2 | import { doRequest } from '../../../helpers/requests' | ||
3 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | ||
4 | import { ActivityPubHttpPayload, buildSignedRequestOptions, computeBody, maybeRetryRequestLater } from './activitypub-http-job-scheduler' | ||
5 | |||
6 | async 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 | |||
34 | function onError (err: Error, jobId: number) { | ||
35 | logger.error('Error when sending ActivityPub request in job %d.', jobId, err) | ||
36 | return Promise.resolve() | ||
37 | } | ||
38 | |||
39 | function onSuccess (jobId: number) { | ||
40 | logger.info('Job %d is a success.', jobId) | ||
41 | return Promise.resolve() | ||
42 | } | ||
43 | |||
44 | // --------------------------------------------------------------------------- | ||
45 | |||
46 | export { | ||
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 @@ | |||
1 | export * 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 @@ | |||
1 | export * from './activitypub-http-job-scheduler' | ||
2 | export * 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 @@ | |||
1 | import { AsyncQueue, forever, queue } from 'async' | ||
2 | import * as Sequelize from 'sequelize' | ||
3 | import { JobCategory } from '../../../shared' | ||
4 | import { logger } from '../../helpers/logger' | ||
5 | import { JOB_STATES, JOBS_FETCH_LIMIT_PER_CYCLE, JOBS_FETCHING_INTERVAL } from '../../initializers' | ||
6 | import { JobModel } from '../../models/job/job' | ||
7 | |||
8 | export 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 | } | ||
13 | type JobQueueCallback = (err: Error) => void | ||
14 | |||
15 | class 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 | |||
142 | export { | ||
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 @@ | |||
1 | export * 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 @@ | |||
1 | import { JobCategory } from '../../../../shared' | ||
2 | import { VideoModel } from '../../../models/video/video' | ||
3 | import { JobHandler, JobScheduler } from '../job-scheduler' | ||
4 | |||
5 | import * as videoFileOptimizer from './video-file-optimizer-handler' | ||
6 | import * as videoFileTranscoder from './video-file-transcoder-handler' | ||
7 | |||
8 | type TranscodingJobPayload = { | ||
9 | videoUUID: string | ||
10 | resolution?: number | ||
11 | } | ||
12 | const jobHandlers: { [ handlerName: string ]: JobHandler<TranscodingJobPayload, VideoModel> } = { | ||
13 | videoFileOptimizer, | ||
14 | videoFileTranscoder | ||
15 | } | ||
16 | const jobCategory: JobCategory = 'transcoding' | ||
17 | |||
18 | const transcodingJobScheduler = new JobScheduler(jobCategory, jobHandlers) | ||
19 | |||
20 | export { | ||
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 @@ | |||
1 | import { VideoResolution } from '../../../../shared' | ||
2 | import { VideoPrivacy } from '../../../../shared/models/videos' | ||
3 | import { logger } from '../../../helpers/logger' | ||
4 | import { VideoModel } from '../../../models/video/video' | ||
5 | import { sendUpdateVideo } from '../../activitypub/send' | ||
6 | |||
7 | async 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 | |||
20 | function onError (err: Error, jobId: number) { | ||
21 | logger.error('Error when transcoding video file in job %d.', jobId, err) | ||
22 | return Promise.resolve() | ||
23 | } | ||
24 | |||
25 | async 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 | |||
44 | export { | ||
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 @@ | |||
1 | import { JobQueue } from '../job-queue' | ||
2 | import { AbstractScheduler } from './abstract-scheduler' | ||
3 | |||
4 | export 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 @@ | |||
1 | import * as express from 'express' | ||
2 | import { param } from 'express-validator/check' | ||
3 | import { isValidJobState } from '../../helpers/custom-validators/jobs' | ||
4 | import { logger } from '../../helpers/logger' | ||
5 | import { areValidationErrors } from './utils' | ||
6 | |||
7 | const 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 | |||
21 | export { | ||
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 @@ | |||
1 | import { values } from 'lodash' | ||
2 | import { AllowNull, Column, CreatedAt, DataType, Model, Table, UpdatedAt } from 'sequelize-typescript' | ||
3 | import { JobCategory, JobState } from '../../../shared/models' | ||
4 | import { JOB_CATEGORIES, JOB_STATES } from '../../initializers' | ||
5 | import { getSort } from '../utils' | ||
6 | |||
7 | @Table({ | ||
8 | tableName: 'job', | ||
9 | indexes: [ | ||
10 | { | ||
11 | fields: [ 'state', 'category' ] | ||
12 | } | ||
13 | ] | ||
14 | }) | ||
15 | export 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 | |||
7 | import { makeGetRequest } from '../../utils/requests/requests' | 7 | import { makeGetRequest } from '../../utils/requests/requests' |
8 | 8 | ||
9 | describe('Test jobs API validators', function () { | 9 | describe('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 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { JobState } from '../../../../shared/models' | ||
5 | import { VideoPrivacy } from '../../../../shared/models/videos' | 6 | import { VideoPrivacy } from '../../../../shared/models/videos' |
6 | import { VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model' | 7 | import { VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model' |
7 | import { completeVideoCheck, getVideo, immutableAssign, reRunServer, viewVideo } from '../../utils' | 8 | import { 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 | ||
4 | import * as program from 'commander' | 4 | import * as program from 'commander' |
5 | import { Video, VideoFile, VideoRateType } from '../../../shared' | 5 | import { Video, VideoFile, VideoRateType } from '../../../shared' |
6 | import { JobState } from '../../../shared/models' | ||
6 | import { | 7 | import { |
7 | flushAndRunMultipleServers, | 8 | flushAndRunMultipleServers, |
8 | flushTests, follow, | 9 | flushTests, follow, |
@@ -346,23 +347,19 @@ function goodbye () { | |||
346 | } | 347 | } |
347 | 348 | ||
348 | async function isTherePendingRequests (servers: ServerInfo[]) { | 349 | async 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 @@ | |||
1 | import * as request from 'supertest' | 1 | import * as request from 'supertest' |
2 | import { JobState } from '../../../../shared/models' | ||
2 | 3 | ||
3 | function getJobsList (url: string, accessToken: string) { | 4 | function 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 | ||
14 | function getJobsListPaginationAndSort (url: string, accessToken: string, start: number, count: number, sort: string) { | 15 | function 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 @@ | |||
1 | export type JobState = 'pending' | 'processing' | 'error' | 'success' | 1 | export type JobState = 'active' | 'complete' | 'failed' | 'inactive' | 'delayed' |
2 | export type JobCategory = 'transcoding' | 'activitypub-http' | 2 | |
3 | export type JobType = 'activitypub-http-unicast' | | ||
4 | 'activitypub-http-broadcast' | | ||
5 | 'activitypub-http-fetcher' | | ||
6 | 'video-file' | ||
3 | 7 | ||
4 | export interface Job { | 8 | export 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 | } |
@@ -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 | ||
258 | acorn-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 | |||
243 | acorn-jsx@^3.0.0: | 264 | acorn-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 | ||
249 | acorn@^3.0.4: | 270 | acorn@^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 | ||
274 | acorn@^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 | |||
253 | acorn@^5.2.1: | 278 | acorn@^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 | |||
343 | append-field@^0.1.0: | 374 | append-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 | ||
720 | body-parser@1.18.2, body-parser@^1.12.4: | 751 | body-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 | ||
993 | character-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 | |||
962 | charenc@~0.0.1: | 999 | charenc@~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 | ||
1028 | clean-css@~3.4.2: | 1065 | clean-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 | ||
1308 | constantinople@^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 | |||
1271 | contains-path@^0.1.0: | 1315 | contains-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 | ||
1414 | css-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 | |||
1370 | css-select@~1.0.0: | 1418 | css-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 | ||
1413 | debug@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: | 1461 | debug@*, 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 | ||
1419 | debug@3.1.0, debug@^3.0.0, debug@^3.1.0: | 1467 | debug@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 | ||
1618 | doctypes@^1.1.0: | ||
1619 | version "1.1.0" | ||
1620 | resolved "https://registry.yarnpkg.com/doctypes/-/doctypes-1.1.0.tgz#ea80b106a87538774e8a3a4a5afe293de489e0a9" | ||
1621 | |||
1570 | dom-serializer@0, dom-serializer@~0.1.0: | 1622 | dom-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 | ||
1666 | double-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 | |||
1614 | duplexer3@^0.1.4: | 1670 | duplexer3@^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 | ||
2013 | express@^4.12.4, express@^4.13.3: | 2069 | express@^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 | ||
2117 | extend@^1.2.1: | ||
2118 | version "1.3.0" | ||
2119 | resolved "https://registry.yarnpkg.com/extend/-/extend-1.3.0.tgz#d1516fb0ff5624d2ebf9123ea1dac5a1994004f8" | ||
2120 | |||
2061 | extend@^3.0.0, extend@~3.0.0, extend@~3.0.1: | 2121 | extend@^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 | ||
2472 | glob@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 | |||
2412 | glob@7.1.2, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@~7.1.1: | 2483 | glob@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 | ||
3168 | is-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 | |||
3175 | is-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 | |||
3097 | is-extendable@^0.1.0, is-extendable@^0.1.1: | 3182 | is-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 | ||
3290 | is-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 | |||
3205 | is-property@^1.0.0: | 3294 | is-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 | ||
3213 | is-regex@^1.0.4: | 3302 | is-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 | ||
3378 | js-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 | |||
3289 | js-tokens@^3.0.2: | 3382 | js-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 | ||
3478 | jstransformer@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 | |||
3385 | jsx-ast-utils@^1.3.4: | 3485 | jsx-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 | ||
3556 | kue@^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 | |||
3456 | kuler@0.0.x: | 3572 | kuler@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 | ||
3567 | lodash.assign@^4.2.0: | 3683 | lodash.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 | ||
3695 | lru-cache@2: | 3811 | lru-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 | ||
3919 | mkdirp@0.5.1, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0: | 4035 | mkdirp@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 | ||
4155 | natural@^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 | |||
4039 | negotiator@0.6.1: | 4163 | negotiator@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 | ||
4175 | nib@~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 | |||
4051 | node-abi@^2.1.1: | 4181 | node-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 | ||
4239 | node-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 | |||
4246 | node-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 | |||
4109 | node-sass@^4.0.0: | 4253 | node-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 | ||
4706 | promise@^7.1.1: | 4850 | promise@^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 | ||
4891 | pug-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 | |||
4899 | pug-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 | |||
4912 | pug-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 | |||
4916 | pug-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 | |||
4928 | pug-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 | |||
4936 | pug-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 | |||
4943 | pug-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 | |||
4950 | pug-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 | |||
4957 | pug-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 | |||
4961 | pug-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 | |||
4967 | pug-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 | |||
4971 | pug@^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 | |||
4747 | pump@^1.0.0, pump@^1.0.1: | 4984 | pump@^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 | ||
5148 | redis-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 | |||
5152 | redis-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 | |||
5156 | redis@^0.12.1: | ||
5157 | version "0.12.1" | ||
5158 | resolved "https://registry.yarnpkg.com/redis/-/redis-0.12.1.tgz#64df76ad0fc8acebaebd2a0645e8a48fac49185e" | ||
5159 | |||
5160 | redis@~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 | |||
5168 | reds@^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 | |||
4911 | reflect-metadata@^0.1.10: | 5175 | reflect-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 | ||
5407 | sax@0.5.x: | ||
5408 | version "0.5.8" | ||
5409 | resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.8.tgz#d472db228eb331c2506b0e8c15524adb939d12c1" | ||
5410 | |||
5143 | scripty@^1.5.0: | 5411 | scripty@^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 | ||
5724 | source-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 | |||
5456 | source-map@0.4.x, source-map@^0.4.2, source-map@^0.4.4: | 5730 | source-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 | ||
5998 | stylus@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 | |||
5724 | superagent@^3.0.0, superagent@^3.6.3: | 6009 | superagent@^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 | |||
5772 | sync-request@^4.1.0: | 6061 | sync-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 | ||
6225 | token-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 | |||
5936 | toposort-class@^1.0.1: | 6229 | toposort-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 | ||
6103 | uglify-js@^2.6: | 6396 | uglify-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 | ||
6155 | underscore@^1.7.0: | 6448 | underscore@>=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 | ||
6574 | uuid@^2.0.1: | ||
6575 | version "2.0.3" | ||
6576 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" | ||
6577 | |||
6281 | uuid@^3.0.0, uuid@^3.1.0, uuid@^3.2.1: | 6578 | uuid@^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 | ||
6628 | void-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 | |||
6331 | webfinger.js@^2.6.6: | 6632 | webfinger.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 | ||
6719 | window-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 | |||
6418 | winston-transport@^3.0.1: | 6723 | winston-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 | ||
6740 | with@^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 | |||
6435 | wkx@^0.4.1: | 6747 | wkx@^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 | ||
6834 | yargs-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 | |||
6522 | yargs-parser@^5.0.0: | 6841 | yargs-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 | ||
6853 | yargs@^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 | |||
6534 | yargs@^7.0.0: | 6872 | yargs@^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" |